diff --git a/docs/guides/commands.md b/docs/guides/commands.md index d75588c..6b49a38 100644 --- a/docs/guides/commands.md +++ b/docs/guides/commands.md @@ -1,27 +1,28 @@ # Commands -Every command works in two modes: +`setup`, `apply`, `pull`, `push`, `cleanup` and `call` work in two modes: - **Interactive** — run without arguments, get prompted for org and resources -- **Direct** — pass an org slug and flags for scripting / CI - -| Command | Interactive | Direct | One-liner | -| --- | --- | --- | --- | -| `npm run setup` | ✅ | — | First-time org wizard — creates `.env.` and `resources//`. | -| `npm run validate` | — | `npm run validate -- ` | Schema-check local YAML/MD with no network call. **Run before every `apply`.** | -| `npm run audit` | — | `npm run audit -- [--type ]` | Read-only drift detector — orphan local YAML, state ghosts, UUID collisions, content-identical clusters, sibling base-slug clusters, dashboard orphans, assistants with inline `model.tools`. Exit 1 on any finding; safe to wire into CI. | -| `npm run promote` | — | `npm run promote -- --pipeline --from --to [--apply]` | Plan or apply a forward-only, dependency-aware promotion defined by `promotion.yml`. | -| `npm run apply` | ✅ | `npm run apply -- [--force]` | **Default deploy verb.** Pull → merge → push in one safe pass; resilient against dashboard drift. | -| `npm run pull` | ✅ | `npm run pull -- [flags]` | Fetch remote state into local files / state file. Local-first by default — won't clobber local edits. | -| `npm run push` | ✅ | `npm run push -- [flags]` | Raw push without a pre-pull. Refuses by default when local YAML files lack state entries (orphan-YAML gate); pass `--allow-new-files` to bypass after confirming intent. **Skip unless you just ran `pull` and are certain state is fresh** — otherwise prefer `apply`. | -| `npm run cleanup` | ✅ | `npm run cleanup -- [--force --confirm ]` | Inspect (default) or delete orphaned remote resources. Destructive run requires `--confirm `. | -| `npm run rollback` | — | `npm run rollback -- --list` or `--to ` | Restore from a snapshot in `.vapi-state..snapshots/` (one is written before every push/apply). | -| `npm run call` | ✅ | `npm run call -- -a ` or `-s ` | Start an interactive WebSocket call against an assistant or squad. | -| `npm run sim` | — | `npm run sim -- --suite --target [--timeout ]` | Run a simulation suite (or specific simulations) against a deployed assistant/squad. Prints the run link; exits 0 passed, 1 failed, 3 incomplete (timeout, Ctrl-C, missing results). | -| `npm run check` | — | `npm run check -- \|--all [--dry-run] [--changed-since ] [--budget-minutes ] [--json ]` | Run the `vapi-checks.yml` simulation checks against the files on disk: each target is built inline (tools, handoffs, judges, personalities; tools mocked fail-closed, servers dead-ended) and run in one simulation run per target, so nothing is deployed. Posts `Vapi Evals` commit statuses when run by the PR workflow. `--dry-run` builds the payloads offline (no key, nothing sent; `--print-payload` writes them). Exits 0 passed, 1 failed, 2 config or build error, 3 incomplete (timeout, interrupt, budget, billing). | -| `npm run migrate` | — | `npm run migrate` | One-time, all orgs at once: slim legacy state files to pure `name → uuid` and seed the per-developer `.vapi-state-hash/` baseline store from the old hashes. Required once after upgrading to the hash-store engine — `pull`/`push`/`apply` refuse legacy-shaped state until it runs. Idempotent. | -| `npm run build` | — | — | Type-check the codebase (`tsc --noEmit`). | -| `npm test` | — | — | Run regression tests (`node:test`). | +- **Direct** — pass an org name and flags, for scripts and CI + +The other commands are direct only. + +| Command | Usage | What it does | +| --- | --- | --- | +| `npm run setup` | `npm run setup [-- ]` | Connect an org: creates `.env.` and `resources//`. | +| `npm run validate` | `npm run validate -- ` | Check resource files offline. Run it before every `apply`. | +| `npm run apply` | `npm run apply -- [types or paths]` | **The default deploy:** pull, merge, then push. See [workflows](workflows.md). | +| `npm run pull` | `npm run pull -- [--force] [--bootstrap]` | Sync platform changes down; never overwrites local edits unless `--force`. | +| `npm run push` | `npm run push -- [--dry-run]` | Push without pulling first. Prefer `apply`. | +| `npm run rollback` | `npm run rollback -- --list` or `--to ` | Restore a pre-deploy snapshot from `.vapi-state..snapshots/`. | +| `npm run cleanup` | `npm run cleanup -- [--force --confirm ]` | List platform resources with no file; delete them only with both flags. | +| `npm run audit` | `npm run audit -- [--type ]` | Report drift between files, state and the platform. Exits 1 on any finding, so it can run in CI. | +| `npm run call` | `npm run call -- -a ` or `-s ` | Talk to an assistant or squad from your terminal. | +| `npm run sim` | `npm run sim -- --suite --target ` | Run a simulation suite against deployed resources. Exits 0 passed, 1 failed, 3 incomplete. | +| `npm run check` | `npm run check -- ` or `--all`, `[--dry-run]` | Run [PR checks](pr-checks.md) against local files, nothing deployed. Exits 0 passed, 1 failed, 2 config or build error, 3 incomplete. | +| `npm run promote` | `npm run promote -- --pipeline

--from --to [--apply]` | Plan, or apply, a [promotion](promotion.md) between orgs. | +| `npm run build` | `npm run build` | Type-check the code and tests. | +| `npm test` | `npm test` | Run the test suite. | ## Interactive Mode @@ -86,3 +87,17 @@ npm run call -- my-org -a my-assistant # Call a squad npm run call -- my-org -s my-squad ``` + +## Upgrading from an older version + +Repositories created before the state-file format changed need a one-time +migration. `pull`, `push` and `apply` refuse to run until it's done: + +```bash +npm run migrate +``` + +It rewrites every org's `.vapi-state..json` to the current +`{ "name": { "uuid": … } }` format and seeds your local +`.vapi-state-hash/` drift baseline from the old file. It's safe to run more +than once. Commit the rewritten state files. diff --git a/docs/guides/configuration.md b/docs/guides/configuration.md index 293cacf..a707d4d 100644 --- a/docs/guides/configuration.md +++ b/docs/guides/configuration.md @@ -1,10 +1,44 @@ # Configuration -## Environment Variables +## Per-org settings (`.env.`) -| Variable | Required | Description | -| --------------- | -------- | ------------------------------------------------ | -| `VAPI_PRIVATE_API_KEY` | ✅ | Vapi private API key from [Private API Keys](https://dashboard.vapi.ai/org/api-keys). The legacy name `VAPI_TOKEN` is still accepted. | -| `VAPI_BASE_URL` | ❌ | API base URL (defaults to `https://api.vapi.ai`) | +`npm run setup` creates one `.env.` file per org. These files are +gitignored: never commit them. -These are stored in `.env.` files, one per configured organization. +| Variable | Required | Description | +| --- | --- | --- | +| `VAPI_PRIVATE_API_KEY` | ✅ | The org's private API key, from [Private API Keys](https://dashboard.vapi.ai/org/api-keys). The older name `VAPI_TOKEN` is also accepted. | +| `VAPI_BASE_URL` | | API base URL. Defaults to `https://api.vapi.ai`; EU orgs use `https://api.eu.vapi.ai` (`npm run setup -- --region eu` sets it). | +| `VAPI_CREDENTIAL_`, `VAPI_PHONE_NUMBER_` | | Generated by `setup` and `pull` inside a marked block: the org's credential and phone-number IDs, by name. Values you set outside the block are kept and take precedence. | + +A variable already set in your shell or CI environment takes precedence over +the file. `.env..local` and `.env.local` are also read, after +`.env.`, and only fill in variables that are still unset. + +## CI and workflow settings + +Set these in GitHub under **Settings → Secrets and variables → Actions**. + +| Name | Kind | Used by | Description | +| --- | --- | --- | --- | +| `VAPI_PROMOTION_TOKENS` | Secret | [Promotion](promotion.md) | JSON map of org name to private API key, for example `{"dev":"…","prod":"…"}`. | +| `VAPI_PROMOTION_ENABLED` | Variable | Promotion | `true` to promote automatically after changes land on `main`. | +| `VAPI_CHECK_TOKENS` | Secret | [PR checks](pr-checks.md) | JSON map of org name to private API key, for checks that run in several orgs or a CI org. | +| `VAPI_PRIVATE_API_KEY` | Secret | PR checks | A single org's key, when every check runs in one org. | +| `VAPI_CHECKS_ENABLED` | Variable | PR checks | `true` to run PR checks. The workflow does nothing without it. | + +## Configuration files + +| File | Purpose | +| --- | --- | +| `promotion.yml` | Promotion pipelines between orgs. Start from `promotion.example.yml`. | +| `vapi-checks.yml` | PR checks. Start from `vapi-checks.example.yml`. | +| `resources//.vapi-ignore` | Platform resources this repo shouldn't manage, as glob patterns. See `resources/.vapi-ignore.example` and [YAML conventions](../learnings/yaml-conventions.md). | + +## Other environment variables + +| Variable | Description | +| --- | --- | +| `CI=true` | Suppresses interactive warnings, such as the one `pull --force` prints. Set automatically by most CI systems. | +| `VAPI_CALL_DEBUG=1` | Makes `npm run call` log message types it would otherwise ignore. | +| `VAPI_GITOPS_ROOT` | Overrides the repository root that `npm run promote` and `npm run check` read from. Mainly for tests. | diff --git a/docs/guides/how-it-works.md b/docs/guides/how-it-works.md index 55de345..3383bf5 100644 --- a/docs/guides/how-it-works.md +++ b/docs/guides/how-it-works.md @@ -1,8 +1,8 @@ # How the engine works -## Organization-Based Structure +## One folder per org -Resources are scoped by organization (not fixed `dev`/`stg`/`prod` names). Each org gets: +Resources are scoped by organization, with names you choose (not fixed `dev`/`stg`/`prod`). Each org gets: - `.env.` — private API key and base URL - `.vapi-state..json` — resource name ↔ UUID mappings (nothing else — committed) @@ -40,7 +40,7 @@ files **`pull`** — downloads platform state. Detects locally modified files and skips them (your work is preserved). Use `--force` to overwrite everything. -**`push`** — reads local files and syncs them to the platform. Handles creates, updates, and deletions. +**`push`** — reads local files and syncs them to the platform: creates and updates. It deletes platform resources whose files you removed only when you pass `--force`. **`apply`** — runs `pull` then `push` in sequence. @@ -110,62 +110,20 @@ Tracks resource ID ↔ Vapi UUID mappings per org: Every resource type has a section. Keys are sorted, so diffs stay readable. -## Project Structure - -``` -vapi-gitops/ -├── docs/ -│ ├── Vapi Prompt Optimization Guide.md -│ ├── changelog.md -│ └── learnings/ # Gotchas, recipes, troubleshooting per area -│ ├── assistants.md -│ ├── tools.md -│ ├── squads.md -│ ├── simulations.md -│ └── ... -├── src/ -│ ├── setup.ts # Setup wizard (interactive) + non-interactive setup -│ ├── setup-args.ts # `npm run setup` argument parsing -│ ├── interactive.ts # Interactive pull/push/apply/call/cleanup flows -│ ├── searchableCheckbox.ts # Custom multi-select prompt component -│ ├── pull.ts # Pull platform state -│ ├── push.ts # Push local state to platform -│ ├── apply.ts # Orchestrator: pull → merge → push -│ ├── call.ts # WebSocket call script -│ ├── cleanup.ts # Orphan cleanup -│ ├── pull-cmd.ts # Entry point: interactive or direct pull -│ ├── push-cmd.ts # Entry point: interactive or direct push -│ ├── apply-cmd.ts # Entry point: interactive or direct apply -│ ├── call-cmd.ts # Entry point: interactive or direct call -│ ├── cleanup-cmd.ts # Entry point: interactive or direct cleanup -│ ├── types.ts # TypeScript interfaces -│ ├── config.ts # Environment & configuration -│ ├── api.ts # Vapi HTTP client -│ ├── state.ts # State file management -│ ├── resources.ts # Resource loading (YAML, MD, TS) -│ ├── resolver.ts # Reference resolution -│ ├── credentials.ts # Credential resolution (name ↔ UUID) -│ ├── delete.ts # Deletion & orphan checks -│ └── check-cmd.ts # Entry point: PR simulation checks (check-*.ts) -├── resources/ -│ └── / # One directory per configured org -│ ├── assistants/ -│ ├── tools/ -│ ├── squads/ -│ ├── structuredOutputs/ -│ ├── evals/ -│ └── simulations/ -│ ├── personalities/ -│ ├── scenarios/ -│ ├── tests/ -│ └── suites/ -├── tests/ -│ ├── credentials.test.ts # Credential walker scoping (P0-1 regression suite) -│ ├── clean-resource.test.ts # null-preservation in pull (P0-3 regression suite) -│ ├── path-matching.test.ts # Short-form path matching (P0-7 regression suite) -│ ├── cleanup-safety.test.ts # --confirm + empty-state gates (P0-4 regression suite) -│ └── cli-arg-parsing.test.ts # Bare-id refusal, --confirm pass-through (P0-7) -├── vapi-checks.example.yml # Copy to vapi-checks.yml for PR simulation checks -├── .env. # Private API key per org (gitignored) -└── .vapi-state..json # State file per org -``` +## Where things live + +| Path | What it is | +| --- | --- | +| `resources//` | Your resources, one folder per org | +| `.vapi-state..json` | Name → UUID mappings per org (committed) | +| `.env.` | API key and generated binding IDs (gitignored) | +| `promotion.yml`, `vapi-checks.yml` | Promotion pipelines and PR checks (copy from the `*.example.yml` files) | +| `resources//.vapi-ignore` | Platform resources this repo shouldn't manage (see `resources/.vapi-ignore.example`) | +| `src/` | The engine; `package.json` scripts name each command's entry point | +| `tests/` | The test suite (`npm test`) | +| `docs/guides/` | These guides | +| `docs/learnings/` | The Vapi field guide | +| `docs/changelog.md` | A template for your own deployment's change log | +| `examples/` | Copyable examples; never loaded by the engine | +| `.github/workflows/` | CI, PR checks and promotion workflows | +| `AGENTS.md`, `CLAUDE.md` | Instructions for coding agents | diff --git a/docs/guides/troubleshooting.md b/docs/guides/troubleshooting.md index fc569b7..a8a7f6d 100644 --- a/docs/guides/troubleshooting.md +++ b/docs/guides/troubleshooting.md @@ -11,18 +11,22 @@ The referenced resource doesn't exist. Check: ## "Cannot delete resource - still referenced" -1. Find which resources reference it (shown in error) -2. Remove the references -3. Push again -4. Then delete the resource file +1. Find which resources reference it (shown in the error) +2. Remove those references and deploy with `npm run apply -- ` +3. Then delete the resource file and deploy again ## Resource not updating -Check the state file has correct UUID: +The file may be mapped to the wrong platform resource, or to one that no +longer exists. -1. Open `.vapi-state..json` -2. Find the resource entry -3. If incorrect, delete entry and re-run push +Run `npm run audit -- `. It reports state entries that point at a +missing resource, or several files that point at the same one, with a +suggested fix for each. + +Don't delete a state entry by hand: the next deploy would treat the file as +new, and stop at the new-file check (or create a duplicate if the check is +bypassed). ## "Credential with ID not found" errors @@ -34,7 +38,13 @@ The credential UUID doesn't exist in the target org. Fix: ## "property X should not exist" API errors -Some properties can't be updated after creation. Add them to `UPDATE_EXCLUDED_KEYS` in `src/config.ts`. +Some properties can't be changed after a resource is created, so the API +rejects them on update. Please [open an issue](https://github.com/VapiAI/gitops/issues/new/choose) +with the resource type and property, so the engine stops sending it. + +As a stopgap you can add the property to `UPDATE_EXCLUDED_KEYS` in +`src/config.ts`. That's an engine change, so expect to resolve it when you +next pull in updates. ## "Refusing to run destructive cleanup" errors @@ -62,11 +72,11 @@ The interactive `npm run cleanup` flow handles both gates for you (it shows the dry-run preview, asks you to confirm, and forwards `--force --confirm ` automatically when you say yes). -## "Unrecognized argument" / push appears to do nothing +## "Unrecognized argument" errors -If you typed `npm run push -- my-org foo` (a bare resource id with no folder -or extension), the CLI now refuses with `Unrecognized argument: foo` rather -than silently running a full apply. Pass either: +A bare resource ID (`npm run push -- my-org foo`, with no folder or +extension) is rejected with `Unrecognized argument: foo`, rather than +falling through to a full deploy. Pass either: - a resource type — `npm run push -- my-org assistants`, or - a path — `npm run push -- my-org assistants/foo.yml` (short form) diff --git a/docs/guides/workflows.md b/docs/guides/workflows.md index b7b3643..a9d359f 100644 --- a/docs/guides/workflows.md +++ b/docs/guides/workflows.md @@ -1,8 +1,12 @@ # Everyday workflows -Recipes for common situations. Each one is the safe path — there are faster shortcuts, but use them only when you understand the trade-offs. The single most important habit: **prefer `apply` over `push`**, because `apply` refreshes platform state before mutating, protecting you against dashboard edits made between your last pull and your push. +Recipes for common situations. Each one is the safe path — there are faster +shortcuts, but use them only when you understand the trade-offs. The single +most important habit: **prefer `apply` over `push`**, because `apply` +refreshes platform state before changing anything, protecting you against +dashboard edits made since your last pull. -## Daily edit-and-deploy +## Deploy changes ```bash # 1. Schema-check locally first — fails fast on YAML shape errors, no network needed. @@ -13,16 +17,36 @@ npm run validate -- npm run apply -- ``` -## Iterating on a single file +To deploy only some resources, pass resource types or file paths. `apply` +and `push` accept the same scoping: ```bash -npm run validate -- +# By resource type +npm run apply -- assistants + +# By file (long form, or short form: folder/filename) npm run apply -- resources//assistants/my-agent.md +npm run apply -- assistants/my-agent.md + +# Several files +npm run apply -- assistants/a.md tools/b.yml ``` -`apply` accepts the same path-scoping as `push`, so you get safety + targeted scope in one command. +A bare resource ID (`npm run apply -- my-agent`, with no folder or +extension) is rejected with `Unrecognized argument: my-agent`, rather than +falling through to a full deploy. Pass a type or a path. + +When you deploy a single squad or assistant, its missing dependencies are +created first: + +``` +Squad push + └─ missing assistants? → auto-create them first + └─ missing tools / structured outputs? → auto-create those first + └─ all references resolved → create the squad ✓ +``` -## First push into a fresh org +## Start in a fresh org ```bash # Interactive wizard — pick "no resources" if you'll author from scratch. @@ -33,47 +57,89 @@ npm run validate -- npm run apply -- ``` -On a fresh org, `apply`'s pull phase bootstraps `.vapi-state..json` from the empty dashboard before pushing your local creates. +On a fresh org, `apply`'s pull phase creates `.vapi-state..json` from +the empty dashboard before pushing your new resources. -## Creating new resources after the first push (orphan-YAML gate) +## Create new resources after the first deploy -After the initial setup, **`push` refuses by default** when it sees a local YAML file that has no entry in the state file. The engine can't tell whether the file is: +After the initial setup, **`push` and `apply` stop** when they find a local +file with no entry in the state file. The engine can't tell whether the file +is: - (a) a NEW resource you intentionally want to create, -- (b) a RENAME of an existing resource (state has the old slug; YAML has the new name), or -- (c) a MOVED file (file copied or restored without state being re-keyed). +- (b) a RENAME of an existing resource (state has the old name; the file has the new one), or +- (c) a MOVED file (copied or restored without the state being updated). -Silently treating every orphan as case (a) used to spawn duplicates on the dashboard. The gate halts push with a verbose message listing every orphan, and pairs each orphan with possible "rename source" candidates (state entries with no matching local file that share a base slug). +Treating every one as new would create duplicates on the platform, so the +deploy halts with a message listing each file, paired with possible "rename +source" candidates (state entries with no matching local file that share a +base name). -To proceed when the orphans are genuinely new resources: +When the files are genuinely new resources: ```bash -npm run push -- --allow-new-files +npm run apply -- --allow-new-files ``` -This works on `apply` too: `npm run apply -- --allow-new-files` propagates the flag through to the push stage. +Check each listed file before you pass the flag: it confirms every one is +new. If a coding agent runs your deploys, it should show you the list rather +than pass the flag itself; [`AGENTS.md`](../../AGENTS.md) instructs it to. -**For AI agents**: do NOT auto-pass `--allow-new-files` without confirming with the human. The gate's verbose message is designed to be surfaced to the user so they can reclassify each orphan (new vs rename vs cruft). Silent bypass defeats the gate. +The check is skipped for: -Suppressed automatically: -- Explicit `--bootstrap` runs (population-from-scratch is expected to be all-new). -- Files matched by `.vapi-ignore` (the engine wasn't going to upload them anyway). -- Selective push (`-- `) where the orphan is outside the selection. +- explicit `--bootstrap` runs, where everything is expected to be new; +- files matched by `.vapi-ignore`, which aren't uploaded anyway; +- files outside a scoped deploy's selection. -## Pulling without losing local work +## Pull without losing local work + +By default, `pull` preserves any files you've modified or deleted locally: ```bash -# Local-first by default — won't overwrite locally modified files. npm run pull -- +# ⏭️ my-assistant (locally changed, skipping) +# ✨ new-tool -> resources/my-org/tools/new-tool.yml +``` + +Detection works in three layers, so it covers both day-to-day and +fresh-clone workflows: -# State-only refresh — re-sync UUID mappings without writing resource files locally. +1. **Content baseline (primary)** — each resource's last-seen platform hash + lives in the per-developer `.vapi-state-hash//` store + (gitignored). Comparing local / baseline / dashboard hashes classifies + every resource as clean, local-ahead (preserved ⬆️), dashboard-ahead + (synced down ⬇️ — local was unchanged, nothing to lose), or both-diverged + (gated behind `--resolve=ours|theirs|fail|defer`). See + [sync behavior](../learnings/sync-behavior.md) for the full matrix. +2. **Git-tracked changes** — when no baseline exists yet, files that show up + in `git status` (modified, deleted, or individually untracked) are + preserved. +3. **mtime fallback** — if git can't help (no commits yet, the resource tree + isn't tracked at all, or git just had nothing to say), files that are + newer than `.vapi-state..json` are still preserved. This is the safety + net for the "fresh clone, edit a file, run pull again" case. + +Interactive `npm run pull` is local-first too: it asks +`Overwrite locally modified files?` (default `No`). Pass `--force` (or answer +`Yes`) to overwrite everything with the platform version. + +Other ways to pull: + +```bash +# Refresh IDs and bindings only, without writing resource files. npm run pull -- --bootstrap -# Pull a single known remote resource by UUID. -npm run pull -- --type assistants --id +# Pull one known resource by UUID (--id needs exactly one --type). +npm run pull -- --type squads --id ``` -## Live testing what you just deployed +`--bootstrap` refreshes `.vapi-state..json`, credential mappings, and the +generated credential and phone-number block in `.env.`, and leaves +`resources//` untouched. Setup and ordinary pulls do the same binding +refresh, and `push` runs it automatically when the state looks empty or +stale. + +## Test what you deployed ```bash # Interactive WebSocket call — speak/listen from the terminal. @@ -84,7 +150,10 @@ npm run call -- -s npm run sim -- --suite --target ``` -## Recovering from a bad deploy +To test changes before they're deployed, on every pull request, see +[PR checks](pr-checks.md). + +## Recover from a bad deploy ```bash # Every push/apply writes a snapshot first. List them: @@ -94,7 +163,7 @@ npm run rollback -- --list npm run rollback -- --to ``` -## Cleaning up orphaned dashboard resources +## Clean up platform resources that have no file ```bash # Dry-run by default — shows what would be deleted, makes no changes. @@ -104,7 +173,10 @@ npm run cleanup -- npm run cleanup -- --force --confirm ``` -**Surgical alternative when the orphan set includes Vapi-default fixtures** (e.g. the seven undeletable stock simulation personalities — see `docs/learnings/simulations.md`): delete individual resources via direct API call, then refresh state: +**When the list includes Vapi's built-in fixtures** (for example the stock +simulation personalities, which can't be deleted — see +[simulations](../learnings/simulations.md)), delete the others individually +through the API, then refresh state: ```bash curl -X DELETE -H "Authorization: Bearer $VAPI_PRIVATE_API_KEY" \ @@ -112,11 +184,14 @@ curl -X DELETE -H "Authorization: Bearer $VAPI_PRIVATE_API_KEY" \ npm run pull -- --bootstrap ``` -This avoids `--force` halting on the first immortal-default 404. +This avoids `--force` stopping at the first built-in resource it can't +delete. ## When to use raw `push` instead of `apply` -Almost never. The only honest case: you just ran `pull`, nothing else has touched the dashboard since, and you need to skip the merge pass for speed. In any multi-developer environment, default to `apply`. +Almost never. The only honest case: you just ran `pull`, nothing else has +touched the dashboard since, and you need to skip the merge pass for speed. +In any multi-developer environment, default to `apply`. If you do use `push`, dry-run it first: @@ -128,107 +203,6 @@ npm run push -- --dry-run 1. `git status` — uncommitted changes are intentional? 2. `npm run validate -- ` — schema clean? -3. `npm run apply -- ` (or `apply -- ` for single-file) +3. `npm run apply -- ` (or `apply -- ` for a single file) 4. After: verify with `npm run call -- -a ` or a `npm run sim` suite 5. If something looks wrong: `npm run rollback -- --list` - -## How to Use This Repo - -1. **Run `npm run setup`** to configure your first org (or `npm run setup -- ` without a terminal) -2. **Edit resources** in `resources//` (`.md` assistants, `.yml` tools/squads/etc.) -3. **Validate** with `npm run validate -- ` -4. **Deploy** with `npm run apply -- ` (pull → merge → push) - -Use: - -- `apply` for deploys — the default; safe against dashboard edits made since your last pull -- `pull` when Vapi might have changed and you only want to sync down -- `push` only right after a `pull`, when nothing else has touched the dashboard (see [When to use raw `push`](#when-to-use-raw-push-instead-of-apply)) - -### Bootstrap State Sync - -Use bootstrap pull when you need the latest platform IDs and org-local bindings without downloading all remote resources: - -```bash -npm run pull -- my-org --bootstrap -``` - -This refreshes `.vapi-state..json`, credential mappings, and the generated credential/phone-number block in `.env.` while leaving `resources//` untouched. Setup and ordinary pulls perform the same binding refresh. If you skip this step, `push` will automatically run it when it detects empty or stale state. - -### Pulling a Single Resource By UUID - -```bash -npm run pull -- my-org --type squads --id -``` - -`--id` must be paired with exactly one resource type. - -### Pulling Without Losing Local Work - -By default, `pull` preserves any files you've locally modified or deleted: - -```bash -npm run pull -- my-org -# ⏭️ my-assistant (locally changed, skipping) -# ✨ new-tool -> resources/my-org/tools/new-tool.yml -``` - -Detection works in three layers, so it covers both day-to-day and fresh-clone -workflows: - -1. **Content baseline (primary)** — each resource's last-seen platform hash - lives in the per-developer `.vapi-state-hash//` store - (gitignored). Comparing local / baseline / dashboard hashes classifies - every resource as clean, local-ahead (preserved ⬆️), dashboard-ahead - (synced down ⬇️ — local was unchanged, nothing to lose), or both-diverged - (gated behind `--resolve=ours|theirs|fail|defer`). See - `docs/learnings/sync-behavior.md` for the full matrix. -2. **Git-tracked changes** — when no baseline exists yet, files that show up - in `git status` (modified, deleted, or individually untracked) are - preserved. -3. **mtime fallback** — if git can't help (no commits yet, the resource tree - isn't tracked at all, or git just had nothing to say), files that are - newer than `.vapi-state..json` are still preserved. This is the safety - net for the "fresh clone, edit a file, run pull again" case. - -Interactive `npm run pull` defaults to local-first too — it asks -`Overwrite locally modified files?` (default `No`) before forwarding the -pull. Pass `--force` directly (or answer `Yes` to that prompt) to overwrite -everything with the platform version. - -### Selective Push - -Push only specific resources instead of everything: - -```bash -# By resource type -npm run push -- my-org assistants -npm run push -- my-org tools - -# By specific file (long form) -npm run push -- my-org resources/my-org/assistants/my-assistant.md - -# By specific file (short form — folder/filename) -npm run push -- my-org assistants/my-assistant.md -npm run push -- my-org simulations/personalities/skeptical-sam.yml - -# Multiple files -npm run push -- my-org resources/my-org/assistants/a.md resources/my-org/tools/b.yml -``` - -> A bare resource id like `npm run push -- my-org my-assistant` (no folder, -> no extension) is **rejected explicitly**. The CLI prints -> `Unrecognized argument: my-assistant` and exits with a non-zero code rather -> than silently falling through to a full apply. Pass either a type -> (`assistants`) or a path (`assistants/my-assistant.md`). - -### Auto-Dependency Resolution - -When pushing a single squad or assistant, missing dependencies (tools, structured outputs, etc.) are automatically created first: - -``` -Squad push - └─ missing assistants? → auto-create them first - └─ missing tools / structured outputs? → auto-create those first - └─ all references resolved → create the squad ✓ -```