@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.
- package/README.md +29 -2
- package/dist/beacons.js +68 -0
- package/dist/dials.js +7 -4
- package/dist/fleets.js +30 -0
- package/dist/flocks.d.ts +7 -1
- package/dist/flocks.js +281 -4
- package/dist/henry.d.ts +5 -1
- package/dist/henry.js +60 -1
- package/dist/hives-coverage.d.ts +12 -0
- package/dist/hives-coverage.js +133 -0
- package/dist/hives-node-transport.js +4 -1
- package/dist/hives-operations.d.ts +1 -0
- package/dist/hives-operations.js +2 -0
- package/dist/hives-platform.d.ts +13 -0
- package/dist/hives-platform.js +133 -0
- package/dist/hives-resources.d.ts +1 -0
- package/dist/hives-resources.js +5 -3
- package/dist/iam-directory.d.ts +15 -0
- package/dist/iam-directory.js +71 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +273 -30
- package/dist/minds.d.ts +1 -1
- package/dist/minds.js +37 -2
- package/dist/onboard/art.d.ts +50 -0
- package/dist/onboard/art.js +139 -0
- package/dist/onboard/brand.d.ts +56 -0
- package/dist/onboard/brand.js +134 -0
- package/dist/onboard/caps.d.ts +90 -0
- package/dist/onboard/caps.js +232 -0
- package/dist/onboard/encode.d.ts +23 -0
- package/dist/onboard/encode.js +235 -0
- package/dist/onboard/eye.d.ts +103 -0
- package/dist/onboard/eye.js +147 -0
- package/dist/onboard/index.d.ts +61 -0
- package/dist/onboard/index.js +562 -0
- package/dist/onboard/mcp.d.ts +35 -0
- package/dist/onboard/mcp.js +118 -0
- package/dist/onboard/raster.d.ts +43 -0
- package/dist/onboard/raster.js +253 -0
- package/dist/onboard/sound.d.ts +2 -0
- package/dist/onboard/sound.js +54 -0
- package/dist/onboard/state.d.ts +24 -0
- package/dist/onboard/state.js +48 -0
- package/dist/onboard/term.d.ts +69 -0
- package/dist/onboard/term.js +292 -0
- package/dist/onboard/ui.d.ts +97 -0
- package/dist/onboard/ui.js +615 -0
- package/dist/orgs.d.ts +16 -0
- package/dist/orgs.js +89 -0
- package/dist/output.d.ts +13 -1
- package/dist/output.js +21 -2
- package/dist/products.d.ts +6 -2
- package/dist/products.js +474 -13
- package/dist/relays-commands/certificates.d.ts +3 -0
- package/dist/relays-commands/certificates.js +20 -0
- package/dist/relays-commands/domains.d.ts +3 -0
- package/dist/relays-commands/domains.js +23 -0
- package/dist/relays-commands/index.d.ts +4 -0
- package/dist/relays-commands/index.js +10 -0
- package/dist/relays-commands/monitoring.d.ts +3 -0
- package/dist/relays-commands/monitoring.js +41 -0
- package/dist/relays-commands/telemetry.d.ts +3 -0
- package/dist/relays-commands/telemetry.js +28 -0
- package/dist/relays-commands/zones.d.ts +7 -0
- package/dist/relays-commands/zones.js +16 -0
- package/dist/runner.js +2 -0
- 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 {
|
|
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
|
|
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
|
|
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
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
.action(async (options) =>
|
|
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
|
-
.
|
|
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
|
-
.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|
|
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
|
|
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('✗')} ${
|
|
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 (${
|
|
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
|
|
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
|
-
|
|
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
|
-
.
|
|
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');
|