@onesi/genesis-cli 0.0.1-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/README.md +64 -0
  2. package/bin/genesis.js +3 -0
  3. package/dist/auth.d.ts +30 -0
  4. package/dist/auth.js +273 -0
  5. package/dist/beacons.d.ts +3 -0
  6. package/dist/beacons.js +162 -0
  7. package/dist/device-login.d.ts +25 -0
  8. package/dist/device-login.js +173 -0
  9. package/dist/dials.d.ts +7 -0
  10. package/dist/dials.js +99 -0
  11. package/dist/fleets.d.ts +4 -0
  12. package/dist/fleets.js +102 -0
  13. package/dist/flocks.d.ts +9 -0
  14. package/dist/flocks.js +326 -0
  15. package/dist/function-input.d.ts +3 -0
  16. package/dist/function-input.js +49 -0
  17. package/dist/henry.d.ts +7 -0
  18. package/dist/henry.js +85 -0
  19. package/dist/hives-coverage.d.ts +12 -0
  20. package/dist/hives-coverage.js +116 -0
  21. package/dist/hives-marketplace.d.ts +10 -0
  22. package/dist/hives-marketplace.js +29 -0
  23. package/dist/hives-node-transport.d.ts +6 -0
  24. package/dist/hives-node-transport.js +150 -0
  25. package/dist/hives-operations.d.ts +10 -0
  26. package/dist/hives-operations.js +63 -0
  27. package/dist/hives-platform.d.ts +13 -0
  28. package/dist/hives-platform.js +133 -0
  29. package/dist/hives-resources.d.ts +9 -0
  30. package/dist/hives-resources.js +94 -0
  31. package/dist/iam-directory.d.ts +15 -0
  32. package/dist/iam-directory.js +71 -0
  33. package/dist/index.d.ts +23 -0
  34. package/dist/index.js +872 -0
  35. package/dist/lifeid-native.d.ts +24 -0
  36. package/dist/lifeid-native.js +113 -0
  37. package/dist/minds-owner.d.ts +3 -0
  38. package/dist/minds-owner.js +42 -0
  39. package/dist/minds.d.ts +3 -0
  40. package/dist/minds.js +125 -0
  41. package/dist/onboard/art.d.ts +50 -0
  42. package/dist/onboard/art.js +139 -0
  43. package/dist/onboard/brand.d.ts +56 -0
  44. package/dist/onboard/brand.js +134 -0
  45. package/dist/onboard/caps.d.ts +90 -0
  46. package/dist/onboard/caps.js +232 -0
  47. package/dist/onboard/encode.d.ts +23 -0
  48. package/dist/onboard/encode.js +235 -0
  49. package/dist/onboard/eye.d.ts +103 -0
  50. package/dist/onboard/eye.js +147 -0
  51. package/dist/onboard/index.d.ts +61 -0
  52. package/dist/onboard/index.js +562 -0
  53. package/dist/onboard/mcp.d.ts +35 -0
  54. package/dist/onboard/mcp.js +118 -0
  55. package/dist/onboard/raster.d.ts +43 -0
  56. package/dist/onboard/raster.js +253 -0
  57. package/dist/onboard/sound.d.ts +2 -0
  58. package/dist/onboard/sound.js +54 -0
  59. package/dist/onboard/state.d.ts +24 -0
  60. package/dist/onboard/state.js +48 -0
  61. package/dist/onboard/term.d.ts +69 -0
  62. package/dist/onboard/term.js +292 -0
  63. package/dist/onboard/ui.d.ts +97 -0
  64. package/dist/onboard/ui.js +615 -0
  65. package/dist/orgs.d.ts +16 -0
  66. package/dist/orgs.js +89 -0
  67. package/dist/output.d.ts +28 -0
  68. package/dist/output.js +46 -0
  69. package/dist/products.d.ts +18 -0
  70. package/dist/products.js +311 -0
  71. package/dist/relays-commands/certificates.d.ts +3 -0
  72. package/dist/relays-commands/certificates.js +20 -0
  73. package/dist/relays-commands/domains.d.ts +3 -0
  74. package/dist/relays-commands/domains.js +23 -0
  75. package/dist/relays-commands/index.d.ts +4 -0
  76. package/dist/relays-commands/index.js +10 -0
  77. package/dist/relays-commands/monitoring.d.ts +3 -0
  78. package/dist/relays-commands/monitoring.js +41 -0
  79. package/dist/relays-commands/telemetry.d.ts +3 -0
  80. package/dist/relays-commands/telemetry.js +28 -0
  81. package/dist/relays-commands/zones.d.ts +5 -0
  82. package/dist/relays-commands/zones.js +9 -0
  83. package/dist/relays-enrollment.d.ts +3 -0
  84. package/dist/relays-enrollment.js +7 -0
  85. package/dist/runner.d.ts +4 -0
  86. package/dist/runner.js +57 -0
  87. package/dist/secret-input.d.ts +14 -0
  88. package/dist/secret-input.js +85 -0
  89. package/dist/store.d.ts +39 -0
  90. package/dist/store.js +33 -0
  91. package/dist/wasm-input.d.ts +2 -0
  92. package/dist/wasm-input.js +31 -0
  93. package/package.json +54 -1
@@ -0,0 +1,29 @@
1
+ import { Option } from 'commander';
2
+ import { readFunctionJson } from './function-input.js';
3
+ function number(value) { if (value === undefined)
4
+ return undefined; if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)))
5
+ throw new Error('Invalid page option.'); return Number(value); }
6
+ /** Environment values belong in a bounded private JSON file, never command-line options. */
7
+ export function registerHivesMarketplace(hives, deps) {
8
+ const m = hives.command('marketplace').description('Existing Hives packages, publication and persisted installations');
9
+ const output = (data) => deps.emit(data, () => JSON.stringify(data, null, 2));
10
+ const read = deps.readRequest ?? readFunctionJson;
11
+ const api = async () => (await deps.client()).hives.marketplace;
12
+ m.command('list <project_id>').option('--query <value>').option('--category <value>').option('--free-only').option('--verified-only')
13
+ .addOption(new Option('--sort <field>').choices(['name', 'installs', 'stars', 'created', 'updated']))
14
+ .addOption(new Option('--order <direction>').choices(['asc', 'desc'])).option('--page <number>').option('--page-size <number>')
15
+ .action(async (project, o) => output(await (await api()).list(project, { q: o.query, category: o.category, free_only: o.freeOnly, verified_only: o.verifiedOnly, sort: o.sort, order: o.order, page: number(o.page), page_size: number(o.pageSize) })));
16
+ m.command('get <project_id> <slug>').option('--version <semver>').action(async (project, slug, o) => output(await (await api()).get(project, slug, { version: o.version })));
17
+ m.command('versions <project_id> <slug>').option('--page <number>').option('--page-size <number>').action(async (project, slug, o) => output(await (await api()).versions(project, slug, { page: number(o.page), page_size: number(o.pageSize) })));
18
+ for (const action of ['categories', 'featured'])
19
+ m.command(`${action} <project_id>`).action(async (project) => output(await (await api())[action](project)));
20
+ m.command('publish <project_id>').requiredOption('--request-file <path>', 'JSON publication with an owned uploaded module_hash and immutable version')
21
+ .action(async (project, o) => output(await (await api()).publish(project, await read(o.requestFile, 512 * 1024))));
22
+ for (const action of ['install', 'update'])
23
+ m.command(`${action} <project_id> <slug>`).requiredOption('--request-file <path>', 'JSON version and env_overrides; use {} for defaults')
24
+ .action(async (project, slug, o) => output(await (await api())[action](project, slug, await read(o.requestFile, 512 * 1024))));
25
+ m.command('installed <project_id>').option('--page <number>').option('--page-size <number>').action(async (project, o) => output(await (await api()).installed(project, { page: number(o.page), page_size: number(o.pageSize) })));
26
+ m.command('installation <project_id> <installation_id>').action(async (project, id) => output(await (await api()).getInstallation(project, id)));
27
+ for (const action of ['reconcile', 'uninstall'])
28
+ m.command(`${action} <project_id> <installation_id>`).action(async (project, id) => output(await (await api())[action](project, id)));
29
+ }
@@ -0,0 +1,6 @@
1
+ import { type GenesisOptions } from '@onesi/genesis';
2
+ export declare function closeHivesNodeTransport(): Promise<void>;
3
+ export declare function hivesOperationBudget(id: string): number;
4
+ /** Hives leaf transport. Caller fetch overrides stay authoritative; only native
5
+ * global fetch gets our Agent. Every operation retains an overall body deadline. */
6
+ export declare function createHivesNodeTransport(opts: GenesisOptions, callerSignal?: AbortSignal): typeof fetch;
@@ -0,0 +1,150 @@
1
+ import { Agent } from 'undici';
2
+ import { Genesis, operationTable } from '@onesi/genesis';
3
+ // Node's native fetch accepts a compatible per-request dispatcher. Never change
4
+ // the global dispatcher or the portable SDK: other products retain their transport.
5
+ const agentOptions = { headersTimeout: 340_000, bodyTimeout: 340_000, connect: { timeout: 5_000 }, connections: 4, pipelining: 1 };
6
+ let agent;
7
+ const nativeAgent = () => agent ??= new Agent(agentOptions);
8
+ export async function closeHivesNodeTransport() {
9
+ const current = agent;
10
+ agent = undefined;
11
+ await current?.destroy();
12
+ }
13
+ export function hivesOperationBudget(id) {
14
+ if (['hives_list_buckets', 'hives_create_bucket', 'hives_get_bucket', 'hives_delete_bucket', 'hives_list_objects', 'hives_upload_object', 'hives_upload_object_by_key', 'hives_delete_object', 'hives_delete_object_by_key', 'hives_download_object_by_key', 'hives_get_cors_config', 'hives_set_cors_config', 'hives_generate_presigned_url', 'hives_reconcile'].includes(id))
15
+ return 100_000;
16
+ if (id === 'hives_cancel_deployment' || id === 'hives_delete_deployment' || id === 'hives_stop_deployment')
17
+ return 330_000;
18
+ // A live SSE subscription is bounded by its caller's window or --follow limit (at most 300 s).
19
+ if (id === 'hives_stream_logs')
20
+ return 330_000;
21
+ if (['hives_create_deployment', 'hives_upload_deployment', 'hives_invoke_deployment', 'hives_invoke_function', 'hives_invoke_function_async'].includes(id))
22
+ return 40_000;
23
+ if (['hives_create_function', 'hives_update_function', 'hives_delete_function'].includes(id))
24
+ return 100_000;
25
+ return 20_000;
26
+ }
27
+ const escape = (value) => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
28
+ const templatePattern = (value) => new RegExp('^' + value.split(/(\{[^}]+\})/).map((part) => part.startsWith('{') ? '[^/]+' : escape(part)).join('') + '$');
29
+ /** Hives leaf transport. Caller fetch overrides stay authoritative; only native
30
+ * global fetch gets our Agent. Every operation retains an overall body deadline. */
31
+ export function createHivesNodeTransport(opts, callerSignal) {
32
+ const supplied = opts.fetch ?? globalThis.fetch;
33
+ const underlying = supplied.bind(globalThis);
34
+ const useNative = supplied === globalThis.fetch;
35
+ const resolver = new Genesis(opts);
36
+ const routes = Object.entries(operationTable)
37
+ .filter(([, op]) => op.service === 'hives' && !op.internal)
38
+ .flatMap(([id, op]) => {
39
+ const direct = resolver.routing === 'direct';
40
+ let base;
41
+ try {
42
+ base = new URL(direct ? (opts.upstreams?.hives ?? op.upstream.base) : resolver.baseUrl);
43
+ }
44
+ catch {
45
+ throw new Error('Invalid Hives service address.');
46
+ }
47
+ const path = direct ? op.upstream.path : op.path;
48
+ const prefix = base.pathname.replace(/\/+$/, '');
49
+ const paths = [path];
50
+ if (!direct && path.startsWith('/v1/hives/'))
51
+ paths.push(path.replace('/v1/hives/', '/hives/v1/'));
52
+ return paths.map((p) => ({ id, origin: base.origin, method: direct ? op.upstream.method : op.method, pattern: templatePattern(prefix + p) }));
53
+ });
54
+ return (async (input, init = {}) => {
55
+ let url;
56
+ try {
57
+ url = new URL(input instanceof Request ? input.url : String(input));
58
+ }
59
+ catch {
60
+ throw new Error('Invalid Hives request address.');
61
+ }
62
+ const method = (init.method ?? (input instanceof Request ? input.method : 'GET')).toUpperCase();
63
+ const route = routes.find((entry) => entry.origin === url.origin && entry.method === method && entry.pattern.test(url.pathname));
64
+ if (!route)
65
+ return underlying(input, init);
66
+ const controller = new AbortController();
67
+ let message = 'Hives transport failed. Refresh the resource and retry.';
68
+ const safeError = () => new Error(message);
69
+ const signals = [...new Set([callerSignal, init.signal, input instanceof Request ? input.signal : undefined].filter((signal) => !!signal))];
70
+ const canceled = () => {
71
+ message = 'Hives request canceled before completion. Refresh the resource before retrying.';
72
+ controller.abort();
73
+ };
74
+ for (const signal of signals)
75
+ signal.addEventListener('abort', canceled, { once: true });
76
+ const timer = setTimeout(() => {
77
+ message = 'Hives request deadline exceeded. Refresh the resource before retrying.';
78
+ controller.abort();
79
+ }, hivesOperationBudget(route.id));
80
+ timer.unref();
81
+ let abortReject = () => { };
82
+ let reader;
83
+ let bodyController;
84
+ const aborted = () => {
85
+ abortReject(safeError());
86
+ bodyController?.error(safeError());
87
+ void reader?.cancel().catch(() => { });
88
+ cleanup();
89
+ };
90
+ controller.signal.addEventListener('abort', aborted, { once: true });
91
+ const abortPromise = new Promise((_, reject) => { abortReject = reject; });
92
+ void abortPromise.catch(() => { });
93
+ const cleanup = () => {
94
+ clearTimeout(timer);
95
+ for (const signal of signals)
96
+ signal.removeEventListener('abort', canceled);
97
+ controller.signal.removeEventListener('abort', aborted);
98
+ };
99
+ if (signals.some((signal) => signal.aborted))
100
+ canceled();
101
+ try {
102
+ if (controller.signal.aborted)
103
+ throw safeError();
104
+ const requestInit = {
105
+ ...init, signal: controller.signal, cache: 'no-store',
106
+ redirect: init.redirect === 'manual' ? 'manual' : 'error',
107
+ ...(useNative ? { dispatcher: nativeAgent() } : {}),
108
+ };
109
+ const pending = underlying(input, requestInit);
110
+ // A caller transport may ignore AbortSignal. Reject on our budget anyway,
111
+ // and discard any late response rather than presenting an acknowledgement.
112
+ void pending.then((response) => { if (controller.signal.aborted)
113
+ void response.body?.cancel().catch(() => { }); }, () => { });
114
+ const response = await Promise.race([pending, abortPromise]);
115
+ if (controller.signal.aborted)
116
+ throw safeError();
117
+ if (!response.body) {
118
+ cleanup();
119
+ return response;
120
+ }
121
+ reader = response.body.getReader();
122
+ const body = new ReadableStream({
123
+ start(value) { bodyController = value; },
124
+ async pull(value) {
125
+ try {
126
+ const next = await reader.read();
127
+ if (controller.signal.aborted)
128
+ return;
129
+ if (next.done) {
130
+ cleanup();
131
+ value.close();
132
+ }
133
+ else
134
+ value.enqueue(next.value);
135
+ }
136
+ catch {
137
+ cleanup();
138
+ value.error(safeError());
139
+ }
140
+ },
141
+ async cancel() { cleanup(); await reader.cancel().catch(() => { }); },
142
+ });
143
+ return new Response(body, { status: response.status, statusText: response.statusText, headers: response.headers });
144
+ }
145
+ catch {
146
+ cleanup();
147
+ throw safeError();
148
+ }
149
+ });
150
+ }
@@ -0,0 +1,10 @@
1
+ import { Command } from 'commander';
2
+ import type { Genesis } from '@onesi/genesis';
3
+ type Deps = {
4
+ client: () => Promise<Genesis>;
5
+ emit: (data: unknown, pretty: () => string) => void;
6
+ readRequest?: (path: string, limit: number) => Promise<Record<string, unknown>>;
7
+ line?: (text: string) => void;
8
+ };
9
+ export declare function registerHivesOperations(hives: Command, deps: Deps): void;
10
+ export {};
@@ -0,0 +1,63 @@
1
+ import { registerHivesResources } from './hives-resources.js';
2
+ import { registerHivesPlatform } from './hives-platform.js';
3
+ import { Option } from 'commander';
4
+ import { readFunctionJson } from './function-input.js';
5
+ function metricsOptions(command) {
6
+ return command.option('--start <time>', 'inclusive UTC timestamp').option('--end <time>', 'exclusive UTC timestamp')
7
+ .addOption(new Option('--resolution <value>', 'time bucket').choices(['minute', 'fiveminutes', 'fifteenminutes', 'hour', 'day']))
8
+ .addOption(new Option('--aggregation <value>', 'supported aggregation').choices(['sum', 'avg', 'min', 'max', 'count', 'p50', 'p90', 'p95', 'p99']));
9
+ }
10
+ const integer = (value, name, max) => {
11
+ if (value === undefined)
12
+ return undefined;
13
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < 1 || Number(value) > max) {
14
+ throw new Error(`${name} must be an integer from 1 to ${max}.`);
15
+ }
16
+ return Number(value);
17
+ };
18
+ const paging = (command) => command.option('--page <number>').option('--per-page <number>');
19
+ const pagination = (o) => ({ page: integer(o.page, '--page', 10000), per_page: integer(o.perPage, '--per-page', 100) });
20
+ export function registerHivesOperations(hives, deps) {
21
+ registerHivesResources(hives, deps);
22
+ registerHivesPlatform(hives, { client: deps.client, emit: deps.emit, line: deps.line });
23
+ const output = (data) => deps.emit(data, () => JSON.stringify(data, null, 2));
24
+ const read = deps.readRequest ?? readFunctionJson;
25
+ const settings = hives.command('settings').description('Stored project preferences and actual engine capabilities');
26
+ settings.command('get <project_id>').action(async (project) => output(await (await deps.client()).hives.settings.get(project)));
27
+ settings.command('update <project_id>').requiredOption('--request-file <path>', 'JSON settings patch')
28
+ .action(async (project, o) => output(await (await deps.client()).hives.settings.update(project, await read(o.requestFile, 64 * 1024))));
29
+ const crons = hives.command('crons').description('Waggle schedules and persisted run outcomes');
30
+ paging(crons.command('list <project_id>')).action(async (project, o) => output(await (await deps.client()).hives.crons.list(project, pagination(o))));
31
+ crons.command('get <project_id> <cron_id>').action(async (project, cron) => output(await (await deps.client()).hives.crons.get(project, cron)));
32
+ crons.command('create <project_id>').requiredOption('--request-file <path>', 'JSON name, schedule, handler, timezone and enabled preference')
33
+ .action(async (project, o) => {
34
+ const body = await read(o.requestFile, 64 * 1024);
35
+ if (typeof body.name !== 'string' || typeof body.schedule !== 'string' || typeof body.handler !== 'string')
36
+ throw new Error('Cron JSON requires name, schedule and handler strings.');
37
+ output(await (await deps.client()).hives.crons.create(project, { ...body, name: body.name, schedule: body.schedule, handler: body.handler }));
38
+ });
39
+ crons.command('update <project_id> <cron_id>').requiredOption('--request-file <path>', 'JSON schedule patch')
40
+ .action(async (project, cron, o) => output(await (await deps.client()).hives.crons.update(project, cron, await read(o.requestFile, 64 * 1024))));
41
+ for (const action of ['delete', 'pause', 'resume', 'trigger']) {
42
+ crons.command(`${action} <project_id> <cron_id>`)
43
+ .description(action === 'trigger' ? 'Admit one run; inspect runs for completion. Does not retry.' : `${action} an owned schedule`)
44
+ .action(async (project, cron) => output(await (await deps.client()).hives.crons[action](project, cron)));
45
+ }
46
+ paging(crons.command('runs <project_id> <cron_id>')).addOption(new Option('--status <state>', 'persisted run status').choices(['pending', 'running', 'success', 'failed', 'timeout', 'cancelled']))
47
+ .option('--since <time>').option('--until <time>')
48
+ .action(async (project, cron, o) => output(await (await deps.client()).hives.crons.runs(project, cron, { ...pagination(o), status: o.status, since: o.since, until: o.until })));
49
+ const metrics = hives.command('metrics').description('Retained completed API observations; unavailable measurements remain null');
50
+ for (const kind of ['project', 'requests', 'latency', 'errors', 'resources']) {
51
+ metricsOptions(metrics.command(`${kind} <project_id>`))
52
+ .action(async (project, q) => output(await (await deps.client()).hives.metrics[kind](project, q)));
53
+ }
54
+ metrics.command('dashboard <project_id>').addOption(new Option('--period <value>').choices(['1h', '24h', '7d', '30d']))
55
+ .action(async (project, q) => output(await (await deps.client()).hives.metrics.dashboard(project, q)));
56
+ for (const kind of ['function', 'invocations']) {
57
+ metricsOptions(metrics.command(`${kind} <project_id> <function_id>`))
58
+ .action(async (project, id, q) => output(await (await deps.client()).hives.metrics[kind](project, id, q)));
59
+ }
60
+ metricsOptions(metrics.command('deployment <project_id> <deployment_id>'))
61
+ .action(async (project, id, q) => output(await (await deps.client()).hives.metrics.deployment(project, id, q)));
62
+ metrics.command('realtime <project_id>').action(async (project) => output(await (await deps.client()).hives.metrics.realtime(project)));
63
+ }
@@ -0,0 +1,13 @@
1
+ import { Command } from 'commander';
2
+ import type { Genesis } from '@onesi/genesis';
3
+ type Deps = {
4
+ client: () => Promise<Genesis>;
5
+ emit: (data: unknown, pretty: () => string) => void;
6
+ /** Print one line as it arrives (live log streams). */
7
+ line?: (text: string) => void;
8
+ readText?: (path: string, limit: number) => Promise<Uint8Array>;
9
+ writeNew?: (path: string, bytes: Uint8Array) => Promise<void>;
10
+ };
11
+ /** Hives logs (query and live SSE), custom domains, regions, the Hives identity, Hives fleets and remote sessions/SFTP. */
12
+ export declare function registerHivesPlatform(hives: Command, deps: Deps): void;
13
+ export {};
@@ -0,0 +1,133 @@
1
+ import { readFile, stat, writeFile } from 'node:fs/promises';
2
+ const SFTP_BYTES = 10 * 1024 * 1024;
3
+ const MANIFEST_BYTES = 64 * 1024;
4
+ function integer(value, name, min, max) {
5
+ if (value === undefined)
6
+ return undefined;
7
+ if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < min || Number(value) > max) {
8
+ throw new Error(`${name} must be an integer from ${min} to ${max}.`);
9
+ }
10
+ return Number(value);
11
+ }
12
+ async function readBounded(path, limit) {
13
+ const metadata = await stat(path);
14
+ if (!metadata.isFile() || metadata.size > limit)
15
+ throw new Error(`Choose a regular file of at most ${limit} bytes.`);
16
+ const bytes = await readFile(path);
17
+ if (bytes.byteLength > limit)
18
+ throw new Error(`File exceeds ${limit} bytes.`);
19
+ return Uint8Array.from(bytes);
20
+ }
21
+ /** Hives logs (query and live SSE), custom domains, regions, the Hives identity, Hives fleets and remote sessions/SFTP. */
22
+ export function registerHivesPlatform(hives, deps) {
23
+ const output = (data) => deps.emit(data, () => JSON.stringify(data, null, 2));
24
+ const line = deps.line ?? ((text) => process.stdout.write(text + '\n'));
25
+ const read = deps.readText ?? readBounded;
26
+ const writeNew = deps.writeNew ?? ((path, bytes) => writeFile(path, bytes, { flag: 'wx', mode: 0o600 }));
27
+ const api = async () => (await deps.client()).hives;
28
+ const logs = hives.command('logs').description('Project logs: retained query and live Server-Sent Events');
29
+ logs.command('query <project_id>').option('--level <level>').option('--source <source>').option('--search <text>')
30
+ .option('--start <time>', 'inclusive UTC timestamp').option('--end <time>', 'exclusive UTC timestamp').option('--request-id <id>')
31
+ .option('--page <number>').option('--per-page <number>')
32
+ .action(async (project, o) => output(await (await api()).logs.query(project, { level: o.level, source: o.source, search: o.search, start: o.start, end: o.end, request_id: o.requestId,
33
+ page: integer(o.page, '--page', 1, 10000), per_page: integer(o.perPage, '--per-page', 1, 100) })));
34
+ logs.command('stream <project_id>').description('Live events. Bounded by --max-events and --seconds; --follow prints each event as it arrives')
35
+ .option('--level <level>').option('--source <source>').option('--heartbeat', 'ask the server for heartbeat events')
36
+ .option('--max-events <number>', 'stop after this many events (1-10000; default 100, unlimited with --follow)')
37
+ .option('--seconds <number>', 'stop after this many seconds (1-300; default 10, or 300 with --follow)')
38
+ .option('--follow', 'print one JSON event per line as it arrives')
39
+ .action(async (project, o) => {
40
+ const q = { level: o.level, source: o.source, ...(o.heartbeat ? { heartbeat: true } : {}) };
41
+ const maxEvents = integer(o.maxEvents, '--max-events', 1, 10000);
42
+ const seconds = integer(o.seconds, '--seconds', 1, 300) ?? (o.follow ? 300 : 10);
43
+ const client = await api();
44
+ if (!o.follow) {
45
+ output(await client.logs.window(project, q, { maxEvents: maxEvents ?? 100, seconds }));
46
+ return;
47
+ }
48
+ const stop = new AbortController();
49
+ const timer = setTimeout(() => stop.abort(), seconds * 1000);
50
+ let count = 0;
51
+ try {
52
+ for await (const event of client.logs.stream(project, q, { signal: stop.signal })) {
53
+ line(JSON.stringify(event));
54
+ count++;
55
+ if (maxEvents !== undefined && count >= maxEvents)
56
+ break;
57
+ }
58
+ }
59
+ catch (error) {
60
+ // Reaching --seconds closes the connection; that is the bounded end, not a failure.
61
+ if (!stop.signal.aborted)
62
+ throw error;
63
+ }
64
+ finally {
65
+ clearTimeout(timer);
66
+ }
67
+ });
68
+ const domains = hives.command('domains').description('Custom domains for a project: add, DNS verification and certificate provisioning');
69
+ domains.command('list <project_id>').option('--page <number>').option('--per-page <number>')
70
+ .action(async (project, o) => output(await (await api()).domains.list(project, { page: integer(o.page, '--page', 1, 10000), per_page: integer(o.perPage, '--per-page', 1, 100) })));
71
+ domains.command('add <project_id> <domain>').action(async (project, domain) => output(await (await api()).domains.add(project, { domain })));
72
+ domains.command('verify <project_id> <domain_id>').description('Check the DNS verification record now')
73
+ .action(async (project, id) => output(await (await api()).domains.verify(project, id)));
74
+ domains.command('provision-ssl <project_id> <domain_id>').description('Request a certificate for a verified domain')
75
+ .action(async (project, id) => output(await (await api()).domains.provisionSsl(project, id)));
76
+ hives.command('regions').description('Regions Hives can place projects in').command('list')
77
+ .action(async () => output(await (await api()).regions.list()));
78
+ hives.command('auth').description('The identity Hives sees for this credential').command('me')
79
+ .action(async () => output(await (await api()).auth.me()));
80
+ const fleets = hives.command('fleets').description('Hives fleets deployed from a map-fleet.toml manifest');
81
+ fleets.command('list').action(async () => output(await (await api()).fleets.list()));
82
+ fleets.command('deploy').requiredOption('--manifest-file <path>', 'map-fleet.toml manifest (at most 64 KiB)')
83
+ .action(async (o) => {
84
+ const manifestToml = new TextDecoder('utf-8', { fatal: true }).decode(await read(o.manifestFile, MANIFEST_BYTES));
85
+ output(await (await api()).fleets.deploy({ manifestToml }));
86
+ });
87
+ fleets.command('status <fleet_id>').action(async (id) => output(await (await api()).fleets.status(id)));
88
+ fleets.command('teardown <fleet_id>').action(async (id) => {
89
+ const result = await (await api()).fleets.teardown(id);
90
+ output(result ?? { torn_down: id });
91
+ });
92
+ const remote = hives.command('remote').description('Remote SSH, terminal and debug sessions, and SFTP, for a project target');
93
+ const sessions = remote.command('sessions').description('Open remote sessions');
94
+ sessions.command('list <project_id>').option('--type <session_type>', 'ssh, terminal or debug').option('--target <target_id>')
95
+ .action(async (project, o) => output(await (await api()).remote.sessions.list(project, { session_type: o.type, target_id: o.target })));
96
+ sessions.command('close <project_id> <session_id>').action(async (project, id) => output(await (await api()).remote.sessions.close(project, id)));
97
+ remote.command('ssh <project_id> <target_id>').requiredOption('--public-key-file <path>', 'your SSH public key (never a private key)')
98
+ .option('--ttl-secs <seconds>', 'session lifetime (60-86400; default 3600)')
99
+ .action(async (project, target, o) => {
100
+ const public_key = new TextDecoder('utf-8', { fatal: true }).decode(await read(o.publicKeyFile, 16 * 1024)).trim();
101
+ if (/PRIVATE KEY/.test(public_key) || !/^(ssh-|ecdsa-|sk-)/.test(public_key))
102
+ throw new Error('Provide an OpenSSH public key file (for example ~/.ssh/id_ed25519.pub).');
103
+ output(await (await api()).remote.ssh(project, { target_id: target, public_key, ttl_secs: integer(o.ttlSecs, '--ttl-secs', 60, 86400) }));
104
+ });
105
+ remote.command('terminal <project_id> <target_id>').option('--shell <path>').option('--cols <number>').option('--rows <number>')
106
+ .action(async (project, target, o) => output(await (await api()).remote.terminal(project, { target_id: target, shell: o.shell, cols: integer(o.cols, '--cols', 1, 1000), rows: integer(o.rows, '--rows', 1, 1000) })));
107
+ remote.command('debug <project_id> <target_id>').option('--protocol <protocol>', 'dap or chrome-devtools')
108
+ .action(async (project, target, o) => output(await (await api()).remote.debug(project, { target_id: target, protocol: o.protocol })));
109
+ const sftp = remote.command('sftp').description('Files on a remote target');
110
+ sftp.command('ls <project_id> <target_id>').option('--path <path>')
111
+ .action(async (project, target, o) => output(await (await api()).remote.sftp.list(project, target, { path: o.path })));
112
+ sftp.command('read <project_id> <target_id> <path>').option('--output-file <path>', 'write the decoded content_base64 to a new private file')
113
+ .action(async (project, target, path, o) => {
114
+ const result = await (await api()).remote.sftp.read(project, target, path);
115
+ if (!o.outputFile) {
116
+ output(result);
117
+ return;
118
+ }
119
+ const encoded = result?.content_base64;
120
+ if (typeof encoded !== 'string' || encoded.length % 4 !== 0 || !/^[A-Za-z0-9+/]*={0,2}$/.test(encoded))
121
+ throw new Error('Hives returned no valid content_base64 for this file.');
122
+ const decoded = Buffer.from(encoded, 'base64');
123
+ if (decoded.toString('base64') !== encoded)
124
+ throw new Error('Hives returned no valid content_base64 for this file.');
125
+ const bytes = Uint8Array.from(decoded);
126
+ await writeNew(o.outputFile, bytes);
127
+ output({ saved: true, bytes: bytes.byteLength });
128
+ });
129
+ sftp.command('write <project_id> <target_id> <path>').requiredOption('--file <local_path>', 'local file to upload (at most 10 MiB)')
130
+ .action(async (project, target, path, o) => output(await (await api()).remote.sftp.write(project, target, { path, content_base64: Buffer.from(await read(o.file, SFTP_BYTES)).toString('base64') })));
131
+ sftp.command('delete <project_id> <target_id> <path>')
132
+ .action(async (project, target, path) => output(await (await api()).remote.sftp.delete(project, target, { path })));
133
+ }
@@ -0,0 +1,9 @@
1
+ import { Command } from 'commander';
2
+ import type { Genesis } from '@onesi/genesis';
3
+ type Deps = {
4
+ client: () => Promise<Genesis>;
5
+ emit: (data: unknown, pretty: () => string) => void;
6
+ readRequest?: (path: string, limit: number) => Promise<Record<string, unknown>>;
7
+ };
8
+ export declare function registerHivesResources(hives: Command, deps: Deps): void;
9
+ export {};
@@ -0,0 +1,94 @@
1
+ import { registerHivesMarketplace } from './hives-marketplace.js';
2
+ import { createReadStream } from 'node:fs';
3
+ import { stat, writeFile } from 'node:fs/promises';
4
+ import { readFunctionJson } from './function-input.js';
5
+ async function bytes(path) {
6
+ const metadata = await stat(path);
7
+ if (!metadata.isFile() || metadata.size > 10 * 1024 * 1024)
8
+ throw new Error('Choose a regular file of at most 10 MiB.');
9
+ const stream = createReadStream(path);
10
+ const chunks = [];
11
+ let total = 0;
12
+ try {
13
+ for await (const chunk of stream) {
14
+ const value = Buffer.from(chunk);
15
+ total += value.byteLength;
16
+ if (total > 10 * 1024 * 1024)
17
+ throw new Error('Object file exceeds 10 MiB.');
18
+ chunks.push(value);
19
+ }
20
+ }
21
+ finally {
22
+ stream.destroy();
23
+ }
24
+ return Uint8Array.from(Buffer.concat(chunks, total));
25
+ }
26
+ function number(value, min, max) { if (value === undefined)
27
+ return undefined; if (!/^\d+$/.test(value) || !Number.isSafeInteger(Number(value)) || Number(value) < min || Number(value) > max)
28
+ throw new Error('Invalid bounded numeric option.'); return Number(value); }
29
+ export function registerHivesResources(hives, deps) {
30
+ registerHivesMarketplace(hives, deps);
31
+ const output = (data) => deps.emit(data, () => JSON.stringify(data, null, 2));
32
+ const read = deps.readRequest ?? readFunctionJson;
33
+ const cells = hives.command('cells').description('Owned GCS buckets and objects; uncertain mutations require readback/reconciliation');
34
+ cells.command('list <project_id>').option('--page <number>').option('--per-page <number>').action(async (project, o) => output(await (await deps.client()).hives.cells.list(project, { page: number(o.page, 1, 10000), per_page: number(o.perPage, 1, 100) })));
35
+ cells.command('get <project_id> <bucket_id>').action(async (project, id) => output(await (await deps.client()).hives.cells.get(project, id)));
36
+ cells.command('create <project_id>').requiredOption('--request-file <path>', 'JSON name, region, visibility and CORS').action(async (project, o) => output(await (await deps.client()).hives.cells.create(project, await read(o.requestFile, 512 * 1024))));
37
+ for (const action of ['remove', 'reconcile'])
38
+ cells.command(`${action} <project_id> <bucket_id>`).action(async (project, id) => output(await (await deps.client()).hives.cells[action](project, id)));
39
+ const cors = cells.command('cors');
40
+ cors.command('get <project_id> <bucket_id>').action(async (project, id) => output(await (await deps.client()).hives.cells.cors.get(project, id)));
41
+ cors.command('set <project_id> <bucket_id>').requiredOption('--request-file <path>', 'JSON object with rules array').action(async (project, id, o) => { const body = await read(o.requestFile, 512 * 1024); if (!Array.isArray(body.rules))
42
+ throw new Error('CORS JSON requires rules.'); output(await (await deps.client()).hives.cells.cors.set(project, id, body.rules)); });
43
+ const objects = cells.command('objects');
44
+ objects.command('list <project_id> <bucket_id>').option('--prefix <value>').option('--delimiter <value>').option('--continuation-token <value>').option('--max-keys <number>').action(async (project, id, o) => output(await (await deps.client()).hives.cells.objects.list(project, id, { prefix: o.prefix, delimiter: o.delimiter, continuation_token: o.continuationToken, max_keys: number(o.maxKeys, 1, 1000) })));
45
+ for (const action of ['put', 'put-flat'])
46
+ objects.command(`${action} <project_id> <bucket_id> <key>`).requiredOption('--file <path>', 'Exact binary bytes up to 10 MiB').option('--content-type <value>').action(async (project, id, key, o) => { const client = await deps.client(); output(await client.hives.cells.objects[action === 'put' ? 'put' : 'putFlat'](project, id, key, await bytes(o.file), { content_type: o.contentType })); });
47
+ for (const action of ['remove', 'remove-flat'])
48
+ objects.command(`${action} <project_id> <bucket_id> <key>`).action(async (project, id, key) => { await (await deps.client()).hives.cells.objects[action === 'remove' ? 'remove' : 'removeFlat'](project, id, key); output({ acknowledged: true }); });
49
+ objects.command('download <project_id> <bucket_id> <key>').requiredOption('--output-file <path>', 'Create a new file; existing files are never overwritten').action(async (project, id, key, o) => { const response = await (await deps.client()).hives.cells.objects.download(project, id, key); const data = new Uint8Array(await response.arrayBuffer()); await writeFile(o.outputFile, data, { flag: 'wx', mode: 0o600 }); output({ saved: true, bytes: data.byteLength }); });
50
+ cells.command('presign <project_id> <bucket_id>').requiredOption('--request-file <path>', 'Explicit GET/PUT capability request; PUT requires exact content_length').action(async (project, id, o) => output(await (await deps.client()).hives.cells.presign(project, id, await read(o.requestFile, 64 * 1024))));
51
+ const queues = hives.command('queues').description('Buzz durable messages and possessed temporary delivery receipts');
52
+ const consumer = queues.command('consumer').description('Persisted standard-queue function binding; inspect state after an unknown mutation outcome');
53
+ for (const action of ['get', 'remove'])
54
+ consumer.command(`${action} <project_id> <queue_id>`).action(async (project, id) => output(await (await deps.client()).hives.queues.consumer[action](project, id)));
55
+ consumer.command('configure <project_id> <queue_id>').requiredOption('--request-file <path>', 'Exact existing queue delivery settings and owned active function').action(async (project, id, o) => output(await (await deps.client()).hives.queues.consumer.configure(project, id, await read(o.requestFile, 4096))));
56
+ for (const action of ['list'])
57
+ queues.command(`${action} <project_id>`).action(async (project) => output(await (await deps.client()).hives.queues[action](project)));
58
+ for (const action of ['get', 'stats', 'consumers', 'pause', 'resume', 'purge', 'remove', 'dead-letter-stats', 'purge-dead-letters']) {
59
+ const names = { 'dead-letter-stats': 'deadLetterStats', 'purge-dead-letters': 'purgeDeadLetters' };
60
+ queues.command(`${action} <project_id> <queue_id>`).action(async (project, id) => { const client = await deps.client(); const method = action in names ? names[action] : action; output(await client.hives.queues[method](project, id)); });
61
+ }
62
+ queues.command('create <project_id>').requiredOption('--request-file <path>', 'Queue name, standard/dead_letter/fifo type and optional delivery settings').action(async (project, o) => output(await (await deps.client()).hives.queues.create(project, await read(o.requestFile, 512 * 1024))));
63
+ queues.command('configure <project_id> <queue_id>').requiredOption('--request-file <path>').action(async (project, id, o) => output(await (await deps.client()).hives.queues.configure(project, id, await read(o.requestFile, 512 * 1024))));
64
+ queues.command('send <project_id> <queue_id>').requiredOption('--request-file <path>', 'Exactly body or body_base64; decoded body 256 KiB. FIFO retains its separate message_group_id and deduplication_id contract').option('--idempotency-key <key>', 'Persist the original key for a funded standard-queue send; never change it after uncertainty').action(async (project, id, o) => output(await (await deps.client()).hives.queues.send(project, id, await read(o.requestFile, 512 * 1024), { idempotencyKey: o.idempotencyKey })));
65
+ queues.command('operations').description('Read original queue operation status without sending another message').command('get <project_id> <queue_id> <operation_id>').action(async (project, id, operation) => output(await (await deps.client()).hives.queues.operations.get(project, id, operation)));
66
+ queues.command('receive <project_id> <queue_id>').option('--max-messages <number>').option('--wait-seconds <number>').action(async (project, id, o) => output(await (await deps.client()).hives.queues.receive(project, id, { maxMessages: number(o.maxMessages, 1, 10), waitSeconds: number(o.waitSeconds, 0, 10) })));
67
+ for (const action of ['ack', 'nack'])
68
+ queues.command(`${action} <project_id> <queue_id> <message_id>`).requiredOption('--receipt-file <path>', 'JSON receiptHandle; not a credential/header/query').action(async (project, id, message, o) => { const body = await read(o.receiptFile, 4096); if (typeof body.receiptHandle !== 'string')
69
+ throw new Error('Receipt JSON requires receiptHandle.'); output(await (await deps.client()).hives.queues[action](project, id, message, body.receiptHandle)); });
70
+ queues.command('dead-letters <project_id> <queue_id>').option('--limit <number>').option('--offset <number>').action(async (project, id, o) => output(await (await deps.client()).hives.queues.deadLetters(project, id, { limit: number(o.limit, 1, 10), offset: number(o.offset, 0, 8192) })));
71
+ for (const action of ['replay', 'delete-dead-letter'])
72
+ queues.command(`${action} <project_id> <queue_id> <message_id>`).action(async (project, id, message) => output(await (await deps.client()).hives.queues[action === 'replay' ? 'replay' : 'deleteDeadLetter'](project, id, message)));
73
+ const topics = hives.command('topics').description('Owned Buzz topics; publication persists deliveries, never retries an unknown outcome');
74
+ topics.command('list <project_id>').action(async (project) => output(await (await deps.client()).hives.topics.list(project)));
75
+ for (const action of ['get', 'remove'])
76
+ topics.command(`${action} <project_id> <topic_id>`).action(async (project, id) => output(await (await deps.client()).hives.topics[action](project, id)));
77
+ topics.command('create <project_id>').requiredOption('--request-file <path>', 'JSON topic name').action(async (project, o) => output(await (await deps.client()).hives.topics.create(project, await read(o.requestFile, 4096))));
78
+ topics.command('publish <project_id> <topic_id>').requiredOption('--request-file <path>', 'Exactly body or body_base64 plus optional string attributes; no automatic retry').action(async (project, id, o) => output(await (await deps.client()).hives.topics.publish(project, id, await read(o.requestFile, 512 * 1024))));
79
+ const subscriptions = topics.command('subscriptions').description('Same-project standard queue destinations; removing a subscription retains queued messages');
80
+ subscriptions.command('list <project_id> <topic_id>').action(async (project, id) => output(await (await deps.client()).hives.topics.subscriptions.list(project, id)));
81
+ subscriptions.command('add <project_id> <topic_id> <queue_id>').action(async (project, id, queue) => output(await (await deps.client()).hives.topics.subscriptions.add(project, id, queue)));
82
+ subscriptions.command('remove <project_id> <topic_id> <subscription_id>').action(async (project, id, subscription) => output(await (await deps.client()).hives.topics.subscriptions.remove(project, id, subscription)));
83
+ const volumes = hives.command('volumes').description('Persistent local storage; refresh saved state after an uncertain acknowledgement');
84
+ volumes.command('list <project_id>').option('--page <number>').option('--per-page <number>').action(async (project, o) => output(await (await deps.client()).hives.volumes.list(project, { page: number(o.page, 1, 10000), per_page: number(o.perPage, 1, 100) })));
85
+ for (const action of ['get', 'unmount', 'remove'])
86
+ volumes.command(`${action} <project_id> <volume_id>`).action(async (project, id) => output(await (await deps.client()).hives.volumes[action](project, id)));
87
+ volumes.command('create <project_id>').requiredOption('--request-file <path>', 'JSON name and size_mib (32–1024); local read_write_once only').action(async (project, o) => output(await (await deps.client()).hives.volumes.create(project, await read(o.requestFile, 4096))));
88
+ volumes.command('mount <project_id> <volume_id>').requiredOption('--request-file <path>', 'JSON target_id, mount_path and explicit read_only; preserve these values when reconnecting').action(async (project, id, o) => output(await (await deps.client()).hives.volumes.mount(project, id, await read(o.requestFile, 4096))));
89
+ const runtime = hives.command('runtimes').description('Compiled serving implementation metadata; not available capacity');
90
+ runtime.command('list').action(async () => output(await (await deps.client()).hives.runtimes.list()));
91
+ runtime.command('project <project_id>').action(async (project) => output(await (await deps.client()).hives.runtimes.project(project)));
92
+ runtime.command('get <name>').action(async (name) => output(await (await deps.client()).hives.runtimes.get(name)));
93
+ runtime.command('select').requiredOption('--wasi <value>').requiredOption('--runner <value>').requiredOption('--artifact <value>').action(async (input) => output(await (await deps.client()).hives.runtimes.select(input)));
94
+ }
@@ -0,0 +1,15 @@
1
+ import type { Command } from 'commander';
2
+ import type { Genesis } from '@onesi/genesis';
3
+ type Dependencies = {
4
+ client: () => Promise<Genesis>;
5
+ orgId: (client: Genesis) => Promise<string>;
6
+ emit: (data: unknown, pretty: () => string) => void;
7
+ };
8
+ /**
9
+ * `genesis projects` and `genesis workspaces`: the IAM organization directory of the org you are
10
+ * signed in to (or --org). IAM accepts only a One account login; API keys get IAM_HUMAN_SESSION_REQUIRED.
11
+ * Writes need iam:write and owner/admin authority in the organization. The gateway binds the organization
12
+ * context (Auth organization-context exchange) itself; this CLI sends one request and changes no session or context.
13
+ */
14
+ export declare function registerIamDirectory(program: Command, dependencies: Dependencies): void;
15
+ export {};
@@ -0,0 +1,71 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { table } from './output.js';
3
+ const KEY = ['--idempotency-key <key>', 'Reuse the key from an uncertain earlier attempt to replay it; otherwise a new one is minted'];
4
+ const rows = (r) => (r?.items ?? []);
5
+ /**
6
+ * `genesis projects` and `genesis workspaces`: the IAM organization directory of the org you are
7
+ * signed in to (or --org). IAM accepts only a One account login; API keys get IAM_HUMAN_SESSION_REQUIRED.
8
+ * Writes need iam:write and owner/admin authority in the organization. The gateway binds the organization
9
+ * context (Auth organization-context exchange) itself; this CLI sends one request and changes no session or context.
10
+ */
11
+ export function registerIamDirectory(program, dependencies) {
12
+ const { client, orgId, emit } = dependencies;
13
+ const scope = async () => { const gen = await client(); return { gen, org: await orgId(gen) }; };
14
+ const projects = program.command('projects').description('Projects in your organization (One account login; writes need owner or admin)');
15
+ projects.command('list', { isDefault: true }).action(async () => {
16
+ const { gen, org } = await scope();
17
+ const r = await gen.projects.list(org);
18
+ emit(r, () => table(rows(r), ['id', 'name', 'slug']));
19
+ });
20
+ projects.command('get <project_id>').action(async (id) => {
21
+ const { gen, org } = await scope();
22
+ const r = await gen.projects.get(org, id);
23
+ emit(r, () => table([r], ['id', 'name', 'slug', 'organization_id']));
24
+ });
25
+ projects.command('create <name>').description('Create a project').requiredOption('--slug <slug>', 'unique within the organization').option(...KEY)
26
+ .action(async (name, o) => {
27
+ const { gen, org } = await scope();
28
+ const r = await gen.projects.create(org, { name, slug: o.slug }, o.idempotencyKey ?? randomUUID());
29
+ emit(r, () => table([r], ['id', 'name', 'slug']));
30
+ });
31
+ projects.command('rename <project_id> <name>').description('Rename a project').option('--slug <slug>', 'also change the slug')
32
+ .action(async (id, name, o) => {
33
+ const { gen, org } = await scope();
34
+ const r = await gen.projects.update(org, id, { name, ...(o.slug ? { slug: o.slug } : {}) });
35
+ emit(r, () => table([r], ['id', 'name', 'slug']));
36
+ });
37
+ projects.command('delete <project_id>').description('Permanently delete a project (refused while workspaces, fleets or grants use it)')
38
+ .action(async (id) => {
39
+ const { gen, org } = await scope();
40
+ await gen.projects.delete(org, id);
41
+ emit({ deleted: id }, () => `Deleted project ${id}.`);
42
+ });
43
+ const workspaces = program.command('workspaces').description('Workspaces in your organization (One account login; writes need owner or admin)');
44
+ workspaces.command('list', { isDefault: true }).action(async () => {
45
+ const { gen, org } = await scope();
46
+ const r = await gen.workspaces.list(org);
47
+ emit(r, () => table(rows(r), ['id', 'name', 'projectId']));
48
+ });
49
+ workspaces.command('get <workspace_id>').action(async (id) => {
50
+ const { gen, org } = await scope();
51
+ const r = await gen.workspaces.get(org, id);
52
+ emit(r, () => table([r], ['id', 'name', 'projectId', 'organizationId']));
53
+ });
54
+ workspaces.command('create <name>').description('Create a workspace').option('--project <project_id>', 'the project it belongs to').option(...KEY)
55
+ .action(async (name, o) => {
56
+ const { gen, org } = await scope();
57
+ const r = await gen.workspaces.create(org, { name, ...(o.project ? { project_id: o.project } : {}) }, o.idempotencyKey ?? randomUUID());
58
+ emit(r, () => table([r], ['id', 'name', 'projectId']));
59
+ });
60
+ workspaces.command('rename <workspace_id> <name>').description('Rename a workspace').action(async (id, name) => {
61
+ const { gen, org } = await scope();
62
+ const r = await gen.workspaces.rename(org, id, name);
63
+ emit(r, () => table([r], ['id', 'name', 'projectId']));
64
+ });
65
+ workspaces.command('delete <workspace_id>').description('Permanently delete a workspace (refused while fleets or grants use it)')
66
+ .action(async (id) => {
67
+ const { gen, org } = await scope();
68
+ await gen.workspaces.delete(org, id);
69
+ emit({ deleted: id }, () => `Deleted workspace ${id}.`);
70
+ });
71
+ }