@onesi/genesis-cli 0.1.13 → 0.2.1

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.
Files changed (67) hide show
  1. package/README.md +29 -2
  2. package/dist/beacons.js +68 -0
  3. package/dist/dials.js +7 -4
  4. package/dist/fleets.js +30 -0
  5. package/dist/flocks.d.ts +7 -1
  6. package/dist/flocks.js +281 -4
  7. package/dist/henry.d.ts +5 -1
  8. package/dist/henry.js +60 -1
  9. package/dist/hives-coverage.d.ts +12 -0
  10. package/dist/hives-coverage.js +133 -0
  11. package/dist/hives-node-transport.js +4 -1
  12. package/dist/hives-operations.d.ts +1 -0
  13. package/dist/hives-operations.js +2 -0
  14. package/dist/hives-platform.d.ts +13 -0
  15. package/dist/hives-platform.js +133 -0
  16. package/dist/hives-resources.d.ts +1 -0
  17. package/dist/hives-resources.js +5 -3
  18. package/dist/iam-directory.d.ts +15 -0
  19. package/dist/iam-directory.js +71 -0
  20. package/dist/index.d.ts +2 -0
  21. package/dist/index.js +273 -30
  22. package/dist/minds.d.ts +1 -1
  23. package/dist/minds.js +37 -2
  24. package/dist/onboard/art.d.ts +50 -0
  25. package/dist/onboard/art.js +139 -0
  26. package/dist/onboard/brand.d.ts +56 -0
  27. package/dist/onboard/brand.js +134 -0
  28. package/dist/onboard/caps.d.ts +90 -0
  29. package/dist/onboard/caps.js +232 -0
  30. package/dist/onboard/encode.d.ts +23 -0
  31. package/dist/onboard/encode.js +235 -0
  32. package/dist/onboard/eye.d.ts +103 -0
  33. package/dist/onboard/eye.js +147 -0
  34. package/dist/onboard/index.d.ts +61 -0
  35. package/dist/onboard/index.js +562 -0
  36. package/dist/onboard/mcp.d.ts +35 -0
  37. package/dist/onboard/mcp.js +118 -0
  38. package/dist/onboard/raster.d.ts +43 -0
  39. package/dist/onboard/raster.js +253 -0
  40. package/dist/onboard/sound.d.ts +2 -0
  41. package/dist/onboard/sound.js +54 -0
  42. package/dist/onboard/state.d.ts +24 -0
  43. package/dist/onboard/state.js +48 -0
  44. package/dist/onboard/term.d.ts +69 -0
  45. package/dist/onboard/term.js +292 -0
  46. package/dist/onboard/ui.d.ts +97 -0
  47. package/dist/onboard/ui.js +615 -0
  48. package/dist/orgs.d.ts +16 -0
  49. package/dist/orgs.js +89 -0
  50. package/dist/output.d.ts +13 -1
  51. package/dist/output.js +21 -2
  52. package/dist/products.d.ts +6 -2
  53. package/dist/products.js +474 -13
  54. package/dist/relays-commands/certificates.d.ts +3 -0
  55. package/dist/relays-commands/certificates.js +20 -0
  56. package/dist/relays-commands/domains.d.ts +3 -0
  57. package/dist/relays-commands/domains.js +23 -0
  58. package/dist/relays-commands/index.d.ts +4 -0
  59. package/dist/relays-commands/index.js +10 -0
  60. package/dist/relays-commands/monitoring.d.ts +3 -0
  61. package/dist/relays-commands/monitoring.js +41 -0
  62. package/dist/relays-commands/telemetry.d.ts +3 -0
  63. package/dist/relays-commands/telemetry.js +28 -0
  64. package/dist/relays-commands/zones.d.ts +7 -0
  65. package/dist/relays-commands/zones.js +16 -0
  66. package/dist/runner.js +2 -0
  67. package/package.json +2 -2
package/dist/index.js CHANGED
@@ -1,14 +1,17 @@
1
1
  import { registerMinds } from './minds.js';
2
2
  import { registerRelaysEnrollment } from './relays-enrollment.js';
3
+ import { RELAYS_COMMANDS } from './relays-commands/index.js';
3
4
  import { registerBeacons } from './beacons.js';
4
5
  import { registerDials } from './dials.js';
5
- import { registerProducts } from './products.js';
6
+ import { registerIamDirectory } from './iam-directory.js';
7
+ import { registerOperationCommands, registerProducts } from './products.js';
8
+ import { registerOrgs } from './orgs.js';
6
9
  import { Command, CommanderError } from 'commander';
7
- import { Genesis, GenesisError, errorCatalog, errorOutcome, isMindsOperation, HivesOperationError, HivesQueueOperationError, MindsAgentError, MindsOwnerError, hivesResponseBytes, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES, DEFAULT_BASE_URL, requireSecureUrl } from '@onesi/genesis';
10
+ import { Genesis, GenesisError, errorBody, errorCatalog, errorOutcome, isMindsOperation, HivesOperationError, HivesQueueOperationError, MindsAgentError, MindsOwnerError, hivesResponseBytes, MAX_FUNCTION_BODY_BYTES, MAX_FUNCTION_JSON_BYTES, DEFAULT_BASE_URL, requireSecureUrl } from '@onesi/genesis';
8
11
  import { authConfig, completeLogin, selectSavedContext, savedLoginProvider, startLogin } from './auth.js';
9
12
  import { completeDeviceLogin, startDeviceLogin } from './device-login.js';
10
13
  import { keychainStore } from './store.js';
11
- import { c, listOf, safe, stdio, table } from './output.js';
14
+ import { c, errorText, listOf, printable, redactMessage, safe, stdio, table } from './output.js';
12
15
  import { spawn } from 'node:child_process';
13
16
  import { addHenryCommands } from './henry.js';
14
17
  import { registerFleets } from './fleets.js';
@@ -16,6 +19,7 @@ import { registerFlocks } from './flocks.js';
16
19
  import { registerRunner } from './runner.js';
17
20
  import { createHivesNodeTransport } from './hives-node-transport.js';
18
21
  import { registerHivesOperations } from './hives-operations.js';
22
+ import { registerHivesCoverage } from './hives-coverage.js';
19
23
  import { readFunctionFile, readFunctionJson, writeFunctionOutput } from './function-input.js';
20
24
  import { basename } from 'node:path';
21
25
  import { readWasmFile } from './wasm-input.js';
@@ -36,7 +40,7 @@ export function buildProgram(deps = {}) {
36
40
  const auth = { ...authConfig(env, f), sleep: deps.sleep };
37
41
  const program = new Command('genesis')
38
42
  .description('One CLI for every Genesis service.')
39
- .version('0.1.13')
43
+ .version('0.2.1')
40
44
  .option('--json', 'machine-readable JSON output')
41
45
  .option('--api-key <key>', 'use this API key (default: GENESIS_API_KEY, then your `genesis login`)')
42
46
  .option('--base-url <url>', 'gateway URL (default: GENESIS_BASE_URL or https://os.one.ml/api)')
@@ -52,7 +56,7 @@ export function buildProgram(deps = {}) {
52
56
  const opts = {
53
57
  apiKey: o.apiKey ?? env.GENESIS_API_KEY, token: env.GENESIS_TOKEN,
54
58
  baseUrl: o.baseUrl ?? env.GENESIS_BASE_URL, routing: o.direct || env.GENESIS_ROUTING === 'direct' ? 'direct' : 'gateway',
55
- fetch: f, userAgent: 'genesis-cli/0.1.13',
59
+ fetch: f, userAgent: 'genesis-cli/0.2.1',
56
60
  credentials: store ? savedLoginProvider(auth, store) : undefined,
57
61
  };
58
62
  return new Genesis(hivesTransport ? { ...opts, fetch: createHivesNodeTransport(opts, deps.signal) } : opts);
@@ -82,23 +86,102 @@ export function buildProgram(deps = {}) {
82
86
  return (await gen.whoami()).org;
83
87
  };
84
88
  const silos = program.command('silos').description('Serverless PostgreSQL');
85
- const silosDatabases = silos.command('databases').description('Read existing owned database metadata; no provisioning');
86
- silosDatabases.command('list <project_id>').action(async (id) => out.log(JSON.stringify(await (await client()).silos.databases.list(id), null, 2)));
87
- silosDatabases.command('get <project_id> <database_id>').action(async (project, database) => out.log(JSON.stringify(await (await client()).silos.databases.get(project, database), null, 2)));
88
- const silosProjects = silos.command('projects').description('Projects in your verified organization');
89
- silosProjects.command('list').action(async () => out.log(JSON.stringify(await (await client()).silos.projects.list(), null, 2)));
90
- silosProjects.command('get <project_id>').action(async (id) => out.log(JSON.stringify(await (await client()).silos.projects.get(id), null, 2)));
91
- silosProjects.command('create').requiredOption('--name <name>', 'Project name').requiredOption('--slug <slug>', 'Unique project slug')
92
- .action(async (options) => out.log(JSON.stringify(await (await client()).silos.projects.create({ name: options.name, slug: options.slug }), null, 2)));
89
+ const silosInstances = silos.command('instances').description('Database instances in the selected Genesis project (genesis select)');
90
+ silosInstances.command('list').action(async () => out.log(JSON.stringify(await (await client()).silos.instances.list(), null, 2)));
91
+ silosInstances.command('get <instance_id>').action(async (id) => out.log(JSON.stringify(await (await client()).silos.instances.get(id), null, 2)));
92
+ silosInstances.command('create').requiredOption('--name <name>', 'Instance name (lowercase, 1-63 characters)')
93
+ .action(async (options) => out.log(JSON.stringify(await (await client()).silos.instances.create({ name: options.name }), null, 2)));
94
+ const silosJson = (value) => out.log(JSON.stringify(value, null, 2));
95
+ silosInstances.command('rename <instance_id>').requiredOption('--name <name>', 'New instance name (lowercase, 1-63 characters)')
96
+ .action(async (id, options) => silosJson(await (await client()).silos.instances.update(id, { name: options.name })));
97
+ silosInstances.command('delete <instance_id>').description('Delete an instance and stop it; its name is free again at once')
98
+ .action(async (id) => silosJson((await (await client()).silos.instances.delete(id)) ?? { deleted: id }));
99
+ silosInstances.command('suspend <instance_id>').description('Checkpoint, then stop the instance')
100
+ .action(async (id) => silosJson(await (await client()).silos.instances.suspend(id)));
101
+ silosInstances.command('resume <instance_id>').description('Start the instance now')
102
+ .action(async (id) => silosJson(await (await client()).silos.instances.resume(id)));
103
+ silosInstances.command('query <instance_id>').description('Run one SQL statement (never replayed)')
104
+ .requiredOption('--sql <sql>', 'SQL statement').option('--params <json>', 'JSON array of positional parameters for $1, $2, …')
105
+ .option('--branch <branch_id>', 'run on this branch instead of the default branch')
106
+ .action(async (id, options) => {
107
+ let params;
108
+ if (options.params !== undefined) {
109
+ try {
110
+ params = JSON.parse(options.params);
111
+ }
112
+ catch {
113
+ params = undefined;
114
+ }
115
+ if (!Array.isArray(params))
116
+ throw new Error('--params must be a JSON array, for example \'[1,"a"]\'.');
117
+ }
118
+ silosJson(await (await client()).silos.instances.query(id, { sql: options.sql, ...(params ? { params } : {}), ...(options.branch ? { branchId: options.branch } : {}) }));
119
+ });
120
+ silosInstances.command('batch <instance_id>').description('Run up to 50 statements in one transaction (never replayed)')
121
+ .option('--file <path>', 'UTF-8 JSON file: {"statements":[{"sql":"…","params":[…]}]}').option('--stdin', 'read that JSON from stdin')
122
+ .action(async (id, o) => {
123
+ let body;
124
+ if (Boolean(o.file) === Boolean(o.stdin))
125
+ throw new Error('Choose exactly one input source: --file or --stdin.');
126
+ const text = await (deps.readSecretInput ?? readSecretInput)(o, 64 * 1024);
127
+ try {
128
+ body = JSON.parse(text);
129
+ }
130
+ catch {
131
+ throw new Error('Batch input must be JSON: {"statements":[{"sql":"…"}]}.');
132
+ }
133
+ silosJson(await (await client()).silos.instances.batch(id, body));
134
+ });
135
+ silosInstances.command('metrics <instance_id>').description('Live counters, storage and activity times')
136
+ .action(async (id) => silosJson(await (await client()).silos.instances.metrics(id)));
137
+ silosInstances.command('listen <instance_id>').description('Wait for NOTIFY on channels (up to --wait ms, at most 15000)')
138
+ .requiredOption('--channel <name...>', 'channel to listen on (repeatable, up to 10)').option('--wait <ms>', 'how long to wait (default 10000)')
139
+ .option('--branch <branch_id>', 'listen on this branch instead of the default branch')
140
+ .action(async (id, o) => {
141
+ const waitMs = o.wait === undefined ? undefined : Number(o.wait);
142
+ if (waitMs !== undefined && !(Number.isInteger(waitMs) && waitMs >= 0 && waitMs <= 15000))
143
+ throw new Error('--wait must be an integer from 0 to 15000.');
144
+ silosJson(await (await client()).silos.instances.listen(id, { channels: o.channel, ...(waitMs !== undefined ? { waitMs } : {}), ...(o.branch ? { branchId: o.branch } : {}) }));
145
+ });
146
+ silos.command('usage').description('Silos usage of the selected Genesis project, last 30 days')
147
+ .action(async () => silosJson(await (await client()).silos.usage()));
148
+ silos.command('regions').description('Regions Silos instances are created in')
149
+ .action(async () => silosJson(await (await client()).silos.regions.list()));
150
+ const silosBranches = silos.command('branches').description('Branches of a database instance');
151
+ silosBranches.command('list <instance_id>').action(async (id) => silosJson(await (await client()).silos.instances.branches.list(id)));
152
+ silosBranches.command('get <instance_id> <branch_id>').action(async (id, branch) => silosJson(await (await client()).silos.instances.branches.get(id, branch)));
153
+ silosBranches.command('create <instance_id>').description('Copy the latest checkpoint of the parent (default: the default branch)')
154
+ .requiredOption('--name <name>', 'Branch name (lowercase, 1-63 characters)').option('--parent <branch_id>', 'parent branch')
155
+ .action(async (id, o) => silosJson(await (await client()).silos.instances.branches.create(id, o.parent ? { name: o.name, parentBranchId: o.parent } : { name: o.name })));
156
+ silosBranches.command('rename <instance_id> <branch_id>').requiredOption('--name <name>', 'New branch name')
157
+ .action(async (id, branch, o) => silosJson(await (await client()).silos.instances.branches.update(id, branch, { name: o.name })));
158
+ silosBranches.command('delete <instance_id> <branch_id>').description('Delete a branch other than the default branch')
159
+ .action(async (id, branch) => silosJson((await (await client()).silos.instances.branches.delete(id, branch)) ?? { deleted: branch }));
160
+ silosBranches.command('restore <instance_id> <branch_id>').description('Replace the branch data with a stored snapshot')
161
+ .requiredOption('--snapshot <snapshot_id>', 'snapshot id from `genesis silos snapshots list`').option('--from <branch_id>', 'branch the snapshot was taken on (default: this branch)')
162
+ .action(async (id, branch, o) => silosJson(await (await client()).silos.instances.branches.restore(id, branch, o.from ? { snapshotId: o.snapshot, sourceBranchId: o.from } : { snapshotId: o.snapshot })));
163
+ const silosSnapshots = silos.command('snapshots').description('Stored snapshots of a database instance');
164
+ silosSnapshots.command('list <instance_id>').action(async (id) => silosJson(await (await client()).silos.instances.snapshots.list(id)));
165
+ silosSnapshots.command('create <instance_id>').description('Checkpoint a branch and store the snapshot').option('--branch <branch_id>', 'branch (default: the default branch)')
166
+ .action(async (id, o) => silosJson(await (await client()).silos.instances.snapshots.create(id, o.branch ? { branchId: o.branch } : {})));
93
167
  const traces = program.command('traces').description('Research and evidence');
94
168
  const tracesRuns = traces.command('runs').description('Research runs in your verified organization');
95
169
  tracesRuns.command('create').requiredOption('--type <type>', 'Run type, e.g. deep_research').requiredOption('--query <query>', 'Research question')
96
170
  .requiredOption('--idempotency-key <key>', 'Stable key; reuse it to replay instead of creating a second run')
97
- .action(async (o) => out.log(JSON.stringify(await (await client()).traces.runs.create({ type: o.type, query: o.query }, o.idempotencyKey), null, 2)));
171
+ .option('--max-sources <n>', 'Most sources the run may collect (1-100)')
172
+ .option('--include-domain <domain...>', 'Search only these domains, e.g. wikipedia.org')
173
+ .action(async (o) => {
174
+ const config = { ...(o.maxSources !== undefined ? { max_sources: positiveInteger(o.maxSources, '--max-sources', 100) } : {}), ...(o.includeDomain?.length ? { include_domains: o.includeDomain } : {}) };
175
+ out.log(JSON.stringify(await (await client()).traces.runs.create({ type: o.type, query: o.query, ...(Object.keys(config).length ? { config } : {}) }, o.idempotencyKey), null, 2));
176
+ });
98
177
  tracesRuns.command('list').action(async () => out.log(JSON.stringify(await (await client()).traces.runs.list(), null, 2)));
99
178
  tracesRuns.command('get <run_id>').action(async (id) => out.log(JSON.stringify(await (await client()).traces.runs.get(id), null, 2)));
100
179
  traces.command('search <query>').requiredOption('--idempotency-key <key>', 'Stable key; metered once')
101
- .action(async (query, o) => out.log(JSON.stringify(await (await client()).traces.search({ query, max_results: 3 }, o.idempotencyKey), null, 2)));
180
+ .option('--max-results <n>', 'Results to return (1-20, default 3)')
181
+ .option('--include-domain <domain...>', 'Search only these domains, e.g. wikipedia.org')
182
+ .action(async (query, o) => out.log(JSON.stringify(await (await client()).traces.search({
183
+ query, max_results: positiveInteger(o.maxResults, '--max-results', 20) ?? 3, ...(o.includeDomain?.length ? { include_domains: o.includeDomain } : {})
184
+ }, o.idempotencyKey), null, 2)));
102
185
  traces.command('usage').action(async () => out.log(JSON.stringify(await (await client()).traces.usage.events(), null, 2)));
103
186
  const blinds = program.command('blinds').description('Blinds credential custody');
104
187
  const show = async (call) => out.log(JSON.stringify(await call((await client()).blinds), null, 2));
@@ -115,7 +198,41 @@ export function buildProgram(deps = {}) {
115
198
  ...(o.domain ? { destination_domains: o.domain } : {}), ...(o.provider ? { provider: o.provider } : {}) }, key(o)));
116
199
  });
117
200
  credentials.command('revoke <variable>').option(...keyOption).action((variable, o) => show(b => b.credentials.revoke(variable, key(o))));
201
+ credentials.command('revoke-all').description('Revoke every credential in the selected organization (irreversible)')
202
+ .requiredOption('--yes', 'Confirm the bulk revoke').option(...keyOption).action((o) => show(b => b.credentials.revokeAll(key(o))));
118
203
  blinds.command('connections').description('Active OAuth connections (metadata only)').action(() => show(b => b.oauth.connections({ signal: deps.signal })));
204
+ const offsetInteger = (value) => {
205
+ if (value === undefined)
206
+ return undefined;
207
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
208
+ throw new Error('--offset must be a non-negative integer.');
209
+ return Number(value);
210
+ };
211
+ const catalog = (o) => {
212
+ const limit = positiveInteger(o.limit, '--limit', 1000), offset = offsetInteger(o.offset);
213
+ return { ...(o.q ? { q: o.q } : {}), ...(o.category ? { category: o.category } : {}),
214
+ ...(limit !== undefined ? { limit } : {}), ...(offset !== undefined ? { offset } : {}) };
215
+ };
216
+ const providers = blinds.command('providers').description('Provider catalog: what a credential can be bound to');
217
+ providers.command('list').option('--q <text>', 'Match slug, name or description').option('--category <name>').option('--auth-mode <mode>', 'oauth2, api_key, ...')
218
+ .option('--oauth', 'Only providers that use OAuth 2.0 (cannot be combined with --auth-mode)').option('--limit <n>', 'Page size, 1 to 1000').option('--offset <n>')
219
+ .action((o) => {
220
+ if (o.oauth && o.authMode)
221
+ throw new Error('--oauth already selects auth mode oauth2; drop --auth-mode.');
222
+ const query = catalog(o);
223
+ return show(b => o.oauth ? b.oauth.providers(query, { signal: deps.signal })
224
+ : b.providers.list({ ...query, ...(o.authMode ? { auth_mode: o.authMode } : {}) }, { signal: deps.signal }));
225
+ });
226
+ providers.command('get <slug>').action((slug) => show(b => b.providers.get(slug, { signal: deps.signal })));
227
+ const settings = blinds.command('settings').description('Consent policy and notifications');
228
+ settings.command('get').action(() => show(b => b.settings.get({ signal: deps.signal })));
229
+ settings.command('set').description('Replace the settings: an omitted --email or --slack turns that notification off').requiredOption('--consent-policy <policy>', 'never, per_variable or per_delegate')
230
+ .option('--email', 'Email notifications on').option('--slack', 'Slack notifications on').option(...keyOption)
231
+ .action((o) => show(b => b.settings.update({ consent_policy: o.consentPolicy, email_notifications: !!o.email, slack_notifications: !!o.slack }, key(o))));
232
+ blinds.command('agents').description('Agents seen using Blinds, with request counts').action(() => show(b => b.agents({ signal: deps.signal })));
233
+ blinds.command('alerts').description('Failed or denied Blinds events').action(() => show(b => b.alerts({ signal: deps.signal })));
234
+ blinds.command('operation <operation_id>').description('Receipt for a mutation, by its Idempotency-Key')
235
+ .action((id) => show(b => b.operations.get(id, { signal: deps.signal })));
119
236
  const consent = blinds.command('consent').description('Agent consent requests and decisions');
120
237
  consent.command('pending').action(() => show(b => b.consent.pending({ signal: deps.signal })));
121
238
  consent.command('list').action(() => show(b => b.consent.records({ signal: deps.signal })));
@@ -130,6 +247,12 @@ export function buildProgram(deps = {}) {
130
247
  .action((name, o) => show(b => b.delegates.create({ name, delegated_variables: o.variable, ttl_seconds: Number(o.ttl), max_depth: Number(o.maxDepth) }, key(o))));
131
248
  delegates.command('revoke <delegate_id>').option(...keyOption).action((id, o) => show(b => b.delegates.revoke(id, key(o))));
132
249
  blinds.command('activity').description('Latest 200 Blinds audit events').action(() => show(b => b.activity({ signal: deps.signal })));
250
+ blinds.command('proxy').description('One HTTPS call made by Blinds under a delegation you hold; values never leave Blinds')
251
+ .requiredOption('--body-file <path>', 'JSON {delegate_id, request_id, method, url, headers?, body?}; templates as {{VARIABLE}}').option(...keyOption)
252
+ .action(async (o) => {
253
+ const body = await readFunctionJson(o.bodyFile, 64 * 1024);
254
+ await show(b => b.proxy(body, key(o)));
255
+ });
133
256
  const owner = blinds.command('owner-enrollment')
134
257
  .description('Original-human enrollment only; preserve operation and locator IDs after unknown outcomes');
135
258
  for (const [action, operation] of Object.entries({ reserve: 'blinds_ownerReserve', sign: 'blinds_ownerSign', attach: 'blinds_ownerAttach', 'p256-ensure': 'blinds_ownerP256Ensure' })) {
@@ -141,7 +264,22 @@ export function buildProgram(deps = {}) {
141
264
  });
142
265
  }
143
266
  registerDials(program, { client, orgId, emit });
267
+ registerIamDirectory(program, { client, orgId, emit });
144
268
  registerProducts(program, { client, emit, signal: deps.signal });
269
+ registerOrgs(program, { client, emit, signal: deps.signal });
270
+ // Agent enrollment sends only the grant's enrollment key (mek_...), never this CLI's login or API key.
271
+ (program.commands.find(c => c.name() === 'iam').commands.find(c => c.name() === 'agents')).command('enroll')
272
+ .description('Enroll an agent under an agent enrollment grant (POST /v1/iam/agents/enroll); the credential secret is shown once')
273
+ .requiredOption('--body-file <path>', 'JSON request body (externalKey, displayName, mindId, permissions, ...)')
274
+ .option('--key-file <path>', 'file holding the enrollment key (mek_...)').option('--key-stdin', 'read the enrollment key from stdin')
275
+ .action(async (o) => {
276
+ const key = o.keyFile || o.keyStdin ? (await (deps.readSecretInput ?? readSecretInput)({ file: o.keyFile, stdin: o.keyStdin }, 1024)).trim() : env.GENESIS_ENROLLMENT_KEY;
277
+ if (!key)
278
+ throw new Error('Give the enrollment key with --key-file, --key-stdin or GENESIS_ENROLLMENT_KEY; it is never accepted as an argument.');
279
+ const body = await readFunctionJson(o.bodyFile, 16 * 1024);
280
+ const result = await new Genesis({ baseUrl: g().baseUrl ?? env.GENESIS_BASE_URL, fetch: f, userAgent: 'genesis-cli/0.2.1' }).iam.agents.enroll(key, body);
281
+ emit(result, () => JSON.stringify(result, null, 2));
282
+ });
145
283
  // ---- auth ------------------------------------------------------------
146
284
  program.command('login').description('Sign in with One in your browser').option('--no-browser', 'just print the link')
147
285
  .option('--device', 'sign in from a machine without a local browser: approve a code from any signed-in browser')
@@ -193,10 +331,12 @@ export function buildProgram(deps = {}) {
193
331
  if (!saved || g().apiKey || env.GENESIS_API_KEY)
194
332
  throw e;
195
333
  // Saved login exists but One account verify isn't reachable yet (auth-v4-backend PR #30 not deployed).
196
- emit({ user: saved.user, organization_id: saved.organization_id, verified: false, reason: e.message }, () => `${c.bold('User')} ${saved.user?.email ?? saved.user?.id}\n${c.bold('Org')} ${saved.organization_id ?? '-'}\n${c.yellow('!')} ${c.dim('From your saved login; One account verify unavailable: ' + e.message)}`);
334
+ const reason = errorText(e);
335
+ emit({ user: saved.user, organization_id: saved.organization_id, verified: false, reason }, () => `${c.bold('User')} ${saved.user?.email ?? saved.user?.id}\n${c.bold('Org')} ${saved.organization_id ?? '-'}\n${c.yellow('!')} ${c.dim('From your saved login; One account verify unavailable: ' + reason)}`);
197
336
  }
198
337
  });
199
- const orgs = program.command('orgs').description('Your organizations');
338
+ // `orgs usage|observations|billing|payment-methods|service` and the Orgs (legal entity) commands are registered by registerProducts and registerOrgs.
339
+ const orgs = (program.commands.find(command => command.name() === 'orgs') ?? program.command('orgs')).description('Your organizations, their usage and billing reads, and Orgs: legal entities, seats, resolutions, documents, filings, formation and the record');
200
340
  orgs.command('list', { isDefault: true }).description('Show your current organization').action(async () => {
201
341
  const s = await (await getStore()).get();
202
342
  if (!s)
@@ -247,7 +387,8 @@ export function buildProgram(deps = {}) {
247
387
  emit({ revoked: id }, () => `${c.green('✓')} Revoked ${id} ${c.dim('(takes effect within 60s)')}`);
248
388
  });
249
389
  // ---- foundry ---------------------------------------------------------
250
- const foundry = program.command('foundry').description('Foundry: models and inference');
390
+ // registerProducts (above) already added the generated `foundry` commands; the hand-written ones join them.
391
+ const foundry = program.commands.find(c => c.name() === 'foundry') ?? program.command('foundry').description('Foundry: models and inference');
251
392
  const foundryBilling = foundry.command('billing').description('Current organization’s native TEST billing; original One account human login required');
252
393
  foundryBilling.command('profile').action(async () => out.log(JSON.stringify(await (await client()).foundry.billing.profile({ signal: deps.signal }), null, 2)));
253
394
  foundryBilling.command('ensure').requiredOption('--operation-key <key>', 'Persisted operation key; reconcile unknown outcomes before retrying').action(async (o) => out.log(JSON.stringify(await (await client()).foundry.billing.ensureProfile(o.operationKey, { signal: deps.signal }), null, 2)));
@@ -285,7 +426,7 @@ export function buildProgram(deps = {}) {
285
426
  const hivesClient = () => client(true);
286
427
  // ---- hives -----------------------------------------------------------
287
428
  const hives = program.command('hives').description('Hives: projects, secrets, deployments, functions, schedules and activity');
288
- registerHivesOperations(hives, { client: hivesClient, emit, readRequest: deps.readHivesRequest });
429
+ registerHivesOperations(hives, { client: hivesClient, emit, readRequest: deps.readHivesRequest, line: (text) => out.log(text) });
289
430
  const deployments = hives.command('deployments').description('Real WASM artifacts and deployment lifecycle');
290
431
  const emitDeployment = (data) => emit(data, () => table(data && typeof data === 'object' && !Array.isArray(data) && !('data' in data) ? [data] : listOf(data), ['id', 'version', 'status', 'module_hash', 'created_at']));
291
432
  deployments.command('list <project_id>')
@@ -358,10 +499,10 @@ export function buildProgram(deps = {}) {
358
499
  const result = await (await hivesClient()).hives.functions.delete(project, id);
359
500
  emit(result, () => `Deleted function ${id}.`);
360
501
  });
361
- function invocationCommand(command, kind) {
502
+ function invocationCommand(command, kind, alias = false) {
362
503
  if (kind === 'functions')
363
504
  command.option('--idempotency-key <key>', 'Persisted original invocation key; required by Genesis funding. Reuse it after uncertainty.');
364
- command.description('Complete real guest execution; write exact bytes, including executed HTTP error bodies')
505
+ command.description(alias ? 'Compatibility alias of invoke (/invoke/async): the same completed guest response, no durable queue' : 'Complete real guest execution; write exact bytes, including executed HTTP error bodies')
365
506
  .option('--request-file <file>', 'JSON method/path/headers/body or body_base64')
366
507
  .option('--body-file <file>', 'Exact binary body (up to 10 MiB; serialized guest envelope may reach its cap earlier)')
367
508
  .option('--method <method>', 'guest HTTP method', 'POST').option('--path <path>', 'guest path including query', '/')
@@ -374,7 +515,8 @@ export function buildProgram(deps = {}) {
374
515
  let request = o.requestFile ? await readFunctionJson(o.requestFile, MAX_FUNCTION_JSON_BYTES) : { method: o.method, path: o.path };
375
516
  if (o.bodyFile)
376
517
  request.body_base64 = Buffer.from(await readFunctionFile(o.bodyFile, MAX_FUNCTION_BODY_BYTES)).toString('base64');
377
- const response = await (await hivesClient()).hives[kind].invoke(project, id, request, { idempotencyKey: o.idempotencyKey });
518
+ const hivesApi = (await hivesClient()).hives;
519
+ const response = alias ? await hivesApi.functions.invokeAsync(project, id, request, { idempotencyKey: o.idempotencyKey }) : await hivesApi[kind].invoke(project, id, request, { idempotencyKey: o.idempotencyKey });
378
520
  const bytes = await hivesResponseBytes(response);
379
521
  if (g().json)
380
522
  out.log(JSON.stringify({ status: response.status, headers: Object.fromEntries(response.headers), body_base64: Buffer.from(bytes).toString('base64') }));
@@ -389,6 +531,8 @@ export function buildProgram(deps = {}) {
389
531
  }
390
532
  invocationCommand(functions.command('invoke <project_id> <function_id>'), 'functions');
391
533
  invocationCommand(deployments.command('invoke <project_id> <deployment_id>'), 'deployments');
534
+ // The service's /invoke/async compatibility alias: the same completed guest response, no durable queue.
535
+ invocationCommand(functions.command('invoke-async <project_id> <function_id>'), 'functions', true);
392
536
  const operations = functions.command('operations').description('Read or release the original funding operation without replaying the guest');
393
537
  operations.command('get <project_id> <function_id> <operation_id>').action(async (project, fn, operation) => {
394
538
  const result = await (await hivesClient()).hives.functions.operations.get(project, fn, operation);
@@ -545,9 +689,12 @@ export function buildProgram(deps = {}) {
545
689
  page: positiveInteger(o.page, '--page', 10000), per_page: positiveInteger(o.perPage, '--per-page', 100) });
546
690
  emit(r, () => table(listOf(r), ['created_at', 'action', 'actor_id', 'resource_type', 'resource_name']));
547
691
  });
692
+ // Last: extends the hives groups defined above (flights, logs, env values, domain reads).
693
+ registerHivesCoverage(hives, { client: hivesClient, emit, readRequest: deps.readHivesRequest });
548
694
  // ---- relays ----------------------------------------------------------
549
695
  const relays = program.command('relays').description('Relays: DNS zones and records');
550
696
  registerRelaysEnrollment(relays, client, emit);
697
+ registerOperationCommands(relays, RELAYS_COMMANDS, { client, emit, signal: deps.signal });
551
698
  relays.command('zones').description('List DNS zones').option('--search <q>').action(async (o) => {
552
699
  const r = await (await client()).relays.zones.list(o.search ? { search: o.search } : {});
553
700
  emit(r, () => table((r.zones ?? []), ['name', 'type', 'record_count', 'dnssec_enabled', 'status']));
@@ -560,7 +707,58 @@ export function buildProgram(deps = {}) {
560
707
  const result = await (await client()).relays.zones.dnssec(zone);
561
708
  emit(result, () => JSON.stringify(result, null, 2));
562
709
  });
710
+ // Writes need relays:write and act on the selected project's existing Relays account.
711
+ const dnsInt = (value, option, minimum, maximum) => {
712
+ if (value === undefined)
713
+ return undefined;
714
+ if (!/^\d+$/.test(value) || Number(value) < minimum || Number(value) > maximum)
715
+ throw new Error(`${option} must be an integer from ${minimum} to ${maximum}.`);
716
+ return Number(value);
717
+ };
718
+ const recordId = (value) => dnsInt(value, '<record_id>', 1, Number.MAX_SAFE_INTEGER);
719
+ relays.command('create-zone <name>').description('Create a DNS zone')
720
+ .option('--type <type>', 'NATIVE, MASTER or SLAVE (default NATIVE)').option('--no-default-records', 'skip the default records')
721
+ .action(async (name, o) => {
722
+ const result = await (await client()).relays.zones.create({ name, ...(o.type ? { type: o.type.toUpperCase() } : {}),
723
+ create_default_records: o.defaultRecords });
724
+ emit(result, () => `${c.green('✓')} Created zone ${name}`);
725
+ });
726
+ relays.command('update-zone <zone>').description('Update a zone\'s type or SOA settings')
727
+ .option('--type <type>', 'NATIVE, MASTER or SLAVE').option('--primary-ns <host>').option('--hostmaster <email>')
728
+ .option('--refresh <seconds>').option('--retry <seconds>').option('--expire <seconds>').option('--soa-ttl <seconds>')
729
+ .action(async (zone, o) => {
730
+ const soa = Object.fromEntries(Object.entries({ primary_ns: o.primaryNs, hostmaster: o.hostmaster,
731
+ refresh: dnsInt(o.refresh, '--refresh', 60, 2 ** 31), retry: dnsInt(o.retry, '--retry', 60, 2 ** 31),
732
+ expire: dnsInt(o.expire, '--expire', 3600, 2 ** 31), ttl: dnsInt(o.soaTtl, '--soa-ttl', 60, 2 ** 31) }).filter(([, v]) => v !== undefined));
733
+ const body = { ...(o.type ? { type: o.type.toUpperCase() } : {}), ...(Object.keys(soa).length ? { soa } : {}) };
734
+ if (!Object.keys(body).length)
735
+ throw new Error('Nothing to update: pass --type or an SOA option.');
736
+ const result = await (await client()).relays.zones.update(zone, body);
737
+ emit(result, () => `${c.green('✓')} Updated zone ${zone}`);
738
+ });
739
+ relays.command('delete-zone <zone>').description('Delete a zone and all its records').action(async (zone) => {
740
+ const result = await (await client()).relays.zones.delete(zone);
741
+ emit(result, () => `${c.green('✓')} Deleted zone ${zone}`);
742
+ });
563
743
  const records = relays.command('records').description('DNS records in a zone');
744
+ records.command('get <zone> <record_id>').description('Read one record').action(async (zone, id) => {
745
+ const result = await (await client()).relays.records.get(zone, recordId(id));
746
+ emit(result, () => JSON.stringify(result, null, 2));
747
+ });
748
+ records.command('update <zone> <record_id>').description('Update a record')
749
+ .option('--name <name>').option('--type <type>').option('--content <content>').option('--ttl <seconds>', 'TTL, 60 to 86400')
750
+ .option('--priority <n>', '0 to 65535').option('--comment <text>').option('--disable', 'disable the record').option('--enable', 'enable the record')
751
+ .action(async (zone, id, o) => {
752
+ if (o.disable && o.enable)
753
+ throw new Error('Pass --disable or --enable, not both.');
754
+ const body = Object.fromEntries(Object.entries({ name: o.name, type: o.type?.toUpperCase(), content: o.content,
755
+ ttl: dnsInt(o.ttl, '--ttl', 60, 86400), priority: dnsInt(o.priority, '--priority', 0, 65535), comment: o.comment,
756
+ disabled: o.disable ? true : o.enable ? false : undefined }).filter(([, v]) => v !== undefined));
757
+ if (!Object.keys(body).length)
758
+ throw new Error('Nothing to update: pass at least one field option.');
759
+ const result = await (await client()).relays.records.update(zone, recordId(id), body);
760
+ emit(result, () => `${c.green('✓')} Updated record ${id} in ${zone}`);
761
+ });
564
762
  records.command('list <zone>', { isDefault: true }).option('--type <type>').action(async (zone, o) => {
565
763
  const r = await (await client()).relays.records.list(zone, o.type ? { type: o.type } : {});
566
764
  emit(r, () => table((r.records ?? []), ['id', 'name', 'type', 'content', 'ttl']));
@@ -573,10 +771,31 @@ export function buildProgram(deps = {}) {
573
771
  await (await client()).relays.records.delete(zone, Number(id));
574
772
  emit({ deleted: Number(id) }, () => `${c.green('✓')} Deleted record ${id} from ${zone}`);
575
773
  });
576
- addHenryCommands(program, client, value => out.log(JSON.stringify(value, null, 2)));
774
+ addHenryCommands(program, client, value => out.log(JSON.stringify(value, null, 2)), { line: text => out.log(text), ...(deps.signal ? { signal: deps.signal } : {}) });
577
775
  registerFleets(program, client, (data) => emit(data, () => JSON.stringify(data, null, 2)));
578
- registerFlocks(program, client, (data) => emit(data, () => JSON.stringify(data, null, 2)), env);
776
+ registerFlocks(program, client, emit, env, deps.readFlocksCode, () => !!g().json);
579
777
  registerBeacons(program, client, emit);
778
+ // ---- setup -----------------------------------------------------------
779
+ // The first-run experience lives in ./onboard and is imported only here, so no other command loads it.
780
+ program.command('setup [step]').description('Set up Genesis: sign in, pick a project, add the MCP server to your agents (resumable)')
781
+ .option('--restart', 'start again from the first step')
782
+ .option('--no-animation', 'draw every step still (also GENESIS_REDUCED_MOTION=1)')
783
+ .option('--screen-reader', 'linear output for screen readers (also GENESIS_SCREEN_READER=1)')
784
+ .option('--screencast', 'mask account names, emails and IDs, for recording demos (also GENESIS_SCREENCAST=1)')
785
+ .option('--sound', 'a soft chime when you are signed in and when setup finishes (also GENESIS_SOUND=1)')
786
+ .action(async (step, o) => {
787
+ const code = await runSetupLazily(o, step);
788
+ if (code)
789
+ throw new CommanderError(code, 'genesis.setup', '');
790
+ });
791
+ const runSetupLazily = async (o, step) => {
792
+ const [{ runSetup }, authModule, deviceModule] = await Promise.all([import('./onboard/index.js'), import('./auth.js'), import('./device-login.js')]);
793
+ return runSetup({ version: program.version() ?? '', env, fetch: f, auth, getStore, client: () => client(), openBrowser: deps.openBrowser ?? defaultOpen,
794
+ baseUrl: g().baseUrl ?? env.GENESIS_BASE_URL ?? DEFAULT_BASE_URL, log: (s) => out.log(s),
795
+ flags: { json: g().json, animation: o.animation, screenReader: o.screenReader, sound: o.sound, screencast: o.screencast, restart: o.restart, step },
796
+ login: { startLogin: authModule.startLogin, completeLogin: authModule.completeLogin, selectSavedContext: authModule.selectSavedContext,
797
+ savedLoginProvider: authModule.savedLoginProvider, startDeviceLogin: deviceModule.startDeviceLogin, completeDeviceLogin: deviceModule.completeDeviceLogin } });
798
+ };
580
799
  // ---- status ----------------------------------------------------------
581
800
  program.command('status').description('Live state of each Genesis service, from the gateway (GET /v1/status)').action(async () => {
582
801
  const configured = g().baseUrl ?? env.GENESIS_BASE_URL ?? DEFAULT_BASE_URL;
@@ -591,7 +810,7 @@ export function buildProgram(deps = {}) {
591
810
  const url = `${base.href.replace(/\/+$/, '')}/v1/status`;
592
811
  let response;
593
812
  try {
594
- response = await f(url, { method: 'GET', headers: { accept: 'application/json', 'user-agent': 'genesis-cli/0.1.13' }, signal: AbortSignal.timeout(15_000) });
813
+ response = await f(url, { method: 'GET', headers: { accept: 'application/json', 'user-agent': 'genesis-cli/0.2.1' }, signal: AbortSignal.timeout(15_000) });
595
814
  }
596
815
  catch (e) {
597
816
  throw new Error(`Gateway unreachable at ${safe(url)}: ${safe(e.message)}`);
@@ -609,9 +828,26 @@ export function buildProgram(deps = {}) {
609
828
  });
610
829
  return program;
611
830
  }
831
+ /**
832
+ * A bare `genesis` on a terminal, before setup is finished, is the first run: it opens `genesis setup`. Everywhere
833
+ * else (a pipe, CI, TERM=dumb, setup complete) it prints the help as before. Only the setup state file is read here.
834
+ */
835
+ async function firstRun(argv, deps) {
836
+ if (argv.length > 2 || deps.out)
837
+ return false;
838
+ const env = deps.env ?? process.env;
839
+ const set = (v) => !!v && v !== '0' && v !== 'false';
840
+ if (!process.stdout.isTTY || !process.stdin.isTTY || env.TERM === 'dumb'
841
+ || set(env.CI) || env.GITHUB_ACTIONS || env.BUILDKITE || env.GITLAB_CI || env.TF_BUILD)
842
+ return false;
843
+ const { SetupStore, complete } = await import('./onboard/state.js');
844
+ return !complete(await SetupStore.for(env).read());
845
+ }
612
846
  export async function main(argv, deps = {}) {
613
847
  const out = deps.out ?? stdio;
614
848
  const program = buildProgram(deps);
849
+ if (await firstRun(argv, deps).catch(() => false))
850
+ argv = [...argv, 'setup'];
615
851
  try {
616
852
  await program.parseAsync(argv);
617
853
  return 0;
@@ -621,20 +857,27 @@ export async function main(argv, deps = {}) {
621
857
  return e.exitCode;
622
858
  const json = program.opts().json;
623
859
  if (e instanceof GenesisError) {
860
+ // Dials needs a project, workspace and environment: from flags, a selected context or an org key's binding.
861
+ const dialsScopeHint = e.operationId?.startsWith('dials_') && /invalid_scope/i.test(`${e.code ?? ''} ${e.message} ${e.type}`)
862
+ ? ' Pass --workspace, --project and --environment, select a context (genesis context select), or use an org API key bound to a project.' : '';
863
+ const receipt = e instanceof HivesOperationError || e instanceof HivesQueueOperationError;
864
+ const code = errorCatalog(e).code, body = json ? errorBody(e) : undefined;
624
865
  if (json)
625
- out.err(JSON.stringify({ error: { type: e.type, status: e.status, message: e.message, request_id: e.requestId, ...errorCatalog(e),
866
+ out.err(JSON.stringify({ error: { type: e.type, status: e.status, message: redactMessage(e.message), request_id: e.requestId, ...errorCatalog(e),
626
867
  ...((e instanceof HivesOperationError || e instanceof HivesQueueOperationError) ? { operation: e.operation } : {}),
627
868
  ...(e.operationId && isMindsOperation(e.operationId) && errorOutcome(e.body) === 'unknown' ? { outcome: 'unknown' } : {}),
628
869
  ...(e instanceof MindsAgentError || e instanceof MindsOwnerError ? { code: e.code, ...(e instanceof MindsAgentError ? { dpop_nonce: e.dpopNonce } : {}),
629
- ...(errorOutcome(e.body) !== undefined ? { outcome: errorOutcome(e.body) } : {}) } : {}) } }));
870
+ ...(errorOutcome(e.body) !== undefined ? { outcome: errorOutcome(e.body) } : {}) } : {}),
871
+ // The service's whole error body (redacted and bounded), e.g. the failed Flocks `run` beside a 503.
872
+ ...(receipt || body === undefined ? {} : { body }) } }));
630
873
  else
631
- out.err(`${c.red('✗')} ${safe(e.message)}${(e instanceof HivesOperationError || e instanceof HivesQueueOperationError) ? ` Operation: ${safe(e.operation.operation_id)} (${safe(e.operation.state)}).` : ''}${e.status ? c.dim(` (${e.status} ${safe(e.type)}${e.requestId ? `, request ${safe(e.requestId)}` : ''})`) : ''}`);
874
+ out.err(`${c.red('✗')} ${printable(e.message)}${dialsScopeHint}${(e instanceof HivesOperationError || e instanceof HivesQueueOperationError) ? ` Operation: ${safe(e.operation.operation_id)} (${safe(e.operation.state)}).` : ''}${e.status ? c.dim(` (${e.status} ${safe(e.type)})`) : ''}${code ? `\n${c.dim(`code: ${safe(code)}`)}` : ''}${e.requestId ? `\n${c.dim(`request_id: ${safe(e.requestId)}`)}` : ''}`);
632
875
  return 1;
633
876
  }
634
- const msg = e.message ?? String(e);
877
+ const msg = redactMessage(e.message ?? String(e));
635
878
  const net = /fetch failed|ENOTFOUND|ECONNREFUSED/.test(msg + (e.cause?.code ?? ''));
636
879
  out.err(json ? JSON.stringify({ error: { type: net ? 'network' : 'error', message: msg } })
637
- : `${c.red('✗')} ${net ? `Could not reach the Genesis API gateway (${safe(msg)}). Try --direct to call service hosts directly.` : safe(msg)}`);
880
+ : `${c.red('✗')} ${net ? `Could not reach the Genesis API gateway (${printable(msg)}). Try --direct to call service hosts directly.` : printable(msg)}`);
638
881
  return 1;
639
882
  }
640
883
  }
package/dist/minds.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  import { type Command } from 'commander';
2
- import type { Genesis } from '@onesi/genesis';
2
+ import { type Genesis } from '@onesi/genesis';
3
3
  export declare function registerMinds(program: Command, client: () => Promise<Genesis>, emit: (data: unknown) => void, agentClient?: () => Promise<Genesis>, signal?: AbortSignal, ownerClient?: () => Promise<Genesis>): void;
package/dist/minds.js CHANGED
@@ -1,8 +1,10 @@
1
1
  import { readFile, stat } from 'node:fs/promises';
2
2
  import { registerMindsOwner } from './minds-owner.js';
3
3
  import { Option } from 'commander';
4
+ import { mindsServices } from '@onesi/genesis';
4
5
  export function registerMinds(program, client, emit, agentClient = client, signal, ownerClient = client) {
5
- const minds = program.command('minds').description('Owned Minds and persisted memory; requires an admitted human Console Sandbox session');
6
+ // `minds engine ...` (generated, products.ts) may already have created the root.
7
+ const minds = (program.commands.find(c => c.name() === 'minds') ?? program.command('minds')).description('Owned Minds and persisted memory; a human Console Sandbox session, or an org API key with minds:read/minds:write/minds:manage');
6
8
  registerMindsOwner(minds, ownerClient, emit, signal);
7
9
  minds.command('list').option('--after <uuid>').option('--limit <number>').description('List real Minds and the current creation context fingerprint').action(async (options) => {
8
10
  emit(await (await client()).minds.list({ ...(options.after ? { after: options.after } : {}), ...(options.limit ? { limit: Number(options.limit) } : {}) }));
@@ -17,7 +19,13 @@ export function registerMinds(program, client, emit, agentClient = client, signa
17
19
  .requiredOption('--context-sha256 <hash>', 'context_sha256 from the current minds list response')
18
20
  .addOption(new Option('--plan <plan>', 'explicit plan; entitlement remains server-authorized').choices(['free', 'pro']))
19
21
  .addOption(new Option('--profile-id <profile>', 'fixed native profile; Pro defaults to D1, Free accepts only F1').choices(['F1', 'D1', 'D2']))
20
- .action(async (name, options) => emit(await (await client()).minds.create(name, { idempotencyKey: options.idempotencyKey, contextSha256: options.contextSha256, ...(options.plan === undefined ? {} : { plan: options.plan }), ...(options.profileId === undefined ? {} : { profileId: options.profileId }) })));
22
+ .option('--services <list>', `comma-separated engine service groups (${mindsServices.join(', ')}); kv and memory always run`)
23
+ .action(async (name, options) => {
24
+ const services = options.services === undefined ? undefined : String(options.services).split(',').map((item) => item.trim()).filter(Boolean);
25
+ if (services && services.some(item => !mindsServices.includes(item)))
26
+ throw new Error(`Unknown Minds service; choose from ${mindsServices.join(', ')}.`);
27
+ emit(await (await client()).minds.create(name, { idempotencyKey: options.idempotencyKey, contextSha256: options.contextSha256, ...(options.plan === undefined ? {} : { plan: options.plan }), ...(options.profileId === undefined ? {} : { profileId: options.profileId }), ...(services === undefined ? {} : { services }) }));
28
+ });
21
29
  minds.command('resume <mind_id>').description('Resume the same Mind and retained volume; 202 means the operation is accepted')
22
30
  .requiredOption('--expected-generation <number>', 'desired_generation from a fresh owned Mind read')
23
31
  .requiredOption('--idempotency-key <uuid>', 'persist this UUID4 with the original generation after an uncertain response')
@@ -27,6 +35,23 @@ export function registerMinds(program, client, emit, agentClient = client, signa
27
35
  .requiredOption('--expected-generation <number>', 'desired_generation from a fresh owned Mind read')
28
36
  .requiredOption('--idempotency-key <uuid>', 'persist this UUID4 and original generation; no automatic retry')
29
37
  .action(async (id, options) => emit(await (await client()).minds.stop(id, Number(options.expectedGeneration), { idempotencyKey: options.idempotencyKey })));
38
+ minds.command('delete <mind_id>').description('Delete the Mind (lifecycle delete; org API keys need minds:write); 202 means accepted')
39
+ .requiredOption('--expected-generation <number>', 'desired_generation from a fresh owned Mind read')
40
+ .requiredOption('--idempotency-key <uuid>', 'persist this UUID4 and original generation; no automatic retry')
41
+ .action(async (id, options) => emit(await (await client()).minds.delete(id, Number(options.expectedGeneration), { idempotencyKey: options.idempotencyKey })));
42
+ minds.command('resize <mind_id>').description('Resize to an existing Pro profile (org API keys need minds:manage); 202 means accepted')
43
+ .requiredOption('--expected-generation <number>', 'desired_generation from a fresh owned Mind read')
44
+ .addOption(new Option('--profile-id <profile>', 'existing Pro profile').choices(['F1', 'D1', 'D2']).makeOptionMandatory())
45
+ .requiredOption('--idempotency-key <uuid>', 'persist this UUID4 and original generation; no automatic retry')
46
+ .action(async (id, options) => emit(await (await client()).minds.resize(id, Number(options.expectedGeneration), options.profileId, { idempotencyKey: options.idempotencyKey })));
47
+ const deliveries = minds.command('webhook-deliveries').description('Durable webhook receipts for an owned Mind; a stopped-idle Mind is woken');
48
+ deliveries.command('create <mind_id>').description('Record one delivery; replay the same delivery_id and body to read the original receipt')
49
+ .requiredOption('--delivery-id <uuid>', 'caller-chosen non-nil UUID; retain it after an uncertain response')
50
+ .requiredOption('--event-reference <reference>', 'opaque upstream event reference ([A-Za-z0-9_.:/-], at most 200)')
51
+ .requiredOption('--event-sha256 <hash>', 'lowercase hex SHA-256 of the upstream event')
52
+ .action(async (id, options) => emit(await (await client()).minds.webhookDeliveries.create(id, { delivery_id: options.deliveryId, event_reference: options.eventReference, event_sha256: options.eventSha256 })));
53
+ deliveries.command('get <mind_id> <delivery_id>').description('Read a receipt this principal issued')
54
+ .action(async (id, delivery) => emit(await (await client()).minds.webhookDeliveries.get(id, delivery)));
30
55
  const memory = minds.command('memory').description('Every operation selects one owned, ready Mind');
31
56
  memory.command('insert <mind_id>').requiredOption('--event-file <path>', 'JSON event with id, content and timestamp; returned id is its storage UUID')
32
57
  .action(async (id, options) => {
@@ -55,6 +80,16 @@ export function registerMinds(program, client, emit, agentClient = client, signa
55
80
  emit(await (await client()).minds.memory.search(mind, text, Number(options.limit)));
56
81
  });
57
82
  const agent = minds.command('agent').description('Use an existing agent credential; does not create an attachment or exchange a human session');
83
+ const createProof = async (path) => ({ signal, dpopProof: await readBounded(path, 4_096) });
84
+ agent.command('create <name>').description('Create a Free/F1 Sandbox Mind with an approved agent-create token; 202 means provisioning')
85
+ .requiredOption('--approval-id <uuid>', 'owner approval admitting this creation')
86
+ .requiredOption('--approval-version <number>', 'approval version')
87
+ .requiredOption('--operation-id <uuid>', 'persisted UUID4; also the idempotency key and the status lookup id')
88
+ .requiredOption('--dpop-proof-file <path>', 'fresh DPoP proof for the controller create method and URL')
89
+ .action(async (name, options) => emit(await (await agentClient()).minds.agent.create({ name, approvalId: options.approvalId, approvalVersion: Number(options.approvalVersion), operationId: options.operationId }, await createProof(options.dpopProofFile))));
90
+ agent.command('create-status <operation_id>').description('Read an agent Mind creation operation')
91
+ .requiredOption('--dpop-proof-file <path>', 'fresh DPoP proof for the controller status method and URL')
92
+ .action(async (operation, options) => emit(await (await agentClient()).minds.agent.createStatus(operation, await createProof(options.dpopProofFile))));
58
93
  const agentMemory = agent.command('memory').description('Private or explicitly authorized group memory; requests are never automatically retried');
59
94
  const scoped = (command) => command.option('--group <agp_id>', 'explicit canonical group; omit for private memory')
60
95
  .option('--dpop-proof-file <path>', 'fresh DPoP proof for the controller capability method and URL');