@onesi/genesis-cli 0.2.6 → 0.2.8
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 +1 -1
- package/dist/fleets.d.ts +4 -2
- package/dist/fleets.js +96 -5
- package/dist/index.js +33 -10
- package/dist/products.js +31 -4
- package/package.json +2 -2
package/README.md
CHANGED
package/dist/fleets.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type { Command } from 'commander';
|
|
2
2
|
import type { Genesis } from '@onesi/genesis';
|
|
3
|
-
|
|
4
|
-
export declare function
|
|
3
|
+
/** Read one key from stdin (at most 4096 bytes, one trailing newline dropped); `label` names it in errors. */
|
|
4
|
+
export declare function readStdinKey(label: string, input?: AsyncIterable<Uint8Array>): Promise<string>;
|
|
5
|
+
export declare const readFoundryKey: (input?: AsyncIterable<Uint8Array>) => Promise<string>;
|
|
6
|
+
export declare function registerFleets(program: Command, client: () => Promise<Genesis>, emit: (data: unknown) => void, keyInput?: () => Promise<string>, computerKeyInput?: () => Promise<string>): void;
|
package/dist/fleets.js
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
|
|
1
|
+
/** Read one key from stdin (at most 4096 bytes, one trailing newline dropped); `label` names it in errors. */
|
|
2
|
+
export async function readStdinKey(label, input = process.stdin) {
|
|
2
3
|
const chunks = [];
|
|
3
4
|
let size = 0;
|
|
4
5
|
for await (const chunk of input) {
|
|
5
6
|
const bytes = Buffer.from(chunk);
|
|
6
7
|
size += bytes.length;
|
|
7
8
|
if (size > 4098)
|
|
8
|
-
throw new Error(
|
|
9
|
+
throw new Error(`${label} must contain at most 4096 bytes.`);
|
|
9
10
|
chunks.push(bytes);
|
|
10
11
|
}
|
|
11
12
|
let key;
|
|
@@ -13,13 +14,23 @@ export async function readFoundryKey(input = process.stdin) {
|
|
|
13
14
|
key = new TextDecoder('utf-8', { fatal: true, ignoreBOM: true }).decode(Buffer.concat(chunks)).replace(/\r?\n$/, '');
|
|
14
15
|
}
|
|
15
16
|
catch {
|
|
16
|
-
throw new Error(
|
|
17
|
+
throw new Error(`Invalid UTF-8 ${label} on stdin.`);
|
|
17
18
|
}
|
|
18
19
|
if (!key || Buffer.byteLength(key) > 4096 || /[\r\n\0]/.test(key))
|
|
19
|
-
throw new Error(
|
|
20
|
+
throw new Error(`Invalid ${label} on stdin.`);
|
|
20
21
|
return key;
|
|
21
22
|
}
|
|
22
|
-
export
|
|
23
|
+
export const readFoundryKey = (input = process.stdin) => readStdinKey('Foundry key', input);
|
|
24
|
+
const readComputerKey = () => readStdinKey('Computer key');
|
|
25
|
+
const SEAT_TYPES = ['helper', 'operator', 'always_on'];
|
|
26
|
+
const seatType = (value) => {
|
|
27
|
+
if (!SEAT_TYPES.includes(value))
|
|
28
|
+
throw new Error('Seat type must be helper, operator or always_on.');
|
|
29
|
+
return value;
|
|
30
|
+
};
|
|
31
|
+
/** Whole-number flags go to the SDK as numbers; it refuses anything that is not a whole number before sending. */
|
|
32
|
+
const count = (value) => /^\d+$/.test(value) ? Number(value) : NaN;
|
|
33
|
+
export function registerFleets(program, client, emit, keyInput = readFoundryKey, computerKeyInput = readComputerKey) {
|
|
23
34
|
const fleets = program.command('fleets').description('Fleets: managed Henry instances and tasks');
|
|
24
35
|
const agents = fleets.command('agents').description('Persistent Henry agents on Fleets-managed compute');
|
|
25
36
|
agents.command('list').action(async () => emit(await (await client()).fleets.agents.list()));
|
|
@@ -83,6 +94,86 @@ export function registerFleets(program, client, emit, keyInput = readFoundryKey)
|
|
|
83
94
|
emit(await (await client()).fleets.connection.set(apiKey, options.model !== undefined ? { model: options.model } : {}));
|
|
84
95
|
});
|
|
85
96
|
connection.command('delete').action(async () => emit(await (await client()).fleets.connection.delete()));
|
|
97
|
+
const seats = fleets.command('seats').description('Agent seats: a persistent Henry agent with a credit purse and computer minutes (plan prices are display only)');
|
|
98
|
+
seats.command('plans').description('List the seat plans').action(async () => emit(await (await client()).fleets.seats.plans()));
|
|
99
|
+
seats.command('hire <type>').description('Hire a seat: helper, operator or always_on')
|
|
100
|
+
.option('--name <name>').option('--borrow-ceiling <credits>', 'overdraft the seat may run up before it pauses (default 0)')
|
|
101
|
+
.option('--pool <pool_id>', 'carve the purse under this pool').option('--no-fund', 'open the purse empty, to fund with top-up')
|
|
102
|
+
.requiredOption('--idempotency-key <key>', 'stable key to reuse on retry')
|
|
103
|
+
.action(async (type, options) => emit(await (await client()).fleets.seats.hire({
|
|
104
|
+
type: seatType(type),
|
|
105
|
+
...(options.name !== undefined ? { name: options.name } : {}),
|
|
106
|
+
...(options.borrowCeiling !== undefined ? { borrow_ceiling_credits: count(options.borrowCeiling) } : {}),
|
|
107
|
+
...(options.pool !== undefined ? { pool_id: options.pool } : {}),
|
|
108
|
+
...(options.fund === false ? { fund: false } : {}),
|
|
109
|
+
idempotency_key: options.idempotencyKey,
|
|
110
|
+
})));
|
|
111
|
+
seats.command('list').action(async () => emit(await (await client()).fleets.seats.list()));
|
|
112
|
+
seats.command('get <seat_id>').action(async (id) => emit(await (await client()).fleets.seats.get(id)));
|
|
113
|
+
seats.command('update <seat_id>').description('Rename a seat or change its borrow ceiling')
|
|
114
|
+
.option('--name <name>').option('--borrow-ceiling <credits>')
|
|
115
|
+
.action(async (id, options) => {
|
|
116
|
+
const body = {
|
|
117
|
+
...(options.name !== undefined ? { name: options.name } : {}),
|
|
118
|
+
...(options.borrowCeiling !== undefined ? { borrow_ceiling_credits: count(options.borrowCeiling) } : {}),
|
|
119
|
+
};
|
|
120
|
+
if (!Object.keys(body).length)
|
|
121
|
+
throw new Error('Provide --name or --borrow-ceiling.');
|
|
122
|
+
emit(await (await client()).fleets.seats.update(id, body));
|
|
123
|
+
});
|
|
124
|
+
seats.command('top-up <seat_id> <credits>').description('Add credits to the seat purse (overdraft is repaid first; pooled seats: top up the pool)')
|
|
125
|
+
.requiredOption('--idempotency-key <key>', 'stable key to reuse on retry')
|
|
126
|
+
.action(async (id, credits, options) => emit(await (await client()).fleets.seats.topUp(id, count(credits), options.idempotencyKey)));
|
|
127
|
+
seats.command('upgrade <seat_id> <type>').description('Move to a larger plan (no downgrade)')
|
|
128
|
+
.action(async (id, type) => emit(await (await client()).fleets.seats.upgrade(id, seatType(type))));
|
|
129
|
+
seats.command('hold <seat_id>').description('No new work; a running task is cancelled with a signed receipt').action(async (id) => emit(await (await client()).fleets.seats.hold(id)));
|
|
130
|
+
seats.command('resume <seat_id>').description('Resume a held or killed seat').action(async (id) => emit(await (await client()).fleets.seats.resume(id)));
|
|
131
|
+
seats.command('kill <seat_id>').description('Cancel running work now and stop the agent (resume allowed)').action(async (id) => emit(await (await client()).fleets.seats.kill(id)));
|
|
132
|
+
seats.command('release <seat_id>').description('Settle the purse and delete the agent (terminal)').action(async (id) => emit(await (await client()).fleets.seats.release(id)));
|
|
133
|
+
seats.command('ledger <seat_id>').description('Read the purse ledger').action(async (id) => emit(await (await client()).fleets.seats.ledger(id)));
|
|
134
|
+
const seatTasks = seats.command('tasks').description('Tasks given to a seat');
|
|
135
|
+
seatTasks.command('list <seat_id>').action(async (id) => emit(await (await client()).fleets.seats.tasks.list(id)));
|
|
136
|
+
seatTasks.command('submit <seat_id> <input>').option('--kind <kind>', 'research or computer', 'research').option('--timeout <seconds>', 'task deadline in seconds (1–3600)')
|
|
137
|
+
.option('--approve-computer', 'open the computer without a separate approval')
|
|
138
|
+
.requiredOption('--idempotency-key <key>', 'stable key to reuse on retry')
|
|
139
|
+
.action(async (id, input, options) => {
|
|
140
|
+
if (!['research', 'computer'].includes(options.kind))
|
|
141
|
+
throw new Error('Task kind must be research or computer.');
|
|
142
|
+
emit(await (await client()).fleets.seats.tasks.submit(id, {
|
|
143
|
+
input, kind: options.kind,
|
|
144
|
+
...(options.timeout !== undefined ? { timeout_seconds: count(options.timeout) } : {}),
|
|
145
|
+
...(options.approveComputer ? { approve_computer: true } : {}),
|
|
146
|
+
idempotency_key: options.idempotencyKey,
|
|
147
|
+
}));
|
|
148
|
+
});
|
|
149
|
+
seatTasks.command('get <seat_id> <task_id>').action(async (id, task) => emit(await (await client()).fleets.seats.tasks.get(id, task)));
|
|
150
|
+
for (const decision of ['approve', 'deny']) {
|
|
151
|
+
seatTasks.command(`${decision} <seat_id> <task_id> <approval_id>`).description(`${decision === 'approve' ? 'Approve' : 'Deny'} an action the task is waiting on`)
|
|
152
|
+
.action(async (id, task, approval) => emit(await (await client()).fleets.seats.tasks.decide(id, task, approval, decision)));
|
|
153
|
+
}
|
|
154
|
+
const pools = fleets.command('pools').description('Seat pools: a shared purse and computer minutes for seats hired with --pool');
|
|
155
|
+
pools.command('list').action(async () => emit(await (await client()).fleets.pools.list()));
|
|
156
|
+
pools.command('create').option('--name <name>').requiredOption('--credits <credits>').option('--computer-minutes <minutes>', 'default 0')
|
|
157
|
+
.requiredOption('--idempotency-key <key>', 'stable key to reuse on retry')
|
|
158
|
+
.action(async (options) => emit(await (await client()).fleets.pools.create({
|
|
159
|
+
...(options.name !== undefined ? { name: options.name } : {}),
|
|
160
|
+
credits: count(options.credits), ...(options.computerMinutes !== undefined ? { computer_minutes: count(options.computerMinutes) } : {}), idempotency_key: options.idempotencyKey,
|
|
161
|
+
})));
|
|
162
|
+
pools.command('get <pool_id>').action(async (id) => emit(await (await client()).fleets.pools.get(id)));
|
|
163
|
+
pools.command('top-up <pool_id>').requiredOption('--credits <credits>').option('--computer-minutes <minutes>')
|
|
164
|
+
.requiredOption('--idempotency-key <key>', 'stable key to reuse on retry')
|
|
165
|
+
.action(async (id, options) => emit(await (await client()).fleets.pools.topUp(id, {
|
|
166
|
+
credits: count(options.credits),
|
|
167
|
+
...(options.computerMinutes !== undefined ? { computer_minutes: count(options.computerMinutes) } : {}),
|
|
168
|
+
idempotency_key: options.idempotencyKey,
|
|
169
|
+
})));
|
|
170
|
+
pools.command('ledger <pool_id>').description('Read the pool purse ledger').action(async (id) => emit(await (await client()).fleets.pools.ledger(id)));
|
|
171
|
+
pools.command('release <pool_id>').description('Settle the pool (terminal)').action(async (id) => emit(await (await client()).fleets.pools.release(id)));
|
|
172
|
+
const computer = fleets.command('computer-connection').description('Organization key seat computer tasks use for Panes and Shells');
|
|
173
|
+
computer.command('get').action(async () => emit(await (await client()).fleets.computerConnection.get()));
|
|
174
|
+
computer.command('set').description('Read the key from stdin and encrypt it with Genesis KMS (never returned)')
|
|
175
|
+
.action(async () => emit(await (await client()).fleets.computerConnection.set(await computerKeyInput())));
|
|
176
|
+
computer.command('delete').action(async () => emit(await (await client()).fleets.computerConnection.delete()));
|
|
86
177
|
const usage = fleets.command('usage').description('Usage observations derived from your tasks');
|
|
87
178
|
usage.command('events').description('List task lifecycle and execution-time usage events').option('--limit <n>').option('--before <task_id>')
|
|
88
179
|
.action(async (options) => emit(await (await client()).fleets.usage.events({ ...(options.limit ? { limit: Number(options.limit) } : {}), ...(options.before ? { before: options.before } : {}) })));
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,12 @@ import { readFunctionFile, readFunctionJson, writeFunctionOutput } from './funct
|
|
|
25
25
|
import { basename } from 'node:path';
|
|
26
26
|
import { readWasmFile } from './wasm-input.js';
|
|
27
27
|
import { readSecretInput, validateSecret, validateSecretValue, parseSecretBatch } from './secret-input.js';
|
|
28
|
+
/** A key or context environment: exactly sandbox or live, never a default for anything else. */
|
|
29
|
+
function keyEnvironment(value) {
|
|
30
|
+
if (value !== 'sandbox' && value !== 'live')
|
|
31
|
+
throw new Error('--environment must be sandbox or live.');
|
|
32
|
+
return value;
|
|
33
|
+
}
|
|
28
34
|
function defaultOpen(url) {
|
|
29
35
|
const cmd = process.platform === 'darwin' ? 'open' : process.platform === 'win32' ? 'cmd' : 'xdg-open';
|
|
30
36
|
const args = process.platform === 'win32' ? ['/c', 'start', '', url] : [url];
|
|
@@ -41,7 +47,7 @@ export function buildProgram(deps = {}) {
|
|
|
41
47
|
const auth = { ...authConfig(env, f), sleep: deps.sleep };
|
|
42
48
|
const program = new Command('genesis')
|
|
43
49
|
.description('One CLI for every Genesis service.')
|
|
44
|
-
.version('0.2.
|
|
50
|
+
.version('0.2.8')
|
|
45
51
|
.option('--json', 'machine-readable JSON output')
|
|
46
52
|
.option('--api-key <key>', 'use this API key (default: GENESIS_API_KEY, then your `genesis login`)')
|
|
47
53
|
.option('--base-url <url>', 'gateway URL (default: GENESIS_BASE_URL or https://os.one.ml/api)')
|
|
@@ -57,7 +63,7 @@ export function buildProgram(deps = {}) {
|
|
|
57
63
|
const opts = {
|
|
58
64
|
apiKey: o.apiKey ?? env.GENESIS_API_KEY, token: env.GENESIS_TOKEN,
|
|
59
65
|
baseUrl: o.baseUrl ?? env.GENESIS_BASE_URL, routing: o.direct || env.GENESIS_ROUTING === 'direct' ? 'direct' : 'gateway',
|
|
60
|
-
fetch: f, userAgent: 'genesis-cli/0.2.
|
|
66
|
+
fetch: f, userAgent: 'genesis-cli/0.2.8',
|
|
61
67
|
credentials: store ? savedLoginProvider(auth, store) : undefined,
|
|
62
68
|
};
|
|
63
69
|
return new Genesis(hivesTransport ? { ...opts, fetch: createHivesNodeTransport(opts, deps.signal) } : opts);
|
|
@@ -278,7 +284,7 @@ export function buildProgram(deps = {}) {
|
|
|
278
284
|
if (!key)
|
|
279
285
|
throw new Error('Give the enrollment key with --key-file, --key-stdin or GENESIS_ENROLLMENT_KEY; it is never accepted as an argument.');
|
|
280
286
|
const body = await readFunctionJson(o.bodyFile, 16 * 1024);
|
|
281
|
-
const result = await new Genesis({ baseUrl: g().baseUrl ?? env.GENESIS_BASE_URL, fetch: f, userAgent: 'genesis-cli/0.2.
|
|
287
|
+
const result = await new Genesis({ baseUrl: g().baseUrl ?? env.GENESIS_BASE_URL, fetch: f, userAgent: 'genesis-cli/0.2.8' }).iam.agents.enroll(key, body);
|
|
282
288
|
emit(result, () => JSON.stringify(result, null, 2));
|
|
283
289
|
});
|
|
284
290
|
// ---- auth ------------------------------------------------------------
|
|
@@ -355,12 +361,14 @@ export function buildProgram(deps = {}) {
|
|
|
355
361
|
});
|
|
356
362
|
const selection = program.command('context').description('Select an authorized Genesis organization, workspace and project');
|
|
357
363
|
selection.command('select').requiredOption('--organization <id>').requiredOption('--workspace <id>').requiredOption('--project <id>')
|
|
358
|
-
.
|
|
364
|
+
.option('--environment <environment>', 'sandbox or live', 'sandbox')
|
|
365
|
+
.description('Bind the saved human login to a Console context (Sandbox unless --environment live) and atomically replace its credentials')
|
|
359
366
|
.action(async (options) => {
|
|
367
|
+
const environmentId = keyEnvironment(options.environment);
|
|
360
368
|
const target = { organizationId: options.organization, workspaceId: options.workspace, projectId: options.project,
|
|
361
|
-
platformId: 'pf_console', fleetId: null, environmentId
|
|
369
|
+
platformId: 'pf_console', fleetId: null, environmentId };
|
|
362
370
|
const next = await selectSavedContext(auth, await getStore(), target, deps.signal);
|
|
363
|
-
emit({ principal_id: next.auth?.principal_id, ...target }, () => `Selected ${target.organizationId} / ${target.workspaceId} / ${target.projectId} (Sandbox).`);
|
|
371
|
+
emit({ principal_id: next.auth?.principal_id, ...target }, () => `Selected ${target.organizationId} / ${target.workspaceId} / ${target.projectId} (${environmentId === 'live' ? 'Live' : 'Sandbox'}).`);
|
|
364
372
|
});
|
|
365
373
|
// ---- keys ------------------------------------------------------------
|
|
366
374
|
const keys = program.command('keys').description('Org API keys (One account / WorkOS)');
|
|
@@ -368,16 +376,31 @@ export function buildProgram(deps = {}) {
|
|
|
368
376
|
const gen = await client();
|
|
369
377
|
const org = await orgId(gen);
|
|
370
378
|
const r = await gen.keys.list(org);
|
|
371
|
-
|
|
379
|
+
// environment is the one Auth bound the key to (its binding); a key with no recorded binding is the org default, Sandbox.
|
|
380
|
+
const rows = r.data.map(k => ({ ...k, environment: k.binding?.resourceEnvironment ?? 'sandbox' }));
|
|
381
|
+
emit(r, () => table(rows, ['id', 'name', 'environment', 'obfuscated_value', 'scopes', 'last_used_at', 'expires_at']));
|
|
372
382
|
});
|
|
373
383
|
keys.command('create <name>').description('Create a key (shown once), or with --blinds hand it straight to Blinds so nobody ever sees it')
|
|
374
384
|
.option('--scopes <list>', 'comma-separated scopes, e.g. hives:read,relays:write')
|
|
375
385
|
.option('--blinds', 'store the key in Blinds; the raw key is never shown to anyone')
|
|
376
386
|
.option('--expires <iso>', 'expiry, e.g. 2027-01-01T00:00:00Z')
|
|
377
|
-
.
|
|
387
|
+
.option('--environment <environment>', 'bind the key to sandbox or live (never changes later)', 'sandbox')
|
|
388
|
+
.option('--project <id>', 'bind the key to this project (default: the organization default project)')
|
|
389
|
+
.option('--workspace <id>', 'the project\'s workspace (needs --project)')
|
|
390
|
+
.action(async (name, o, cmd) => {
|
|
391
|
+
const resourceEnvironment = keyEnvironment(o.environment);
|
|
392
|
+
if (o.workspace && !o.project)
|
|
393
|
+
throw new Error('--workspace needs --project.');
|
|
394
|
+
// No --environment, --project or --workspace: send no binding, so Auth applies its own default (the org default
|
|
395
|
+
// project in sandbox; an org with no default project still gets a key, as before).
|
|
396
|
+
const chosen = cmd.getOptionValueSource('environment') === 'cli' || !!o.project;
|
|
397
|
+
const binding = chosen ? { resourceEnvironment, ...(o.project ? { projectId: o.project } : {}), ...(o.workspace ? { workspaceId: o.workspace } : {}) } : undefined;
|
|
378
398
|
const gen = await client();
|
|
379
399
|
const org = await orgId(gen);
|
|
380
|
-
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 } : {}) });
|
|
400
|
+
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 } : {}), ...(binding ? { binding } : {}) });
|
|
401
|
+
const bound = k.binding?.resourceEnvironment;
|
|
402
|
+
if (binding && bound !== resourceEnvironment)
|
|
403
|
+
out.err(c.yellow(`Auth bound this key to ${bound ?? 'an unreported environment'}, not ${resourceEnvironment}. Revoke it if that is not what you want.`));
|
|
381
404
|
emit(k, () => o.blinds
|
|
382
405
|
? `${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.')}`
|
|
383
406
|
: `${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`);
|
|
@@ -813,7 +836,7 @@ export function buildProgram(deps = {}) {
|
|
|
813
836
|
const url = `${base.href.replace(/\/+$/, '')}/v1/status`;
|
|
814
837
|
let response;
|
|
815
838
|
try {
|
|
816
|
-
response = await f(url, { method: 'GET', headers: { accept: 'application/json', 'user-agent': 'genesis-cli/0.2.
|
|
839
|
+
response = await f(url, { method: 'GET', headers: { accept: 'application/json', 'user-agent': 'genesis-cli/0.2.8' }, signal: AbortSignal.timeout(15_000) });
|
|
817
840
|
}
|
|
818
841
|
catch (e) {
|
|
819
842
|
throw new Error(`Gateway unreachable at ${safe(url)}: ${safe(e.message)}`);
|
package/dist/products.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { idempotencyHeaders, ifMatchHeaders, mindsEngineCommands, operationTable } from '@onesi/genesis';
|
|
2
|
-
import { readFile, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { MAX_FOUNDRY_FILE_BYTES, foundryFileMimeType, foundryFileUploadBody, isFoundryMediaType, idempotencyHeaders, ifMatchHeaders, mindsEngineCommands, operationTable } from '@onesi/genesis';
|
|
2
|
+
import { readFile, stat, writeFile } from 'node:fs/promises';
|
|
3
3
|
import { basename } from 'node:path';
|
|
4
4
|
import { readFunctionJson } from './function-input.js';
|
|
5
5
|
/** Command words -> operation. Positional arguments are the operation's path parameters, query
|
|
@@ -60,7 +60,7 @@ export const PRODUCT_COMMANDS = {
|
|
|
60
60
|
'operations records': 'lanes_listOperationalRecords', 'operations get': 'lanes_getOperation',
|
|
61
61
|
} },
|
|
62
62
|
auto: { description: "Auto: your organization's operating structure: orgs, mandates, positions, authority checks, budgets, policies, cadences, continuity and credentials", commands: {
|
|
63
|
-
whoami: 'auto_whoami',
|
|
63
|
+
whoami: 'auto_whoami', mcp: 'auto_callMcp',
|
|
64
64
|
'orgs list': 'auto_listOrgs', 'orgs get': 'auto_getOrg', 'orgs create': 'auto_createOrg', 'orgs update': 'auto_updateOrg', 'orgs archive': 'auto_archiveOrg',
|
|
65
65
|
'mandates list': 'auto_listMandates', 'mandates get': 'auto_getMandate', 'mandates create': 'auto_createMandate',
|
|
66
66
|
'mandates update': 'auto_updateMandate', 'mandates archive': 'auto_archiveMandate', 'mandates propose': 'auto_proposeAction',
|
|
@@ -245,6 +245,15 @@ export const PRODUCT_COMMANDS = {
|
|
|
245
245
|
'mail domains list': 'boxes_mail_domains_list',
|
|
246
246
|
'mail domains verify': 'boxes_mail_domains_verify',
|
|
247
247
|
'mail inbox list': 'boxes_mail_inbox_list',
|
|
248
|
+
'mail agents list': 'boxes_mail_agents_list',
|
|
249
|
+
'mail agents create': 'boxes_mail_agents_create',
|
|
250
|
+
'mail agents get': 'boxes_mail_agents_get',
|
|
251
|
+
'mail agents delete': 'boxes_mail_agents_delete',
|
|
252
|
+
'mail agents inbox list': 'boxes_mail_agents_inbox_list',
|
|
253
|
+
'mail agents send': 'boxes_mail_agents_send',
|
|
254
|
+
'mail agents threads list': 'boxes_mail_agents_threads_list',
|
|
255
|
+
'mail agents threads get': 'boxes_mail_agents_threads_get',
|
|
256
|
+
'mail agents messages get': 'boxes_mail_agents_messages_get',
|
|
248
257
|
'mail mailboxes list': 'boxes_mail_mailboxes_list',
|
|
249
258
|
'mail messages get': 'boxes_mail_messages_get',
|
|
250
259
|
'mail messages delete': 'boxes_mail_messages_delete',
|
|
@@ -450,7 +459,14 @@ export function registerOperationCommands(root, commands, dependencies) {
|
|
|
450
459
|
const boxes = op.service === 'boxes';
|
|
451
460
|
const multipart = boxes && op.requestContentType === 'multipart/form-data';
|
|
452
461
|
const boxesRaw = boxes && !!op.rawResponse;
|
|
453
|
-
|
|
462
|
+
// Foundry file upload: one real file from disk (`--file <path>`) and an optional `--purpose`.
|
|
463
|
+
const foundryUpload = id === 'foundry_uploadFile';
|
|
464
|
+
if (foundryUpload) {
|
|
465
|
+
command.requiredOption('--file <path>', 'Local file to upload (1 byte to 8 MiB)');
|
|
466
|
+
command.option('--content-type <type>', 'Media type of the file (default: from the file extension; Foundry refuses application/octet-stream)');
|
|
467
|
+
command.option('--purpose <purpose>', 'What the file is for, e.g. fine-tune, batch or assistants');
|
|
468
|
+
}
|
|
469
|
+
else if (multipart) {
|
|
454
470
|
command.option('--file <part=path>', 'File part, repeatable: part name and local path', (v, all = []) => [...all, v]);
|
|
455
471
|
command.option('--field <part=value>', 'Text part, repeatable', (v, all = []) => [...all, v]);
|
|
456
472
|
}
|
|
@@ -481,6 +497,17 @@ export function registerOperationCommands(root, commands, dependencies) {
|
|
|
481
497
|
input.body = await readFunctionJson(options.bodyFile, BODY_LIMIT);
|
|
482
498
|
const headers = { ...idempotencyHeaders(id, options.idempotencyKey), ...(op.ifMatch ? ifMatchHeaders(id, options.ifMatch) : {}) };
|
|
483
499
|
const extra = options;
|
|
500
|
+
if (foundryUpload) {
|
|
501
|
+
const path = options.file;
|
|
502
|
+
if (options.contentType !== undefined && !isFoundryMediaType(options.contentType))
|
|
503
|
+
throw new Error('--content-type must look like type/subtype.');
|
|
504
|
+
const info = await stat(path).catch(() => { throw new Error(`Could not open ${path}.`); });
|
|
505
|
+
if (!info.isFile())
|
|
506
|
+
throw new Error(`${path} is not a regular file.`);
|
|
507
|
+
if (info.size > MAX_FOUNDRY_FILE_BYTES)
|
|
508
|
+
throw new Error('Choose a file of 1 byte to 8 MiB.');
|
|
509
|
+
input.body = foundryFileUploadBody(new Blob([new Uint8Array(await readFile(path))], { type: options.contentType ?? foundryFileMimeType(basename(path)) }), { filename: basename(path), purpose: options.purpose });
|
|
510
|
+
}
|
|
484
511
|
if (multipart) {
|
|
485
512
|
const form = new FormData();
|
|
486
513
|
const split = (entry) => { const at = entry.indexOf('='); if (at < 1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onesi/genesis-cli",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"repository": {
|
|
5
5
|
"type": "git",
|
|
6
6
|
"url": "git+https://github.com/l1felabs/genesis-api.git",
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"dependencies": {
|
|
34
34
|
"commander": "^14.0.0",
|
|
35
35
|
"undici": "6.29.0",
|
|
36
|
-
"@onesi/genesis": "0.2.
|
|
36
|
+
"@onesi/genesis": "0.2.8"
|
|
37
37
|
},
|
|
38
38
|
"optionalDependencies": {
|
|
39
39
|
"@napi-rs/keyring": "^1.2.0"
|