@orchagent/cli 0.3.117 → 0.3.119
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/dist/commands/index.js +2 -0
- package/dist/commands/storage.js +351 -0
- package/package.json +1 -1
package/dist/commands/index.js
CHANGED
|
@@ -45,6 +45,7 @@ const metrics_1 = require("./metrics");
|
|
|
45
45
|
const dag_1 = require("./dag");
|
|
46
46
|
const completion_1 = require("./completion");
|
|
47
47
|
const validate_1 = require("./validate");
|
|
48
|
+
const storage_1 = require("./storage");
|
|
48
49
|
function registerCommands(program) {
|
|
49
50
|
(0, login_1.registerLoginCommand)(program);
|
|
50
51
|
(0, logout_1.registerLogoutCommand)(program);
|
|
@@ -90,4 +91,5 @@ function registerCommands(program) {
|
|
|
90
91
|
(0, dag_1.registerDagCommand)(program);
|
|
91
92
|
(0, completion_1.registerCompletionCommand)(program);
|
|
92
93
|
(0, validate_1.registerValidateCommand)(program);
|
|
94
|
+
(0, storage_1.registerStorageCommand)(program);
|
|
93
95
|
}
|
|
@@ -0,0 +1,351 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.registerStorageCommand = registerStorageCommand;
|
|
40
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
41
|
+
const config_1 = require("../lib/config");
|
|
42
|
+
const api_1 = require("../lib/api");
|
|
43
|
+
const errors_1 = require("../lib/errors");
|
|
44
|
+
const output_1 = require("../lib/output");
|
|
45
|
+
// ============================================
|
|
46
|
+
// HELPERS
|
|
47
|
+
// ============================================
|
|
48
|
+
const NAMESPACE_RE = /^[a-z][a-z0-9-]{0,63}$/;
|
|
49
|
+
const KEY_RE = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,255}$/;
|
|
50
|
+
async function resolveWorkspaceId(config, slug) {
|
|
51
|
+
const configFile = await (0, config_1.loadConfig)();
|
|
52
|
+
const targetSlug = slug ?? configFile.workspace;
|
|
53
|
+
const response = await (0, api_1.request)(config, 'GET', '/workspaces');
|
|
54
|
+
if (targetSlug) {
|
|
55
|
+
const workspace = response.workspaces.find((w) => w.slug === targetSlug);
|
|
56
|
+
if (!workspace) {
|
|
57
|
+
throw new errors_1.CliError(`Workspace '${targetSlug}' not found.`);
|
|
58
|
+
}
|
|
59
|
+
return workspace.id;
|
|
60
|
+
}
|
|
61
|
+
if (response.workspaces.length === 0) {
|
|
62
|
+
throw new errors_1.CliError('No workspaces found. Create one with `orch workspace create <name>`.');
|
|
63
|
+
}
|
|
64
|
+
if (response.workspaces.length === 1) {
|
|
65
|
+
return response.workspaces[0].id;
|
|
66
|
+
}
|
|
67
|
+
const slugs = response.workspaces.map((w) => w.slug).join(', ');
|
|
68
|
+
throw new errors_1.CliError(`Multiple workspaces available: ${slugs}\n` +
|
|
69
|
+
'Specify one with --workspace <slug> or run `orch workspace use <slug>`.');
|
|
70
|
+
}
|
|
71
|
+
function validateNamespace(ns) {
|
|
72
|
+
if (!NAMESPACE_RE.test(ns)) {
|
|
73
|
+
throw new errors_1.CliError(`Invalid namespace '${ns}'.\n\n` +
|
|
74
|
+
'Namespaces must:\n' +
|
|
75
|
+
' - Start with a lowercase letter\n' +
|
|
76
|
+
' - Be 1-64 chars of lowercase letters, digits, and hyphens\n\n' +
|
|
77
|
+
'Examples: signals, my-data, competitors');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function validateKey(key) {
|
|
81
|
+
if (!KEY_RE.test(key)) {
|
|
82
|
+
throw new errors_1.CliError(`Invalid key '${key}'.\n\n` +
|
|
83
|
+
'Keys must:\n' +
|
|
84
|
+
' - Start with a letter or digit\n' +
|
|
85
|
+
' - Be 1-256 chars of letters, digits, dots, hyphens, and underscores\n\n' +
|
|
86
|
+
'Examples: 2026-03-05, config.v2, weekly-report');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function formatDate(iso) {
|
|
90
|
+
if (!iso)
|
|
91
|
+
return '-';
|
|
92
|
+
return new Date(iso).toLocaleString();
|
|
93
|
+
}
|
|
94
|
+
function formatBytes(bytes) {
|
|
95
|
+
if (bytes < 1024)
|
|
96
|
+
return `${bytes} B`;
|
|
97
|
+
if (bytes < 1024 * 1024)
|
|
98
|
+
return `${(bytes / 1024).toFixed(1)} KB`;
|
|
99
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
100
|
+
}
|
|
101
|
+
async function readStdin() {
|
|
102
|
+
if (process.stdin.isTTY) {
|
|
103
|
+
throw new errors_1.CliError('No JSON value provided.\n\n' +
|
|
104
|
+
'Usage:\n' +
|
|
105
|
+
' orch storage set <ns> <key> \'{"k":"v"}\' Inline JSON\n' +
|
|
106
|
+
' orch storage set <ns> <key> @file.json Read from file\n' +
|
|
107
|
+
' echo \'{"k":"v"}\' | orch storage set <ns> <key> - Read from stdin\n' +
|
|
108
|
+
' cat data.json | orch storage set <ns> <key> Pipe (implicit stdin)');
|
|
109
|
+
}
|
|
110
|
+
const chunks = [];
|
|
111
|
+
for await (const chunk of process.stdin) {
|
|
112
|
+
chunks.push(Buffer.from(chunk));
|
|
113
|
+
}
|
|
114
|
+
if (!chunks.length) {
|
|
115
|
+
throw new errors_1.CliError('No data received on stdin.');
|
|
116
|
+
}
|
|
117
|
+
return Buffer.concat(chunks).toString('utf8');
|
|
118
|
+
}
|
|
119
|
+
async function parseJsonArg(arg) {
|
|
120
|
+
let raw;
|
|
121
|
+
if (!arg || arg === '-') {
|
|
122
|
+
// Read from stdin
|
|
123
|
+
raw = await readStdin();
|
|
124
|
+
}
|
|
125
|
+
else if (arg.startsWith('@')) {
|
|
126
|
+
// Support @file.json and @- syntax
|
|
127
|
+
const source = arg.slice(1);
|
|
128
|
+
if (source === '-') {
|
|
129
|
+
raw = await readStdin();
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
const fs = await Promise.resolve().then(() => __importStar(require('fs/promises')));
|
|
133
|
+
try {
|
|
134
|
+
raw = await fs.readFile(source, 'utf-8');
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
if (err.code === 'ENOENT') {
|
|
138
|
+
throw new errors_1.CliError(`File not found: ${source}`);
|
|
139
|
+
}
|
|
140
|
+
throw new errors_1.CliError(`Failed to read ${source}: ${err.message}`);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
raw = arg;
|
|
146
|
+
}
|
|
147
|
+
try {
|
|
148
|
+
return JSON.parse(raw);
|
|
149
|
+
}
|
|
150
|
+
catch {
|
|
151
|
+
throw new errors_1.CliError('Invalid JSON value.\n\n' +
|
|
152
|
+
'Pass valid JSON, @file.json, or pipe via stdin:\n' +
|
|
153
|
+
' orch storage set signals 2026-03-05 \'{"pending": []}\'\n' +
|
|
154
|
+
' orch storage set signals 2026-03-05 @data.json\n' +
|
|
155
|
+
' echo \'{"pending": []}\' | orch storage set signals 2026-03-05 -');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
// ============================================
|
|
159
|
+
// COMMAND REGISTRATION
|
|
160
|
+
// ============================================
|
|
161
|
+
function registerStorageCommand(program) {
|
|
162
|
+
const storage = program
|
|
163
|
+
.command('storage')
|
|
164
|
+
.description('Manage agent storage (persistent shared key-value documents)')
|
|
165
|
+
.action(() => { storage.help(); });
|
|
166
|
+
// orch storage list [namespace]
|
|
167
|
+
storage
|
|
168
|
+
.command('list [namespace]')
|
|
169
|
+
.description('List namespaces, or list keys in a namespace')
|
|
170
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
171
|
+
.option('--limit <n>', 'Max keys to return (default: 100)', '100')
|
|
172
|
+
.option('--cursor <cursor>', 'Pagination cursor from previous response')
|
|
173
|
+
.option('--json', 'Output as JSON')
|
|
174
|
+
.action(async (namespace, options) => {
|
|
175
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
176
|
+
if (!config.apiKey) {
|
|
177
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
178
|
+
}
|
|
179
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
180
|
+
const headers = { 'X-Workspace-Id': workspaceId };
|
|
181
|
+
if (!namespace) {
|
|
182
|
+
// List namespaces
|
|
183
|
+
const result = await (0, api_1.request)(config, 'GET', '/storage', { headers });
|
|
184
|
+
if (options.json) {
|
|
185
|
+
(0, output_1.printJson)(result);
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
if (result.namespaces.length === 0) {
|
|
189
|
+
process.stdout.write('No storage namespaces found.\n');
|
|
190
|
+
process.stdout.write(chalk_1.default.gray('\nCreate one: orch storage set my-namespace my-key \'{"hello": "world"}\'\n'));
|
|
191
|
+
return;
|
|
192
|
+
}
|
|
193
|
+
for (const ns of result.namespaces) {
|
|
194
|
+
process.stdout.write(` ${ns}\n`);
|
|
195
|
+
}
|
|
196
|
+
process.stdout.write(chalk_1.default.gray(`\n${result.namespaces.length} namespace(s)\n`));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// List keys in namespace
|
|
200
|
+
validateNamespace(namespace);
|
|
201
|
+
const limit = parseInt(options.limit ?? '100', 10);
|
|
202
|
+
let path = `/storage/${namespace}?limit=${limit}`;
|
|
203
|
+
if (options.cursor)
|
|
204
|
+
path += `&cursor=${encodeURIComponent(options.cursor)}`;
|
|
205
|
+
const result = await (0, api_1.request)(config, 'GET', path, { headers });
|
|
206
|
+
if (options.json) {
|
|
207
|
+
(0, output_1.printJson)(result);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
if (result.keys.length === 0) {
|
|
211
|
+
process.stdout.write(`No keys found in namespace '${namespace}'.\n`);
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
for (const key of result.keys) {
|
|
215
|
+
process.stdout.write(` ${key}\n`);
|
|
216
|
+
}
|
|
217
|
+
const countText = `${result.keys.length} key(s)`;
|
|
218
|
+
process.stdout.write(chalk_1.default.gray(`\n${countText}`));
|
|
219
|
+
if (result.has_more) {
|
|
220
|
+
process.stdout.write(chalk_1.default.gray(` (more available, use --cursor "${result.cursor}")`));
|
|
221
|
+
}
|
|
222
|
+
process.stdout.write('\n');
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
// orch storage get <namespace> <key>
|
|
226
|
+
storage
|
|
227
|
+
.command('get <namespace> <key>')
|
|
228
|
+
.description('Get a document by namespace and key')
|
|
229
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
230
|
+
.option('--json', 'Output as JSON (full response with metadata)')
|
|
231
|
+
.option('--raw', 'Output only the value (no metadata)')
|
|
232
|
+
.action(async (namespace, key, options) => {
|
|
233
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
234
|
+
if (!config.apiKey) {
|
|
235
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
236
|
+
}
|
|
237
|
+
validateNamespace(namespace);
|
|
238
|
+
validateKey(key);
|
|
239
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
240
|
+
const result = await (0, api_1.request)(config, 'GET', `/storage/${namespace}/${key}`, { headers: { 'X-Workspace-Id': workspaceId } });
|
|
241
|
+
if (options.raw) {
|
|
242
|
+
process.stdout.write(JSON.stringify(result.value, null, 2) + '\n');
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
if (options.json) {
|
|
246
|
+
(0, output_1.printJson)(result);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
// Pretty output
|
|
250
|
+
process.stdout.write(chalk_1.default.gray(`namespace: ${result.namespace} key: ${result.key} `) +
|
|
251
|
+
chalk_1.default.gray(`v${result.version} ${formatBytes(result.size_bytes)} `) +
|
|
252
|
+
chalk_1.default.gray(`updated: ${formatDate(result.updated_at)} by ${result.updated_by}\n\n`));
|
|
253
|
+
process.stdout.write(JSON.stringify(result.value, null, 2) + '\n');
|
|
254
|
+
});
|
|
255
|
+
// orch storage set <namespace> <key> [value]
|
|
256
|
+
storage
|
|
257
|
+
.command('set <namespace> <key> [value]')
|
|
258
|
+
.description('Create or update a document (JSON string, @file.json, or - for stdin)')
|
|
259
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
260
|
+
.option('--version <n>', 'Expected version for compare-and-swap (CAS)')
|
|
261
|
+
.action(async (namespace, key, value, options) => {
|
|
262
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
263
|
+
if (!config.apiKey) {
|
|
264
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
265
|
+
}
|
|
266
|
+
validateNamespace(namespace);
|
|
267
|
+
validateKey(key);
|
|
268
|
+
const parsed = await parseJsonArg(value);
|
|
269
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
270
|
+
const headers = {
|
|
271
|
+
'X-Workspace-Id': workspaceId,
|
|
272
|
+
'X-Orchagent-Client': 'cli',
|
|
273
|
+
'Content-Type': 'application/json',
|
|
274
|
+
};
|
|
275
|
+
if (options.version) {
|
|
276
|
+
headers['If-Match'] = options.version;
|
|
277
|
+
}
|
|
278
|
+
const result = await (0, api_1.request)(config, 'PUT', `/storage/${namespace}/${key}`, {
|
|
279
|
+
body: JSON.stringify(parsed),
|
|
280
|
+
headers,
|
|
281
|
+
});
|
|
282
|
+
process.stdout.write(chalk_1.default.green('\u2713') +
|
|
283
|
+
` ${namespace}/${key} ` +
|
|
284
|
+
chalk_1.default.gray(`v${result.version} (${formatBytes(result.size_bytes)})\n`));
|
|
285
|
+
});
|
|
286
|
+
// orch storage patch <namespace> <key> [value]
|
|
287
|
+
storage
|
|
288
|
+
.command('patch <namespace> <key> [value]')
|
|
289
|
+
.description('Merge-patch a document (JSON string, @file.json, or - for stdin)')
|
|
290
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
291
|
+
.action(async (namespace, key, value, options) => {
|
|
292
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
293
|
+
if (!config.apiKey) {
|
|
294
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
295
|
+
}
|
|
296
|
+
validateNamespace(namespace);
|
|
297
|
+
validateKey(key);
|
|
298
|
+
const parsed = await parseJsonArg(value);
|
|
299
|
+
if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) {
|
|
300
|
+
throw new errors_1.CliError('Patch value must be a JSON object (not array or primitive).');
|
|
301
|
+
}
|
|
302
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
303
|
+
const result = await (0, api_1.request)(config, 'PATCH', `/storage/${namespace}/${key}`, {
|
|
304
|
+
body: JSON.stringify(parsed),
|
|
305
|
+
headers: {
|
|
306
|
+
'X-Workspace-Id': workspaceId,
|
|
307
|
+
'X-Orchagent-Client': 'cli',
|
|
308
|
+
'Content-Type': 'application/json',
|
|
309
|
+
},
|
|
310
|
+
});
|
|
311
|
+
process.stdout.write(chalk_1.default.green('\u2713') +
|
|
312
|
+
` Patched ${namespace}/${key} ` +
|
|
313
|
+
chalk_1.default.gray(`v${result.version} (${formatBytes(result.size_bytes)})\n`));
|
|
314
|
+
});
|
|
315
|
+
// orch storage delete <namespace> [key]
|
|
316
|
+
storage
|
|
317
|
+
.command('delete <namespace> [key]')
|
|
318
|
+
.description('Delete a document, or all documents in a namespace (with --all)')
|
|
319
|
+
.option('--workspace <slug>', 'Workspace slug (default: current workspace)')
|
|
320
|
+
.option('--all', 'Delete all documents in the namespace')
|
|
321
|
+
.action(async (namespace, key, options) => {
|
|
322
|
+
const config = await (0, config_1.getResolvedConfig)();
|
|
323
|
+
if (!config.apiKey) {
|
|
324
|
+
throw new errors_1.CliError('Missing API key. Run `orch login` first.');
|
|
325
|
+
}
|
|
326
|
+
validateNamespace(namespace);
|
|
327
|
+
const workspaceId = await resolveWorkspaceId(config, options.workspace);
|
|
328
|
+
const headers = {
|
|
329
|
+
'X-Workspace-Id': workspaceId,
|
|
330
|
+
'X-Orchagent-Client': 'cli',
|
|
331
|
+
};
|
|
332
|
+
if (options.all) {
|
|
333
|
+
// Delete entire namespace
|
|
334
|
+
const result = await (0, api_1.request)(config, 'DELETE', `/storage/${namespace}`, { headers });
|
|
335
|
+
process.stdout.write(chalk_1.default.green('\u2713') +
|
|
336
|
+
` Deleted namespace '${namespace}' (${result.documents_deleted ?? 0} document(s))\n`);
|
|
337
|
+
}
|
|
338
|
+
else if (key) {
|
|
339
|
+
// Delete single document
|
|
340
|
+
validateKey(key);
|
|
341
|
+
await (0, api_1.request)(config, 'DELETE', `/storage/${namespace}/${key}`, { headers });
|
|
342
|
+
process.stdout.write(chalk_1.default.green('\u2713') + ` Deleted ${namespace}/${key}\n`);
|
|
343
|
+
}
|
|
344
|
+
else {
|
|
345
|
+
throw new errors_1.CliError('Specify a key to delete, or use --all to delete the entire namespace.\n\n' +
|
|
346
|
+
'Examples:\n' +
|
|
347
|
+
` orch storage delete ${namespace} my-key\n` +
|
|
348
|
+
` orch storage delete ${namespace} --all`);
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
package/package.json
CHANGED