@onesi/genesis-cli 0.0.1-stage → 0.1.13

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 (49) hide show
  1. package/README.md +37 -0
  2. package/bin/genesis.js +3 -0
  3. package/dist/auth.d.ts +30 -0
  4. package/dist/auth.js +273 -0
  5. package/dist/beacons.d.ts +3 -0
  6. package/dist/beacons.js +94 -0
  7. package/dist/device-login.d.ts +25 -0
  8. package/dist/device-login.js +173 -0
  9. package/dist/dials.d.ts +7 -0
  10. package/dist/dials.js +96 -0
  11. package/dist/fleets.d.ts +4 -0
  12. package/dist/fleets.js +72 -0
  13. package/dist/flocks.d.ts +3 -0
  14. package/dist/flocks.js +72 -0
  15. package/dist/function-input.d.ts +3 -0
  16. package/dist/function-input.js +49 -0
  17. package/dist/henry.d.ts +3 -0
  18. package/dist/henry.js +26 -0
  19. package/dist/hives-marketplace.d.ts +10 -0
  20. package/dist/hives-marketplace.js +29 -0
  21. package/dist/hives-node-transport.d.ts +6 -0
  22. package/dist/hives-node-transport.js +147 -0
  23. package/dist/hives-operations.d.ts +9 -0
  24. package/dist/hives-operations.js +61 -0
  25. package/dist/hives-resources.d.ts +9 -0
  26. package/dist/hives-resources.js +94 -0
  27. package/dist/index.d.ts +21 -0
  28. package/dist/index.js +640 -0
  29. package/dist/lifeid-native.d.ts +24 -0
  30. package/dist/lifeid-native.js +113 -0
  31. package/dist/minds-owner.d.ts +3 -0
  32. package/dist/minds-owner.js +42 -0
  33. package/dist/minds.d.ts +3 -0
  34. package/dist/minds.js +90 -0
  35. package/dist/output.d.ts +28 -0
  36. package/dist/output.js +46 -0
  37. package/dist/products.d.ts +14 -0
  38. package/dist/products.js +85 -0
  39. package/dist/relays-enrollment.d.ts +3 -0
  40. package/dist/relays-enrollment.js +7 -0
  41. package/dist/runner.d.ts +4 -0
  42. package/dist/runner.js +55 -0
  43. package/dist/secret-input.d.ts +14 -0
  44. package/dist/secret-input.js +85 -0
  45. package/dist/store.d.ts +39 -0
  46. package/dist/store.js +33 -0
  47. package/dist/wasm-input.d.ts +2 -0
  48. package/dist/wasm-input.js +31 -0
  49. package/package.json +54 -1
package/dist/index.js ADDED
@@ -0,0 +1,640 @@
1
+ import { registerMinds } from './minds.js';
2
+ import { registerRelaysEnrollment } from './relays-enrollment.js';
3
+ import { registerBeacons } from './beacons.js';
4
+ import { registerDials } from './dials.js';
5
+ import { registerProducts } from './products.js';
6
+ 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';
8
+ import { authConfig, completeLogin, selectSavedContext, savedLoginProvider, startLogin } from './auth.js';
9
+ import { completeDeviceLogin, startDeviceLogin } from './device-login.js';
10
+ import { keychainStore } from './store.js';
11
+ import { c, listOf, safe, stdio, table } from './output.js';
12
+ import { spawn } from 'node:child_process';
13
+ import { addHenryCommands } from './henry.js';
14
+ import { registerFleets } from './fleets.js';
15
+ import { registerFlocks } from './flocks.js';
16
+ import { registerRunner } from './runner.js';
17
+ import { createHivesNodeTransport } from './hives-node-transport.js';
18
+ import { registerHivesOperations } from './hives-operations.js';
19
+ import { readFunctionFile, readFunctionJson, writeFunctionOutput } from './function-input.js';
20
+ import { basename } from 'node:path';
21
+ import { readWasmFile } from './wasm-input.js';
22
+ import { readSecretInput, validateSecret, validateSecretValue, parseSecretBatch } from './secret-input.js';
23
+ function defaultOpen(url) {
24
+ const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
25
+ const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
26
+ try {
27
+ spawn(cmd, args, { stdio: 'ignore', detached: true }).on('error', () => { }).unref();
28
+ }
29
+ catch { /* print-only fallback */ }
30
+ }
31
+ export function buildProgram(deps = {}) {
32
+ const out = deps.out ?? stdio;
33
+ const env = deps.env ?? process.env;
34
+ const f = deps.fetch ?? fetch;
35
+ const getStore = deps.store ?? keychainStore;
36
+ const auth = { ...authConfig(env, f), sleep: deps.sleep };
37
+ const program = new Command('genesis')
38
+ .description('One CLI for every Genesis service.')
39
+ .version('0.1.13')
40
+ .option('--json', 'machine-readable JSON output')
41
+ .option('--api-key <key>', 'use this API key (default: GENESIS_API_KEY, then your `genesis login`)')
42
+ .option('--base-url <url>', 'gateway URL (default: GENESIS_BASE_URL or https://os.one.ml/api)')
43
+ .option('--direct', 'skip the gateway and call each service host directly (for local dev)')
44
+ .option('--org <org_id>', 'organization to act on')
45
+ .showHelpAfterError()
46
+ .exitOverride()
47
+ .configureOutput({ writeOut: (s) => out.log(s.trimEnd()), writeErr: (s) => out.err(s.trimEnd()) });
48
+ const g = () => program.opts();
49
+ const client = async (hivesTransport = false) => {
50
+ const o = g();
51
+ const store = await getStore().catch(() => undefined);
52
+ const opts = {
53
+ apiKey: o.apiKey ?? env.GENESIS_API_KEY, token: env.GENESIS_TOKEN,
54
+ 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',
56
+ credentials: store ? savedLoginProvider(auth, store) : undefined,
57
+ };
58
+ return new Genesis(hivesTransport ? { ...opts, fetch: createHivesNodeTransport(opts, deps.signal) } : opts);
59
+ };
60
+ const emit = (data, pretty) => out.log(g().json ? JSON.stringify(data, null, 2) : pretty());
61
+ const positiveInteger = (value, option, maximum) => {
62
+ if (value === undefined)
63
+ return undefined;
64
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < 1 || Number(value) > maximum) {
65
+ throw new Error(`${option} must be an integer from 1 to ${maximum}.`);
66
+ }
67
+ return Number(value);
68
+ };
69
+ const projectName = (name) => {
70
+ if (name.length < 3 || name.length > 64 || !/^[a-z][a-z0-9-]*[a-z0-9]$/.test(name)) {
71
+ throw new Error('Project name must be 3–64 lowercase letters, numbers or hyphens, start with a letter and end with a letter or number.');
72
+ }
73
+ return name;
74
+ };
75
+ registerRunner(program, client, emit);
76
+ const orgId = async (gen) => {
77
+ if (g().org)
78
+ return g().org;
79
+ const s = await (await getStore().catch(() => undefined))?.get();
80
+ if (s?.organization_id && !g().apiKey && !env.GENESIS_API_KEY)
81
+ return s.organization_id;
82
+ return (await gen.whoami()).org;
83
+ };
84
+ 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)));
93
+ const traces = program.command('traces').description('Research and evidence');
94
+ const tracesRuns = traces.command('runs').description('Research runs in your verified organization');
95
+ tracesRuns.command('create').requiredOption('--type <type>', 'Run type, e.g. deep_research').requiredOption('--query <query>', 'Research question')
96
+ .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)));
98
+ tracesRuns.command('list').action(async () => out.log(JSON.stringify(await (await client()).traces.runs.list(), null, 2)));
99
+ tracesRuns.command('get <run_id>').action(async (id) => out.log(JSON.stringify(await (await client()).traces.runs.get(id), null, 2)));
100
+ 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)));
102
+ traces.command('usage').action(async () => out.log(JSON.stringify(await (await client()).traces.usage.events(), null, 2)));
103
+ const blinds = program.command('blinds').description('Blinds credential custody');
104
+ const show = async (call) => out.log(JSON.stringify(await call((await client()).blinds), null, 2));
105
+ const key = (o) => ({ signal: deps.signal, ...(o.idempotencyKey ? { idempotencyKey: o.idempotencyKey } : {}) });
106
+ const keyOption = ['--idempotency-key <uuid>', 'Reuse the UUID from an uncertain earlier attempt; otherwise a new one is minted'];
107
+ const credentials = blinds.command('credentials').description('Credential variable names (values never leave Blinds)');
108
+ credentials.command('list').action(() => show(b => b.credentials.list({ signal: deps.signal })));
109
+ credentials.command('set <variable>').description('Store a static credential from a file or stdin, never argv')
110
+ .requiredOption('--type <type>', 'api_key, bearer, ...').option('--file <path>', 'Read the value from this file').option('--stdin', 'Read the value from stdin')
111
+ .option('--domain <domain...>', 'Destination domains the value may be injected into').option('--provider <slug>').option(...keyOption)
112
+ .action(async (variable, o) => {
113
+ const value = (await (deps.readSecretInput ?? readSecretInput)(o, 16 * 1024)).replace(/\r?\n$/, '');
114
+ await show(b => b.credentials.store({ variable_name: variable, value, credential_type: o.type,
115
+ ...(o.domain ? { destination_domains: o.domain } : {}), ...(o.provider ? { provider: o.provider } : {}) }, key(o)));
116
+ });
117
+ credentials.command('revoke <variable>').option(...keyOption).action((variable, o) => show(b => b.credentials.revoke(variable, key(o))));
118
+ blinds.command('connections').description('Active OAuth connections (metadata only)').action(() => show(b => b.oauth.connections({ signal: deps.signal })));
119
+ const consent = blinds.command('consent').description('Agent consent requests and decisions');
120
+ consent.command('pending').action(() => show(b => b.consent.pending({ signal: deps.signal })));
121
+ consent.command('list').action(() => show(b => b.consent.records({ signal: deps.signal })));
122
+ consent.command('approve <consent_id>').requiredOption('--signature <sig>', 'One account approval signature').option('--expires-in <seconds>').option(...keyOption)
123
+ .action((id, o) => show(b => b.consent.approve(id, { signature: o.signature, ...(o.expiresIn ? { expires_in_seconds: Number(o.expiresIn) } : {}) }, key(o))));
124
+ consent.command('deny <consent_id>').option(...keyOption).action((id, o) => show(b => b.consent.deny(id, key(o))));
125
+ consent.command('revoke <consent_id>').option(...keyOption).action((id, o) => show(b => b.consent.revoke(id, key(o))));
126
+ const delegates = blinds.command('delegates').description('Which agents hold which variables');
127
+ delegates.command('list').action(() => show(b => b.delegates.list({ signal: deps.signal })));
128
+ delegates.command('create <name>').requiredOption('--variable <name...>', 'Variables to delegate').requiredOption('--ttl <seconds>', 'Lifetime in seconds')
129
+ .option('--max-depth <n>', 'Sub-delegation depth', '1').option(...keyOption)
130
+ .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
+ delegates.command('revoke <delegate_id>').option(...keyOption).action((id, o) => show(b => b.delegates.revoke(id, key(o))));
132
+ blinds.command('activity').description('Latest 200 Blinds audit events').action(() => show(b => b.activity({ signal: deps.signal })));
133
+ const owner = blinds.command('owner-enrollment')
134
+ .description('Original-human enrollment only; preserve operation and locator IDs after unknown outcomes');
135
+ for (const [action, operation] of Object.entries({ reserve: 'blinds_ownerReserve', sign: 'blinds_ownerSign', attach: 'blinds_ownerAttach', 'p256-ensure': 'blinds_ownerP256Ensure' })) {
136
+ owner.command(action).requiredOption('--body-file <path>', 'Closed JSON request, up to 2 KiB; original human login required')
137
+ .action(async (options) => {
138
+ const body = await readFunctionJson(options.bodyFile, 2048);
139
+ const result = await (await client()).request(operation, { body }, { signal: deps.signal });
140
+ out.log(JSON.stringify(result, null, 2));
141
+ });
142
+ }
143
+ registerDials(program, { client, orgId, emit });
144
+ registerProducts(program, { client, emit, signal: deps.signal });
145
+ // ---- auth ------------------------------------------------------------
146
+ program.command('login').description('Sign in with One in your browser').option('--no-browser', 'just print the link')
147
+ .option('--device', 'sign in from a machine without a local browser: approve a code from any signed-in browser')
148
+ .action(async (o) => {
149
+ const store = await getStore();
150
+ if (o.device) {
151
+ const start = await startDeviceLogin(auth, deps.signal);
152
+ const uri = safe(start.verificationUri), complete = safe(start.verificationUriComplete), userCode = safe(start.userCode);
153
+ if (g().json)
154
+ out.err(JSON.stringify({ user_code: userCode, verification_uri: uri,
155
+ verification_uri_complete: complete, expires_in: start.expiresIn }));
156
+ else
157
+ out.err(`\n On any device, open ${c.bold(uri)}\n and enter the code ${c.bold(userCode)}\n\n Or open ${complete}\n ${c.dim(`Waiting for approval (expires in ${Math.round(start.expiresIn / 60)} min)…`)}\n`);
158
+ const s = await completeDeviceLogin(auth, start, deps.signal);
159
+ await store.set(s);
160
+ emit({ user: s.user, organization_id: s.organization_id, store: store.kind, via: 'device' }, () => `${c.green('✓')} Signed in as ${c.bold(s.user?.email ?? s.user?.id ?? 'you')}${s.organization_id ? ` (${s.organization_id})` : ''}. Token saved to your ${store.kind === 'os-keychain' ? 'OS keychain' : store.kind}.`);
161
+ return;
162
+ }
163
+ const attempt = await startLogin(auth, { signal: deps.signal });
164
+ let s;
165
+ try {
166
+ out.err(`\n Open ${attempt.browserUrl}\n ${c.dim('Waiting for One account sign-in in the browser…')}\n`);
167
+ if (o.browser)
168
+ (deps.openBrowser ?? defaultOpen)(attempt.browserUrl);
169
+ s = await completeLogin(auth, attempt);
170
+ }
171
+ finally {
172
+ attempt.close();
173
+ }
174
+ await store.set(s);
175
+ emit({ user: s.user, organization_id: s.organization_id, store: store.kind }, () => `${c.green('✓')} Signed in as ${c.bold(s.user?.email ?? s.user?.id ?? 'you')}${s.organization_id ? ` (${s.organization_id})` : ''}. Token saved to your ${store.kind === 'os-keychain' ? 'OS keychain' : store.kind}.`);
176
+ });
177
+ program.command('logout').description('Forget your saved login').action(async () => {
178
+ const had = await (await getStore()).clear();
179
+ emit({ logged_out: had }, () => had ? `${c.green('✓')} Signed out.` : 'You were not signed in.');
180
+ });
181
+ program.command('whoami').description('Show who you are signed in as').action(async () => {
182
+ const gen = await client();
183
+ const saved = await (await getStore().catch(() => undefined))?.get();
184
+ try {
185
+ const p = await gen.whoami();
186
+ emit({ ...p, user: saved?.user }, () => [
187
+ `${c.bold('Org')} ${p.org}`, `${c.bold('Principal')} ${p.principal ?? '-'}`,
188
+ `${c.bold('Via')} ${p.kind}`, `${c.bold('Scopes')} ${p.scopes.join(', ') || '-'}`,
189
+ ...(saved?.user?.email ? [`${c.bold('User')} ${saved.user.email}`] : [])
190
+ ].join('\n'));
191
+ }
192
+ catch (e) {
193
+ if (!saved || g().apiKey || env.GENESIS_API_KEY)
194
+ throw e;
195
+ // 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)}`);
197
+ }
198
+ });
199
+ const orgs = program.command('orgs').description('Your organizations');
200
+ orgs.command('list', { isDefault: true }).description('Show your current organization').action(async () => {
201
+ const s = await (await getStore()).get();
202
+ if (!s)
203
+ throw new Error('Not signed in. Run `genesis login`.');
204
+ const rows = s.organization_id ? [{ id: s.organization_id, current: '*' }] : [];
205
+ emit({ current: s.organization_id ?? null, organizations: rows }, () => `${table(rows, ['id', 'current'])}\n${c.dim('Listing every org you belong to needs a One account memberships endpoint (not built yet). Switch with `genesis orgs switch <org_id>`.')}`);
206
+ });
207
+ orgs.command('switch <org_id>').description('Switch your login to another organization').action(async (id) => {
208
+ const store = await getStore();
209
+ const s = await store.get();
210
+ if (!s)
211
+ throw new Error('Not signed in. Run `genesis login`.');
212
+ const n = await selectSavedContext(auth, store, { organizationId: id }, deps.signal);
213
+ emit({ organization_id: n.organization_id }, () => `${c.green('✓')} Now using ${c.bold(n.organization_id ?? id)}.`);
214
+ });
215
+ const selection = program.command('context').description('Select an authorized Genesis organization, workspace and project');
216
+ selection.command('select').requiredOption('--organization <id>').requiredOption('--workspace <id>').requiredOption('--project <id>')
217
+ .description('Bind the saved human login to a Console Sandbox context and atomically replace its credentials')
218
+ .action(async (options) => {
219
+ const target = { organizationId: options.organization, workspaceId: options.workspace, projectId: options.project,
220
+ platformId: 'pf_console', fleetId: null, environmentId: 'sandbox' };
221
+ const next = await selectSavedContext(auth, await getStore(), target, deps.signal);
222
+ emit({ principal_id: next.auth?.principal_id, ...target }, () => `Selected ${target.organizationId} / ${target.workspaceId} / ${target.projectId} (Sandbox).`);
223
+ });
224
+ // ---- keys ------------------------------------------------------------
225
+ const keys = program.command('keys').description('Org API keys (One account / WorkOS)');
226
+ keys.command('list').action(async () => {
227
+ const gen = await client();
228
+ const org = await orgId(gen);
229
+ const r = await gen.keys.list(org);
230
+ emit(r, () => table(r.data, ['id', 'name', 'obfuscated_value', 'scopes', 'last_used_at', 'expires_at']));
231
+ });
232
+ keys.command('create <name>').description('Create a key (shown once), or with --blinds hand it straight to Blinds so nobody ever sees it')
233
+ .option('--scopes <list>', 'comma-separated scopes, e.g. hives:read,relays:write')
234
+ .option('--blinds', 'store the key in Blinds; the raw key is never shown to anyone')
235
+ .option('--expires <iso>', 'expiry, e.g. 2027-01-01T00:00:00Z')
236
+ .action(async (name, o) => {
237
+ const gen = await client();
238
+ const org = await orgId(gen);
239
+ const k = await gen.keys.create(org, { name, custody: o.blinds ? 'blinds' : 'reveal', ...(o.scopes ? { scopes: o.scopes.split(',').map((x) => x.trim()).filter(Boolean) } : {}), ...(o.expires ? { expires_at: o.expires } : {}) });
240
+ emit(k, () => o.blinds
241
+ ? `${c.green('✓')} Key ${c.bold(k.id)} created and stored in Blinds as ${c.bold(String(k.blinds_credential_ref))}.\n ${c.dim('No one, human or agent, will ever see the raw key.')}`
242
+ : `${c.green('✓')} Key ${c.bold(k.id)} created. Copy it now; it won't be shown again:\n\n ${c.bold(String(k.value))}\n`);
243
+ });
244
+ keys.command('revoke <key_id>').action(async (id) => {
245
+ const gen = await client();
246
+ await gen.keys.revoke(id);
247
+ emit({ revoked: id }, () => `${c.green('✓')} Revoked ${id} ${c.dim('(takes effect within 60s)')}`);
248
+ });
249
+ // ---- foundry ---------------------------------------------------------
250
+ const foundry = program.command('foundry').description('Foundry: models and inference');
251
+ const foundryBilling = foundry.command('billing').description('Current organization’s native TEST billing; original One account human login required');
252
+ foundryBilling.command('profile').action(async () => out.log(JSON.stringify(await (await client()).foundry.billing.profile({ signal: deps.signal }), null, 2)));
253
+ 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)));
254
+ foundryBilling.command('checkout').requiredOption('--purchase-key <key>', 'Persisted purchase key; server controls the TEST amount').action(async (o) => out.log(JSON.stringify(await (await client()).foundry.billing.checkoutCredits(o.purchaseKey, { signal: deps.signal }), null, 2)));
255
+ foundryBilling.command('balance').action(async () => out.log(JSON.stringify(await (await client()).foundry.billing.creditBalance({ signal: deps.signal }), null, 2)));
256
+ foundry.command('models').action(async () => {
257
+ const r = await (await client()).foundry.models.list();
258
+ const rows = listOf(r).map((m) => ({ id: m.id, owned_by: m.owned_by ?? m.provider ?? '' }));
259
+ emit(r, () => table(rows, ['id', 'owned_by']));
260
+ });
261
+ foundry.command('chat <prompt...>').description('Chat with a model').option('-m, --model <model>', 'model id', env.GENESIS_MODEL)
262
+ .option('--no-stream', 'wait for the full answer').option('-s, --system <text>', 'system prompt')
263
+ .action(async (prompt, o) => {
264
+ if (!o.model)
265
+ throw new Error('Pick a model with --model (see `genesis foundry models`) or set GENESIS_MODEL.');
266
+ const gen = await client();
267
+ const messages = [...(o.system ? [{ role: 'system', content: o.system }] : []), { role: 'user', content: prompt.join(' ') }];
268
+ if (o.stream && !g().json) {
269
+ for await (const d of gen.foundry.chatStream({ model: o.model, messages }))
270
+ process.stdout.write(d);
271
+ process.stdout.write('\n');
272
+ return;
273
+ }
274
+ const r = await gen.foundry.chat({ model: o.model, messages });
275
+ emit(r, () => r.choices?.[0]?.message?.content ?? JSON.stringify(r));
276
+ });
277
+ registerMinds(program, client, data => emit(data, () => JSON.stringify(data, null, 2)), async () => new Genesis({
278
+ apiKey: g().apiKey ?? env.GENESIS_API_KEY, token: env.GENESIS_TOKEN, baseUrl: g().baseUrl ?? env.GENESIS_BASE_URL,
279
+ routing: g().direct || env.GENESIS_ROUTING === 'direct' ? 'direct' : 'gateway', fetch: f,
280
+ }), deps.signal, async () => {
281
+ if (g().org !== undefined)
282
+ throw new Error('Minds owner commands use the selected Console organization; --org is not supported.');
283
+ return client();
284
+ });
285
+ const hivesClient = () => client(true);
286
+ // ---- hives -----------------------------------------------------------
287
+ const hives = program.command('hives').description('Hives: projects, secrets, deployments, functions, schedules and activity');
288
+ registerHivesOperations(hives, { client: hivesClient, emit, readRequest: deps.readHivesRequest });
289
+ const deployments = hives.command('deployments').description('Real WASM artifacts and deployment lifecycle');
290
+ 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
+ deployments.command('list <project_id>')
292
+ .option('--page <number>', 'page (1–10000)').option('--per-page <number>', 'items (1–100)')
293
+ .option('--status <status>', 'persisted deployment status').option('--author <id>', 'creator ID')
294
+ .action(async (project, o) => {
295
+ emitDeployment(await (await hivesClient()).hives.deployments.list(project, {
296
+ page: positiveInteger(o.page, '--page', 10000), per_page: positiveInteger(o.perPage, '--per-page', 100), status: o.status, author: o.author,
297
+ }));
298
+ });
299
+ deployments.command('get <project_id> <deployment_id>')
300
+ .action(async (project, deployment) => emitDeployment(await (await hivesClient()).hives.deployments.get(project, deployment)));
301
+ deployments.command('upload <project_id> <wasm_file>').description('Upload actual WASM bytes; Git options are labels only')
302
+ .option('--git-sha <sha>', 'commit label (7–40 characters)').option('--git-branch <branch>', 'branch label').option('--git-message <message>', 'commit label')
303
+ .action(async (project, path, o) => {
304
+ const file = await (deps.readWasmFile ?? readWasmFile)(path);
305
+ emitDeployment(await (await hivesClient()).hives.deployments.upload(project, file, {
306
+ filename: basename(path), git_sha: o.gitSha, git_branch: o.gitBranch, git_message: o.gitMessage,
307
+ }));
308
+ });
309
+ deployments.command('reuse <project_id> <module_hash>').description('Deploy a real artifact already owned by this org and project')
310
+ .option('--no-auto-promote', 'keep the completed deployment ready for explicit promotion')
311
+ .action(async (project, module_hash, o) => {
312
+ if (!/^[a-f0-9]{64}$/.test(module_hash))
313
+ throw new Error('module_hash must be the lowercase SHA-256 returned by a real Hives upload.');
314
+ emitDeployment(await (await hivesClient()).hives.deployments.create(project, { module_hash, auto_promote: o.autoPromote }));
315
+ });
316
+ deployments.command('promote <project_id> <deployment_id>')
317
+ .action(async (project, deployment) => emitDeployment(await (await hivesClient()).hives.deployments.promote(project, deployment)));
318
+ deployments.command('rollback <project_id> <deployment_id>').description('Activate this owned previous deployment')
319
+ .option('--reason <text>', 'audit reason')
320
+ .action(async (project, deployment, o) => emitDeployment(await (await hivesClient()).hives.deployments.rollback(project, deployment, { reason: o.reason })));
321
+ deployments.command('cancel <project_id> <deployment_id>')
322
+ .action(async (project, deployment) => emitDeployment(await (await hivesClient()).hives.deployments.cancel(project, deployment)));
323
+ deployments.command('stop <project_id> <deployment_id>').description('Stop the runtime; read its state before explicitly reconciling an uncertain result')
324
+ .action(async (project, deployment) => emitDeployment(await (await hivesClient()).hives.deployments.stop(project, deployment)));
325
+ deployments.command('delete <project_id> <deployment_id>').description('Delete a deployment that is not currently active')
326
+ .action(async (project, deployment) => { const result = await (await hivesClient()).hives.deployments.delete(project, deployment); emit(result, () => `Deleted deployment ${deployment}.`); });
327
+ deployments.command('logs <project_id> <deployment_id>').description('Read real persisted deployment logs')
328
+ .option('--page <number>', 'page (1–10000)').option('--per-page <number>', 'items (1–100)')
329
+ .option('--level <level>', 'minimum level').option('--search <text>', 'search log messages')
330
+ .option('--start <iso>', 'RFC 3339 start time').option('--end <iso>', 'RFC 3339 end time')
331
+ .action(async (project, deployment, o) => {
332
+ for (const timestamp of [o.start, o.end])
333
+ if (timestamp !== undefined && !Number.isFinite(Date.parse(timestamp)))
334
+ throw new Error('Log timestamps must use RFC 3339.');
335
+ const result = await (await hivesClient()).hives.deployments.logs(project, deployment, {
336
+ page: positiveInteger(o.page, '--page', 10000), per_page: positiveInteger(o.perPage, '--per-page', 100),
337
+ level: o.level, search: o.search, start: o.start, end: o.end,
338
+ });
339
+ emit(result, () => table(result.logs, ['timestamp', 'level', 'message', 'source']));
340
+ });
341
+ const emitFunction = (value) => emit(value, () => table(listOf(value).length ? listOf(value) : [value], ['id', 'name', 'status', 'handler', 'memory_mb', 'timeout_ms']));
342
+ const functions = hives.command('functions').description('Owned WASM functions; invocation returns the completed raw guest response');
343
+ functions.command('list <project_id>').option('--page <n>').option('--per-page <n>').option('--status <status>').option('--search <text>')
344
+ .action(async (project, o) => emitFunction(await (await hivesClient()).hives.functions.list(project, {
345
+ page: positiveInteger(o.page, '--page', 1000000), per_page: positiveInteger(o.perPage, '--per-page', 100), status: o.status, search: o.search,
346
+ })));
347
+ functions.command('get <project_id> <function_id>').action(async (project, id) => emitFunction(await (await hivesClient()).hives.functions.get(project, id)));
348
+ functions.command('create <project_id> <name> <module_hash>').option('--request-file <file>', 'JSON metadata and encrypted env overrides; input values are never printed')
349
+ .action(async (project, name, hash, o) => {
350
+ if (!/^[a-f0-9]{64}$/.test(hash))
351
+ throw new Error('Use a full lowercase artifact hash from an owned Hives upload.');
352
+ const extra = o.requestFile ? await readFunctionJson(o.requestFile, 256 * 1024) : {};
353
+ emitFunction(await (await hivesClient()).hives.functions.create(project, { ...extra, name, handler: `wasm://sha256/${hash}` }));
354
+ });
355
+ functions.command('update <project_id> <function_id>').requiredOption('--request-file <file>', 'JSON supported metadata/env/remove_env fields; values are never printed')
356
+ .action(async (project, id, o) => emitFunction(await (await hivesClient()).hives.functions.update(project, id, await readFunctionJson(o.requestFile, 256 * 1024))));
357
+ functions.command('delete <project_id> <function_id>').action(async (project, id) => {
358
+ const result = await (await hivesClient()).hives.functions.delete(project, id);
359
+ emit(result, () => `Deleted function ${id}.`);
360
+ });
361
+ function invocationCommand(command, kind) {
362
+ if (kind === 'functions')
363
+ 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')
365
+ .option('--request-file <file>', 'JSON method/path/headers/body or body_base64')
366
+ .option('--body-file <file>', 'Exact binary body (up to 10 MiB; serialized guest envelope may reach its cap earlier)')
367
+ .option('--method <method>', 'guest HTTP method', 'POST').option('--path <path>', 'guest path including query', '/')
368
+ .option('--output <file>', 'create a new private file containing exact response bytes')
369
+ .action(async (project, id, o) => {
370
+ if (o.requestFile && o.bodyFile)
371
+ throw new Error('Choose --request-file or --body-file.');
372
+ if (g().json && o.output)
373
+ throw new Error('Choose --json or --output.');
374
+ let request = o.requestFile ? await readFunctionJson(o.requestFile, MAX_FUNCTION_JSON_BYTES) : { method: o.method, path: o.path };
375
+ if (o.bodyFile)
376
+ 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 });
378
+ const bytes = await hivesResponseBytes(response);
379
+ if (g().json)
380
+ out.log(JSON.stringify({ status: response.status, headers: Object.fromEntries(response.headers), body_base64: Buffer.from(bytes).toString('base64') }));
381
+ else {
382
+ out.err(JSON.stringify({ status: response.status, headers: Object.fromEntries(response.headers), bytes: bytes.length }));
383
+ if (o.output)
384
+ await (deps.writeOutputFile ?? writeFunctionOutput)(o.output, bytes);
385
+ else
386
+ (deps.writeBytes ?? ((value) => { process.stdout.write(value); }))(bytes);
387
+ }
388
+ });
389
+ }
390
+ invocationCommand(functions.command('invoke <project_id> <function_id>'), 'functions');
391
+ invocationCommand(deployments.command('invoke <project_id> <deployment_id>'), 'deployments');
392
+ const operations = functions.command('operations').description('Read or release the original funding operation without replaying the guest');
393
+ operations.command('get <project_id> <function_id> <operation_id>').action(async (project, fn, operation) => {
394
+ const result = await (await hivesClient()).hives.functions.operations.get(project, fn, operation);
395
+ emit(result, () => JSON.stringify(result, null, 2));
396
+ });
397
+ operations.command('release <project_id> <function_id> <operation_id>').description('Release an unused reservation only when the backend proves no guest was dispatched')
398
+ .action(async (project, fn, operation) => {
399
+ const result = await (await hivesClient()).hives.functions.operations.release(project, fn, operation);
400
+ emit(result, () => JSON.stringify(result, null, 2));
401
+ });
402
+ const secrets = hives.command('secrets').description('Project secrets encrypted with Genesis KMS');
403
+ const readInput = async (options, limit) => {
404
+ if (Boolean(options.file) === Boolean(options.stdin))
405
+ throw new Error('Choose exactly one input source: a file or --stdin. Secret values are not accepted as command arguments.');
406
+ return (deps.readSecretInput ?? readSecretInput)(options, limit);
407
+ };
408
+ const emitSecret = (data) => emit(data, () => table(listOf(data), ['id', 'name', 'version', 'description', 'updated_at']));
409
+ const context = hives.command('context').description('Explicit Hives binding for the current verified interactive Genesis selection');
410
+ context.command('get').action(async () => {
411
+ const r = await (await hivesClient()).hives.context.get();
412
+ emit(r, () => table([r.project], ['id', 'name', 'slug', 'kind', 'status']));
413
+ });
414
+ context.command('create <name>').option('--kind <kind>', 'worker|function|site|api', 'worker').option('--description <text>', 'project description')
415
+ .action(async (name, o) => {
416
+ if (!['worker', 'function', 'site', 'api'].includes(o.kind))
417
+ throw new Error('Choose worker, function, site or api.');
418
+ if (o.description && o.description.length > 500)
419
+ throw new Error('Project descriptions must contain at most 500 characters.');
420
+ const r = await (await hivesClient()).hives.context.set({ create: { name: projectName(name), kind: o.kind, description: o.description } });
421
+ emit(r, () => table([r.project], ['id', 'name', 'slug', 'kind', 'status']));
422
+ });
423
+ context.command('link <project_id>').description('Link a Hives project owned by the verified org')
424
+ .action(async (id) => { const r = await (await hivesClient()).hives.context.set({ link: { project_id: id } }); emit(r, () => table([r.project], ['id', 'name', 'slug', 'kind', 'status'])); });
425
+ context.command('detach').description('Remove this selection binding; retain the Hives project and its resources')
426
+ .action(async () => { const r = await (await hivesClient()).hives.context.detach(); emit(r, () => 'Detached selection; retained Hives project ' + r.hivesProjectId + '.'); });
427
+ const environment = hives.command('environment').description('Encrypted project environment; names-only metadata and explicit KMS reveal');
428
+ environment.command('list <project_id>').option('--page <number>', 'page number (1–10000)').option('--per-page <number>', 'items per page (1–100)')
429
+ .action(async (id, o) => {
430
+ const r = await (await hivesClient()).hives.environment.list(id, { page: positiveInteger(o.page, '--page', 10000), per_page: positiveInteger(o.perPage, '--per-page', 100) });
431
+ emit(r, () => table(r.data, ['name']));
432
+ });
433
+ environment.command('upsert <project_id>').description('Encrypt a flat JSON map; empty string values are allowed')
434
+ .option('--file <path>', 'UTF-8 JSON input file').option('--stdin', 'read JSON from stdin')
435
+ .action(async (id, o) => {
436
+ const text = await readInput(o, 2 * 1024 * 1024);
437
+ let body;
438
+ try {
439
+ body = JSON.parse(text);
440
+ }
441
+ catch {
442
+ throw new Error('Environment input must be a flat JSON map of names to string values.');
443
+ }
444
+ const r = await (await hivesClient()).hives.environment.upsert(id, body);
445
+ emit(r, () => table(r.updated, ['name']));
446
+ });
447
+ environment.command('reveal <project_id> <key>').description('Explicit audit-logged environment reveal; requires --stdout')
448
+ .option('--stdout', 'write sensitive plaintext to stdout (avoid retained output)')
449
+ .action(async (id, key, o) => {
450
+ if (!o.stdout)
451
+ throw new Error('Environment reveal requires --stdout. Avoid logging or retaining the plaintext.');
452
+ const r = await (await hivesClient()).hives.environment.reveal(id, key);
453
+ emit(r, () => r.value);
454
+ });
455
+ secrets.command('list <project_id>').description('List secret metadata without values')
456
+ .option('--page <number>', 'page number (1–10000)').option('--per-page <number>', 'items per page (1–100)')
457
+ .action(async (id, o) => {
458
+ const q = { page: positiveInteger(o.page, '--page', 10000), per_page: positiveInteger(o.perPage, '--per-page', 100) };
459
+ emitSecret(await (await hivesClient()).hives.secrets.list(id, q));
460
+ });
461
+ secrets.command('get <project_id> <secret_id>').description('Inspect secret metadata without its value')
462
+ .action(async (project, secret) => {
463
+ const r = await (await hivesClient()).hives.secrets.get(project, secret);
464
+ emit(r, () => table([r], ['id', 'name', 'version', 'description', 'updated_at']));
465
+ });
466
+ secrets.command('create <project_id> <name>').description('Create a secret; plaintext is read from a file or stdin')
467
+ .option('--value-file <path>', 'UTF-8 file containing the value').option('--stdin', 'read value from stdin')
468
+ .option('--description <text>', 'description (up to 500 characters)')
469
+ .action(async (project, name, o) => {
470
+ const body = validateSecret({ name, value: await readInput({ file: o.valueFile, stdin: o.stdin }, 65536), description: o.description });
471
+ const r = await (await hivesClient()).hives.secrets.create(project, body);
472
+ emit(r, () => table([r], ['id', 'name', 'version']));
473
+ });
474
+ secrets.command('update <project_id> <secret_id>').description('Replace a secret value, creating a new version')
475
+ .option('--value-file <path>', 'UTF-8 file containing the new value').option('--stdin', 'read value from stdin')
476
+ .action(async (project, secret, o) => {
477
+ const value = validateSecretValue(await readInput({ file: o.valueFile, stdin: o.stdin }, 65536));
478
+ const r = await (await hivesClient()).hives.secrets.update(project, secret, { value });
479
+ emit(r, () => table([r], ['id', 'name', 'version']));
480
+ });
481
+ secrets.command('delete <project_id> <secret_id>').description('Delete a secret in your authenticated org')
482
+ .action(async (project, secret) => { const r = await (await hivesClient()).hives.secrets.delete(project, secret); emit(r, () => `Deleted secret ${secret}.`); });
483
+ secrets.command('reveal <project_id> <secret_id>').description('Explicit, audit-logged plaintext reveal; requires --stdout')
484
+ .option('--stdout', 'write sensitive plaintext to stdout (avoid logs and retained output)')
485
+ .action(async (project, secret, o) => {
486
+ if (!o.stdout)
487
+ throw new Error('Reveal requires --stdout. This writes sensitive plaintext to your terminal or pipe; avoid logging or retaining it.');
488
+ const r = await (await hivesClient()).hives.secrets.reveal(project, secret);
489
+ emit(r, () => r.value);
490
+ });
491
+ secrets.command('bulk <project_id>').description('PUT a JSON array of secret names and values; existing names receive new versions')
492
+ .option('--file <path>', 'UTF-8 JSON input file').option('--stdin', 'read JSON from stdin')
493
+ .action(async (project, o) => {
494
+ const batch = parseSecretBatch(await readInput(o, 256 * 1024));
495
+ const r = await (await hivesClient()).hives.secrets.bulkUpsert(project, batch);
496
+ emit(r, () => table(r.secrets, ['id', 'name', 'version']));
497
+ });
498
+ const projects = hives.command('projects').description('Hives projects');
499
+ projects.command('list', { isDefault: true }).option('--search <text>', 'filter by project name')
500
+ .option('--page <number>', 'page number (1–10000)').option('--per-page <number>', 'items per page (1–100)')
501
+ .action(async (o) => {
502
+ const r = await (await hivesClient()).hives.projects.list({ search: o.search,
503
+ page: positiveInteger(o.page, '--page', 10000), per_page: positiveInteger(o.perPage, '--per-page', 100) });
504
+ emit(r, () => table(listOf(r), ['id', 'name', 'slug', 'region', 'total_deployments', 'active_instances']));
505
+ });
506
+ projects.command('get <project_id>').action(async (id) => {
507
+ const r = await (await hivesClient()).hives.projects.get(id);
508
+ emit(r, () => JSON.stringify(r, null, 2));
509
+ });
510
+ projects.command('create <name>').description('Create a project in your authenticated org')
511
+ .option('--kind <kind>', 'worker, function, site or api', 'worker')
512
+ .option('--description <text>', 'project description (up to 500 characters)')
513
+ .action(async (name, o) => {
514
+ if (!['worker', 'function', 'site', 'api'].includes(o.kind))
515
+ throw new Error('--kind must be worker, function, site or api.');
516
+ if (o.description && o.description.length > 500)
517
+ throw new Error('--description must be at most 500 characters.');
518
+ const r = await (await hivesClient()).hives.projects.create({ name: projectName(name), kind: o.kind,
519
+ ...(o.description === undefined ? {} : { description: o.description }) });
520
+ emit(r, () => table([r], ['id', 'name', 'slug', 'status']));
521
+ });
522
+ projects.command('delete <project_id>').description('Delete a project in your authenticated org')
523
+ .action(async (id) => { const r = await (await hivesClient()).hives.projects.delete(id); emit(r ?? { deleted: id }, () => `Deleted project ${id}.`); });
524
+ projects.command('rename <project_id> <name>').description('Rename a project in your authenticated org')
525
+ .action(async (id, name) => {
526
+ const r = await (await hivesClient()).hives.projects.update(id, { name: projectName(name) });
527
+ emit(r, () => table([r], ['id', 'name', 'slug', 'status']));
528
+ });
529
+ projects.command('activity <project_id>').description('Read persisted project activity')
530
+ .option('--action <action>', 'filter by action')
531
+ .option('--actor-type <type>', 'filter by actor type')
532
+ .option('--resource-type <type>', 'filter by resource type')
533
+ .option('--since <timestamp>', 'inclusive start timestamp in ISO 8601')
534
+ .option('--until <timestamp>', 'inclusive end timestamp in ISO 8601')
535
+ .option('--page <number>', 'page number (1–10000)')
536
+ .option('--per-page <number>', 'items per page (1–100)')
537
+ .action(async (id, o) => {
538
+ for (const [option, value] of [['--since', o.since], ['--until', o.until]]) {
539
+ if (value !== undefined && (!/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})$/.test(value) || !Number.isFinite(Date.parse(value)))) {
540
+ throw new Error(`${option} must be an ISO 8601 timestamp.`);
541
+ }
542
+ }
543
+ const r = await (await hivesClient()).hives.projects.activity(id, { action: o.action,
544
+ actor_type: o.actorType, resource_type: o.resourceType, since: o.since, until: o.until,
545
+ page: positiveInteger(o.page, '--page', 10000), per_page: positiveInteger(o.perPage, '--per-page', 100) });
546
+ emit(r, () => table(listOf(r), ['created_at', 'action', 'actor_id', 'resource_type', 'resource_name']));
547
+ });
548
+ // ---- relays ----------------------------------------------------------
549
+ const relays = program.command('relays').description('Relays: DNS zones and records');
550
+ registerRelaysEnrollment(relays, client, emit);
551
+ relays.command('zones').description('List DNS zones').option('--search <q>').action(async (o) => {
552
+ const r = await (await client()).relays.zones.list(o.search ? { search: o.search } : {});
553
+ emit(r, () => table((r.zones ?? []), ['name', 'type', 'record_count', 'dnssec_enabled', 'status']));
554
+ });
555
+ relays.command('zone <zone>').description('Read one DNS zone').action(async (zone) => {
556
+ const result = await (await client()).relays.zones.get(zone);
557
+ emit(result, () => JSON.stringify(result, null, 2));
558
+ });
559
+ relays.command('dnssec <zone>').description('Read DNSSEC status and authoritative key availability').action(async (zone) => {
560
+ const result = await (await client()).relays.zones.dnssec(zone);
561
+ emit(result, () => JSON.stringify(result, null, 2));
562
+ });
563
+ const records = relays.command('records').description('DNS records in a zone');
564
+ records.command('list <zone>', { isDefault: true }).option('--type <type>').action(async (zone, o) => {
565
+ const r = await (await client()).relays.records.list(zone, o.type ? { type: o.type } : {});
566
+ emit(r, () => table((r.records ?? []), ['id', 'name', 'type', 'content', 'ttl']));
567
+ });
568
+ records.command('add <zone> <name> <type> <content>').option('--ttl <seconds>', 'TTL', '3600').action(async (zone, name, type, content, o) => {
569
+ const r = await (await client()).relays.records.create(zone, { name, type: type.toUpperCase(), content, ttl: Number(o.ttl) });
570
+ emit(r, () => `${c.green('✓')} Added ${type.toUpperCase()} ${name} → ${content} in ${zone}${r.id ? ` (id ${r.id})` : ''}`);
571
+ });
572
+ records.command('rm <zone> <record_id>').action(async (zone, id) => {
573
+ await (await client()).relays.records.delete(zone, Number(id));
574
+ emit({ deleted: Number(id) }, () => `${c.green('✓')} Deleted record ${id} from ${zone}`);
575
+ });
576
+ addHenryCommands(program, client, value => out.log(JSON.stringify(value, null, 2)));
577
+ registerFleets(program, client, (data) => emit(data, () => JSON.stringify(data, null, 2)));
578
+ registerFlocks(program, client, (data) => emit(data, () => JSON.stringify(data, null, 2)), env);
579
+ registerBeacons(program, client, emit);
580
+ // ---- status ----------------------------------------------------------
581
+ program.command('status').description('Live state of each Genesis service, from the gateway (GET /v1/status)').action(async () => {
582
+ const configured = g().baseUrl ?? env.GENESIS_BASE_URL ?? DEFAULT_BASE_URL;
583
+ // Same transport rule as the SDK client (https, or http only on exact loopback hosts).
584
+ try {
585
+ requireSecureUrl(configured, 'base URL');
586
+ }
587
+ catch (e) {
588
+ throw new Error(`Refusing base URL ${safe(configured)}: ${safe(e.message)}`);
589
+ }
590
+ const base = new URL(configured);
591
+ const url = `${base.href.replace(/\/+$/, '')}/v1/status`;
592
+ let response;
593
+ try {
594
+ response = await f(url, { method: 'GET', headers: { accept: 'application/json', 'user-agent': 'genesis-cli/0.1.13' }, signal: AbortSignal.timeout(15_000) });
595
+ }
596
+ catch (e) {
597
+ throw new Error(`Gateway unreachable at ${safe(url)}: ${safe(e.message)}`);
598
+ }
599
+ if (!response.ok)
600
+ throw new Error(`Gateway status check failed: GET ${safe(url)} returned HTTP ${response.status}`);
601
+ const report = await response.json().catch(() => undefined);
602
+ if (!report || !Array.isArray(report.services))
603
+ throw new Error(`Gateway status check failed: GET ${safe(url)} did not return a service list`);
604
+ const rows = report.services;
605
+ emit(report, () => [
606
+ table(rows.map((s) => ({ service: s.service, state: s.state, http_status: s.http_status, latency_ms: s.latency_ms, reason: s.reason })), ['service', 'state', 'http_status', 'latency_ms', 'reason']),
607
+ ...(report.checked_at ? [c.dim(`checked ${safe(report.checked_at)} via ${safe(url)}`)] : []),
608
+ ].join('\n'));
609
+ });
610
+ return program;
611
+ }
612
+ export async function main(argv, deps = {}) {
613
+ const out = deps.out ?? stdio;
614
+ const program = buildProgram(deps);
615
+ try {
616
+ await program.parseAsync(argv);
617
+ return 0;
618
+ }
619
+ catch (e) {
620
+ if (e instanceof CommanderError)
621
+ return e.exitCode;
622
+ const json = program.opts().json;
623
+ if (e instanceof GenesisError) {
624
+ if (json)
625
+ out.err(JSON.stringify({ error: { type: e.type, status: e.status, message: e.message, request_id: e.requestId, ...errorCatalog(e),
626
+ ...((e instanceof HivesOperationError || e instanceof HivesQueueOperationError) ? { operation: e.operation } : {}),
627
+ ...(e.operationId && isMindsOperation(e.operationId) && errorOutcome(e.body) === 'unknown' ? { outcome: 'unknown' } : {}),
628
+ ...(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) } : {}) } : {}) } }));
630
+ 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)}` : ''})`) : ''}`);
632
+ return 1;
633
+ }
634
+ const msg = e.message ?? String(e);
635
+ const net = /fetch failed|ENOTFOUND|ECONNREFUSED/.test(msg + (e.cause?.code ?? ''));
636
+ 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)}`);
638
+ return 1;
639
+ }
640
+ }