@sequenceholdings/studio-cli 0.1.18 → 0.1.22

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,14 +1,18 @@
1
1
  # `@sequenceholdings/studio-cli` — `seq-studio`
2
2
 
3
- Standalone CLI for the Sequence platform: Lattice processes, Artifact Studio
4
- apps, Managed Functions, Managed Secrets, Data Pipelines stage specs, and platform git repos. Runs from
5
- any repo against the platform over HTTP — no monorepo checkout required.
3
+ Standalone CLI for the Sequence platform: typed agents, Lattice processes, Artifact Studio
4
+ apps, Managed Functions, Managed Secrets, ORM namespaces, Data Pipelines stage
5
+ specs, and platform git repos. Runs from any repo against the platform over
6
+ HTTP — no monorepo checkout required.
6
7
 
7
8
  ```
8
9
  seq-studio process lint
9
10
  seq-studio process plan -e <env>
10
11
  seq-studio process apply -e <env>
12
+ seq-studio agents validate
13
+ seq-studio agents plan -e <env>
11
14
  seq-studio artifact deploy -e <env>
15
+ seq-studio orm init lending
12
16
  seq-studio pipeline validate
13
17
  seq-studio envs list
14
18
  seq-studio doctor
@@ -16,8 +20,8 @@ seq-studio doctor
16
20
 
17
21
  > **Note:** all network commands require a Sequence platform account with the
18
22
  > appropriate permissions. Without one, only the offline commands
19
- > (`init`, `lint`, `simulate`, `build`, `bundle inspect`, `pipeline init`,
20
- > `pipeline validate`) work.
23
+ > (`init`, `lint`, `simulate`, `build`, `bundle inspect`, `agents init`,
24
+ > `agents validate`, offline `agents plan`, `pipeline init`, `pipeline validate`) work.
21
25
  > Sequence-internal contributors: see `INTERNAL.md` in the monorepo for
22
26
  > rollout SOPs, preview environments, and publishing docs.
23
27
 
@@ -64,12 +68,14 @@ Use `seq-studio logout` to remove the shared Sequence session, or
64
68
  ### Headless auth (CI) — M2M
65
69
 
66
70
  When there's no interactive login (CI, automation), set the service-account
67
- secret and `seq-studio` mints a token via the Auth0 client-credentials grant
68
- (M2M wins when the secret is set, otherwise the cached user token is used):
71
+ secret and `seq-studio` mints a token via the Auth0 client-credentials grant.
72
+ A valid cached user session wins over an ambient M2M secret (common under
73
+ Doppler `atlas/dev`); force the service account with `SEQAPI_AUTH_MODE=m2m`:
69
74
 
70
75
  ```bash
71
76
  export AUTH0_M2M_CLIENT_SECRET=... # built-in Sequence environments
72
77
  export AUTH0_M2M_CLIENT_SECRET_BSM_STAGING=... # registered bsm-staging realm
78
+ export SEQAPI_AUTH_MODE=m2m # optional: ignore a leftover user session
73
79
  seq-studio artifact deploy -e <env>
74
80
  ```
75
81
 
@@ -128,6 +134,107 @@ Pass `--env <name>` (or `-e <name>`) on commands that talk to the platform.
128
134
  the artifact folder's `.artifact-studio/config.json` `defaultEnv` (set by
129
135
  `artifact link` / `artifact env use`).
130
136
 
137
+ ## Agent commands
138
+
139
+ Typed agent repositories export one or more `defineAgent(...)` values from files
140
+ named `agent.ts`. Definitions are compiled in a credential-scrubbed child process,
141
+ validated with the published `@sequenceholdings/agent-spec` contract, normalized,
142
+ and hashed before deployment. Apply creates or updates only the definitions in the
143
+ bundle; it never implicitly deletes agents.
144
+
145
+ | Command | What it does |
146
+ |---------|--------------|
147
+ | `seq-studio agents init <dir>` | Scaffold a standalone typed agent repository |
148
+ | `seq-studio agents validate [--dir <dir>] [--target <APP_ENV>] [--only <id1,id2>]` | Compile and validate locally, without API access |
149
+ | `seq-studio agents plan [--dir <dir>] [--only <id1,id2>]` | Offline compile/hash plan |
150
+ | `seq-studio agents plan [--dir <dir>] -e <env> [--target <APP_ENV>] [--only <id1,id2>]` | Diff creates, updates, and unchanged definitions against an environment |
151
+ | `seq-studio agents apply [--dir <dir>] -e <env> [--target <APP_ENV>] [--only <id1,id2>] [--yes]` | Apply creates and updates after showing the plan |
152
+ | `seq-studio agents list -e <env>` | List visible runtime agents |
153
+ | `seq-studio agents show <id> -e <env>` | Show one runtime agent |
154
+
155
+ For `validate`, `plan`, and `apply`, `--only` accepts a comma-separated list of
156
+ agent IDs after deployment-environment selection; every requested ID must be
157
+ selected.
158
+
159
+ An optional `deploy-manifest.json` targets definitions by deployment identity:
160
+
161
+ ```json
162
+ {
163
+ "schemaVersion": 1,
164
+ "definitions": [
165
+ {
166
+ "id": "680000000000000000000001",
167
+ "path": "support/agent.ts",
168
+ "environments": ["local", "staging", "production"]
169
+ }
170
+ ]
171
+ }
172
+ ```
173
+
174
+ Standalone workflow:
175
+
176
+ ```bash
177
+ pnpm dlx @sequenceholdings/studio-cli agents init support-agent
178
+ cd support-agent
179
+ pnpm install
180
+ pnpm exec seq-studio agents validate
181
+ pnpm exec seq-studio agents plan
182
+ # Authenticate only when ready to inspect or apply an environment:
183
+ pnpm exec seq-studio login
184
+ pnpm exec seq-studio agents plan -e <env>
185
+ pnpm exec seq-studio agents apply -e <env>
186
+ ```
187
+
188
+ Use `--repo agents/<name> [--ref <ref>]` or `--git-url <url>` instead of
189
+ `--dir` to materialize reviewed source from the platform git service.
190
+
191
+ ## ORM commands
192
+
193
+ `seq-studio orm` authors and deploys governed ORM v2 namespaces: TypeScript
194
+ table definitions, Drizzle-authored read-only views, and policies plus named
195
+ GraphQL documents compiled into persisted operations. Import Drizzle query
196
+ helpers from `@sequenceholdings/orm/drizzle`; managed view builders are
197
+ compiled to canonical SQL before registration.
198
+
199
+ | Command | What it does |
200
+ |---------|--------------|
201
+ | `seq-studio orm init <dir>` | Scaffold one v2 namespace package (`sequence.config.ts`, `schema/*.ts`, `graphql/**`, and codegen config). |
202
+ | `seq-studio orm generate [dir]` | Generate `schema.graphql`, `operations.manifest.json`, `typePolicies.gen.ts`, and consumer codegen when `codegen.ts` is present. |
203
+ | `seq-studio orm validate [dir]` | Parse and compile the namespace, then verify its committed migration chain is current. |
204
+ | `seq-studio orm plan [dir] -e <env>` | Compare the compiled namespace with registry state without applying database changes. |
205
+ | `seq-studio orm diff [dir] [--check]` | Author the next committed migration, or verify the migration/snapshot chain offline for CI. |
206
+ | `seq-studio orm apply [dir] -e <env>` | Author a migration if needed, register/apply the namespace, activate its operation set, publish roles/capabilities, and refresh generated outputs. |
207
+ | `seq-studio orm migrate-from-yaml <dir>` | Convert a legacy YAML namespace to TypeScript authoring while preserving its committed migrations. |
208
+
209
+ The everyday loop is:
210
+
211
+ ```bash
212
+ seq-studio orm init lending
213
+ cd lending
214
+ pnpm install
215
+
216
+ # Edit sequence.config.ts, schema/*.ts, and graphql/**/*.ts.
217
+ seq-studio orm generate .
218
+ seq-studio orm plan . -e local
219
+ seq-studio orm apply . -e local
220
+ seq-studio orm validate .
221
+ ```
222
+
223
+ `apply --dry-run` rehearses a migration against a disposable branch copy of
224
+ the target environment's data. `apply --no-create` is the CI guard that refuses
225
+ to provision a missing namespace. Destructive DDL requires explicit
226
+ `--allow-destructive` consent.
227
+
228
+ > **ORM v2 branch status:** the scaffold currently depends on
229
+ > `@sequenceholdings/orm@^2.0.0`, which is not published yet; the registry's
230
+ > latest release is the v1 `0.1.1` package. A clean standalone scaffold cannot
231
+ > complete `pnpm install` until the v2 ORM dependency chain is released. Do not
232
+ > use monorepo workspace links as evidence that the external workflow is ready.
233
+ > Sequence contributors should see
234
+ > [`docs/platform/orm-v2.md`](../../../docs/platform/orm-v2.md) for the branch
235
+ > architecture, internal verification workflow, demo, and remaining rollout
236
+ > gates.
237
+
131
238
  ## Process commands
132
239
 
133
240
  | Command | What it does |
@@ -193,6 +300,81 @@ cd my-processes
193
300
  seq-studio process lint # finds both processes
194
301
  ```
195
302
 
303
+ ## Managed Function commands
304
+
305
+ | Command | What it does |
306
+ |---------|--------------|
307
+ | `seq-studio functions init <dir>` | Scaffold one standalone TypeScript managed function |
308
+ | `seq-studio functions build [--dir <dir>]` | Validate a local manifest, bundle, lockfile, and size |
309
+ | `seq-studio functions deploy --dir <dir> -e <env>` | Preview secrets and deploy a local function |
310
+ | `seq-studio functions build --repo managed-functions/<name> [--path <dir>] -e <env>` | Materialize and validate a function from the platform Git Service |
311
+ | `seq-studio functions deploy --repo managed-functions/<name> [--path <dir>] -e <env>` | Materialize and deploy a function from the platform Git Service |
312
+
313
+ A normal function repo keeps `managed-function.yml` at its root and omits
314
+ `--path`. A repo may also contain related, independently deployed functions:
315
+
316
+ ```text
317
+ functions/
318
+ get-loan/
319
+ managed-function.yml
320
+ package.json
321
+ pnpm-lock.yaml
322
+ index.ts
323
+ update-loan/
324
+ managed-function.yml
325
+ package.json
326
+ pnpm-lock.yaml
327
+ index.ts
328
+ ```
329
+
330
+ Select exactly one function directory for each build or deploy:
331
+
332
+ ```bash
333
+ seq-studio functions build --repo managed-functions/encompass \
334
+ --path functions/get-loan -e staging
335
+ seq-studio functions deploy --repo managed-functions/encompass \
336
+ --path functions/get-loan -e staging
337
+ ```
338
+
339
+ Each selected directory is a self-contained function package. Functions in the
340
+ same repo share Git review and commit provenance, but keep separate manifests,
341
+ versions, runtime resources, secrets, and permissions. `--path` accepts only a
342
+ canonical relative directory inside a remote repo; use `--dir` for local source.
343
+
344
+ ### Scaling
345
+
346
+ Managed functions scale to zero by default. Set a bounded warm pool in
347
+ `managed-function.yml` when first-request latency matters:
348
+
349
+ ```yaml
350
+ limits:
351
+ min_instances: 1
352
+ max_instances: 3
353
+ ```
354
+
355
+ `min_instances` defaults to `0`, cannot exceed `max_instances`, and incurs
356
+ Cloud Run idle-instance charges while warm.
357
+
358
+ ### ORM data access
359
+
360
+ A function declares its ORM Data API reach in `capabilities.data`, grouped by
361
+ namespace: `tables` it may read, v1 `actions` and ORM v2 persisted `operations`
362
+ it may invoke, and whether raw read `query` is allowed. At invoke time the
363
+ platform mints a short-lived data token scoped to exactly these refs — an
364
+ operation is scoped as `<namespace>/ops/<OperationName>` (the GraphQL operation
365
+ name from the namespace's `graphql/` documents, case-sensitive), and anything
366
+ undeclared is denied by the Data API. A function that reaches any namespace
367
+ must also attach a top-level `service_account`:
368
+
369
+ ```yaml
370
+ service_account: lucky-svc
371
+ capabilities:
372
+ data:
373
+ lucky:
374
+ tables: [lucky_draws]
375
+ operations: [RollLuckyNumber]
376
+ ```
377
+
196
378
  ## Artifact commands
197
379
 
198
380
  `seq-studio artifact <sub>` is the entry point for Artifact Studio. It runs
@@ -205,11 +387,11 @@ routed through `~/.config/lattice/config.toml` and the shared seqapi token.
205
387
  | `seq-studio artifact link [dir] -e <env> [--project <id>]` | `link [dir] --env <env>` |
206
388
  | `seq-studio artifact build [dir]` | `build [dir]` |
207
389
  | `seq-studio artifact plan [dir] -e <env>` | `plan [dir] --env <env>` |
208
- | `seq-studio artifact deploy [dir] -e <env> [--skip-unchanged] [--no-create] [--project <id>]` | `deploy [dir] --env <env>` — `--skip-unchanged` no-ops (before building) when the remote active deployment's `sourceHash` already matches the local source; `--no-create` errors instead of creating a missing project; `--project <id>` targets a project directly when duplicate slugs make the lookup ambiguous (slug must still match the manifest) |
390
+ | `seq-studio artifact deploy [dir] -e <env> [--skip-unchanged] [--no-create] [--project <id>]` | `deploy [dir] --env <env>` — `--skip-unchanged` no-ops (before building) when the remote active deployment's `sourceHash` **and** CLI/atlas-ui peer versions already match; `--no-create` errors instead of creating a missing project; `--project <id>` targets a project directly when duplicate slugs make the lookup ambiguous (slug must still match the manifest) |
209
391
  | `seq-studio artifact dev [dir] -e <env>` | `dev [dir] --env <env>` |
210
392
  | `seq-studio artifact pull <project-id> -e <env> [--out <dir>]` | `pull <project-id> --env <env> [--out <dir>]` |
211
393
  | `seq-studio artifact list -e <env>` | `list --env <env>` — projects visible on the environment (slug, id, active version, visibility) |
212
- | `seq-studio artifact show <slug-or-id> -e <env>` | `show <slug-or-id> --env <env>` — one project's detail incl. active deployment + git provenance |
394
+ | `seq-studio artifact show <slug-or-id> -e <env>` | `show <slug-or-id> --env <env>` — one project's detail incl. active deployment, git provenance, and (for newer deployments) the building `artifact-studio` / force-aliased `atlas-ui` versions |
213
395
  | `seq-studio artifact promote <deployment-id> -e <env>` | `promote <deployment-id> --env <env>` |
214
396
  | `seq-studio artifact rollback <deployment-id> -e <env>` | `rollback <deployment-id> --env <env>` |
215
397
 
@@ -219,6 +401,18 @@ routed through `~/.config/lattice/config.toml` and the shared seqapi token.
219
401
  directory's `.artifact-studio/config.json` — run them from the linked artifact
220
402
  folder (or run `seq-studio artifact link` first).
221
403
 
404
+ **Active (official) deploys require a clean `main` checkout with a resolved
405
+ git commit** for both Git Service (`--repo`) and local sources. Feature
406
+ branches, dirty trees, non-git sources, and detached checkouts that cannot be
407
+ attributed to `main` are preview-only
408
+ (`seq-studio artifact dev` / preview channel); promotion applies the same
409
+ provenance check.
410
+
411
+ **CLI atlas-ui stamp (DES-254):** builds force-alias `@sequenceholdings/atlas-ui`
412
+ to the CLI's copy — not the artifact's declared semver. Deployments record
413
+ `cliVersion` + `atlasUiVersion`. Contract:
414
+ `@sequenceholdings/artifact-studio` → `VERSION-PIN.md`.
415
+
222
416
  ### Source for `build` / `plan` / `deploy`
223
417
 
224
418
  These three commands read their source tree from one of three places — the rest
@@ -230,8 +424,10 @@ of the pipeline (build → upload) is identical regardless of origin:
230
424
  | `--repo <ns>/<name>` | a repo in the platform Git Service, e.g. `--repo artifacts/accounts` |
231
425
  | `--git-url <url>` | a public, credential-free HTTPS git URL (cloned to a temp dir) |
232
426
 
233
- `--ref <branch\|tag\|commit>` selects the revision (default: the repo's default
234
- branch). A remote source is materialized to a temp dir, built, and torn down;
427
+ `--ref <branch\|tag\|commit>` selects the revision for `build` and `plan`
428
+ (default: the repo's default branch). Active `deploy` requires `main`; other
429
+ refs are preview-only. A remote source is materialized to a temp dir, built,
430
+ and torn down;
235
431
  the target project is resolved from the source's `artifact.bundle.yml`
236
432
  `project_id`. The deployment records the resolved commit as its `git_commit`
237
433
  provenance. `--repo` needs `-e <env>` + a platform login. M2M/CI builds accept
@@ -311,11 +507,34 @@ JSON API — the same repos `--repo <ns>/<name>` sources build from.
311
507
  | `seq-studio repos clone <ns>/<name> \| --url <clone-url> \| --id <uuid> -e <env> [--ref <r>] [--out <dir>] [--force]` | smart-HTTP `git clone` when `ATLAS_GIT_PAT` is set (`--url`/`--id` need no seqapi); otherwise JSON materialize + PAT hint |
312
508
  | `seq-studio repos pull <ns>/<name> -e <env> [--ref <r>] [--out <dir>] [--force]` | always materialize via JSON API (no `.git` dir); refuses a non-empty destination unless `--force` |
313
509
  | `seq-studio repos delete <ns>/<name> -e <env> [--yes]` | delete a repo — interactive confirm unless `--yes` |
510
+ | `seq-studio repos ci show <ns>/<name> -e <env> [--ref <r>]` | preview CI checks discovered from the ref |
511
+ | `seq-studio repos ci require <ns>/<name> --check <name> -e <env>` | reserved for requiring a named CI check; currently refuses to write until the sandboxed runner is live |
512
+ | `seq-studio repos ci import <ns>/<name> -e <env> [--ref <r>]` | reserved for requiring every discovered check; currently refuses to write until the sandboxed runner is live |
314
513
 
315
514
  `show` prints the smart-HTTP clone URL (`…/repos/<id>/git`). Basic auth:
316
515
  any username, PAT as password. Prefer `repos clone` over hand-rolling the
317
516
  tree API.
318
517
 
518
+ PR CI discovers `ci/check` from the first available `lint`, `typecheck`, or
519
+ `check` script and `ci/test` from `test`. A `.seq/ci.json` takes precedence;
520
+ its `checks` array can declare script/argv checks or be empty to opt out.
521
+
522
+ For example:
523
+
524
+ { "checks": [
525
+ { "phase": "check", "script": "lint" },
526
+ { "phase": "test", "command": ["pnpm", "test"] }
527
+ ] }
528
+
529
+ Each check needs `phase` (`check` or `test`) and exactly one of `script` or
530
+ `command`; `name` is optional and otherwise defaults to `ci/<phase>`
531
+ (`-2`, etc. for additional checks in that phase).
532
+ Discovery alone never blocks a merge. Until the sandboxed executor is live,
533
+ `repos ci show` is preview-only, Settings controls are disabled, and
534
+ `require`/`import` refuse to write (discovery currently posts `neutral`
535
+ check-runs). Once the executor is live, repo owners can opt in by requiring
536
+ check names in Settings or with `repos ci require`/`repos ci import`.
537
+
319
538
  ## Pipeline commands
320
539
 
321
540
  `seq-studio pipeline <sub>` authors and validates Data Pipelines **stage
@@ -330,6 +549,8 @@ install it alongside the CLI to use this family.
330
549
  | `seq-studio pipeline validate [dir] [--assets <file\|url>] [--json]` | Run the full offline spec gate: envelope + body validation, `schema_ref` resolution, and repo-level graph validation (reference resolution, single-writer, cycles, column subsets, serving projection checks). Exit 0/1 |
331
550
  | `seq-studio pipeline plan --repo pipelines/<slug> --ref <sha\|branch> -e <env> [--json]` | Plan a Pipeline deploy (materialize → SDK/`validateSpecGraph` → compile → live-diff → provision findings). Does **not** run Databricks `bundle validate` (that is a Trigger deploy-path hard gate). Exit 1 on destructive findings (CI-safe). `--json` emits the stable plan envelope |
332
551
  | `seq-studio pipeline deploy --repo pipelines/<slug> --ref <sha> -e <env> [--approved-by <sub>] [--no-wait]` | Plan then enqueue deploy; Trigger runs `bundle validate` then `bundle deploy` against reviewed bytes. Polls to terminal unless `--no-wait`. Production/banksouth require a pinned 40-hex SHA (client + server) |
552
+ | `seq-studio pipeline adopt --stage <slug> --ref <sha\|branch> -e <env> --native-id <id> --approved-by <you> [--resource-key <key>] [--kind job\|dlt_pipeline] [--old-source-removal-pr <url>] [--repo pipelines/<slug>]` | Bind a live Databricks job/pipeline into the stage without recreation (`bundle deployment bind` on Trigger). Always requires `--approved-by` naming the caller. When the key is still in the monorepo DAB, pass `--old-source-removal-pr` and follow the returned cutover checklist: unbind the old bundle state without deleting the remote, then remove its DAB declaration and add the target-specific adopted-resource entry in the same PR before redeploying. |
553
+ | `seq-studio pipeline unbind --stage <slug> --ref <sha\|branch> -e <env> --approved-by <you> [--resource-key <key>] [--repo pipelines/<slug>]` | Release an adopted binding on Trigger; the remote object stays live (never deleted) |
333
554
  | `seq-studio pipeline run-now --stage <slug> -e <env> [--repo pipelines/<slug>] [--json]` | Run the stage's active job or DLT pipeline immediately and print its Databricks run URL |
334
555
  | `seq-studio pipeline promote --stage <slug> --version <v> -e <env> [--repo pipelines/<slug>] [--approved-by <you>] [--no-wait]` | Promote a validated version to another environment. Prod/banksouth require `--approved-by` naming the authenticated caller (approvals are self-recorded); `--repo` disambiguates a slug that exists in multiple Pipelines |
335
556
  | `seq-studio pipeline rollback --stage <slug> -e <env> [--repo pipelines/<slug>] [--approved-by <you>] [--no-wait]` | Redeploy the previously retired deployment's version. Prod/banksouth require `--approved-by` — approvals are explicit even for rollbacks |
@@ -0,0 +1,13 @@
1
+ import { type AgentDefinition } from '@sequenceholdings/agent-spec';
2
+ import { type CommandContext } from '../functions/commands.js';
3
+ export interface ApplySummary {
4
+ created: number;
5
+ updated: number;
6
+ unchanged: number;
7
+ }
8
+ /** Each chunk is hashed over its own definitions — the apply route validates the body it received. */
9
+ export declare function applyInChunks({ context, definitions, source, }: {
10
+ context: CommandContext;
11
+ definitions: AgentDefinition[];
12
+ source: string;
13
+ }): Promise<ApplySummary>;
@@ -0,0 +1,43 @@
1
+ import { hashAgentBundle, } from '@sequenceholdings/agent-spec';
2
+ import { postJson } from '../atlas-client.js';
3
+ import { clientOptions } from '../functions/commands.js';
4
+ const LOG = '[seq-studio]';
5
+ // Chunked to stay inside the HTTP client's timeout; each agent commits independently anyway.
6
+ const APPLY_CHUNK_SIZE = 20;
7
+ /** Each chunk is hashed over its own definitions — the apply route validates the body it received. */
8
+ export async function applyInChunks({ context, definitions, source, }) {
9
+ const chunks = [];
10
+ for (let i = 0; i < definitions.length; i += APPLY_CHUNK_SIZE) {
11
+ chunks.push(definitions.slice(i, i + APPLY_CHUNK_SIZE));
12
+ }
13
+ const total = { created: 0, updated: 0, unchanged: 0 };
14
+ let applied = 0;
15
+ for (const [index, chunk] of chunks.entries()) {
16
+ try {
17
+ const result = await postJson({
18
+ ...clientOptions(context),
19
+ path: '/api/agents/deploy/apply',
20
+ body: {
21
+ definitions: chunk,
22
+ bundleHash: hashAgentBundle({ definitions: chunk }),
23
+ source,
24
+ },
25
+ });
26
+ total.created += result.summary.created;
27
+ total.updated += result.summary.updated;
28
+ total.unchanged += result.summary.unchanged;
29
+ applied += chunk.length;
30
+ if (chunks.length > 1) {
31
+ console.log(`${LOG} batch ${index + 1}/${chunks.length}: ${applied} of ${definitions.length} agents`);
32
+ }
33
+ }
34
+ catch (error) {
35
+ // Name what already landed. Without it a mid-run failure reads as a total
36
+ // failure, and the operator cannot tell which agents are already on the
37
+ // new definition.
38
+ console.error(`${LOG} batch ${index + 1}/${chunks.length} failed after ${applied} of ${definitions.length} agents applied — re-run to resume`);
39
+ throw error;
40
+ }
41
+ }
42
+ return total;
43
+ }
@@ -0,0 +1,10 @@
1
+ import type { ParsedArgs } from '../process/commands.js';
2
+ import { agentsInitCommand } from './scaffold.js';
3
+ export { agentsInitCommand };
4
+ export declare function agentsValidateCommand(args: ParsedArgs): Promise<number>;
5
+ export declare function agentsPlanCommand(args: ParsedArgs): Promise<number>;
6
+ export declare function agentsApplyCommand(args: ParsedArgs): Promise<number>;
7
+ export declare function agentsListCommand(args: ParsedArgs): Promise<number>;
8
+ export declare function agentsShowCommand(args: ParsedArgs): Promise<number>;
9
+ export declare const AGENTS_USAGE = "usage:\n seq-studio agents init <dir> scaffold a typed agent\n seq-studio agents validate [--dir d] [--target app] [--only ids] offline compile + validation\n seq-studio agents plan [--dir d] [-e <env>] [--only ids] offline bundle plan or live diff\n seq-studio agents apply [--dir d] -e <env> [--only ids] [--yes] apply creates/updates; never deletes\n seq-studio agents list -e <env> list visible agents\n seq-studio agents show <id> -e <env> show one agent\n\n Source: local --dir (default .), --repo agents/<name>, or --git-url <url>.\n Use --ref for remote sources. --target selects the deployment APP_ENV when it\n differs from the CLI environment alias (notably OpCo registrations). --only\n accepts a comma-separated list of agent IDs after environment selection.\n";
10
+ export declare function runAgentsCommand(sub: string | undefined, args: ParsedArgs): Promise<number>;
@@ -0,0 +1,238 @@
1
+ import { getJson, postJson } from '../atlas-client.js';
2
+ import { readConfig } from '../config.js';
3
+ import { applyInChunks } from './apply-chunks.js';
4
+ import { confirmYes } from '../prompt.js';
5
+ import { buildContext, clientOptions, flagBool, printError, requestedEnvironment, } from '../functions/commands.js';
6
+ import { compileAgentSource as compileSource, materializeAgentSource as materialize, } from './source.js';
7
+ import { agentsInitCommand } from './scaffold.js';
8
+ export { agentsInitCommand };
9
+ const LOG = '[seq-studio]';
10
+ function requestedAgentIds(args) {
11
+ const only = args.flags.only;
12
+ if (only === undefined)
13
+ return undefined;
14
+ if (typeof only !== 'string') {
15
+ throw new Error('--only must be a comma-separated list of agent IDs');
16
+ }
17
+ const ids = only
18
+ .split(',')
19
+ .map((id) => id.trim())
20
+ .filter(Boolean);
21
+ if (ids.length === 0) {
22
+ throw new Error('--only must include at least one agent ID');
23
+ }
24
+ return [...new Set(ids)];
25
+ }
26
+ export async function agentsValidateCommand(args) {
27
+ const { source } = await materialize({ args, requireEnvironment: false });
28
+ try {
29
+ const target = typeof args.flags.target === 'string' ? args.flags.target : undefined;
30
+ const bundle = await compileSource({
31
+ directory: source.dir,
32
+ targetEnvironment: target,
33
+ deployEnvironments: await deployEnvironmentNames(),
34
+ onlyIds: requestedAgentIds(args),
35
+ });
36
+ if (bundle.definitions.length === 0) {
37
+ console.error(`${LOG} no named agent.ts definitions found`);
38
+ return 1;
39
+ }
40
+ console.log(`${LOG} valid: ${bundle.definitions.length} definition${bundle.definitions.length === 1 ? '' : 's'}, bundle ${bundle.hash}`);
41
+ return 0;
42
+ }
43
+ finally {
44
+ await source.cleanup();
45
+ }
46
+ }
47
+ async function deploymentBundle({ args, source, context, }) {
48
+ const target = (typeof args.flags.target === 'string' ? args.flags.target : undefined) ??
49
+ context.env.name;
50
+ return compileSource({
51
+ directory: source.dir,
52
+ targetEnvironment: target,
53
+ deployEnvironments: await deployEnvironmentNames(),
54
+ onlyIds: requestedAgentIds(args),
55
+ });
56
+ }
57
+ /**
58
+ * Names the CLI can actually deploy to. A target outside both this set and the
59
+ * manifest's own vocabulary is a typo, not a selection.
60
+ */
61
+ async function deployEnvironmentNames() {
62
+ try {
63
+ return Object.keys((await readConfig()).envs);
64
+ }
65
+ catch {
66
+ // Never let an unreadable config block a deploy — the guard is a safety
67
+ // net, and the manifest's own vocabulary still constrains the target.
68
+ return [];
69
+ }
70
+ }
71
+ function printPlan(plan) {
72
+ for (const entry of plan.entries) {
73
+ const marker = entry.action === 'create' ? '+' : entry.action === 'update' ? '~' : '=';
74
+ // Name the fields an update would rewrite, so an operator can tell an
75
+ // intended edit from unexpected drift before confirming an apply.
76
+ const fields = entry.changedFields?.length
77
+ ? `: ${entry.changedFields.join(', ')}`
78
+ : '';
79
+ console.log(` ${marker} ${entry.name} (${entry.id}) ${entry.action}${fields}`);
80
+ }
81
+ console.log(`${LOG} plan: ${plan.summary.creates} create, ${plan.summary.updates} update, ${plan.summary.unchanged} unchanged`);
82
+ }
83
+ export async function agentsPlanCommand(args) {
84
+ const environment = requestedEnvironment(args);
85
+ if (!environment) {
86
+ const { source } = await materialize({ args, requireEnvironment: false });
87
+ try {
88
+ // Honor --target the same way validate does. Without this, offline plan
89
+ // silently compiles the unfiltered manifest set even when the caller
90
+ // asked for a specific environment's overrides.
91
+ const target = typeof args.flags.target === 'string' ? args.flags.target : undefined;
92
+ const bundle = await compileSource({
93
+ directory: source.dir,
94
+ targetEnvironment: target,
95
+ deployEnvironments: await deployEnvironmentNames(),
96
+ onlyIds: requestedAgentIds(args),
97
+ });
98
+ console.log(`${LOG} offline plan: ${bundle.definitions.length} valid definition${bundle.definitions.length === 1 ? '' : 's'}, bundle ${bundle.hash}`);
99
+ console.log(`${LOG} pass -e <env> for create/update/unchanged live diff`);
100
+ return bundle.definitions.length > 0 ? 0 : 1;
101
+ }
102
+ finally {
103
+ await source.cleanup();
104
+ }
105
+ }
106
+ const { source, context } = await materialize({
107
+ args,
108
+ requireEnvironment: true,
109
+ });
110
+ if (!context)
111
+ throw new Error('Missing deployment context');
112
+ try {
113
+ const bundle = await deploymentBundle({ args, source, context });
114
+ const plan = await postJson({
115
+ ...clientOptions(context),
116
+ path: '/api/agents/deploy/plan',
117
+ body: {
118
+ definitions: bundle.definitions,
119
+ bundleHash: bundle.hash,
120
+ source: source.provenance.gitCommit ?? 'local',
121
+ },
122
+ });
123
+ printPlan(plan);
124
+ return 0;
125
+ }
126
+ finally {
127
+ await source.cleanup();
128
+ }
129
+ }
130
+ export async function agentsApplyCommand(args) {
131
+ const { source, context } = await materialize({
132
+ args,
133
+ requireEnvironment: true,
134
+ });
135
+ if (!context)
136
+ throw new Error('Missing deployment context');
137
+ try {
138
+ const bundle = await deploymentBundle({ args, source, context });
139
+ const plan = await postJson({
140
+ ...clientOptions(context),
141
+ path: '/api/agents/deploy/plan',
142
+ body: {
143
+ definitions: bundle.definitions,
144
+ bundleHash: bundle.hash,
145
+ source: source.provenance.gitCommit ?? 'local',
146
+ },
147
+ });
148
+ printPlan(plan);
149
+ const preview = [
150
+ `${LOG} apply ${bundle.definitions.length} agent definitions to ${context.env.name}`,
151
+ `${LOG} bundle ${bundle.hash}`,
152
+ `${LOG} no agents will be deleted`,
153
+ ];
154
+ if (!(await confirmYes({ preview, confirmed: flagBool(args.flags, 'yes') }))) {
155
+ return 1;
156
+ }
157
+ const summary = await applyInChunks({
158
+ context,
159
+ definitions: bundle.definitions,
160
+ source: source.provenance.gitCommit ?? 'local',
161
+ });
162
+ console.log(`${LOG} applied: ${summary.created} created, ${summary.updated} updated, ${summary.unchanged} unchanged`);
163
+ return 0;
164
+ }
165
+ finally {
166
+ await source.cleanup();
167
+ }
168
+ }
169
+ export async function agentsListCommand(args) {
170
+ const context = await buildContext(args);
171
+ const agents = await getJson({
172
+ ...clientOptions(context),
173
+ path: '/api/agents/agents?view=list',
174
+ });
175
+ for (const agent of agents) {
176
+ console.log(`${agent.id} ${agent.name} managed=${agent.managedBy ?? 'user'}`);
177
+ }
178
+ return 0;
179
+ }
180
+ export async function agentsShowCommand(args) {
181
+ const id = args.positional[0];
182
+ if (!id) {
183
+ console.error('usage: seq-studio agents show <id> -e <env>');
184
+ return 1;
185
+ }
186
+ const context = await buildContext(args);
187
+ const agent = await getJson({
188
+ ...clientOptions(context),
189
+ path: `/api/agents/agents/${encodeURIComponent(id)}`,
190
+ });
191
+ console.log(JSON.stringify(agent, null, 2));
192
+ return 0;
193
+ }
194
+ export const AGENTS_USAGE = `usage:
195
+ seq-studio agents init <dir> scaffold a typed agent
196
+ seq-studio agents validate [--dir d] [--target app] [--only ids] offline compile + validation
197
+ seq-studio agents plan [--dir d] [-e <env>] [--only ids] offline bundle plan or live diff
198
+ seq-studio agents apply [--dir d] -e <env> [--only ids] [--yes] apply creates/updates; never deletes
199
+ seq-studio agents list -e <env> list visible agents
200
+ seq-studio agents show <id> -e <env> show one agent
201
+
202
+ Source: local --dir (default .), --repo agents/<name>, or --git-url <url>.
203
+ Use --ref for remote sources. --target selects the deployment APP_ENV when it
204
+ differs from the CLI environment alias (notably OpCo registrations). --only
205
+ accepts a comma-separated list of agent IDs after environment selection.
206
+ `;
207
+ export async function runAgentsCommand(sub, args) {
208
+ try {
209
+ switch (sub) {
210
+ case 'init':
211
+ return await agentsInitCommand(args);
212
+ case 'validate':
213
+ return await agentsValidateCommand(args);
214
+ case 'plan':
215
+ return await agentsPlanCommand(args);
216
+ case 'apply':
217
+ return await agentsApplyCommand(args);
218
+ case 'list':
219
+ return await agentsListCommand(args);
220
+ case 'show':
221
+ return await agentsShowCommand(args);
222
+ case 'help':
223
+ case '--help':
224
+ case '-h':
225
+ case undefined:
226
+ console.log(AGENTS_USAGE);
227
+ return sub ? 0 : 1;
228
+ default:
229
+ console.error(`unknown agents command: ${sub}`);
230
+ console.error(AGENTS_USAGE);
231
+ return 1;
232
+ }
233
+ }
234
+ catch (error) {
235
+ printError(error);
236
+ return 1;
237
+ }
238
+ }
@@ -0,0 +1,2 @@
1
+ import type { ParsedArgs } from '../process/commands.js';
2
+ export declare function agentsInitCommand(args: ParsedArgs): Promise<number>;