@sequenceholdings/studio-cli 0.1.18 → 0.1.21
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 +167 -11
- package/dist/agents/apply-chunks.d.ts +13 -0
- package/dist/agents/apply-chunks.js +43 -0
- package/dist/agents/commands.d.ts +10 -0
- package/dist/agents/commands.js +218 -0
- package/dist/agents/scaffold.d.ts +2 -0
- package/dist/agents/scaffold.js +77 -0
- package/dist/agents/source.d.ts +18 -0
- package/dist/agents/source.js +121 -0
- package/dist/auth.d.ts +19 -17
- package/dist/auth.js +100 -33
- package/dist/functions/commands.d.ts +2 -10
- package/dist/functions/commands.js +17 -25
- package/dist/functions/source-selection.d.ts +24 -0
- package/dist/functions/source-selection.js +67 -0
- package/dist/main.d.ts +1 -0
- package/dist/main.js +6 -0
- package/dist/orm/delegate.js +11 -6
- package/dist/process/build.js +2 -1
- package/dist/process/lint.js +8 -0
- package/package.json +8 -6
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
|
|
5
|
-
any repo against the platform over
|
|
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`, `
|
|
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
|
-
|
|
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,101 @@ 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>]` | Compile and validate locally, without API access |
|
|
149
|
+
| `seq-studio agents plan [--dir <dir>]` | Offline compile/hash plan |
|
|
150
|
+
| `seq-studio agents plan [--dir <dir>] -e <env> [--target <APP_ENV>]` | Diff creates, updates, and unchanged definitions against an environment |
|
|
151
|
+
| `seq-studio agents apply [--dir <dir>] -e <env> [--target <APP_ENV>] [--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
|
+
An optional `deploy-manifest.json` targets definitions by deployment identity:
|
|
156
|
+
|
|
157
|
+
```json
|
|
158
|
+
{
|
|
159
|
+
"schemaVersion": 1,
|
|
160
|
+
"definitions": [
|
|
161
|
+
{
|
|
162
|
+
"id": "680000000000000000000001",
|
|
163
|
+
"path": "support/agent.ts",
|
|
164
|
+
"environments": ["local", "staging", "production"]
|
|
165
|
+
}
|
|
166
|
+
]
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Standalone workflow:
|
|
171
|
+
|
|
172
|
+
```bash
|
|
173
|
+
pnpm dlx @sequenceholdings/studio-cli agents init support-agent
|
|
174
|
+
cd support-agent
|
|
175
|
+
pnpm install
|
|
176
|
+
pnpm exec seq-studio agents validate
|
|
177
|
+
pnpm exec seq-studio agents plan
|
|
178
|
+
# Authenticate only when ready to inspect or apply an environment:
|
|
179
|
+
pnpm exec seq-studio login
|
|
180
|
+
pnpm exec seq-studio agents plan -e <env>
|
|
181
|
+
pnpm exec seq-studio agents apply -e <env>
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Use `--repo agents/<name> [--ref <ref>]` or `--git-url <url>` instead of
|
|
185
|
+
`--dir` to materialize reviewed source from the platform git service.
|
|
186
|
+
|
|
187
|
+
## ORM commands
|
|
188
|
+
|
|
189
|
+
`seq-studio orm` authors and deploys governed ORM v2 namespaces: TypeScript
|
|
190
|
+
table definitions and policies plus named GraphQL documents compiled into
|
|
191
|
+
persisted operations.
|
|
192
|
+
|
|
193
|
+
| Command | What it does |
|
|
194
|
+
|---------|--------------|
|
|
195
|
+
| `seq-studio orm init <dir>` | Scaffold one v2 namespace package (`sequence.config.ts`, `schema/*.ts`, `graphql/**`, and codegen config). |
|
|
196
|
+
| `seq-studio orm generate [dir]` | Generate `schema.graphql`, `operations.manifest.json`, `typePolicies.gen.ts`, and consumer codegen when `codegen.ts` is present. |
|
|
197
|
+
| `seq-studio orm validate [dir]` | Parse and compile the namespace, then verify its committed migration chain is current. |
|
|
198
|
+
| `seq-studio orm plan [dir] -e <env>` | Compare the compiled namespace with registry state without applying database changes. |
|
|
199
|
+
| `seq-studio orm diff [dir] [--check]` | Author the next committed migration, or verify the migration/snapshot chain offline for CI. |
|
|
200
|
+
| `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. |
|
|
201
|
+
| `seq-studio orm migrate-from-yaml <dir>` | Convert a legacy YAML namespace to TypeScript authoring while preserving its committed migrations. |
|
|
202
|
+
|
|
203
|
+
The everyday loop is:
|
|
204
|
+
|
|
205
|
+
```bash
|
|
206
|
+
seq-studio orm init lending
|
|
207
|
+
cd lending
|
|
208
|
+
pnpm install
|
|
209
|
+
|
|
210
|
+
# Edit sequence.config.ts, schema/*.ts, and graphql/**/*.ts.
|
|
211
|
+
seq-studio orm generate .
|
|
212
|
+
seq-studio orm plan . -e local
|
|
213
|
+
seq-studio orm apply . -e local
|
|
214
|
+
seq-studio orm validate .
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
`apply --dry-run` rehearses a migration against a disposable branch copy of
|
|
218
|
+
the target environment's data. `apply --no-create` is the CI guard that refuses
|
|
219
|
+
to provision a missing namespace. Destructive DDL requires explicit
|
|
220
|
+
`--allow-destructive` consent.
|
|
221
|
+
|
|
222
|
+
> **ORM v2 branch status:** the scaffold currently depends on
|
|
223
|
+
> `@sequenceholdings/orm@^2.0.0`, which is not published yet; the registry's
|
|
224
|
+
> latest release is the v1 `0.1.1` package. A clean standalone scaffold cannot
|
|
225
|
+
> complete `pnpm install` until the v2 ORM dependency chain is released. Do not
|
|
226
|
+
> use monorepo workspace links as evidence that the external workflow is ready.
|
|
227
|
+
> Sequence contributors should see
|
|
228
|
+
> [`docs/platform/orm-v2.md`](../../../docs/platform/orm-v2.md) for the branch
|
|
229
|
+
> architecture, internal verification workflow, demo, and remaining rollout
|
|
230
|
+
> gates.
|
|
231
|
+
|
|
131
232
|
## Process commands
|
|
132
233
|
|
|
133
234
|
| Command | What it does |
|
|
@@ -193,6 +294,47 @@ cd my-processes
|
|
|
193
294
|
seq-studio process lint # finds both processes
|
|
194
295
|
```
|
|
195
296
|
|
|
297
|
+
## Managed Function commands
|
|
298
|
+
|
|
299
|
+
| Command | What it does |
|
|
300
|
+
|---------|--------------|
|
|
301
|
+
| `seq-studio functions init <dir>` | Scaffold one standalone TypeScript managed function |
|
|
302
|
+
| `seq-studio functions build [--dir <dir>]` | Validate a local manifest, bundle, lockfile, and size |
|
|
303
|
+
| `seq-studio functions deploy --dir <dir> -e <env>` | Preview secrets and deploy a local function |
|
|
304
|
+
| `seq-studio functions build --repo managed-functions/<name> [--path <dir>] -e <env>` | Materialize and validate a function from the platform Git Service |
|
|
305
|
+
| `seq-studio functions deploy --repo managed-functions/<name> [--path <dir>] -e <env>` | Materialize and deploy a function from the platform Git Service |
|
|
306
|
+
|
|
307
|
+
A normal function repo keeps `managed-function.yml` at its root and omits
|
|
308
|
+
`--path`. A repo may also contain related, independently deployed functions:
|
|
309
|
+
|
|
310
|
+
```text
|
|
311
|
+
functions/
|
|
312
|
+
get-loan/
|
|
313
|
+
managed-function.yml
|
|
314
|
+
package.json
|
|
315
|
+
pnpm-lock.yaml
|
|
316
|
+
index.ts
|
|
317
|
+
update-loan/
|
|
318
|
+
managed-function.yml
|
|
319
|
+
package.json
|
|
320
|
+
pnpm-lock.yaml
|
|
321
|
+
index.ts
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
Select exactly one function directory for each build or deploy:
|
|
325
|
+
|
|
326
|
+
```bash
|
|
327
|
+
seq-studio functions build --repo managed-functions/encompass \
|
|
328
|
+
--path functions/get-loan -e staging
|
|
329
|
+
seq-studio functions deploy --repo managed-functions/encompass \
|
|
330
|
+
--path functions/get-loan -e staging
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
Each selected directory is a self-contained function package. Functions in the
|
|
334
|
+
same repo share Git review and commit provenance, but keep separate manifests,
|
|
335
|
+
versions, runtime resources, secrets, and permissions. `--path` accepts only a
|
|
336
|
+
canonical relative directory inside a remote repo; use `--dir` for local source.
|
|
337
|
+
|
|
196
338
|
## Artifact commands
|
|
197
339
|
|
|
198
340
|
`seq-studio artifact <sub>` is the entry point for Artifact Studio. It runs
|
|
@@ -205,11 +347,11 @@ routed through `~/.config/lattice/config.toml` and the shared seqapi token.
|
|
|
205
347
|
| `seq-studio artifact link [dir] -e <env> [--project <id>]` | `link [dir] --env <env>` |
|
|
206
348
|
| `seq-studio artifact build [dir]` | `build [dir]` |
|
|
207
349
|
| `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`
|
|
350
|
+
| `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
351
|
| `seq-studio artifact dev [dir] -e <env>` | `dev [dir] --env <env>` |
|
|
210
352
|
| `seq-studio artifact pull <project-id> -e <env> [--out <dir>]` | `pull <project-id> --env <env> [--out <dir>]` |
|
|
211
353
|
| `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
|
|
354
|
+
| `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
355
|
| `seq-studio artifact promote <deployment-id> -e <env>` | `promote <deployment-id> --env <env>` |
|
|
214
356
|
| `seq-studio artifact rollback <deployment-id> -e <env>` | `rollback <deployment-id> --env <env>` |
|
|
215
357
|
|
|
@@ -219,6 +361,18 @@ routed through `~/.config/lattice/config.toml` and the shared seqapi token.
|
|
|
219
361
|
directory's `.artifact-studio/config.json` — run them from the linked artifact
|
|
220
362
|
folder (or run `seq-studio artifact link` first).
|
|
221
363
|
|
|
364
|
+
**Active (official) deploys require a clean `main` checkout with a resolved
|
|
365
|
+
git commit** for both Git Service (`--repo`) and local sources. Feature
|
|
366
|
+
branches, dirty trees, non-git sources, and detached checkouts that cannot be
|
|
367
|
+
attributed to `main` are preview-only
|
|
368
|
+
(`seq-studio artifact dev` / preview channel); promotion applies the same
|
|
369
|
+
provenance check.
|
|
370
|
+
|
|
371
|
+
**CLI atlas-ui stamp (DES-254):** builds force-alias `@sequenceholdings/atlas-ui`
|
|
372
|
+
to the CLI's copy — not the artifact's declared semver. Deployments record
|
|
373
|
+
`cliVersion` + `atlasUiVersion`. Contract:
|
|
374
|
+
`@sequenceholdings/artifact-studio` → `VERSION-PIN.md`.
|
|
375
|
+
|
|
222
376
|
### Source for `build` / `plan` / `deploy`
|
|
223
377
|
|
|
224
378
|
These three commands read their source tree from one of three places — the rest
|
|
@@ -230,8 +384,10 @@ of the pipeline (build → upload) is identical regardless of origin:
|
|
|
230
384
|
| `--repo <ns>/<name>` | a repo in the platform Git Service, e.g. `--repo artifacts/accounts` |
|
|
231
385
|
| `--git-url <url>` | a public, credential-free HTTPS git URL (cloned to a temp dir) |
|
|
232
386
|
|
|
233
|
-
`--ref <branch\|tag\|commit>` selects the revision
|
|
234
|
-
|
|
387
|
+
`--ref <branch\|tag\|commit>` selects the revision for `build` and `plan`
|
|
388
|
+
(default: the repo's default branch). Active `deploy` requires `main`; other
|
|
389
|
+
refs are preview-only. A remote source is materialized to a temp dir, built,
|
|
390
|
+
and torn down;
|
|
235
391
|
the target project is resolved from the source's `artifact.bundle.yml`
|
|
236
392
|
`project_id`. The deployment records the resolved commit as its `git_commit`
|
|
237
393
|
provenance. `--repo` needs `-e <env>` + a platform login. M2M/CI builds accept
|
|
@@ -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] offline compile + validation\n seq-studio agents plan [--dir d] [-e <env>] offline bundle plan or live diff\n seq-studio agents apply [--dir d] -e <env> [--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).\n";
|
|
10
|
+
export declare function runAgentsCommand(sub: string | undefined, args: ParsedArgs): Promise<number>;
|
|
@@ -0,0 +1,218 @@
|
|
|
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
|
+
export async function agentsValidateCommand(args) {
|
|
11
|
+
const { source } = await materialize({ args, requireEnvironment: false });
|
|
12
|
+
try {
|
|
13
|
+
const target = typeof args.flags.target === 'string' ? args.flags.target : undefined;
|
|
14
|
+
const bundle = await compileSource({
|
|
15
|
+
directory: source.dir,
|
|
16
|
+
targetEnvironment: target,
|
|
17
|
+
deployEnvironments: await deployEnvironmentNames(),
|
|
18
|
+
});
|
|
19
|
+
if (bundle.definitions.length === 0) {
|
|
20
|
+
console.error(`${LOG} no named agent.ts definitions found`);
|
|
21
|
+
return 1;
|
|
22
|
+
}
|
|
23
|
+
console.log(`${LOG} valid: ${bundle.definitions.length} definition${bundle.definitions.length === 1 ? '' : 's'}, bundle ${bundle.hash}`);
|
|
24
|
+
return 0;
|
|
25
|
+
}
|
|
26
|
+
finally {
|
|
27
|
+
await source.cleanup();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async function deploymentBundle({ args, source, context, }) {
|
|
31
|
+
const target = (typeof args.flags.target === 'string' ? args.flags.target : undefined) ??
|
|
32
|
+
context.env.name;
|
|
33
|
+
return compileSource({
|
|
34
|
+
directory: source.dir,
|
|
35
|
+
targetEnvironment: target,
|
|
36
|
+
deployEnvironments: await deployEnvironmentNames(),
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Names the CLI can actually deploy to. A target outside both this set and the
|
|
41
|
+
* manifest's own vocabulary is a typo, not a selection.
|
|
42
|
+
*/
|
|
43
|
+
async function deployEnvironmentNames() {
|
|
44
|
+
try {
|
|
45
|
+
return Object.keys((await readConfig()).envs);
|
|
46
|
+
}
|
|
47
|
+
catch {
|
|
48
|
+
// Never let an unreadable config block a deploy — the guard is a safety
|
|
49
|
+
// net, and the manifest's own vocabulary still constrains the target.
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function printPlan(plan) {
|
|
54
|
+
for (const entry of plan.entries) {
|
|
55
|
+
const marker = entry.action === 'create' ? '+' : entry.action === 'update' ? '~' : '=';
|
|
56
|
+
// Name the fields an update would rewrite, so an operator can tell an
|
|
57
|
+
// intended edit from unexpected drift before confirming an apply.
|
|
58
|
+
const fields = entry.changedFields?.length
|
|
59
|
+
? `: ${entry.changedFields.join(', ')}`
|
|
60
|
+
: '';
|
|
61
|
+
console.log(` ${marker} ${entry.name} (${entry.id}) ${entry.action}${fields}`);
|
|
62
|
+
}
|
|
63
|
+
console.log(`${LOG} plan: ${plan.summary.creates} create, ${plan.summary.updates} update, ${plan.summary.unchanged} unchanged`);
|
|
64
|
+
}
|
|
65
|
+
export async function agentsPlanCommand(args) {
|
|
66
|
+
const environment = requestedEnvironment(args);
|
|
67
|
+
if (!environment) {
|
|
68
|
+
const { source } = await materialize({ args, requireEnvironment: false });
|
|
69
|
+
try {
|
|
70
|
+
// Honor --target the same way validate does. Without this, offline plan
|
|
71
|
+
// silently compiles the unfiltered manifest set even when the caller
|
|
72
|
+
// asked for a specific environment's overrides.
|
|
73
|
+
const target = typeof args.flags.target === 'string' ? args.flags.target : undefined;
|
|
74
|
+
const bundle = await compileSource({
|
|
75
|
+
directory: source.dir,
|
|
76
|
+
targetEnvironment: target,
|
|
77
|
+
deployEnvironments: await deployEnvironmentNames(),
|
|
78
|
+
});
|
|
79
|
+
console.log(`${LOG} offline plan: ${bundle.definitions.length} valid definition${bundle.definitions.length === 1 ? '' : 's'}, bundle ${bundle.hash}`);
|
|
80
|
+
console.log(`${LOG} pass -e <env> for create/update/unchanged live diff`);
|
|
81
|
+
return bundle.definitions.length > 0 ? 0 : 1;
|
|
82
|
+
}
|
|
83
|
+
finally {
|
|
84
|
+
await source.cleanup();
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
const { source, context } = await materialize({
|
|
88
|
+
args,
|
|
89
|
+
requireEnvironment: true,
|
|
90
|
+
});
|
|
91
|
+
if (!context)
|
|
92
|
+
throw new Error('Missing deployment context');
|
|
93
|
+
try {
|
|
94
|
+
const bundle = await deploymentBundle({ args, source, context });
|
|
95
|
+
const plan = await postJson({
|
|
96
|
+
...clientOptions(context),
|
|
97
|
+
path: '/api/agents/deploy/plan',
|
|
98
|
+
body: {
|
|
99
|
+
definitions: bundle.definitions,
|
|
100
|
+
bundleHash: bundle.hash,
|
|
101
|
+
source: source.provenance.gitCommit ?? 'local',
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
printPlan(plan);
|
|
105
|
+
return 0;
|
|
106
|
+
}
|
|
107
|
+
finally {
|
|
108
|
+
await source.cleanup();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
export async function agentsApplyCommand(args) {
|
|
112
|
+
const { source, context } = await materialize({
|
|
113
|
+
args,
|
|
114
|
+
requireEnvironment: true,
|
|
115
|
+
});
|
|
116
|
+
if (!context)
|
|
117
|
+
throw new Error('Missing deployment context');
|
|
118
|
+
try {
|
|
119
|
+
const bundle = await deploymentBundle({ args, source, context });
|
|
120
|
+
const plan = await postJson({
|
|
121
|
+
...clientOptions(context),
|
|
122
|
+
path: '/api/agents/deploy/plan',
|
|
123
|
+
body: {
|
|
124
|
+
definitions: bundle.definitions,
|
|
125
|
+
bundleHash: bundle.hash,
|
|
126
|
+
source: source.provenance.gitCommit ?? 'local',
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
printPlan(plan);
|
|
130
|
+
const preview = [
|
|
131
|
+
`${LOG} apply ${bundle.definitions.length} agent definitions to ${context.env.name}`,
|
|
132
|
+
`${LOG} bundle ${bundle.hash}`,
|
|
133
|
+
`${LOG} no agents will be deleted`,
|
|
134
|
+
];
|
|
135
|
+
if (!(await confirmYes({ preview, confirmed: flagBool(args.flags, 'yes') }))) {
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
const summary = await applyInChunks({
|
|
139
|
+
context,
|
|
140
|
+
definitions: bundle.definitions,
|
|
141
|
+
source: source.provenance.gitCommit ?? 'local',
|
|
142
|
+
});
|
|
143
|
+
console.log(`${LOG} applied: ${summary.created} created, ${summary.updated} updated, ${summary.unchanged} unchanged`);
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
await source.cleanup();
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
export async function agentsListCommand(args) {
|
|
151
|
+
const context = await buildContext(args);
|
|
152
|
+
const agents = await getJson({
|
|
153
|
+
...clientOptions(context),
|
|
154
|
+
path: '/api/agents/agents?view=list',
|
|
155
|
+
});
|
|
156
|
+
for (const agent of agents) {
|
|
157
|
+
console.log(`${agent.id} ${agent.name} managed=${agent.managedBy ?? 'user'}`);
|
|
158
|
+
}
|
|
159
|
+
return 0;
|
|
160
|
+
}
|
|
161
|
+
export async function agentsShowCommand(args) {
|
|
162
|
+
const id = args.positional[0];
|
|
163
|
+
if (!id) {
|
|
164
|
+
console.error('usage: seq-studio agents show <id> -e <env>');
|
|
165
|
+
return 1;
|
|
166
|
+
}
|
|
167
|
+
const context = await buildContext(args);
|
|
168
|
+
const agent = await getJson({
|
|
169
|
+
...clientOptions(context),
|
|
170
|
+
path: `/api/agents/agents/${encodeURIComponent(id)}`,
|
|
171
|
+
});
|
|
172
|
+
console.log(JSON.stringify(agent, null, 2));
|
|
173
|
+
return 0;
|
|
174
|
+
}
|
|
175
|
+
export const AGENTS_USAGE = `usage:
|
|
176
|
+
seq-studio agents init <dir> scaffold a typed agent
|
|
177
|
+
seq-studio agents validate [--dir d] [--target app] offline compile + validation
|
|
178
|
+
seq-studio agents plan [--dir d] [-e <env>] offline bundle plan or live diff
|
|
179
|
+
seq-studio agents apply [--dir d] -e <env> [--yes] apply creates/updates; never deletes
|
|
180
|
+
seq-studio agents list -e <env> list visible agents
|
|
181
|
+
seq-studio agents show <id> -e <env> show one agent
|
|
182
|
+
|
|
183
|
+
Source: local --dir (default .), --repo agents/<name>, or --git-url <url>.
|
|
184
|
+
Use --ref for remote sources. --target selects the deployment APP_ENV when it
|
|
185
|
+
differs from the CLI environment alias (notably OpCo registrations).
|
|
186
|
+
`;
|
|
187
|
+
export async function runAgentsCommand(sub, args) {
|
|
188
|
+
try {
|
|
189
|
+
switch (sub) {
|
|
190
|
+
case 'init':
|
|
191
|
+
return await agentsInitCommand(args);
|
|
192
|
+
case 'validate':
|
|
193
|
+
return await agentsValidateCommand(args);
|
|
194
|
+
case 'plan':
|
|
195
|
+
return await agentsPlanCommand(args);
|
|
196
|
+
case 'apply':
|
|
197
|
+
return await agentsApplyCommand(args);
|
|
198
|
+
case 'list':
|
|
199
|
+
return await agentsListCommand(args);
|
|
200
|
+
case 'show':
|
|
201
|
+
return await agentsShowCommand(args);
|
|
202
|
+
case 'help':
|
|
203
|
+
case '--help':
|
|
204
|
+
case '-h':
|
|
205
|
+
case undefined:
|
|
206
|
+
console.log(AGENTS_USAGE);
|
|
207
|
+
return sub ? 0 : 1;
|
|
208
|
+
default:
|
|
209
|
+
console.error(`unknown agents command: ${sub}`);
|
|
210
|
+
console.error(AGENTS_USAGE);
|
|
211
|
+
return 1;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
printError(error);
|
|
216
|
+
return 1;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { randomBytes } from 'node:crypto';
|
|
2
|
+
import { existsSync } from 'node:fs';
|
|
3
|
+
import { mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { basename, join, resolve } from 'node:path';
|
|
5
|
+
import { currentVersion } from '../update-check.js';
|
|
6
|
+
const LOG = '[seq-studio]';
|
|
7
|
+
const scaffoldSource = ({ id, name, }) => `import { defineAgent } from '@sequenceholdings/agent-spec'
|
|
8
|
+
|
|
9
|
+
export const agent = defineAgent({
|
|
10
|
+
id: '${id}',
|
|
11
|
+
name: '${name}',
|
|
12
|
+
model: {
|
|
13
|
+
provider: 'anthropic',
|
|
14
|
+
name: 'claude-sonnet-4-6',
|
|
15
|
+
},
|
|
16
|
+
systemPrompt: 'Describe the agent role and operating instructions.',
|
|
17
|
+
})
|
|
18
|
+
`;
|
|
19
|
+
export async function agentsInitCommand(args) {
|
|
20
|
+
const target = args.positional[0];
|
|
21
|
+
if (!target) {
|
|
22
|
+
console.error('usage: seq-studio agents init <dir>');
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
const directory = resolve(target);
|
|
26
|
+
const file = join(directory, 'agent.ts');
|
|
27
|
+
if (existsSync(file)) {
|
|
28
|
+
console.error(`${LOG} ${file} already exists`);
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
const name = basename(directory)
|
|
32
|
+
.split(/[-_]+/)
|
|
33
|
+
.filter(Boolean)
|
|
34
|
+
.map((part) => `${part[0]?.toUpperCase() ?? ''}${part.slice(1)}`)
|
|
35
|
+
.join(' ');
|
|
36
|
+
await mkdir(directory, { recursive: true });
|
|
37
|
+
await writeFile(file, scaffoldSource({ id: randomBytes(12).toString('hex'), name }));
|
|
38
|
+
await writeFile(join(directory, 'package.json'), `${JSON.stringify({
|
|
39
|
+
name: basename(directory),
|
|
40
|
+
version: '0.0.1',
|
|
41
|
+
private: true,
|
|
42
|
+
type: 'module',
|
|
43
|
+
scripts: { validate: 'seq-studio agents validate' },
|
|
44
|
+
dependencies: { '@sequenceholdings/agent-spec': '^0.1.0' },
|
|
45
|
+
devDependencies: {
|
|
46
|
+
'@sequenceholdings/studio-cli': `^${currentVersion()}`,
|
|
47
|
+
},
|
|
48
|
+
}, null, 2)}\n`);
|
|
49
|
+
await writeFile(join(directory, 'tsconfig.json'), `${JSON.stringify({
|
|
50
|
+
compilerOptions: {
|
|
51
|
+
target: 'ES2022',
|
|
52
|
+
module: 'NodeNext',
|
|
53
|
+
moduleResolution: 'NodeNext',
|
|
54
|
+
strict: true,
|
|
55
|
+
noEmit: true,
|
|
56
|
+
},
|
|
57
|
+
include: ['agent.ts'],
|
|
58
|
+
}, null, 2)}\n`);
|
|
59
|
+
await writeFile(join(directory, 'pnpm-workspace.yaml'), `packages:
|
|
60
|
+
- '.'
|
|
61
|
+
|
|
62
|
+
minimumReleaseAge: 10080
|
|
63
|
+
minimumReleaseAgeExclude:
|
|
64
|
+
- '@sequenceholdings/agent-spec'
|
|
65
|
+
- '@sequenceholdings/atlas-ui'
|
|
66
|
+
- '@sequenceholdings/lattice-form-renderer'
|
|
67
|
+
- '@sequenceholdings/artifact-studio'
|
|
68
|
+
- '@sequenceholdings/lattice'
|
|
69
|
+
- '@sequenceholdings/studio-cli'
|
|
70
|
+
strictDepBuilds: true
|
|
71
|
+
allowBuilds:
|
|
72
|
+
esbuild: true
|
|
73
|
+
`);
|
|
74
|
+
console.log(`${LOG} scaffolded typed agent in ${directory}`);
|
|
75
|
+
console.log('Next: pnpm install && seq-studio agents validate');
|
|
76
|
+
return 0;
|
|
77
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { type CompiledAgentBundle } from '@sequenceholdings/agent-spec/compiler';
|
|
2
|
+
import { type ResolvedSource, type SourceSpec } from '@sequenceholdings/artifact-studio/source-resolver';
|
|
3
|
+
import type { ParsedArgs } from '../process/commands.js';
|
|
4
|
+
import { type CommandContext } from '../functions/commands.js';
|
|
5
|
+
export declare function materializeAgentSource({ args, requireEnvironment, }: {
|
|
6
|
+
args: ParsedArgs;
|
|
7
|
+
requireEnvironment: boolean;
|
|
8
|
+
}): Promise<{
|
|
9
|
+
spec: SourceSpec;
|
|
10
|
+
source: ResolvedSource;
|
|
11
|
+
context: CommandContext | null;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function compileAgentSource({ directory, targetEnvironment, deployEnvironments, }: {
|
|
14
|
+
directory: string;
|
|
15
|
+
targetEnvironment?: string;
|
|
16
|
+
/** Registered deployment environments, so a real env absent from the manifest is not read as a typo. */
|
|
17
|
+
deployEnvironments?: readonly string[];
|
|
18
|
+
}): Promise<CompiledAgentBundle>;
|