@onesi/genesis-cli 0.0.0-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.
- package/README.md +36 -2
- package/bin/genesis.js +3 -0
- package/dist/auth.d.ts +30 -0
- package/dist/auth.js +273 -0
- package/dist/beacons.d.ts +3 -0
- package/dist/beacons.js +94 -0
- package/dist/device-login.d.ts +25 -0
- package/dist/device-login.js +173 -0
- package/dist/dials.d.ts +7 -0
- package/dist/dials.js +96 -0
- package/dist/fleets.d.ts +4 -0
- package/dist/fleets.js +72 -0
- package/dist/flocks.d.ts +3 -0
- package/dist/flocks.js +72 -0
- package/dist/function-input.d.ts +3 -0
- package/dist/function-input.js +49 -0
- package/dist/henry.d.ts +3 -0
- package/dist/henry.js +26 -0
- package/dist/hives-marketplace.d.ts +10 -0
- package/dist/hives-marketplace.js +29 -0
- package/dist/hives-node-transport.d.ts +6 -0
- package/dist/hives-node-transport.js +147 -0
- package/dist/hives-operations.d.ts +9 -0
- package/dist/hives-operations.js +61 -0
- package/dist/hives-resources.d.ts +9 -0
- package/dist/hives-resources.js +94 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.js +640 -0
- package/dist/lifeid-native.d.ts +24 -0
- package/dist/lifeid-native.js +113 -0
- package/dist/minds-owner.d.ts +3 -0
- package/dist/minds-owner.js +42 -0
- package/dist/minds.d.ts +3 -0
- package/dist/minds.js +90 -0
- package/dist/output.d.ts +28 -0
- package/dist/output.js +46 -0
- package/dist/products.d.ts +14 -0
- package/dist/products.js +85 -0
- package/dist/relays-enrollment.d.ts +3 -0
- package/dist/relays-enrollment.js +7 -0
- package/dist/runner.d.ts +4 -0
- package/dist/runner.js +55 -0
- package/dist/secret-input.d.ts +14 -0
- package/dist/secret-input.js +85 -0
- package/dist/store.d.ts +39 -0
- package/dist/store.js +33 -0
- package/dist/wasm-input.d.ts +2 -0
- package/dist/wasm-input.js +31 -0
- package/package.json +51 -3
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export declare const LIFEID_ISSUER = "https://auth.life.one";
|
|
2
|
+
export declare const GENESIS_CLI_PRODUCT = "genesis-cli";
|
|
3
|
+
export declare const CALLBACK_PATH = "/lifeid/callback";
|
|
4
|
+
/** Exact Auth-owned admission metadata; supplying it does not register a client. */
|
|
5
|
+
export interface NativeRegistration {
|
|
6
|
+
productId: string;
|
|
7
|
+
redirectUri: string;
|
|
8
|
+
audience: string;
|
|
9
|
+
}
|
|
10
|
+
export interface NativeAttempt {
|
|
11
|
+
readonly browserUrl: string;
|
|
12
|
+
readonly codeVerifier: string;
|
|
13
|
+
readonly registration: NativeRegistration;
|
|
14
|
+
readonly code: Promise<string>;
|
|
15
|
+
readonly signal: AbortSignal;
|
|
16
|
+
close(): void;
|
|
17
|
+
}
|
|
18
|
+
export declare function validateRegistration(r: NativeRegistration): number;
|
|
19
|
+
/** Bind the sole registered port before the caller opens or prints the browser URL. */
|
|
20
|
+
export declare function startNativeAttempt(registration: NativeRegistration, options?: {
|
|
21
|
+
signal?: AbortSignal;
|
|
22
|
+
timeoutMs?: number;
|
|
23
|
+
organizationHint?: string;
|
|
24
|
+
}): Promise<NativeAttempt>;
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import { createHash, randomBytes } from 'node:crypto';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
export const LIFEID_ISSUER = 'https://auth.life.one';
|
|
4
|
+
export const GENESIS_CLI_PRODUCT = 'genesis-cli';
|
|
5
|
+
export const CALLBACK_PATH = '/lifeid/callback';
|
|
6
|
+
export function validateRegistration(r) {
|
|
7
|
+
const m = /^http:\/\/127\.0\.0\.1:([1-9][0-9]{3,4})\/lifeid\/callback$/.exec(r.redirectUri);
|
|
8
|
+
const port = m ? Number(m[1]) : 0;
|
|
9
|
+
if (r.productId !== GENESIS_CLI_PRODUCT || !m || port < 1024 || port > 65535
|
|
10
|
+
|| !/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(r.audience)) {
|
|
11
|
+
throw new Error('One account native login requires the exact admitted Genesis product, loopback callback and audience.');
|
|
12
|
+
}
|
|
13
|
+
return port;
|
|
14
|
+
}
|
|
15
|
+
/** Bind the sole registered port before the caller opens or prints the browser URL. */
|
|
16
|
+
export async function startNativeAttempt(registration, options = {}) {
|
|
17
|
+
registration = { ...registration };
|
|
18
|
+
const port = validateRegistration(registration);
|
|
19
|
+
const timeout = options.timeoutMs ?? 300_000;
|
|
20
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > 300_000)
|
|
21
|
+
throw new Error('Invalid login deadline.');
|
|
22
|
+
if (options.signal?.aborted)
|
|
23
|
+
throw new Error('One account login cancelled.');
|
|
24
|
+
if (options.organizationHint && !/^o_[0-9A-HJKMNP-TV-Z]{26}$/.test(options.organizationHint)) {
|
|
25
|
+
throw new Error('Invalid One account organization hint.');
|
|
26
|
+
}
|
|
27
|
+
const verifier = randomBytes(48).toString('base64url');
|
|
28
|
+
const controller = new AbortController();
|
|
29
|
+
let settled = false;
|
|
30
|
+
let resolveCode;
|
|
31
|
+
let rejectCode;
|
|
32
|
+
const code = new Promise((resolve, reject) => { resolveCode = resolve; rejectCode = reject; });
|
|
33
|
+
// Binding/open-browser errors may happen before the caller awaits the code.
|
|
34
|
+
void code.catch(() => { });
|
|
35
|
+
const server = createServer({ maxHeaderSize: 4096 }, (req, res) => {
|
|
36
|
+
const reject = (status) => {
|
|
37
|
+
res.writeHead(status, { 'content-type': 'text/plain', 'cache-control': 'no-store', connection: 'close' });
|
|
38
|
+
res.end('Invalid One account callback.');
|
|
39
|
+
};
|
|
40
|
+
// A malformed request cannot consume a pending code. No body is read/logged.
|
|
41
|
+
if (settled || req.method !== 'GET' || req.headers.host !== `127.0.0.1:${port}`
|
|
42
|
+
|| req.headers.origin !== undefined || req.headers['transfer-encoding'] !== undefined
|
|
43
|
+
|| (req.headers['content-length'] !== undefined && req.headers['content-length'] !== '0')) {
|
|
44
|
+
reject(400);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const m = /^\/lifeid\/callback\?life_code=([A-Za-z0-9_-]{43})$/.exec(req.url ?? '');
|
|
48
|
+
if (!m) {
|
|
49
|
+
reject(400);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
settled = true;
|
|
53
|
+
res.writeHead(200, { 'content-type': 'text/plain; charset=utf-8', 'cache-control': 'no-store',
|
|
54
|
+
'referrer-policy': 'no-referrer', 'content-security-policy': "default-src 'none'", connection: 'close' });
|
|
55
|
+
res.end('One account callback received. Return to Genesis CLI.');
|
|
56
|
+
server.close();
|
|
57
|
+
resolveCode(m[1]);
|
|
58
|
+
});
|
|
59
|
+
server.requestTimeout = 5000;
|
|
60
|
+
server.headersTimeout = 5000;
|
|
61
|
+
server.keepAliveTimeout = 1;
|
|
62
|
+
server.maxConnections = 4;
|
|
63
|
+
const close = () => {
|
|
64
|
+
clearTimeout(timer);
|
|
65
|
+
options.signal?.removeEventListener('abort', onAbort);
|
|
66
|
+
server.close();
|
|
67
|
+
server.closeAllConnections();
|
|
68
|
+
if (!settled) {
|
|
69
|
+
settled = true;
|
|
70
|
+
rejectCode(new Error('One account login cancelled.'));
|
|
71
|
+
}
|
|
72
|
+
controller.abort();
|
|
73
|
+
};
|
|
74
|
+
const onAbort = () => close();
|
|
75
|
+
const timer = setTimeout(() => {
|
|
76
|
+
if (!settled) {
|
|
77
|
+
settled = true;
|
|
78
|
+
rejectCode(new Error('One account login timed out. Run `genesis login` again.'));
|
|
79
|
+
}
|
|
80
|
+
close();
|
|
81
|
+
}, timeout);
|
|
82
|
+
options.signal?.addEventListener('abort', onAbort, { once: true });
|
|
83
|
+
try {
|
|
84
|
+
await new Promise((resolve, reject) => {
|
|
85
|
+
const aborted = () => { cleanup(); reject(new Error('One account login cancelled.')); };
|
|
86
|
+
const cleanup = () => { server.removeListener('error', error); controller.signal.removeEventListener('abort', aborted); };
|
|
87
|
+
const error = () => { cleanup(); reject(new Error('The registered One account callback port is unavailable. No other port was tried.')); };
|
|
88
|
+
server.once('error', error);
|
|
89
|
+
controller.signal.addEventListener('abort', aborted, { once: true });
|
|
90
|
+
if (controller.signal.aborted) {
|
|
91
|
+
aborted();
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
server.listen({ port, host: '127.0.0.1', signal: controller.signal }, () => { cleanup(); resolve(); });
|
|
95
|
+
});
|
|
96
|
+
if (controller.signal.aborted || options.signal?.aborted)
|
|
97
|
+
throw new Error('One account login cancelled.');
|
|
98
|
+
}
|
|
99
|
+
catch (error) {
|
|
100
|
+
close();
|
|
101
|
+
throw error;
|
|
102
|
+
}
|
|
103
|
+
server.on('error', () => close());
|
|
104
|
+
const url = new URL('/v4/lifeid/start', LIFEID_ISSUER);
|
|
105
|
+
url.searchParams.set('product_id', registration.productId);
|
|
106
|
+
url.searchParams.set('return_path', '/');
|
|
107
|
+
url.searchParams.set('code_challenge', createHash('sha256').update(verifier).digest('base64url'));
|
|
108
|
+
url.searchParams.set('code_challenge_method', 'S256');
|
|
109
|
+
if (options.organizationHint)
|
|
110
|
+
url.searchParams.set('org_hint', options.organizationHint);
|
|
111
|
+
return { browserUrl: url.toString(), codeVerifier: verifier, registration: { ...registration }, code,
|
|
112
|
+
signal: controller.signal, close };
|
|
113
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
export function registerMindsOwner(minds, client, emit, signal) {
|
|
3
|
+
const intent = (command, revision = true) => {
|
|
4
|
+
command.requiredOption('--operation-id <uuid>', 'retain this non-nil UUID when reconciling the same intent');
|
|
5
|
+
if (revision)
|
|
6
|
+
command.requiredOption('--expected-version <integer>', 'current membership or attachment version; zero for first creation');
|
|
7
|
+
return command;
|
|
8
|
+
};
|
|
9
|
+
const version = (value) => {
|
|
10
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
|
|
11
|
+
throw new Error('Expected version must be a nonnegative safe integer.');
|
|
12
|
+
return Number(value);
|
|
13
|
+
};
|
|
14
|
+
const body = (options) => ({ operationId: options.operationId, expectedVersion: version(options.expectedVersion) });
|
|
15
|
+
const groups = minds.command('groups').description('Canonical owner-managed groups; uses the original selected Console human session');
|
|
16
|
+
groups.command('list').action(async () => emit(await (await client()).minds.groups.list({ signal })));
|
|
17
|
+
intent(groups.command('create <name>'), false).action(async (name, options) => emit(await (await client()).minds.groups.create({ operationId: options.operationId, name }, { signal })));
|
|
18
|
+
const members = groups.command('members').description('Canonical group memberships and retained removal versions');
|
|
19
|
+
members.command('list <group_id>').action(async (group) => emit(await (await client()).minds.groups.members.list(group, { signal })));
|
|
20
|
+
intent(members.command('add <group_id> <agent_id>')).action(async (group, agent, options) => emit(await (await client()).minds.groups.members.add(group, { ...body(options), agentId: agent }, { signal })));
|
|
21
|
+
intent(members.command('remove <group_id> <agent_id>')).action(async (group, agent, options) => emit(await (await client()).minds.groups.members.remove(group, agent, body(options), { signal })));
|
|
22
|
+
const attachments = minds.command('attachments').description('Attach existing canonical agents or groups; never issues credentials');
|
|
23
|
+
attachments.command('list <mind_id>').action(async (mind) => emit(await (await client()).minds.attachments.list(mind, { signal })));
|
|
24
|
+
attachments.command('attach <mind_id>').requiredOption('--request-file <path>', 'closed JSON attachment intent with operationId, expectedVersion, subject, permissions and expiresAt')
|
|
25
|
+
.action(async (mind, options) => {
|
|
26
|
+
const metadata = await stat(options.requestFile);
|
|
27
|
+
if (!metadata.isFile() || metadata.size > 8192)
|
|
28
|
+
throw new Error('Attachment request must be at most 8 KiB.');
|
|
29
|
+
const bytes = await readFile(options.requestFile);
|
|
30
|
+
if (bytes.length > 8192)
|
|
31
|
+
throw new Error('Attachment request must be at most 8 KiB.');
|
|
32
|
+
let request;
|
|
33
|
+
try {
|
|
34
|
+
request = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
35
|
+
}
|
|
36
|
+
catch {
|
|
37
|
+
throw new Error('Attachment request must contain valid JSON.');
|
|
38
|
+
}
|
|
39
|
+
emit(await (await client()).minds.attachments.attach(mind, request, { signal }));
|
|
40
|
+
});
|
|
41
|
+
intent(attachments.command('detach <mind_id> <attachment_id>')).action(async (mind, attachment, options) => emit(await (await client()).minds.attachments.detach(mind, attachment, body(options), { signal })));
|
|
42
|
+
}
|
package/dist/minds.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { type Command } from 'commander';
|
|
2
|
+
import type { Genesis } from '@onesi/genesis';
|
|
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
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { readFile, stat } from 'node:fs/promises';
|
|
2
|
+
import { registerMindsOwner } from './minds-owner.js';
|
|
3
|
+
import { Option } from 'commander';
|
|
4
|
+
export function registerMinds(program, client, emit, agentClient = client, signal, ownerClient = client) {
|
|
5
|
+
const minds = program.command('minds').description('Owned Minds and persisted memory; requires an admitted human Console Sandbox session');
|
|
6
|
+
registerMindsOwner(minds, ownerClient, emit, signal);
|
|
7
|
+
minds.command('list').option('--after <uuid>').option('--limit <number>').description('List real Minds and the current creation context fingerprint').action(async (options) => {
|
|
8
|
+
emit(await (await client()).minds.list({ ...(options.after ? { after: options.after } : {}), ...(options.limit ? { limit: Number(options.limit) } : {}) }));
|
|
9
|
+
});
|
|
10
|
+
minds.command('get <mind_id>').action(async (id) => emit(await (await client()).minds.get(id)));
|
|
11
|
+
minds.command('usage').description('Read selected-project native usage for an explicit window of at most 93 days')
|
|
12
|
+
.requiredOption('--from <timestamp>', 'RFC3339 window start')
|
|
13
|
+
.requiredOption('--to <timestamp>', 'RFC3339 window end')
|
|
14
|
+
.action(async (options) => emit(await (await client()).minds.usage({ from: options.from, to: options.to })));
|
|
15
|
+
minds.command('create <name>').description('Create a Sandbox Mind; Free/F1 by default or an admitted Pro profile; 202 means provisioning')
|
|
16
|
+
.requiredOption('--idempotency-key <uuid>', 'persist this UUID4 and retain it after an uncertain response')
|
|
17
|
+
.requiredOption('--context-sha256 <hash>', 'context_sha256 from the current minds list response')
|
|
18
|
+
.addOption(new Option('--plan <plan>', 'explicit plan; entitlement remains server-authorized').choices(['free', 'pro']))
|
|
19
|
+
.addOption(new Option('--profile-id <profile>', 'fixed native profile; Pro defaults to D1, Free accepts only F1').choices(['F1', 'D1', 'D2']))
|
|
20
|
+
.action(async (name, options) => emit(await (await client()).minds.create(name, { idempotencyKey: options.idempotencyKey, contextSha256: options.contextSha256, ...(options.plan === undefined ? {} : { plan: options.plan }), ...(options.profileId === undefined ? {} : { profileId: options.profileId }) })));
|
|
21
|
+
minds.command('resume <mind_id>').description('Resume the same Mind and retained volume; 202 means the operation is accepted')
|
|
22
|
+
.requiredOption('--expected-generation <number>', 'desired_generation from a fresh owned Mind read')
|
|
23
|
+
.requiredOption('--idempotency-key <uuid>', 'persist this UUID4 with the original generation after an uncertain response')
|
|
24
|
+
.addOption(new Option('--wake-reason <reason>', 'reason for this resume intent').choices(['manual', 'scheduled_job', 'webhook', 'memory_consolidation', 'activity']))
|
|
25
|
+
.action(async (id, options) => emit(await (await client()).minds.resume(id, Number(options.expectedGeneration), { idempotencyKey: options.idempotencyKey, ...(options.wakeReason === undefined ? {} : { wakeReason: options.wakeReason }) })));
|
|
26
|
+
minds.command('stop <mind_id>').description('Stop the same Mind without deleting its retained volume; 202 means accepted')
|
|
27
|
+
.requiredOption('--expected-generation <number>', 'desired_generation from a fresh owned Mind read')
|
|
28
|
+
.requiredOption('--idempotency-key <uuid>', 'persist this UUID4 and original generation; no automatic retry')
|
|
29
|
+
.action(async (id, options) => emit(await (await client()).minds.stop(id, Number(options.expectedGeneration), { idempotencyKey: options.idempotencyKey })));
|
|
30
|
+
const memory = minds.command('memory').description('Every operation selects one owned, ready Mind');
|
|
31
|
+
memory.command('insert <mind_id>').requiredOption('--event-file <path>', 'JSON event with id, content and timestamp; returned id is its storage UUID')
|
|
32
|
+
.action(async (id, options) => {
|
|
33
|
+
const metadata = await stat(options.eventFile);
|
|
34
|
+
if (!metadata.isFile() || metadata.size > 65_536)
|
|
35
|
+
throw new Error('Event file must be at most 64 KiB.');
|
|
36
|
+
const bytes = await readFile(options.eventFile);
|
|
37
|
+
if (bytes.length > 65_536)
|
|
38
|
+
throw new Error('Event file exceeds 64 KiB.');
|
|
39
|
+
const event = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
40
|
+
emit(await (await client()).minds.memory.insert(id, event));
|
|
41
|
+
});
|
|
42
|
+
memory.command('get <mind_id> <storage_id>').action(async (mind, storage) => emit(await (await client()).minds.memory.get(mind, storage)));
|
|
43
|
+
memory.command('update <mind_id> <storage_id>').requiredOption('--content-file <path>', 'JSON {event_id,content}; preserves stored metadata, never automatically retries')
|
|
44
|
+
.action(async (mind, storage, options) => {
|
|
45
|
+
const metadata = await stat(options.contentFile);
|
|
46
|
+
if (!metadata.isFile() || metadata.size > 65_536)
|
|
47
|
+
throw new Error('Update file must be at most 64 KiB.');
|
|
48
|
+
const bytes = await readFile(options.contentFile);
|
|
49
|
+
if (bytes.length > 65_536)
|
|
50
|
+
throw new Error('Update file exceeds 64 KiB.');
|
|
51
|
+
const body = JSON.parse(new TextDecoder('utf-8', { fatal: true }).decode(bytes));
|
|
52
|
+
emit(await (await client()).minds.memory.update(mind, storage, body));
|
|
53
|
+
});
|
|
54
|
+
memory.command('search <mind_id> <text>').option('--limit <number>', '1–100 results', '20').action(async (mind, text, options) => {
|
|
55
|
+
emit(await (await client()).minds.memory.search(mind, text, Number(options.limit)));
|
|
56
|
+
});
|
|
57
|
+
const agent = minds.command('agent').description('Use an existing agent credential; does not create an attachment or exchange a human session');
|
|
58
|
+
const agentMemory = agent.command('memory').description('Private or explicitly authorized group memory; requests are never automatically retried');
|
|
59
|
+
const scoped = (command) => command.option('--group <agp_id>', 'explicit canonical group; omit for private memory')
|
|
60
|
+
.option('--dpop-proof-file <path>', 'fresh DPoP proof for the controller capability method and URL');
|
|
61
|
+
const readBounded = async (path, limit) => {
|
|
62
|
+
const metadata = await stat(path);
|
|
63
|
+
if (!metadata.isFile() || metadata.size > limit)
|
|
64
|
+
throw new Error('Agent input file exceeds its byte limit.');
|
|
65
|
+
const bytes = await readFile(path);
|
|
66
|
+
if (bytes.length > limit)
|
|
67
|
+
throw new Error('Agent input file exceeds its byte limit.');
|
|
68
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
69
|
+
};
|
|
70
|
+
const agentOptions = async (options) => ({ signal,
|
|
71
|
+
...(options.group === undefined ? {} : { group: options.group }),
|
|
72
|
+
...(options.dpopProofFile === undefined ? {} : { dpopProof: await readBounded(options.dpopProofFile, 16_384) }),
|
|
73
|
+
});
|
|
74
|
+
scoped(agentMemory.command('insert <mind_id>').requiredOption('--event-file <path>', 'JSON event; retain the returned storage UUID'))
|
|
75
|
+
.action(async (mind, options) => {
|
|
76
|
+
const content = await readBounded(options.eventFile, 65_536);
|
|
77
|
+
let event;
|
|
78
|
+
try {
|
|
79
|
+
event = JSON.parse(content);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
throw new Error('Agent event file must contain valid JSON.');
|
|
83
|
+
}
|
|
84
|
+
emit(await (await agentClient()).minds.agent.memory.insert(mind, event, await agentOptions(options)));
|
|
85
|
+
});
|
|
86
|
+
scoped(agentMemory.command('get <mind_id> <storage_id>'))
|
|
87
|
+
.action(async (mind, storage, options) => emit(await (await agentClient()).minds.agent.memory.get(mind, storage, await agentOptions(options))));
|
|
88
|
+
scoped(agentMemory.command('search <mind_id> <text>').option('--limit <number>', '1–100 results', '20'))
|
|
89
|
+
.action(async (mind, text, options) => emit(await (await agentClient()).minds.agent.memory.search(mind, text, Number(options.limit), await agentOptions(options))));
|
|
90
|
+
}
|
package/dist/output.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface Out {
|
|
2
|
+
log(s: string): void;
|
|
3
|
+
err(s: string): void;
|
|
4
|
+
}
|
|
5
|
+
export declare const stdio: Out;
|
|
6
|
+
export declare const c: {
|
|
7
|
+
bold: (s: string) => string;
|
|
8
|
+
dim: (s: string) => string;
|
|
9
|
+
green: (s: string) => string;
|
|
10
|
+
yellow: (s: string) => string;
|
|
11
|
+
red: (s: string) => string;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Make server-provided text safe to print so it cannot drive the terminal: drop
|
|
15
|
+
* whole OSC sequences (OSC 8 hyperlinks included, BEL- or ST-terminated, 7- and
|
|
16
|
+
* 8-bit), CSI and other ESC sequences, then any remaining C0, DEL or C1 character,
|
|
17
|
+
* and finally bidi controls and invisible characters that can reorder or hide
|
|
18
|
+
* text (Trojan Source): LRE/RLE/PDF/LRO/RLO (U+202A-202E), LRI/RLI/FSI/PDI
|
|
19
|
+
* (U+2066-2069), ZWSP/ZWNJ/ZWJ/LRM/RLM (U+200B-200F), ALM (U+061C), BOM/ZWNBSP
|
|
20
|
+
* (U+FEFF), line/paragraph separators (U+2028-2029), word joiner and invisible
|
|
21
|
+
* operators (U+2060-2064), Mongolian vowel separator (U+180E), soft hyphen
|
|
22
|
+
* (U+00AD) and tag characters (U+E0000-E007F).
|
|
23
|
+
*/
|
|
24
|
+
export declare function safe(v: unknown): string;
|
|
25
|
+
/** Render rows as an aligned table; every cell is passed through safe(). */
|
|
26
|
+
export declare function table(rows: Record<string, unknown>[], cols: string[]): string;
|
|
27
|
+
/** Pull the array out of the various list envelopes ({data}, {zones}, {records}, bare array). */
|
|
28
|
+
export declare function listOf(x: any, ...keys: string[]): any[];
|
package/dist/output.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export const stdio = { log: (s) => process.stdout.write(s + '\n'), err: (s) => process.stderr.write(s + '\n') };
|
|
2
|
+
const tty = () => !!process.stdout.isTTY && !process.env.NO_COLOR;
|
|
3
|
+
export const c = {
|
|
4
|
+
bold: (s) => (tty() ? `\x1b[1m${s}\x1b[0m` : s),
|
|
5
|
+
dim: (s) => (tty() ? `\x1b[2m${s}\x1b[0m` : s),
|
|
6
|
+
green: (s) => (tty() ? `\x1b[32m${s}\x1b[0m` : s),
|
|
7
|
+
yellow: (s) => (tty() ? `\x1b[33m${s}\x1b[0m` : s),
|
|
8
|
+
red: (s) => (tty() ? `\x1b[31m${s}\x1b[0m` : s),
|
|
9
|
+
};
|
|
10
|
+
/**
|
|
11
|
+
* Make server-provided text safe to print so it cannot drive the terminal: drop
|
|
12
|
+
* whole OSC sequences (OSC 8 hyperlinks included, BEL- or ST-terminated, 7- and
|
|
13
|
+
* 8-bit), CSI and other ESC sequences, then any remaining C0, DEL or C1 character,
|
|
14
|
+
* and finally bidi controls and invisible characters that can reorder or hide
|
|
15
|
+
* text (Trojan Source): LRE/RLE/PDF/LRO/RLO (U+202A-202E), LRI/RLI/FSI/PDI
|
|
16
|
+
* (U+2066-2069), ZWSP/ZWNJ/ZWJ/LRM/RLM (U+200B-200F), ALM (U+061C), BOM/ZWNBSP
|
|
17
|
+
* (U+FEFF), line/paragraph separators (U+2028-2029), word joiner and invisible
|
|
18
|
+
* operators (U+2060-2064), Mongolian vowel separator (U+180E), soft hyphen
|
|
19
|
+
* (U+00AD) and tag characters (U+E0000-E007F).
|
|
20
|
+
*/
|
|
21
|
+
export function safe(v) {
|
|
22
|
+
return String(v)
|
|
23
|
+
.replace(/(?:\x1b\]|\x9d)[^\x07\x1b\x9c]*(?:\x07|\x1b\\|\x9c)?/g, '')
|
|
24
|
+
.replace(/(?:\x1b\[|\x9b)[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]?/g, '')
|
|
25
|
+
.replace(/\x1b[\x20-\x2f]*[\x30-\x7e]?/g, '')
|
|
26
|
+
.replace(/[\x00-\x1f\x7f-\x9f]/g, '')
|
|
27
|
+
.replace(/[\u202a-\u202e\u2066-\u2069\u200b-\u200f\u061c\ufeff\u2028\u2029\u2060-\u2064\u180e\u00ad\u{e0000}-\u{e007f}]/gu, '');
|
|
28
|
+
}
|
|
29
|
+
/** Render rows as an aligned table; every cell is passed through safe(). */
|
|
30
|
+
export function table(rows, cols) {
|
|
31
|
+
if (!rows.length)
|
|
32
|
+
return c.dim('(none)');
|
|
33
|
+
const cell = (v) => (v === undefined || v === null ? '' : safe(Array.isArray(v) ? v.join(',') : v));
|
|
34
|
+
const w = cols.map((k) => Math.max(k.length, ...rows.map((r) => cell(r[k]).length)));
|
|
35
|
+
const line = (vals) => vals.map((v, i) => v.padEnd(w[i])).join(' ').trimEnd();
|
|
36
|
+
return [c.bold(line(cols.map((k) => k.toUpperCase()))), ...rows.map((r) => line(cols.map((k) => cell(r[k]))))].join('\n');
|
|
37
|
+
}
|
|
38
|
+
/** Pull the array out of the various list envelopes ({data}, {zones}, {records}, bare array). */
|
|
39
|
+
export function listOf(x, ...keys) {
|
|
40
|
+
if (Array.isArray(x))
|
|
41
|
+
return x;
|
|
42
|
+
for (const k of [...keys, 'data', 'items', 'results'])
|
|
43
|
+
if (Array.isArray(x?.[k]))
|
|
44
|
+
return x[k];
|
|
45
|
+
return [];
|
|
46
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import { type Genesis, type OperationId } from '@onesi/genesis';
|
|
3
|
+
/** Command words -> operation. Positional arguments are the operation's path parameters, query
|
|
4
|
+
* parameters become options, a body comes from --body-file, and --idempotency-key carries the
|
|
5
|
+
* Idempotency-Key header (required where the service requires it). */
|
|
6
|
+
export declare const PRODUCT_COMMANDS: Record<string, {
|
|
7
|
+
description: string;
|
|
8
|
+
commands: Record<string, OperationId>;
|
|
9
|
+
}>;
|
|
10
|
+
export declare function registerProducts(program: Command, dependencies: {
|
|
11
|
+
client: () => Promise<Genesis>;
|
|
12
|
+
emit: (data: unknown, pretty: () => string) => void;
|
|
13
|
+
signal?: AbortSignal;
|
|
14
|
+
}): void;
|
package/dist/products.js
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { idempotencyHeaders, operationTable } from '@onesi/genesis';
|
|
2
|
+
import { readFunctionJson } from './function-input.js';
|
|
3
|
+
/** Command words -> operation. Positional arguments are the operation's path parameters, query
|
|
4
|
+
* parameters become options, a body comes from --body-file, and --idempotency-key carries the
|
|
5
|
+
* Idempotency-Key header (required where the service requires it). */
|
|
6
|
+
export const PRODUCT_COMMANDS = {
|
|
7
|
+
rigs: { description: 'Rigs: your desktops (read-only; One account login with a selected project)', commands: { list: 'rigs_listRigs' } },
|
|
8
|
+
panes: { description: 'Panes: your browsers (read-only; One account login with a selected project)', commands: { list: 'panes_listPanes' } },
|
|
9
|
+
shells: { description: 'Shells: your sandboxes (read-only; One account login with a selected project)', commands: { list: 'shells_listShells' } },
|
|
10
|
+
gears: { description: 'Gears: durable workflows, runs and signals', commands: {
|
|
11
|
+
'workflows list': 'gears_listWorkflows', 'workflows get': 'gears_getWorkflow', 'workflows register': 'gears_registerWorkflow',
|
|
12
|
+
'runs list': 'gears_listRuns', 'runs get': 'gears_getRun', 'runs history': 'gears_getRunHistory',
|
|
13
|
+
'runs start': 'gears_startRun', 'runs signal': 'gears_signalRun', records: 'gears_listOperationalRecords',
|
|
14
|
+
} },
|
|
15
|
+
lanes: { description: 'Lanes: agent context, cache, ledger and routing (Sandbox; One account login)', commands: {
|
|
16
|
+
'records get': 'lanes_getRecord', 'records write': 'lanes_writeRecord', 'records snapshot': 'lanes_snapshotRecords', 'records restore': 'lanes_restoreRecords',
|
|
17
|
+
retrieve: 'lanes_retrieve', 'frames create': 'lanes_createFrame', answer: 'lanes_answer',
|
|
18
|
+
'ledger frame': 'lanes_getLedgerFrame', 'ledger snapshot': 'lanes_snapshotLedger', 'ledger restore': 'lanes_restoreLedger',
|
|
19
|
+
'cache lookup': 'lanes_cacheLookup', 'cache write': 'lanes_cacheWrite', 'cache snapshot': 'lanes_snapshotCache', 'cache restore': 'lanes_restoreCache',
|
|
20
|
+
'recovery restore': 'lanes_recoveryRestore', 'tenant export': 'lanes_exportTenant', 'tenant delete': 'lanes_deleteTenant',
|
|
21
|
+
'policy evaluate': 'lanes_evaluatePolicy', 'foundry-routes enforce': 'lanes_enforceFoundryRoute',
|
|
22
|
+
'promotions list': 'lanes_listPromotionCandidates', 'promotions propose': 'lanes_proposePromotion', 'promotions review': 'lanes_reviewPromotion',
|
|
23
|
+
'operations records': 'lanes_listOperationalRecords', 'operations get': 'lanes_getOperation',
|
|
24
|
+
} },
|
|
25
|
+
flares: { description: 'Flares: agent notifications, conversations, commitments, presence, trust and choreography (Sandbox; One account login)', commands: {
|
|
26
|
+
list: 'flares_listFlares', get: 'flares_getFlare', send: 'flares_sendFlare', 'send-batch': 'flares_sendFlareBatch',
|
|
27
|
+
acknowledge: 'flares_acknowledgeFlare', act: 'flares_actOnFlare',
|
|
28
|
+
'conversations list': 'flares_listConversations', 'conversations get': 'flares_getConversation', 'conversations create': 'flares_createConversation',
|
|
29
|
+
'conversations send': 'flares_sendConversationMessage', 'conversations close': 'flares_closeConversation',
|
|
30
|
+
'commitments list': 'flares_listCommitments', 'commitments get': 'flares_getCommitment', 'commitments create': 'flares_createCommitment',
|
|
31
|
+
'commitments accept': 'flares_acceptCommitment', 'commitments start': 'flares_startCommitment', 'commitments progress': 'flares_reportCommitmentProgress',
|
|
32
|
+
'commitments fulfill': 'flares_fulfillCommitment', 'commitments fail': 'flares_failCommitment',
|
|
33
|
+
'presence heartbeat': 'flares_presenceHeartbeat', 'presence get': 'flares_getPresence',
|
|
34
|
+
'trust score': 'flares_getTrustScore', 'trust check': 'flares_checkTrust', 'trust endorsements': 'flares_listEndorsements',
|
|
35
|
+
'trust endorse': 'flares_createEndorsement', 'trust revoke': 'flares_revokeEndorsement',
|
|
36
|
+
'choreography workflows list': 'flares_listChoreographyWorkflows', 'choreography workflows create': 'flares_createChoreographyWorkflow',
|
|
37
|
+
'choreography workflows start': 'flares_startChoreographyExecution',
|
|
38
|
+
'choreography executions list': 'flares_listChoreographyExecutions', 'choreography executions get': 'flares_getChoreographyExecution',
|
|
39
|
+
'choreography executions complete-step': 'flares_completeChoreographyStep', 'choreography executions fail-step': 'flares_failChoreographyStep',
|
|
40
|
+
'choreography executions cancel': 'flares_cancelChoreographyExecution',
|
|
41
|
+
} },
|
|
42
|
+
};
|
|
43
|
+
const BODY_LIMIT = 1024 * 1024;
|
|
44
|
+
const flag = (name) => name.replace(/_/g, '-').replace(/([a-z])([A-Z])/g, '$1-$2').toLowerCase();
|
|
45
|
+
/** Commander's option key for a flag: --recipient-did -> recipientDid. */
|
|
46
|
+
const key = (name) => flag(name).replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
47
|
+
export function registerProducts(program, dependencies) {
|
|
48
|
+
for (const [service, { description, commands }] of Object.entries(PRODUCT_COMMANDS)) {
|
|
49
|
+
const root = program.command(service).description(description);
|
|
50
|
+
const groups = new Map();
|
|
51
|
+
for (const [words, id] of Object.entries(commands)) {
|
|
52
|
+
const op = operationTable[id];
|
|
53
|
+
const parts = words.split(' ');
|
|
54
|
+
let parent = root;
|
|
55
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
56
|
+
const key = parts.slice(0, i + 1).join(' ');
|
|
57
|
+
if (!groups.has(key))
|
|
58
|
+
groups.set(key, parent.command(parts[i]));
|
|
59
|
+
parent = groups.get(key);
|
|
60
|
+
}
|
|
61
|
+
const bodyRequired = op.inputSchema.required.includes('body');
|
|
62
|
+
const command = parent.command([parts.at(-1), ...op.pathParams.map(p => `<${p}>`)].join(' ')).description(`${op.summary} (${op.method} ${op.path})`);
|
|
63
|
+
for (const q of op.queryParams)
|
|
64
|
+
command.option(`--${flag(q)} <value>`);
|
|
65
|
+
if (op.hasBody)
|
|
66
|
+
command[bodyRequired ? 'requiredOption' : 'option']('--body-file <path>', 'JSON request body, up to 1 MiB');
|
|
67
|
+
if (op.idempotencyKey === 'required')
|
|
68
|
+
command.requiredOption('--idempotency-key <key>', 'Stable key; reuse it to replay instead of acting twice');
|
|
69
|
+
else if (op.idempotencyKey)
|
|
70
|
+
command.option('--idempotency-key <key>', 'Optional stable key; a retry with it returns the original result');
|
|
71
|
+
command.action(async (...args) => {
|
|
72
|
+
const options = args[op.pathParams.length];
|
|
73
|
+
const input = Object.fromEntries(op.pathParams.map((p, i) => [p, args[i]]));
|
|
74
|
+
for (const q of op.queryParams)
|
|
75
|
+
if (options[key(q)] !== undefined)
|
|
76
|
+
input[q] = options[key(q)];
|
|
77
|
+
if (options.bodyFile)
|
|
78
|
+
input.body = await readFunctionJson(options.bodyFile, BODY_LIMIT);
|
|
79
|
+
const headers = idempotencyHeaders(id, options.idempotencyKey);
|
|
80
|
+
const result = await (await dependencies.client()).request(id, input, { headers, signal: dependencies.signal });
|
|
81
|
+
dependencies.emit(result ?? { ok: true }, () => JSON.stringify(result ?? { ok: true }, null, 2));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function registerRelaysEnrollment(relays, client, emit) {
|
|
2
|
+
const enrollment = relays.command('enrollment').description('Explicit empty Relays account for the current verified human selection');
|
|
3
|
+
for (const action of ['get', 'activate'])
|
|
4
|
+
enrollment.command(action).allowExcessArguments(false)
|
|
5
|
+
.description(action === 'get' ? 'Read status without creating an account' : 'Activate once; read status after any unconfirmed response')
|
|
6
|
+
.action(async () => { const value = await (await client()).relays.enrollment[action](); emit(value, () => JSON.stringify(value, null, 2)); });
|
|
7
|
+
}
|
package/dist/runner.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { Command } from 'commander';
|
|
2
|
+
import type { Genesis } from '@onesi/genesis';
|
|
3
|
+
/** Register only Runner commands. The authenticated credential determines org and subject. */
|
|
4
|
+
export declare function registerRunner(program: Command, client: () => Promise<Genesis>, emit: (data: unknown, pretty: () => string) => void): void;
|
package/dist/runner.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** Register only Runner commands. The authenticated credential determines org and subject. */
|
|
2
|
+
export function registerRunner(program, client, emit) {
|
|
3
|
+
const runner = program.command('runner').description('Runner: agent task execution');
|
|
4
|
+
const output = (value) => emit(value, () => JSON.stringify(value, null, 2));
|
|
5
|
+
runner.command('permission-grant').description('Request only your current native human Sandbox execution permission; preserve operation/scope/expiry on retry')
|
|
6
|
+
.requiredOption('--organization <id>', 'current approved Life organization assertion')
|
|
7
|
+
.requiredOption('--project <id>', 'current approved project assertion')
|
|
8
|
+
.requiredOption('--workspace <id>', 'current approved workspace assertion')
|
|
9
|
+
.requiredOption('--operation-id <uuid>', 'stable canonical UUIDv4 for this immutable authorization operation')
|
|
10
|
+
.requiredOption('--request-id <uuid>', 'fresh canonical UUIDv4 correlation for this attempt')
|
|
11
|
+
.requiredOption('--expires-at-ms <ms>', 'exact retained future integer expiry, at most 30 minutes and current session/access expiry')
|
|
12
|
+
.action(async (o) => {
|
|
13
|
+
if (!/^[1-9]\d*$/.test(o.expiresAtMs) || !Number.isSafeInteger(Number(o.expiresAtMs)))
|
|
14
|
+
throw new TypeError('Runner permission expiry must be a positive safe integer literal');
|
|
15
|
+
const input = { schemaVersion: 1, organizationId: o.organization, projectId: o.project,
|
|
16
|
+
workspaceId: o.workspace, platformId: 'pf_console', fleetId: null, resourceEnvironment: 'sandbox',
|
|
17
|
+
operationId: o.operationId, requestId: o.requestId, expiresAtMs: Number(o.expiresAtMs) };
|
|
18
|
+
output(await (await client()).runner.permission.grant(input));
|
|
19
|
+
});
|
|
20
|
+
runner.command('catalog').description('Authorized workspaces and execution routes')
|
|
21
|
+
.action(async () => output(await (await client()).runner.catalog()));
|
|
22
|
+
const jobs = runner.command('jobs').description('Jobs belonging to your verified identity');
|
|
23
|
+
jobs.command('list', { isDefault: true }).description('Newest 100 jobs')
|
|
24
|
+
.action(async () => output(await (await client()).runner.jobs.list()));
|
|
25
|
+
jobs.command('start').description('Start a bounded task; refs must come from catalog')
|
|
26
|
+
.requiredOption('--task-id <id>', 'opaque task ID')
|
|
27
|
+
.requiredOption('--text <text>', 'task instructions (at most 32768 characters)')
|
|
28
|
+
.requiredOption('--workspace <ref>', 'authorized workspace key')
|
|
29
|
+
.requiredOption('--kind <kind>', 'external-harness or internal-model')
|
|
30
|
+
.requiredOption('--route <id>', 'authorized execution route')
|
|
31
|
+
.requiredOption('--idempotency-key <key>', 'stable key for retries of the same task')
|
|
32
|
+
.option('--duration-ms <ms>', 'maximum execution duration', '600000')
|
|
33
|
+
.option('--output-bytes <bytes>', 'maximum output size', '1048576')
|
|
34
|
+
.option('--max-events <count>', 'retained event limit', '200')
|
|
35
|
+
.action(async (o) => {
|
|
36
|
+
if (!['external-harness', 'internal-model'].includes(o.kind))
|
|
37
|
+
throw new TypeError('Runner kind must be external-harness or internal-model');
|
|
38
|
+
const body = {
|
|
39
|
+
task: { id: o.taskId, text: o.text }, workspaceRef: o.workspace,
|
|
40
|
+
executionRef: { kind: o.kind, routeId: o.route },
|
|
41
|
+
limits: { maxDurationMs: Number(o.durationMs), maxOutputBytes: Number(o.outputBytes), maxEvents: Number(o.maxEvents) },
|
|
42
|
+
idempotencyKey: o.idempotencyKey,
|
|
43
|
+
};
|
|
44
|
+
output(await (await client()).runner.jobs.start(body));
|
|
45
|
+
});
|
|
46
|
+
jobs.command('get <job_id>').description('Read the authoritative snapshot')
|
|
47
|
+
.action(async (id) => output(await (await client()).runner.jobs.get(id)));
|
|
48
|
+
jobs.command('events <job_id>').description('Read one event page; continue with nextAfterSeq while hasMore')
|
|
49
|
+
.option('--after-seq <seq>', 'exclusive event cursor', '0')
|
|
50
|
+
.action(async (id, o) => output(await (await client()).runner.jobs.events(id, Number(o.afterSeq))));
|
|
51
|
+
jobs.command('result <job_id>').description('Read the verified completed provider deliverable')
|
|
52
|
+
.action(async (id) => output(await (await client()).runner.jobs.result(id)));
|
|
53
|
+
jobs.command('cancel <job_id>').description('Request cancellation; then watch status and cleanupPending')
|
|
54
|
+
.action(async (id) => output(await (await client()).runner.jobs.cancel(id)));
|
|
55
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface SecretInputOptions {
|
|
2
|
+
file?: string;
|
|
3
|
+
stdin?: boolean;
|
|
4
|
+
}
|
|
5
|
+
export interface SecretInput {
|
|
6
|
+
name: string;
|
|
7
|
+
value: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
}
|
|
10
|
+
/** Read plaintext only from an explicitly selected file or stdin, never argv. */
|
|
11
|
+
export declare function readSecretInput(options: SecretInputOptions, limit: number): Promise<string>;
|
|
12
|
+
export declare function validateSecretValue(value: string): string;
|
|
13
|
+
export declare function validateSecret(input: unknown): SecretInput;
|
|
14
|
+
export declare function parseSecretBatch(text: string): SecretInput[];
|