Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
237 changes: 101 additions & 136 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,18 @@
# VES AI

Make product analytics actionable for AI agents.

**Core differentiator:** VES AI closes the product improvement loop by making product analytics data actionable for AI agents.

VES AI is local-first:
- You run the CLI on your machine.
- You use your own PostHog + Google Cloud.
- You keep outputs in `~/.vesai/workspace` as durable, git-friendly artifacts.
VES AI is a local-first session replay analysis runtime for agent workflows.

## Install

```bash
curl -fsSL https://ves.ai/install | bash
```

Installer flow:
1. Clone/update repo at `~/.vesai/app/vesai`
2. Install dependencies with Bun
3. Install Playwright Chromium
4. Link `vesai` at `~/.local/bin/vesai`
5. Start `vesai quickstart`
The installer clones VES AI to `~/.vesai/app/vesai`, installs dependencies, links `vesai`, and runs `vesai quickstart`.

## Auto Update

Every `vesai` command syncs the installed CLI with the latest `origin/main` before execution.

## Prerequisites

Expand All @@ -29,171 +21,157 @@ Installer flow:
- `gcloud`
- `ffmpeg`

Before quickstart:
Before `quickstart`:

```bash
gcloud auth login
gcloud auth application-default login
gcloud config set project <project-id>
```

If `gcloud` throws `unsupported hash type blake2b` / `blake2s`:
## Setup Model

```bash
export CLOUDSDK_PYTHON=/usr/bin/python3
```
VES AI now has two setup steps:

1. `vesai quickstart` configures global machine-level runtime.
2. `vesai init` configures the current repository as a VES AI project.

## Quickstart
### 1) Global setup (`vesai quickstart`)

```bash
vesai quickstart
```

Quickstart configures:
- PostHog host + User API key
- PostHog project selection
- PostHog group key
- Replay domain filter
- GCP project + Vertex region
- GCS bucket create/select
- Render concurrency (defaults to ~50% of available RAM, ~512MB per renderer)
- Product description context for analysis prompts
This sets:
- Global Google Cloud + Vertex config
- Global bucket config for rendered artifacts
- Global machine render memory budget (`runtime.maxRenderMemoryMb`)
- Local render runtime dependencies (Chromium)

PostHog API key requirements:
- Key type: User API key
- Scope: `All access + MCP server scope`
- URL: `https://app.posthog.com/settings/user-api-keys`
Render services scale dynamically up/down based on current free RAM, capped by your configured memory budget.

Non-interactive usage:
Example:

```bash
vesai quickstart \
--non-interactive \
--posthog-api-key phx_... \
--posthog-project-id 123 \
--posthog-group-key organization \
--domain-filter app.example.com \
--product-description "B2B SaaS for support teams"
vesai quickstart --max-render-memory-mb 8192
```

## CLI Surface
Global config is stored in `~/.vesai/core.json`.

### 2) Project setup (`vesai init`)

Replay intelligence:
Run this inside your product repo:

```bash
vesai replays session <session_id>
vesai replays user <email>
vesai replays group <group_id>
vesai replays query "<text>"
vesai replays list
vesai init
```

PostHog analytics intelligence:
This creates and configures project-local artifacts:
- `.vesai/project.json`
- `.vesai/workspace/{sessions,users,groups,research}`
- `.vesai/jobs`, `.vesai/cache`, `.vesai/logs`

`vesai init` also:
- Generates a UUID `projectId` by default (or uses `--project-id`)
- Prompts for PostHog project settings
- Prompts for `lookbackDays` (default `180`)
- Adds `.vesai/` to repository `.gitignore`
- Throws a descriptive error if `.gitignore` cannot be updated (locked/read-only)

Example:

```bash
vesai events
vesai properties
vesai schema data
vesai schema warehouse
vesai insights hogql "<question>"
vesai insights sql "<query>"
vesai errors list
vesai logs query --from ... --to ...
vesai init --lookback-days 180
```

Agent mode patterns:
## CLI Surface

```bash
vesai replays query --group acme --min-active 30 --dry-run
vesai replays query --group acme --min-active 30
vesai insights sql "SELECT event, count() FROM events GROUP BY event LIMIT 20"
```
vesai user <useremail>
vesai group <group_id>
vesai research "<question>"

JSON is default for data commands. Use `--no-json` for human-readable summaries.
vesai daemon start
vesai daemon watch
vesai daemon status
vesai daemon stop

## Replay Querying Notes
vesai quickstart
vesai init
vesai config show
vesai doctor
```

`vesai replays query "checkout friction"` is **literal metadata search** plus filters. It does not infer intent from language on its own.
Data commands return JSON by default. Use `--no-json` for readable text.

For strong signal, pair text with structured filters:
Examples:

```bash
vesai replays query "checkout" --url /checkout --min-active 30 --from 2026-02-01 --to 2026-02-15
vesai replays query --group acme --where plan=enterprise --url /checkout
vesai user bryce@company.com
vesai group acme-co
vesai research "What drives checkout abandonment?"
```

## User Analysis Contract

`vesai replays user <email>` does:
1. Find all matching sessions for the user
2. Ensure every session is rendered to video
3. Analyze each session individually
4. Run one aggregate Gemini call across all session analyses + metadata
5. Write comprehensive user story markdown to workspace

## Filesystem Layout

```text
~/.vesai/
vesai.json
cache/
logs/
tmp/
workspace/
sessions/
users/
groups/
app/
vesai/
```
## Command Behavior

## Daemon Commands
### `vesai user <useremail>`
- Finds all sessions for the user
- Ensures those sessions are rendered and analyzed
- Produces one aggregate user story

```bash
vesai daemon start # background
vesai daemon watch # foreground (Ctrl+C to stop)
vesai daemon status
vesai daemon stop
```
### `vesai group <group_id>`
- Resolves users under the group ID (PostHog group key mapping)
- Builds each user story from all their sessions
- Produces one aggregate group story

## Troubleshooting
### `vesai research "<question>"`
- Uses only already analyzed sessions in `.vesai/workspace/sessions`
- Selects relevant sessions as context
- Sends context to Gemini and returns a research answer

### Bucket location errors
## Daemon Model

If bucket creation fails with invalid location constraint, use a valid location:
- Multi-region: `US`, `EU`, `ASIA`
- Or supported region like `us-central1`
`vesai daemon` is project-scoped and runs against the current repo’s `.vesai` directory.

### Permission mismatch (`storage.objects.create` denied)
Behavior:
- First run performs backfill from `now - lookbackDays` to now.
- Heartbeat then continuously pulls sessions from `lastPulledAt` to now.
- New sessions are queued for render + analysis.
- After session jobs complete, affected user and group stories are re-run.

Common cause: ADC identity differs from `gcloud auth list` active account.
## Global vs Project Separation

Reset ADC:
### Global core (`~/.vesai`)
- Machine-level memory budget (`maxRenderMemoryMb`)
- Render service/runtime dependencies
- GCloud bucket/project/model settings
- Shared cross-process render slot locks (`~/.vesai/render-locks`)

```bash
gcloud auth application-default revoke
gcloud auth application-default login
gcloud auth application-default set-quota-project <project-id>
```
### Project-local (`<repo>/.vesai`)
- Project UUID
- PostHog API key/project/domain/group config
- Session/user/group/research markdown artifacts
- Daemon state, job queue, cache, logs

### Missing Playwright executable
## Storage Layout in GCS

```bash
bunx playwright install chromium
```
Rendered artifacts are prefixed by VES AI project UUID:

### Vertex model access errors (`gemini-3-pro-preview` not found)
- `projects/<project-uuid>/events/<session-id>.json`
- `projects/<project-uuid>/videos/<session-id>.webm`

- Verify Vertex AI API is enabled on the selected project
- Verify selected region supports the configured model
- Update config if needed:
This keeps all project artifacts isolated under top-level project folders in one bucket.

## Config Commands

```bash
vesai config set vertex.model gemini-3-pro-preview
vesai config set vertex.location us-central1
vesai config show
vesai config validate
vesai config set core.runtime.maxRenderMemoryMb 8192
vesai config set project.daemon.lookbackDays 180
```

Run `vesai doctor` to confirm local setup state.
Use `core.` paths for global config and `project.` paths for repo-local config.

## Development

Expand All @@ -202,17 +180,4 @@ bun install
bun run lint
bun run typecheck
bun run test
bun run vesai -- --help
```

Website:

```bash
bun run website:dev
bun run website:build
bun run website:start
```

Quality gates:
- Husky pre-commit: `bun run precommit`
- CI: `.github/workflows/ci.yml` runs the same `lint + typecheck + test`
66 changes: 64 additions & 2 deletions bin/vesai
Original file line number Diff line number Diff line change
@@ -1,5 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
cd "$ROOT_DIR"
exec bun cli/index.ts "$@"

resolve_realpath_or_empty() {
local path="$1"
if [ -d "$path" ]; then
(cd "$path" && pwd -P)
else
echo ""
fi
}

sync_latest_main() {
if ! command -v git >/dev/null 2>&1; then
echo "VES AI auto-update requires git." >&2
return 1
fi

if ! git -C "$ROOT_DIR" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
return 0
fi

local vesai_home="${VESAI_HOME:-$HOME/.vesai}"
local installed_root
installed_root="$(resolve_realpath_or_empty "$vesai_home/app/vesai")"
local repo_root
repo_root="$(resolve_realpath_or_empty "$ROOT_DIR")"
local is_installed_repo="0"
if [ -n "$installed_root" ] && [ "$repo_root" = "$installed_root" ]; then
is_installed_repo="1"
fi

local lock_dir="$ROOT_DIR/.git/vesai-auto-update.lock"
local lock_wait_attempts=0
until mkdir "$lock_dir" 2>/dev/null; do
lock_wait_attempts=$((lock_wait_attempts + 1))
if [ "$lock_wait_attempts" -ge 300 ]; then
echo "VES AI auto-update lock timed out." >&2
return 1
fi
sleep 0.1
done

trap 'rmdir "$lock_dir" >/dev/null 2>&1 || true' RETURN

git -C "$ROOT_DIR" fetch --quiet --depth 1 origin main

if [ "$is_installed_repo" = "1" ]; then
git -C "$ROOT_DIR" checkout --quiet main
git -C "$ROOT_DIR" reset --quiet --hard origin/main
else
local current_branch
current_branch="$(git -C "$ROOT_DIR" symbolic-ref --short -q HEAD || true)"
if [ "$current_branch" = "main" ] &&
git -C "$ROOT_DIR" diff --quiet &&
git -C "$ROOT_DIR" diff --cached --quiet; then
git -C "$ROOT_DIR" merge --quiet --ff-only origin/main
fi
fi

trap - RETURN
rmdir "$lock_dir" >/dev/null 2>&1 || true
}

sync_latest_main
exec bun "$ROOT_DIR/cli/index.ts" "$@"
Loading