@shardflux/cli 0.4.1 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +226 -0
- package/README.md +517 -34
- package/dist/account-args.d.ts +13 -0
- package/dist/account-args.js +425 -0
- package/dist/account-commands.d.ts +18 -0
- package/dist/account-commands.js +1657 -0
- package/dist/args.d.ts +15 -3
- package/dist/args.js +292 -41
- package/dist/bin.js +5 -16
- package/dist/browser.d.ts +7 -0
- package/dist/browser.js +39 -0
- package/dist/commands.d.ts +69 -5
- package/dist/commands.js +440 -30
- package/dist/credentials.d.ts +77 -0
- package/dist/credentials.js +217 -0
- package/dist/errors.d.ts +63 -11
- package/dist/errors.js +114 -21
- package/dist/format.d.ts +89 -6
- package/dist/format.js +479 -10
- package/dist/handlers.d.ts +2 -0
- package/dist/handlers.js +7 -0
- package/dist/index.d.ts +19 -7
- package/dist/index.js +13 -6
- package/dist/input.d.ts +25 -0
- package/dist/input.js +167 -0
- package/dist/main.d.ts +16 -2
- package/dist/main.js +477 -31
- package/dist/update-check.d.ts +64 -0
- package/dist/update-check.js +128 -0
- package/package.json +3 -3
package/dist/format.js
CHANGED
|
@@ -44,9 +44,15 @@ export function operationSummary(op) {
|
|
|
44
44
|
return `${op.kind} ${op.state}${op.state_reason ? ` (${op.state_reason})` : ''}`;
|
|
45
45
|
}
|
|
46
46
|
export const WORKSPACE_HEADERS = ['ID', 'KEY', 'STATE', 'DESIRED', 'TEMPLATE', 'OPERATION', 'CREATED'];
|
|
47
|
-
/**
|
|
47
|
+
/** `file_first` as people write it (`--mode file-first`). */
|
|
48
|
+
export function modeText(mode) {
|
|
49
|
+
return mode === 'file_first' ? 'file-first' : (mode ?? 'processful');
|
|
50
|
+
}
|
|
51
|
+
/** Observed state plus what sets the workspace apart: file-first, session, draft or test instance, and how it ended. */
|
|
48
52
|
export function workspaceState(w) {
|
|
49
53
|
const tags = [];
|
|
54
|
+
if (w.mode === 'file_first')
|
|
55
|
+
tags.push('file-first');
|
|
50
56
|
if (w.purpose === 'template_draft')
|
|
51
57
|
tags.push('draft');
|
|
52
58
|
else if (w.purpose === 'template_test')
|
|
@@ -101,21 +107,41 @@ export function startupTail(s) {
|
|
|
101
107
|
const lines = s.output_tail.replace(/\n$/, '').split('\n');
|
|
102
108
|
return `startup output (last ${Buffer.byteLength(s.output_tail)} bytes):\n${lines.map((l) => ` | ${l}`).join('\n')}\n`;
|
|
103
109
|
}
|
|
110
|
+
/** A pending suspend-when-idle request in one line (0.5.1; contracts §20.6). */
|
|
111
|
+
export function suspendRequestText(r) {
|
|
112
|
+
return `after ${r.after_seconds} s idle, not before ${time(r.not_before)} (requested ${time(r.requested_at)})`;
|
|
113
|
+
}
|
|
114
|
+
/** `shard ws suspend --when-idle`: the recorded request, or the suspend already in progress. */
|
|
115
|
+
export function suspendWhenIdleText(w, r) {
|
|
116
|
+
if (r.suspend_request) {
|
|
117
|
+
const s = r.suspend_request;
|
|
118
|
+
return (`Suspend when idle: ${w.key} (${w.id}) is suspended once it has been idle for ${s.after_seconds} s, not before ${time(s.not_before)}.\n` +
|
|
119
|
+
'A running command, an attached stream or a keepalive postpones it; the next tool call or a resume cancels it.\n' +
|
|
120
|
+
`cancel with: shard workspaces suspend ${w.key} --cancel-when-idle\n`);
|
|
121
|
+
}
|
|
122
|
+
const op = r.operation;
|
|
123
|
+
return `A suspend of ${w.key} (${w.id}) is already in progress (operation ${op.id} ${op.kind} ${op.state}); nothing was scheduled.\nwait with: shard operations wait ${op.id}\n`;
|
|
124
|
+
}
|
|
104
125
|
export function workspaceDetail(w) {
|
|
105
126
|
const c = w.ceilings;
|
|
106
127
|
const startup = startupText(w.startup);
|
|
128
|
+
const suspendRequest = w.idle?.suspend_request;
|
|
107
129
|
return kv([
|
|
108
130
|
['id', w.id],
|
|
109
131
|
['key', w.workspace_key],
|
|
110
132
|
['state', `${w.observed_state} (desired ${w.desired_state})`],
|
|
111
133
|
['ready', isReady(w) ? 'yes' : 'no'],
|
|
112
134
|
['template', `${w.template.slug} v${w.template.version} (${w.template.version_id})`],
|
|
135
|
+
// The mode (0.5.0; contracts §29) when the API reports it; a file-first workspace's state is its tree revision.
|
|
136
|
+
...(w.mode !== undefined ? [['mode', modeText(w.mode)]] : []),
|
|
137
|
+
...(w.mode === 'file_first' ? [['tree revision', String(w.tree_revision)]] : []),
|
|
113
138
|
['cell', w.cell_id ? `${w.cell_id} ${w.cell_endpoint ?? '(endpoint unresolved)'}` : '-'],
|
|
114
139
|
['ceilings', c ? `cpu ${c.cpu_millis}m (${c.sources.cpu_millis}), memory ${c.memory_mib} MiB (${c.sources.memory_mib}), disk ${c.disk_gib} GiB (${c.sources.disk_gib})` : '-'],
|
|
115
140
|
['grants', pairs(w.grants)],
|
|
116
141
|
['caps', pairs(w.caps)],
|
|
117
142
|
['operation', w.active_operation ? `${operationSummary(w.active_operation)} ${w.active_operation.id}` : '-'],
|
|
118
143
|
['pending', w.pending_reason ?? '-'],
|
|
144
|
+
...(suspendRequest ? [['suspend when idle', suspendRequestText(suspendRequest)]] : []),
|
|
119
145
|
...(startup ? [['startup', startup]] : []),
|
|
120
146
|
['forked from', w.forked_from_workspace_id ?? '-'],
|
|
121
147
|
...(w.lifetime !== undefined
|
|
@@ -152,6 +178,31 @@ export function operationDetail(op) {
|
|
|
152
178
|
export function sessionTable(sessions) {
|
|
153
179
|
return table(['ID', 'LABEL', 'PRINCIPAL', 'TOKENS', 'LAST TOKEN', 'REVOKED'], sessions.map((s) => [s.id, s.agent_label, `${s.principal_type}:${s.principal_id}`, String(s.tokens_issued), time(s.last_token_issued_at), time(s.revoked_at)]));
|
|
154
180
|
}
|
|
181
|
+
/** `files stat`: one path's metadata; the revision only when requested and returned. */
|
|
182
|
+
export function fileInfoText(f) {
|
|
183
|
+
return kv([
|
|
184
|
+
['path', f.path],
|
|
185
|
+
['type', f.type],
|
|
186
|
+
['size', String(f.size)],
|
|
187
|
+
['mode', f.mode],
|
|
188
|
+
['owner', f.uid !== undefined || f.gid !== undefined ? `${f.uid ?? '-'}:${f.gid ?? '-'}` : '-'],
|
|
189
|
+
['modified', time(f.modified_at)],
|
|
190
|
+
...(f.symlink_target !== undefined ? [['target', f.symlink_target]] : []),
|
|
191
|
+
...(f.revision !== undefined ? [['revision', f.revision]] : []),
|
|
192
|
+
]);
|
|
193
|
+
}
|
|
194
|
+
/** `files search`: grep-style `path:line:column: text`, context lines as `path-line- text`. */
|
|
195
|
+
export function searchText(r) {
|
|
196
|
+
if (r.matches.length === 0)
|
|
197
|
+
return 'No matches.\n';
|
|
198
|
+
const lines = [];
|
|
199
|
+
for (const m of r.matches) {
|
|
200
|
+
(m.before ?? []).forEach((t, i, all) => lines.push(`${m.path}-${m.line - all.length + i}- ${t}`));
|
|
201
|
+
lines.push(`${m.path}:${m.line}:${m.column}: ${m.text}`);
|
|
202
|
+
(m.after ?? []).forEach((t, i) => lines.push(`${m.path}-${m.line + i + 1}- ${t}`));
|
|
203
|
+
}
|
|
204
|
+
return `${lines.join('\n')}\n`;
|
|
205
|
+
}
|
|
155
206
|
export function fileTable(list) {
|
|
156
207
|
return table(['TYPE', 'SIZE', 'MODIFIED', 'NAME'], list.entries.map((e) => [e.type, String(e.size), time(e.modified_at), e.name]));
|
|
157
208
|
}
|
|
@@ -198,10 +249,66 @@ function objectTable(rows, columns) {
|
|
|
198
249
|
const cols = columns.filter(([k]) => objs.some((o) => k in o));
|
|
199
250
|
return table(cols.map(([, h]) => h), objs.map((o) => cols.map(([k]) => (k === 'percent_used' && typeof o[k] === 'number' ? `${o[k]}%` : scalar(o[k])))));
|
|
200
251
|
}
|
|
201
|
-
const USAGE_KNOWN = new Set(['plan', 'period', 'measurement', 'allowance_exhausted', 'meters', 'allowances', 'exhausted', 'alert_thresholds']);
|
|
252
|
+
const USAGE_KNOWN = new Set(['plan', 'period', 'measurement', 'allowance_exhausted', 'exhausted_reason', 'egress_override', 'meters', 'allowances', 'exhausted', 'alert_thresholds', 'spend_cap']);
|
|
253
|
+
/** Minor units as money (the overage block): `$3.00` for usd, else `3.00 EUR`. A fractional minor amount (a sub-cent rate) keeps its digits. */
|
|
254
|
+
export function capMoney(minor, currency) {
|
|
255
|
+
if (typeof minor !== 'number')
|
|
256
|
+
return '-';
|
|
257
|
+
const major = minor / 100;
|
|
258
|
+
const digits = Number.isInteger(minor) ? 2 : Math.min(6, Math.max(2, String(major).split('.')[1]?.length ?? 2));
|
|
259
|
+
const n = major.toFixed(digits);
|
|
260
|
+
return currency.toLowerCase() === 'usd' ? `$${n}` : `${n} ${currency.toUpperCase()}`;
|
|
261
|
+
}
|
|
262
|
+
/** What each spend_cap.state means for the reader (API contract: SpendCap.state). */
|
|
263
|
+
const OVERAGE_STATES = {
|
|
264
|
+
unavailable: 'not available on this plan',
|
|
265
|
+
off: 'starts are refused once an allowance is used up; an owner or billing member can turn overage on under Usage & billing',
|
|
266
|
+
paused: 'a plan payment is past due; overage resumes once it is paid',
|
|
267
|
+
within_allowance: 'on, no usage past the allowances yet',
|
|
268
|
+
accruing: 'usage past the allowances is charged on the next invoice',
|
|
269
|
+
warning: '80% or more of the spend cap is used',
|
|
270
|
+
reached: 'the spend cap is reached: new opens/resumes are refused and running workspaces are suspended',
|
|
271
|
+
};
|
|
272
|
+
/**
|
|
273
|
+
* The opt-in overage block (0.5.1; `spend_cap` of the summary, SDK 0.10.0): state, cap, charges of the cap, the
|
|
274
|
+
* projected date the cap is reached, and per allowance the units past it and their amount. Empty without spend_cap
|
|
275
|
+
* (an API older than overage).
|
|
276
|
+
*/
|
|
277
|
+
function spendCapText(cap) {
|
|
278
|
+
if (!isObject(cap) || typeof cap.state !== 'string')
|
|
279
|
+
return '';
|
|
280
|
+
const cur = typeof cap.currency === 'string' ? cap.currency : 'usd';
|
|
281
|
+
const rows = [['overage', `${cap.state}${OVERAGE_STATES[cap.state] ? ` (${OVERAGE_STATES[cap.state]})` : ''}`]];
|
|
282
|
+
if (cap.state === 'unavailable')
|
|
283
|
+
return kv(rows);
|
|
284
|
+
const charges = typeof cap.charges_minor === 'number' ? cap.charges_minor : 0;
|
|
285
|
+
if (cap.state !== 'off') {
|
|
286
|
+
const plan = typeof cap.max_cap_minor === 'number' ? capMoney(cap.max_cap_minor, cur) : undefined;
|
|
287
|
+
if (cap.cap_minor === null || cap.cap_minor === undefined)
|
|
288
|
+
rows.push(['spend cap', `not set: ${capMoney(cap.effective_cap_minor, cur)}${plan ? ' (the plan price)' : ''} applies`]);
|
|
289
|
+
else if (typeof cap.effective_cap_minor === 'number' && typeof cap.cap_minor === 'number' && cap.effective_cap_minor < cap.cap_minor) {
|
|
290
|
+
rows.push(['spend cap', `${capMoney(cap.cap_minor, cur)}, lowered to ${capMoney(cap.effective_cap_minor, cur)}${plan ? ' (the plan price)' : ''}`]);
|
|
291
|
+
}
|
|
292
|
+
else
|
|
293
|
+
rows.push(['spend cap', `${capMoney(cap.cap_minor, cur)}${plan ? ` (at most ${plan}, the plan price)` : ''}`]);
|
|
294
|
+
}
|
|
295
|
+
if (cap.state !== 'off' || charges > 0) {
|
|
296
|
+
const percent = typeof cap.percent_of_cap === 'number' ? ` (${cap.percent_of_cap}%)` : '';
|
|
297
|
+
rows.push(['overage charges', `${capMoney(charges, cur)} of ${capMoney(cap.effective_cap_minor, cur)}${percent}, ${capMoney(cap.remaining_minor, cur)} left; billed on the next invoice`]);
|
|
298
|
+
}
|
|
299
|
+
if (typeof cap.projected_reached_at === 'string')
|
|
300
|
+
rows.push(['cap reached', `${time(cap.projected_reached_at)} (projected at this period's average use)`]);
|
|
301
|
+
let out = kv(rows);
|
|
302
|
+
const lines = Array.isArray(cap.lines) ? cap.lines.filter(isObject) : [];
|
|
303
|
+
if (lines.some((l) => (typeof l.units_over === 'number' && l.units_over > 0) || (typeof l.amount_minor === 'number' && l.amount_minor > 0))) {
|
|
304
|
+
out += `\n${table(['OVERAGE', 'UNIT', 'PAST ALLOWANCE', 'BILLED', 'RATE', 'AMOUNT'], lines.map((l) => [scalar(l.allowance), scalar(l.unit), scalar(l.units_over), scalar(l.billed_units), capMoney(l.rate_minor, cur), capMoney(l.amount_minor, cur)]))}`;
|
|
305
|
+
}
|
|
306
|
+
return out;
|
|
307
|
+
}
|
|
202
308
|
/**
|
|
203
309
|
* The usage summary (GET /v1/organizations/{id}/usage/summary): plan, period, measurement
|
|
204
|
-
* freshness, then meters and allowances as tables
|
|
310
|
+
* freshness, then meters and allowances as tables (cap state `overage` past an allowance while opt-in overage is on),
|
|
311
|
+
* then the overage block. Columns absent from the response are omitted.
|
|
205
312
|
*/
|
|
206
313
|
export function usageText(summary) {
|
|
207
314
|
if (!isObject(summary))
|
|
@@ -217,8 +324,10 @@ export function usageText(summary) {
|
|
|
217
324
|
head.push(['measured through', time(m.measured_through)]);
|
|
218
325
|
if (isObject(s.measurement) && s.measurement.estimate_status !== undefined)
|
|
219
326
|
head.push(['estimate', scalar(s.measurement.estimate_status)]);
|
|
220
|
-
if (typeof s.allowance_exhausted === 'boolean')
|
|
221
|
-
|
|
327
|
+
if (typeof s.allowance_exhausted === 'boolean') {
|
|
328
|
+
const reason = typeof s.exhausted_reason === 'string' ? `${s.exhausted_reason}: ` : '';
|
|
329
|
+
head.push(['allowance exhausted', s.allowance_exhausted ? `yes (${reason}new opens/resumes are refused)` : 'no']);
|
|
330
|
+
}
|
|
222
331
|
for (const [k, v] of Object.entries(s))
|
|
223
332
|
if (!USAGE_KNOWN.has(k) && (typeof v !== 'object' || v === null))
|
|
224
333
|
head.push([k.replace(/_/g, ' '), scalar(v)]);
|
|
@@ -246,13 +355,34 @@ export function usageText(summary) {
|
|
|
246
355
|
]);
|
|
247
356
|
if (allowances)
|
|
248
357
|
out += `\n${allowances}`;
|
|
358
|
+
const overage = spendCapText(s.spend_cap);
|
|
359
|
+
if (overage)
|
|
360
|
+
out += `\n${overage}`;
|
|
249
361
|
return out || json(summary);
|
|
250
362
|
}
|
|
363
|
+
/** Request ids and error codes pasted unquoted into the suggested command must be shell-inert. */
|
|
364
|
+
const SHELL_SAFE = /^[A-Za-z0-9._:-]{1,200}$/;
|
|
251
365
|
/**
|
|
252
|
-
*
|
|
253
|
-
* operation
|
|
366
|
+
* The `shard feedback` command a failed command suggests (0.5.0+), with the error's request id and code so the founder
|
|
367
|
+
* can find the logs (a failed operation's or execution's own error code, e.g. capacity_unavailable); the agent fills in
|
|
368
|
+
* the text.
|
|
254
369
|
*/
|
|
255
|
-
export function
|
|
370
|
+
export function feedbackCommand(e) {
|
|
371
|
+
const code = (e.code === 'operation_failed' || e.code === 'execution_failed') && typeof e.details?.error_code === 'string' ? e.details.error_code : e.code;
|
|
372
|
+
const parts = ['shard feedback --category bug'];
|
|
373
|
+
if (e.request_id && SHELL_SAFE.test(e.request_id))
|
|
374
|
+
parts.push(`--request-id ${e.request_id}`);
|
|
375
|
+
if (SHELL_SAFE.test(code))
|
|
376
|
+
parts.push(`--error-code ${code}`);
|
|
377
|
+
parts.push('"<what you expected>"');
|
|
378
|
+
return parts.join(' ');
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* One stderr line: `error: <message> [code=... status=... request_id=...]` plus a hint when there is one, and the
|
|
382
|
+
* `feedback:` line (the command to report it) when given. A failed operation or execution also shows
|
|
383
|
+
* `retryable=true|false`.
|
|
384
|
+
*/
|
|
385
|
+
export function errorText(e, hint, feedback) {
|
|
256
386
|
const meta = [`code=${e.code}`];
|
|
257
387
|
if (e.status !== undefined)
|
|
258
388
|
meta.push(`status=${e.status}`);
|
|
@@ -260,15 +390,17 @@ export function errorText(e, hint) {
|
|
|
260
390
|
meta.push(`request_id=${e.request_id}`);
|
|
261
391
|
if (e.operation_id)
|
|
262
392
|
meta.push(`operation_id=${e.operation_id}`);
|
|
393
|
+
if (e.execution_id)
|
|
394
|
+
meta.push(`execution_id=${e.execution_id}`);
|
|
263
395
|
if (e.source === 'cell')
|
|
264
396
|
meta.push('source=cell');
|
|
265
|
-
if (e.code === 'operation_failed' && e.retryable !== undefined)
|
|
397
|
+
if ((e.code === 'operation_failed' || e.code === 'execution_failed') && e.retryable !== undefined)
|
|
266
398
|
meta.push(`retryable=${e.retryable}`);
|
|
267
399
|
const reason = e.details && typeof e.details.reason === 'string' ? ` (${e.details.reason})` : '';
|
|
268
400
|
const issues = Array.isArray(e.details?.issues)
|
|
269
401
|
? e.details.issues.filter(isObject).map((i) => ` ${scalar(i.path)}: ${scalar(i.message)}\n`).join('')
|
|
270
402
|
: '';
|
|
271
|
-
return `error: ${e.message}${reason} [${meta.join(' ')}]\n${issues}${hint ? `hint: ${hint}\n` : ''}`;
|
|
403
|
+
return `error: ${e.message}${reason} [${meta.join(' ')}]\n${issues}${hint ? `hint: ${hint}\n` : ''}${feedback ? `feedback: ${feedback}\n` : ''}`;
|
|
272
404
|
}
|
|
273
405
|
// ---- Templates v2 (contracts §19) ---------------------------------------------------------------
|
|
274
406
|
export function templateTable(rows) {
|
|
@@ -396,3 +528,340 @@ export function settingsText(s) {
|
|
|
396
528
|
['egress', egress],
|
|
397
529
|
]).replace(/^/gm, ' ');
|
|
398
530
|
}
|
|
531
|
+
// ---- File-first executions (0.5.0; contracts §29.8) ----------------------------------------------------------------------
|
|
532
|
+
const CHANGE_LETTER = { added: 'A', modified: 'M', deleted: 'D' };
|
|
533
|
+
/** How many changed paths the human output lists before "... N more" (--json lists every one the result has). */
|
|
534
|
+
export const MAX_CHANGED_SHOWN = 100;
|
|
535
|
+
/**
|
|
536
|
+
* What an execution changed, one `A|M|D path` line each (the letters of a short VCS status; a directory ends in `/`), at
|
|
537
|
+
* most `max`; then how many more the result has, and a note when the cell listed only the first 10,000.
|
|
538
|
+
*/
|
|
539
|
+
export function changedText(changed, changedTruncated, max = MAX_CHANGED_SHOWN) {
|
|
540
|
+
const shown = changed.slice(0, max).map((c) => `${CHANGE_LETTER[c.change] ?? '?'} ${c.path}${c.type === 'directory' ? '/' : ''}\n`);
|
|
541
|
+
const more = changed.length - shown.length;
|
|
542
|
+
const tail = more > 0 ? `... ${more} more (--json lists every one)\n` : '';
|
|
543
|
+
const cut = changedTruncated ? `... and more: the result lists only the first ${changed.length} changes\n` : '';
|
|
544
|
+
return shown.join('') + tail + cut;
|
|
545
|
+
}
|
|
546
|
+
/** An execution's exit status in words: `exit code 0`, `killed by signal 15`, `killed by signal 9 (timed out)`. */
|
|
547
|
+
function exitStatus(r) {
|
|
548
|
+
const signal = r.termSignal !== null && r.termSignal > 0 ? `killed by signal ${r.termSignal}` : null;
|
|
549
|
+
if (r.timedOut)
|
|
550
|
+
return `${signal ?? 'killed'}, timed out`;
|
|
551
|
+
if (signal && (r.exitCode === null || r.exitCode < 0))
|
|
552
|
+
return signal;
|
|
553
|
+
return `exit code ${r.exitCode ?? '-'}`;
|
|
554
|
+
}
|
|
555
|
+
/** The execution's state: `succeeded (exit code 0)`, `failed: <message> (<reason>)`, `running`. */
|
|
556
|
+
export function executionStateText(r) {
|
|
557
|
+
if (r.state === 'succeeded')
|
|
558
|
+
return `succeeded (${exitStatus(r)})`;
|
|
559
|
+
if (r.state === 'failed' || r.state === 'lost')
|
|
560
|
+
return `${r.state}${r.error?.message ? `: ${r.error.message}` : ''}${r.errorReason ? ` (${r.errorReason})` : ''}`;
|
|
561
|
+
return r.state;
|
|
562
|
+
}
|
|
563
|
+
/** `3 -> 4`, `3 (unchanged)`, `3 (nothing published)`, or the base revision a pending execution runs on. */
|
|
564
|
+
function revisionText(r) {
|
|
565
|
+
if (r.pending)
|
|
566
|
+
return `${r.baseRevision} (the command runs on it)`;
|
|
567
|
+
if (r.treeRevision === null)
|
|
568
|
+
return `${r.baseRevision} (nothing published)`;
|
|
569
|
+
return r.treeRevision === r.baseRevision ? `${r.baseRevision} (unchanged)` : `${r.baseRevision} -> ${r.treeRevision}`;
|
|
570
|
+
}
|
|
571
|
+
/**
|
|
572
|
+
* The summary `shard ws exec -v` prints after a file-first command's output, and `shard executions get` with it (on
|
|
573
|
+
* stderr): execution id, state and exit status, base -> tree revision, `replayed` when true, truncated output, timings
|
|
574
|
+
* (`key=value`: queue_ms, run_ms, publish_ms, total_ms and the host's), then what changed.
|
|
575
|
+
*/
|
|
576
|
+
export function executionSummary(r, maxChanged = MAX_CHANGED_SHOWN, opts = {}) {
|
|
577
|
+
const truncated = [r.stdoutTruncated ? 'stdout' : '', r.stderrTruncated ? 'stderr' : ''].filter(Boolean).join(' and ');
|
|
578
|
+
const rows = [
|
|
579
|
+
['execution', r.executionId],
|
|
580
|
+
['state', executionStateText(r)],
|
|
581
|
+
['revision', revisionText(r)],
|
|
582
|
+
];
|
|
583
|
+
// `executions get` passes replayed: false: every result it reads is a recorded one, so the line would say nothing.
|
|
584
|
+
if (r.replayed && opts.replayed !== false)
|
|
585
|
+
rows.push(['replayed', 'yes (the recorded result; the command did not run again)']);
|
|
586
|
+
if (truncated)
|
|
587
|
+
rows.push(['truncated', `${truncated} (at the output limit)`]);
|
|
588
|
+
if (Object.keys(r.timings).length)
|
|
589
|
+
rows.push(['timings', pairs(r.timings)]);
|
|
590
|
+
if (!r.pending)
|
|
591
|
+
rows.push(['changed', r.changed.length === 0 ? 'nothing' : `${r.changed.length}${r.changedTruncated ? '+' : ''} path${r.changed.length === 1 ? '' : 's'}`]);
|
|
592
|
+
return kv(rows) + changedText(r.changed, r.changedTruncated, maxChanged);
|
|
593
|
+
}
|
|
594
|
+
/** Bytes as UTF-8 text; null when they are not valid UTF-8. */
|
|
595
|
+
function utf8(bytes) {
|
|
596
|
+
try {
|
|
597
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(bytes);
|
|
598
|
+
}
|
|
599
|
+
catch {
|
|
600
|
+
return null;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* The `--json` document of an execution (`shard ws exec` on a file-first workspace, `shard executions get`): the cell's
|
|
605
|
+
* ExecutionResult with stdout/stderr as UTF-8 text (like the processful exec's), plus `stdout_base64` / `stderr_base64`
|
|
606
|
+
* with the exact bytes when a stream is not valid UTF-8 (its text then has U+FFFD for the invalid sequences).
|
|
607
|
+
*/
|
|
608
|
+
export function executionJson(r) {
|
|
609
|
+
const stream = (name, bytes) => {
|
|
610
|
+
const text = utf8(bytes);
|
|
611
|
+
return text !== null ? { [name]: text } : { [name]: r.text(name), [`${name}_base64`]: Buffer.from(bytes).toString('base64') };
|
|
612
|
+
};
|
|
613
|
+
return {
|
|
614
|
+
execution_id: r.executionId,
|
|
615
|
+
state: r.state,
|
|
616
|
+
exit_code: r.exitCode,
|
|
617
|
+
term_signal: r.termSignal,
|
|
618
|
+
timed_out: r.timedOut,
|
|
619
|
+
base_revision: r.baseRevision,
|
|
620
|
+
tree_revision: r.treeRevision,
|
|
621
|
+
changed: r.changed,
|
|
622
|
+
changed_truncated: r.changedTruncated,
|
|
623
|
+
...stream('stdout', r.stdout),
|
|
624
|
+
stdout_truncated: r.stdoutTruncated,
|
|
625
|
+
...stream('stderr', r.stderr),
|
|
626
|
+
stderr_truncated: r.stderrTruncated,
|
|
627
|
+
replayed: r.replayed,
|
|
628
|
+
timings: r.timings,
|
|
629
|
+
error: r.error,
|
|
630
|
+
created_at: r.createdAt,
|
|
631
|
+
finished_at: r.finishedAt,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
// ---- Account plane (0.5.0) ----------------------------------------------------------------------------------------
|
|
635
|
+
/** "*" for the row that is the saved default, else "" (a narrow first column). */
|
|
636
|
+
const mark = (on) => (on ? '*' : '');
|
|
637
|
+
export function orgTable(rows, defaultId) {
|
|
638
|
+
return table(['', 'ID', 'SLUG', 'NAME', 'ROLE', 'CREATED'], rows.map((o) => [mark(o.id === defaultId), o.id, o.slug, o.name, o.role ?? '-', time(o.created_at)]));
|
|
639
|
+
}
|
|
640
|
+
export function projectTable(rows, defaultId) {
|
|
641
|
+
return table(['', 'ID', 'SLUG', 'NAME', 'CREATED'], rows.map((p) => [mark(p.id === defaultId), p.id, p.slug, p.name, time(p.created_at)]));
|
|
642
|
+
}
|
|
643
|
+
/** active, expired or revoked (as the web app shows it). */
|
|
644
|
+
export function keyStatus(k, now = Date.now()) {
|
|
645
|
+
if (k.revoked_at)
|
|
646
|
+
return 'revoked';
|
|
647
|
+
if (k.expires_at && Date.parse(k.expires_at) <= now)
|
|
648
|
+
return 'expired';
|
|
649
|
+
return 'active';
|
|
650
|
+
}
|
|
651
|
+
export function toolsText(tools) {
|
|
652
|
+
if (tools.length === 0)
|
|
653
|
+
return '(none: read-only)';
|
|
654
|
+
return tools.length === 6 ? 'all' : tools.join(',');
|
|
655
|
+
}
|
|
656
|
+
export function apiKeyTable(rows, savedId) {
|
|
657
|
+
return table(['', 'ID', 'KEY ID', 'NAME', 'TOOLS', 'STATUS', 'CREATED', 'EXPIRES', 'LAST USED'], rows.map((k) => [mark(k.id === savedId), k.id, k.key_id, k.name, toolsText(k.tool_permissions), keyStatus(k), time(k.created_at), time(k.expires_at), time(k.last_used_at)]));
|
|
658
|
+
}
|
|
659
|
+
export function memberTable(rows) {
|
|
660
|
+
return table(['USER', 'EMAIL', 'NAME', 'ROLE', 'JOINED'], rows.map((m) => [m.user_id, m.email, m.display_name ?? '-', m.role, time(m.joined_at)]));
|
|
661
|
+
}
|
|
662
|
+
export function invitationTable(rows) {
|
|
663
|
+
return table(['ID', 'EMAIL', 'ROLE', 'STATUS', 'EXPIRES', 'CREATED'], rows.map((i) => [i.id, i.email, i.role, i.status, time(i.expires_at), time(i.created_at)]));
|
|
664
|
+
}
|
|
665
|
+
export function authSessionTable(rows) {
|
|
666
|
+
return table(['', 'ID', 'CLIENT', 'CREATED', 'LAST SEEN', 'EXPIRES', 'IP', 'USER AGENT'], rows.map((s) => [s.current ? '*' : '', s.id, s.client, time(s.created_at), time(s.last_seen_at), time(s.absolute_expires_at), s.client_ip ?? '-', s.user_agent ? (s.user_agent.length > 60 ? `${s.user_agent.slice(0, 59)}…` : s.user_agent) : '-']));
|
|
667
|
+
}
|
|
668
|
+
/** 900 + "usd" -> "9.00 USD" (minor units; zero-decimal currencies shown as whole numbers). */
|
|
669
|
+
/**
|
|
670
|
+
* A usage time series (GET .../usage or /v1/workspaces/{id}/usage): the range and freshness, then one row per bucket
|
|
671
|
+
* and meter. `FINAL` is no while the bucket can still change (the ledger finalizes hours after they end).
|
|
672
|
+
*/
|
|
673
|
+
export function usageSeriesText(series) {
|
|
674
|
+
if (!isObject(series))
|
|
675
|
+
return json(series);
|
|
676
|
+
const s = series;
|
|
677
|
+
const head = [];
|
|
678
|
+
if (s.workspace_id)
|
|
679
|
+
head.push(['workspace', scalar(s.workspace_id)]);
|
|
680
|
+
head.push(['range', `${time(scalar(s.from))} .. ${time(scalar(s.to))}${s.granularity ? ` (by ${scalar(s.granularity)})` : ''}`]);
|
|
681
|
+
const m = isObject(s.measurement) ? s.measurement : {};
|
|
682
|
+
if (m.measured_through !== undefined)
|
|
683
|
+
head.push(['measured through', time(m.measured_through)]);
|
|
684
|
+
const data = Array.isArray(s.data) ? s.data : [];
|
|
685
|
+
const rows = data.map((r) => (isObject(r) ? { ...r, start: time(r.start), finalized: r.finalized === true ? 'yes' : 'no' } : r));
|
|
686
|
+
const table = objectTable(rows, [
|
|
687
|
+
['start', 'START'],
|
|
688
|
+
['meter', 'METER'],
|
|
689
|
+
['raw_quantity', 'RAW'],
|
|
690
|
+
['billable_quantity', 'BILLABLE'],
|
|
691
|
+
['finalized', 'FINAL'],
|
|
692
|
+
]);
|
|
693
|
+
return `${kv(head)}\n${table || 'no usage in this range\n'}`;
|
|
694
|
+
}
|
|
695
|
+
/** The period estimate (GET .../usage/estimate): the bill so far and where each allowance is heading. */
|
|
696
|
+
export function usageEstimateText(estimate) {
|
|
697
|
+
if (!isObject(estimate))
|
|
698
|
+
return json(estimate);
|
|
699
|
+
const e = estimate;
|
|
700
|
+
const currency = typeof e.currency === 'string' ? e.currency : 'USD';
|
|
701
|
+
const amount = (v) => (typeof v === 'number' ? money(v, currency) : '-');
|
|
702
|
+
const fee = isObject(e.subscription_fee) ? e.subscription_fee.amount_minor : e.subscription_fee;
|
|
703
|
+
const head = [];
|
|
704
|
+
if (isObject(e.plan))
|
|
705
|
+
head.push(['plan', scalar(e.plan.plan_key)]);
|
|
706
|
+
if (isObject(e.period))
|
|
707
|
+
head.push(['period', `${time(scalar(e.period.start))} .. ${time(scalar(e.period.end))}`]);
|
|
708
|
+
head.push(['subscription', amount(fee)], ['usage charges', amount(e.usage_charges_minor)], ['estimated total', amount(e.estimated_total_minor)]);
|
|
709
|
+
if (typeof e.overage === 'string')
|
|
710
|
+
head.push(['overage', e.overage]);
|
|
711
|
+
let out = kv(head);
|
|
712
|
+
const projections = objectTable(e.projections, [
|
|
713
|
+
['allowance', 'ALLOWANCE'],
|
|
714
|
+
['unit', 'UNIT'],
|
|
715
|
+
['included', 'INCLUDED'],
|
|
716
|
+
['used', 'USED'],
|
|
717
|
+
['projected_at_period_end', 'PROJECTED'],
|
|
718
|
+
['projected_exhaustion_at', 'RUNS OUT'],
|
|
719
|
+
['enforcement', 'ENFORCEMENT'],
|
|
720
|
+
]);
|
|
721
|
+
if (projections)
|
|
722
|
+
out += `\n${projections}`;
|
|
723
|
+
const notes = Array.isArray(e.notes) ? e.notes.filter((n) => typeof n === 'string') : [];
|
|
724
|
+
if (notes.length)
|
|
725
|
+
out += `\n${notes.map((n) => `note: ${n}`).join('\n')}\n`;
|
|
726
|
+
return out;
|
|
727
|
+
}
|
|
728
|
+
export function money(amountMinor, currency) {
|
|
729
|
+
const zeroDecimal = new Set(['jpy', 'krw', 'vnd', 'clp', 'isk', 'ugx', 'xaf', 'xof']);
|
|
730
|
+
const c = currency.toLowerCase();
|
|
731
|
+
return `${zeroDecimal.has(c) ? String(amountMinor) : (amountMinor / 100).toFixed(2)} ${c.toUpperCase()}`;
|
|
732
|
+
}
|
|
733
|
+
export function planTable(catalog) {
|
|
734
|
+
const rows = catalog.plans.map((p) => {
|
|
735
|
+
const prices = p.prices.length ? p.prices.map((x) => `${money(x.amount_minor, x.currency)}/${x.interval}`).join(', ') : 'free';
|
|
736
|
+
const buy = p.prices.some((x) => x.purchasable) ? 'yes' : '-';
|
|
737
|
+
return [p.plan_key, p.name, prices, buy];
|
|
738
|
+
});
|
|
739
|
+
return `${table(['PLAN', 'NAME', 'PRICE', 'PURCHASABLE'], rows)}catalog ${catalog.catalog_version} (${catalog.mode})\n`;
|
|
740
|
+
}
|
|
741
|
+
export function billingStatusText(s, org) {
|
|
742
|
+
const sub = s.subscription;
|
|
743
|
+
const pending = s.pending_checkout;
|
|
744
|
+
return kv([
|
|
745
|
+
['organization', `${org.name} (${org.slug})`],
|
|
746
|
+
['plan', s.plan ? `${s.plan.plan_key} (${s.plan.source}, catalog ${s.plan.catalog_version})` : '-'],
|
|
747
|
+
['billing state', s.billing_state],
|
|
748
|
+
['subscription', sub ? `${sub.status}${sub.plan_key ? ` ${sub.plan_key}` : ''}${sub.cancel_at_period_end ? ', cancels at period end' : ''}` : 'none'],
|
|
749
|
+
['period end', sub ? time(sub.current_period_end) : '-'],
|
|
750
|
+
...(sub?.grace_until ? [['grace until', time(sub.grace_until)]] : []),
|
|
751
|
+
['pending checkout', pending ? `${pending.id} ${pending.plan_key ?? '-'} ${pending.status}${pending.expires_at ? ` (expires ${time(pending.expires_at)})` : ''}` : '-'],
|
|
752
|
+
['purchase', s.purchase_available ? 'available (shard billing upgrade <plan>)' : 'not available on this API'],
|
|
753
|
+
]);
|
|
754
|
+
}
|
|
755
|
+
export function invoiceTable(rows) {
|
|
756
|
+
return table(['NUMBER', 'STATUS', 'TOTAL', 'PAID', 'DUE', 'PERIOD', 'CREATED', 'URL'], rows.map((i) => [
|
|
757
|
+
i.number ?? i.id,
|
|
758
|
+
i.status ?? '-',
|
|
759
|
+
money(i.total, i.currency),
|
|
760
|
+
money(i.amount_paid, i.currency),
|
|
761
|
+
money(i.amount_remaining, i.currency),
|
|
762
|
+
i.period_start || i.period_end ? `${time(i.period_start).slice(0, 10)}..${time(i.period_end).slice(0, 10)}` : '-',
|
|
763
|
+
time(i.created_at),
|
|
764
|
+
i.hosted_invoice_url ?? '-',
|
|
765
|
+
]));
|
|
766
|
+
}
|
|
767
|
+
export function auditTable(rows) {
|
|
768
|
+
return table(['TIME', 'ACTION', 'OUTCOME', 'ACTOR', 'TARGET', 'REQUEST'], rows.map((e) => [time(e.occurred_at), e.action, e.outcome, `${e.actor.type}${e.actor.id ? `:${e.actor.id}` : ''}`, e.target ? `${e.target.type}${e.target.id ? `:${e.target.id}` : ''}` : '-', e.request_id ?? '-']));
|
|
769
|
+
}
|
|
770
|
+
export function dataExportText(e, out) {
|
|
771
|
+
return kv([
|
|
772
|
+
['export', `${e.kind} ${e.id}`],
|
|
773
|
+
['written to', `${out} (${e.size_bytes} bytes, sha256 ${e.sha256})`],
|
|
774
|
+
['created', time(e.created_at)],
|
|
775
|
+
['expires', `${time(e.expires_at)} (the server copy; download it again before then with the same command)`],
|
|
776
|
+
// The API's entries are sentences ("Workspace file contents: ... ."): run them together as sentences.
|
|
777
|
+
...(e.not_included.length ? [['not included', e.not_included.map((x) => x.trim()).join(' ')]] : []),
|
|
778
|
+
]);
|
|
779
|
+
}
|
|
780
|
+
export function volumeTable(rows) {
|
|
781
|
+
return table(['ID', 'NAME', 'STATE', 'QUOTA', 'SHARED', 'ATTACHED', 'CREATED'], rows.map((v) => [v.id, v.name, `${v.state}${v.deleted_at ? ' (deleted)' : ''}`, `${v.quota_gib} GiB`, v.org_shared ? 'organization' : 'project', String(v.live_attachments), time(v.created_at)]));
|
|
782
|
+
}
|
|
783
|
+
export function volumeDetail(v) {
|
|
784
|
+
const err = v.error;
|
|
785
|
+
return kv([
|
|
786
|
+
['id', v.id],
|
|
787
|
+
['name', v.name],
|
|
788
|
+
['state', `${v.state}${v.state_reason ? ` (${v.state_reason})` : ''}`],
|
|
789
|
+
['quota', `${v.quota_gib} GiB`],
|
|
790
|
+
['shared', v.org_shared ? 'every project of the organization' : `project ${v.project_id}`],
|
|
791
|
+
['attachments', String(v.live_attachments)],
|
|
792
|
+
['usage', pairs(v.usage)],
|
|
793
|
+
['operation', v.active_operation ? `${operationSummary(v.active_operation)} ${v.active_operation.id}` : '-'],
|
|
794
|
+
['error', err ? `${scalar(err.code)}${err.message ? `: ${scalar(err.message)}` : ''}` : '-'],
|
|
795
|
+
['created', time(v.created_at)],
|
|
796
|
+
['deleted', time(v.deleted_at)],
|
|
797
|
+
]);
|
|
798
|
+
}
|
|
799
|
+
export function attachmentTable(rows) {
|
|
800
|
+
return table(['ID', 'WORKSPACE', 'VOLUME', 'MOUNT', 'MODE', 'STATE', 'MOUNTED', 'ATTACHED'], rows.map((a) => [a.id, a.workspace_id, a.volume_id, a.mount_path, a.mode, a.state, a.mounted ? 'yes' : 'no', time(a.attached_at)]));
|
|
801
|
+
}
|
|
802
|
+
function egressPolicyLine(p) {
|
|
803
|
+
if (!p)
|
|
804
|
+
return '-';
|
|
805
|
+
if (p.mode !== 'allowlist')
|
|
806
|
+
return p.mode;
|
|
807
|
+
const rules = p.rules.map((r) => `${r.host}:${r.ports.join('/')}`);
|
|
808
|
+
return `allowlist ${[...rules, ...p.cidrs].join(', ') || '(empty: nothing allowed)'}`;
|
|
809
|
+
}
|
|
810
|
+
export function projectEgressText(p) {
|
|
811
|
+
const pr = p.propagation;
|
|
812
|
+
return kv([
|
|
813
|
+
['project', p.project_id],
|
|
814
|
+
['version', String(p.version)],
|
|
815
|
+
['policy', p.policy ? `${egressPolicyLine({ mode: p.policy.mode ?? 'allow_all', rules: p.policy.rules, cidrs: p.policy.cidrs })} (v${p.policy.version})` : 'none (the platform default applies)'],
|
|
816
|
+
['effective', `${egressPolicyLine(p.effective)} (${p.effective.source})`],
|
|
817
|
+
['override', p.organization_override ? `${p.organization_override.mode}: ${p.organization_override.reason} until ${time(p.organization_override.lifts_at)}` : '-'],
|
|
818
|
+
['workspaces', `${pr.workspaces}: ${pr.enforced} enforced, ${pr.pending} pending, ${pr.failed} failed, ${pr.not_enforced} not enforced${pr.truncated ? ' (truncated)' : ''}`],
|
|
819
|
+
]);
|
|
820
|
+
}
|
|
821
|
+
export function workspaceEgressText(w) {
|
|
822
|
+
const e = w.enforcement;
|
|
823
|
+
return kv([
|
|
824
|
+
['workspace', w.workspace_id],
|
|
825
|
+
['version', String(w.version)],
|
|
826
|
+
['override', w.override ? `${egressPolicyLine(w.override.kind === 'cleared' ? null : { mode: w.override.mode ?? 'allow_all', rules: w.override.rules, cidrs: w.override.cidrs })} (v${w.override.version}${w.override.kind === 'cleared' ? ', cleared' : ''})` : 'none'],
|
|
827
|
+
['project policy', w.project_policy ? `${egressPolicyLine({ mode: w.project_policy.mode ?? 'allow_all', rules: w.project_policy.rules, cidrs: w.project_policy.cidrs })} (v${w.project_policy.version})` : 'none'],
|
|
828
|
+
['template ceiling', w.template_egress ? `${w.template_egress.mode}${w.template_egress.allow_hosts.length ? ` ${w.template_egress.allow_hosts.join(', ')}` : ''}` : '-'],
|
|
829
|
+
['effective', `${egressPolicyLine(w.effective_policy)} (${w.effective.source})`],
|
|
830
|
+
['enforcement', `${e.state}${e.reason ? ` (${e.reason})` : ''}; applied v${e.applied_version ?? '-'} of v${e.effective_version ?? '-'}`],
|
|
831
|
+
]);
|
|
832
|
+
}
|
|
833
|
+
export function egressVersionTable(rows) {
|
|
834
|
+
return table(['VERSION', 'KIND', 'POLICY', 'BY', 'CREATED'], rows.map((v) => [String(v.version), v.kind, v.kind === 'cleared' ? '-' : egressPolicyLine({ mode: v.mode ?? 'allow_all', rules: v.rules, cidrs: v.cidrs }), `${v.created_by.type}:${v.created_by.id}`, time(v.created_at)]));
|
|
835
|
+
}
|
|
836
|
+
// ---- Overage settings (0.5.1; shard billing overage) -----------------------------------------------------------
|
|
837
|
+
const RATE_UNITS = { ram_gib_hours: 'RAM GiB-hour', cpu_hours: 'CPU-hour' };
|
|
838
|
+
/**
|
|
839
|
+
* The spend policy's overage settings for people (GET/PUT /v1/organizations/{id}/spend-policy): the state, the spend
|
|
840
|
+
* cap and its bounds, the rates, and the next step. `saved` adds the version the change was saved as.
|
|
841
|
+
*/
|
|
842
|
+
export function overagePolicyText(p, orgName, saved = false) {
|
|
843
|
+
const m = (minor) => capMoney(minor, p.currency);
|
|
844
|
+
const bound = p.spend_cap_max_minor === null ? '' : `${m(p.spend_cap_min_minor)} to ${m(p.spend_cap_max_minor)}, the plan price`;
|
|
845
|
+
const cap = p.spend_cap_minor === null ? 'no spend cap set' : `spend cap ${m(p.spend_cap_minor)} per billing period`;
|
|
846
|
+
const lines = [];
|
|
847
|
+
switch (p.overage_state) {
|
|
848
|
+
case 'unavailable':
|
|
849
|
+
lines.push(`${orgName}: overage is not available on this plan: it needs a paid plan with opt-in overage (shard billing plans).`);
|
|
850
|
+
break;
|
|
851
|
+
case 'off':
|
|
852
|
+
lines.push(`${orgName}: overage is off (${cap}): new opens and resumes are refused once an allowance is used up.`);
|
|
853
|
+
lines.push(`Turn it on${p.spend_cap_minor === null ? ` with a spend cap${bound ? ` of ${bound}` : ''}: shard billing overage on --cap <dollars>` : ': shard billing overage on'}`);
|
|
854
|
+
break;
|
|
855
|
+
case 'paused':
|
|
856
|
+
lines.push(`${orgName}: overage is on but paused while a plan payment is past due (update the payment method: shard billing portal --open); ${cap}.`);
|
|
857
|
+
break;
|
|
858
|
+
default:
|
|
859
|
+
lines.push(`${orgName}: overage is on, ${cap}${bound ? ` (${bound})` : ''}.`);
|
|
860
|
+
}
|
|
861
|
+
if (p.overage_state !== 'unavailable' && p.rates.length > 0) {
|
|
862
|
+
lines.push(`Rates past the allowances: ${p.rates.map((r) => `${m(r.amount_minor_per_unit)} per ${RATE_UNITS[r.allowance] ?? r.unit}`).join(', ')}, charged on the next invoice (this period: shard usage).`);
|
|
863
|
+
}
|
|
864
|
+
if (saved)
|
|
865
|
+
lines.push(`Saved (policy version ${p.version}).`);
|
|
866
|
+
return `${lines.join('\n')}\n`;
|
|
867
|
+
}
|
package/dist/handlers.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Every command's handler, keyed by its path ("workspaces open", "auth login"): the project-key commands of
|
|
3
|
+
* commands.ts and the account plane of account-commands.ts (0.5.0). args.test.ts checks that it matches COMMANDS.
|
|
4
|
+
*/
|
|
5
|
+
import { ACCOUNT_HANDLERS } from "./account-commands.js";
|
|
6
|
+
import { KEY_HANDLERS } from "./commands.js";
|
|
7
|
+
export const HANDLERS = { ...KEY_HANDLERS, ...ACCOUNT_HANDLERS };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,13 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @shardflux/cli — `shard`, the Shardflux command line on @shardflux/sdk.
|
|
3
|
-
* The executable is src/bin.ts; `run()` drives the same code in-process.
|
|
3
|
+
* The executable is src/bin.ts (`main()`); `run()` drives the same code in-process. A package that embeds the CLI
|
|
4
|
+
* (the `shardflux` npm bundle) calls `main({ distribution: { package, version } })` so the version check names it.
|
|
4
5
|
*/
|
|
5
|
-
export { run, abortableSleep } from './main.js';
|
|
6
|
-
export {
|
|
6
|
+
export { run, main, abortableSleep } from './main.js';
|
|
7
|
+
export type { MainOptions, RunOptions } from './main.js';
|
|
8
|
+
export { COMMANDS, GLOBAL_OPTIONS, CLI_VERSION, HELP_SECTIONS, commandHelp, findCommand, groupHelp, normalizeWords, parseCommandLine, parseDuration, parseEnvPairs, parsePositiveInt, resolveApiUrl, resolveWake, resolveWakeTimeout, topHelp } from './args.js';
|
|
7
9
|
export type { CommandSpec, OptionSpec, Parsed, PositionalSpec, Values } from './args.js';
|
|
8
|
-
export { HANDLERS
|
|
9
|
-
export
|
|
10
|
-
export {
|
|
10
|
+
export { HANDLERS } from './handlers.js';
|
|
11
|
+
export { KEY_HANDLERS, execExitCode, resolveWorkspace, whenIdleSeconds } from './commands.js';
|
|
12
|
+
export type { Ctx, Handler, Io, ResolvedApiKey, Writer } from './commands.js';
|
|
13
|
+
export { ACCOUNT_HANDLERS, DEFAULT_API_URL, defaultOrgName, exportLine, parseTime } from './account-commands.js';
|
|
14
|
+
export type { ShellKind } from './account-commands.js';
|
|
15
|
+
export { CREDENTIALS_FILE, CredentialStore, CredentialsFileError, configDir, ensurePrivateDir, parseCredentials, writePrivateFile } from './credentials.js';
|
|
16
|
+
export type { CredentialsDoc, Profile, StoredApiKey, StoredSession, StoredUser } from './credentials.js';
|
|
17
|
+
export { Input } from './input.js';
|
|
18
|
+
export type { StdinLike } from './input.js';
|
|
19
|
+
export { CHECK_INTERVAL_MS, CLI_DISTRIBUTION, NOTICE_WAIT_MS, UPDATE_CHECK_FILE, UpdateCheck, noticeOf, statusFromEntry, updateCheckDisabled, updateView } from './update-check.js';
|
|
20
|
+
export type { Distribution, UpdateView } from './update-check.js';
|
|
21
|
+
export { openUrl, openerFor } from './browser.js';
|
|
22
|
+
export { EXIT, AuthConfigError, CheckoutEndedError, ExecutionFailedError, ExecutionPendingError, InterruptedError, MfaPendingError, NotFoundError, UsageError, describeError, exitCodeFor, redact } from './errors.js';
|
|
11
23
|
export type { ErrorInfo } from './errors.js';
|
|
12
|
-
export { errorText, json, table, time, usageText, workspaceDetail, workspaceTable } from './format.js';
|
|
24
|
+
export { changedText, errorText, executionJson, executionSummary, feedbackCommand, json, suspendRequestText, suspendWhenIdleText, table, time, usageText, workspaceDetail, workspaceTable } from './format.js';
|
|
13
25
|
export { TimingRecorder, timingJson, timingText } from './timing.js';
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @shardflux/cli — `shard`, the Shardflux command line on @shardflux/sdk.
|
|
3
|
-
* The executable is src/bin.ts; `run()` drives the same code in-process.
|
|
3
|
+
* The executable is src/bin.ts (`main()`); `run()` drives the same code in-process. A package that embeds the CLI
|
|
4
|
+
* (the `shardflux` npm bundle) calls `main({ distribution: { package, version } })` so the version check names it.
|
|
4
5
|
*/
|
|
5
|
-
export { run, abortableSleep } from "./main.js";
|
|
6
|
-
export { COMMANDS, GLOBAL_OPTIONS, CLI_VERSION, commandHelp, findCommand, normalizeWords, parseCommandLine, parseDuration, parseEnvPairs, parsePositiveInt, resolveApiUrl, resolveWake, resolveWakeTimeout, topHelp } from "./args.js";
|
|
7
|
-
export { HANDLERS
|
|
8
|
-
export {
|
|
9
|
-
export {
|
|
6
|
+
export { run, main, abortableSleep } from "./main.js";
|
|
7
|
+
export { COMMANDS, GLOBAL_OPTIONS, CLI_VERSION, HELP_SECTIONS, commandHelp, findCommand, groupHelp, normalizeWords, parseCommandLine, parseDuration, parseEnvPairs, parsePositiveInt, resolveApiUrl, resolveWake, resolveWakeTimeout, topHelp } from "./args.js";
|
|
8
|
+
export { HANDLERS } from "./handlers.js";
|
|
9
|
+
export { KEY_HANDLERS, execExitCode, resolveWorkspace, whenIdleSeconds } from "./commands.js";
|
|
10
|
+
export { ACCOUNT_HANDLERS, DEFAULT_API_URL, defaultOrgName, exportLine, parseTime } from "./account-commands.js";
|
|
11
|
+
export { CREDENTIALS_FILE, CredentialStore, CredentialsFileError, configDir, ensurePrivateDir, parseCredentials, writePrivateFile } from "./credentials.js";
|
|
12
|
+
export { Input } from "./input.js";
|
|
13
|
+
export { CHECK_INTERVAL_MS, CLI_DISTRIBUTION, NOTICE_WAIT_MS, UPDATE_CHECK_FILE, UpdateCheck, noticeOf, statusFromEntry, updateCheckDisabled, updateView } from "./update-check.js";
|
|
14
|
+
export { openUrl, openerFor } from "./browser.js";
|
|
15
|
+
export { EXIT, AuthConfigError, CheckoutEndedError, ExecutionFailedError, ExecutionPendingError, InterruptedError, MfaPendingError, NotFoundError, UsageError, describeError, exitCodeFor, redact } from "./errors.js";
|
|
16
|
+
export { changedText, errorText, executionJson, executionSummary, feedbackCommand, json, suspendRequestText, suspendWhenIdleText, table, time, usageText, workspaceDetail, workspaceTable } from "./format.js";
|
|
10
17
|
export { TimingRecorder, timingJson, timingText } from "./timing.js";
|