@shardflux/cli 0.1.0 → 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.
- package/README.md +70 -8
- package/dist/args.d.ts +10 -1
- package/dist/args.js +326 -12
- package/dist/commands.d.ts +9 -1
- package/dist/commands.js +585 -11
- package/dist/format.d.ts +20 -1
- package/dist/format.js +132 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/main.js +5 -1
- package/package.json +2 -2
package/dist/format.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* `--json` output is the API's own objects (see README "JSON output"), so
|
|
4
4
|
* these functions only shape what people read.
|
|
5
5
|
*/
|
|
6
|
-
import type { AgentSession, FileList, Operation, WorkspaceView } from '@shardflux/sdk';
|
|
6
|
+
import type { AgentSession, DraftState, FileList, Operation, Secret, SecretAccessEvent, SecretVersion, TemplateBuild, TemplateDiffEntry, TemplateDraft, TemplateFileEntry, TemplateSummary, WorkspaceChange, WorkspaceSecretBindings, WorkspaceView } from '@shardflux/sdk';
|
|
7
7
|
import type { ErrorInfo } from './errors.js';
|
|
8
8
|
export declare function json(value: unknown): string;
|
|
9
9
|
export declare function table(headers: readonly string[], rows: readonly (readonly string[])[]): string;
|
|
@@ -14,6 +14,8 @@ export declare function time(iso: string | null | undefined): string;
|
|
|
14
14
|
export declare function pairs(obj: Record<string, unknown> | null | undefined): string;
|
|
15
15
|
export declare function operationSummary(op: Operation | null | undefined): string;
|
|
16
16
|
export declare const WORKSPACE_HEADERS: readonly ["ID", "KEY", "STATE", "DESIRED", "TEMPLATE", "OPERATION", "CREATED"];
|
|
17
|
+
/** Observed state plus what sets the workspace apart: session, draft or test instance, and how it ended. */
|
|
18
|
+
export declare function workspaceState(w: WorkspaceView): string;
|
|
17
19
|
export declare function workspaceRow(w: WorkspaceView): string[];
|
|
18
20
|
export declare function workspaceTable(ws: readonly WorkspaceView[]): string;
|
|
19
21
|
export declare function isReady(w: WorkspaceView): boolean;
|
|
@@ -23,6 +25,12 @@ export declare function operationTable(ops: readonly Operation[]): string;
|
|
|
23
25
|
export declare function operationDetail(op: Operation): string;
|
|
24
26
|
export declare function sessionTable(sessions: readonly AgentSession[]): string;
|
|
25
27
|
export declare function fileTable(list: FileList): string;
|
|
28
|
+
export declare function secretTable(rows: readonly Secret[]): string;
|
|
29
|
+
/** Secret metadata; there is no value to show (values are write-only). */
|
|
30
|
+
export declare function secretDetail(s: Secret): string;
|
|
31
|
+
export declare function secretVersionTable(rows: readonly SecretVersion[]): string;
|
|
32
|
+
export declare function secretAccessTable(rows: readonly SecretAccessEvent[]): string;
|
|
33
|
+
export declare function bindingsText(b: WorkspaceSecretBindings): string;
|
|
26
34
|
/**
|
|
27
35
|
* The usage summary (GET /v1/organizations/{id}/usage/summary): plan, period, measurement
|
|
28
36
|
* freshness, then meters and allowances as tables. Columns absent from the response are omitted.
|
|
@@ -30,3 +38,14 @@ export declare function fileTable(list: FileList): string;
|
|
|
30
38
|
export declare function usageText(summary: unknown): string;
|
|
31
39
|
/** One stderr line: `error: <message> [code=... status=... request_id=...]` plus a hint when there is one. */
|
|
32
40
|
export declare function errorText(e: ErrorInfo, hint?: string): string;
|
|
41
|
+
export declare function templateTable(rows: readonly TemplateSummary[]): string;
|
|
42
|
+
/** Permission bits as the four-digit octal `ls -l` users read (e.g. 0755). */
|
|
43
|
+
export declare function octal(mode: number): string;
|
|
44
|
+
export declare function templateFileTable(rows: readonly TemplateFileEntry[]): string;
|
|
45
|
+
export declare function templateEntryText(e: TemplateFileEntry): string;
|
|
46
|
+
export declare function diffTable(rows: readonly TemplateDiffEntry[]): string;
|
|
47
|
+
export declare function diffSummaryText(summary: unknown): string;
|
|
48
|
+
export declare function changesTable(rows: readonly WorkspaceChange[]): string;
|
|
49
|
+
export declare function draftText(d: TemplateDraft): string;
|
|
50
|
+
export declare function draftStateTable(rows: readonly DraftState[]): string;
|
|
51
|
+
export declare function buildText(b: TemplateBuild): string;
|
package/dist/format.js
CHANGED
|
@@ -44,8 +44,21 @@ 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
|
+
/** Observed state plus what sets the workspace apart: session, draft or test instance, and how it ended. */
|
|
48
|
+
export function workspaceState(w) {
|
|
49
|
+
const tags = [];
|
|
50
|
+
if (w.purpose === 'template_draft')
|
|
51
|
+
tags.push('draft');
|
|
52
|
+
else if (w.purpose === 'template_test')
|
|
53
|
+
tags.push('test instance');
|
|
54
|
+
else if (w.lifetime === 'session')
|
|
55
|
+
tags.push('session');
|
|
56
|
+
if (w.deleted_at)
|
|
57
|
+
tags.push(w.ended_reason ? `ended: ${w.ended_reason}` : 'deleted');
|
|
58
|
+
return tags.length ? `${w.observed_state} (${tags.join(', ')})` : w.observed_state;
|
|
59
|
+
}
|
|
47
60
|
export function workspaceRow(w) {
|
|
48
|
-
const state = w
|
|
61
|
+
const state = workspaceState(w);
|
|
49
62
|
return [w.id, w.workspace_key, state, w.desired_state, `${w.template.slug}@${w.template.version}`, operationSummary(w.active_operation), time(w.created_at)];
|
|
50
63
|
}
|
|
51
64
|
export function workspaceTable(ws) {
|
|
@@ -69,6 +82,15 @@ export function workspaceDetail(w) {
|
|
|
69
82
|
['operation', w.active_operation ? `${operationSummary(w.active_operation)} ${w.active_operation.id}` : '-'],
|
|
70
83
|
['pending', w.pending_reason ?? '-'],
|
|
71
84
|
['forked from', w.forked_from_workspace_id ?? '-'],
|
|
85
|
+
...(w.lifetime !== undefined
|
|
86
|
+
? [
|
|
87
|
+
['lifetime', w.lifetime === 'session' ? `session (ends after ${w.idle_timeout_seconds ?? '-'} s idle, or on close)` : 'persistent'],
|
|
88
|
+
['purpose', w.purpose],
|
|
89
|
+
['disk layout', w.disk_layout],
|
|
90
|
+
...(w.origin ? [['origin', w.origin.kind === 'fork' ? `fork of ${w.origin.workspace_id}` : `draft state ${w.origin.checkpoint_id ?? '(capturing)'} of ${w.origin.draft_workspace_id}`]] : []),
|
|
91
|
+
...(w.ended_reason ? [['ended', w.ended_reason]] : []),
|
|
92
|
+
]
|
|
93
|
+
: []),
|
|
72
94
|
['created', time(w.created_at)],
|
|
73
95
|
['deleted', time(w.deleted_at)],
|
|
74
96
|
]);
|
|
@@ -97,6 +119,38 @@ export function sessionTable(sessions) {
|
|
|
97
119
|
export function fileTable(list) {
|
|
98
120
|
return table(['TYPE', 'SIZE', 'MODIFIED', 'NAME'], list.entries.map((e) => [e.type, String(e.size), time(e.modified_at), e.name]));
|
|
99
121
|
}
|
|
122
|
+
export function secretTable(rows) {
|
|
123
|
+
return table(['ID', 'NAME', 'SCOPE', 'VERSION', 'TOOLS', 'UPDATED', 'DELETED'], rows.map((s) => [s.id, s.name, s.scope, String(s.current_version), s.permissions.allowed_tools.join(','), time(s.updated_at), time(s.deleted_at)]));
|
|
124
|
+
}
|
|
125
|
+
const idList = (ids, all) => (ids === null ? all : ids.length ? ids.join(', ') : '(none)');
|
|
126
|
+
/** Secret metadata; there is no value to show (values are write-only). */
|
|
127
|
+
export function secretDetail(s) {
|
|
128
|
+
return kv([
|
|
129
|
+
['id', s.id],
|
|
130
|
+
['name', s.name],
|
|
131
|
+
['scope', s.scope === 'project' ? `project ${s.project_id ?? '-'}` : `organization ${s.organization_id}`],
|
|
132
|
+
['description', s.description || '-'],
|
|
133
|
+
['version', String(s.current_version)],
|
|
134
|
+
['tools', s.permissions.allowed_tools.join(', ')],
|
|
135
|
+
...(s.scope === 'organization' ? [['projects', idList(s.permissions.allowed_project_ids, '(every project)')]] : []),
|
|
136
|
+
['workspaces', idList(s.permissions.allowed_workspace_ids, '(any)')],
|
|
137
|
+
['created', `${time(s.created_at)} by ${s.created_by.type}:${s.created_by.id}`],
|
|
138
|
+
['updated', time(s.updated_at)],
|
|
139
|
+
['rotated', time(s.rotated_at)],
|
|
140
|
+
['deleted', time(s.deleted_at)],
|
|
141
|
+
]);
|
|
142
|
+
}
|
|
143
|
+
export function secretVersionTable(rows) {
|
|
144
|
+
return table(['VERSION', 'STATE', 'CREATED', 'BY', 'DESTROYED', 'REASON'], rows.map((v) => [String(v.version), v.state, time(v.created_at), `${v.created_by.type}:${v.created_by.id}`, time(v.destroyed_at), v.destroyed_reason ?? '-']));
|
|
145
|
+
}
|
|
146
|
+
export function secretAccessTable(rows) {
|
|
147
|
+
return table(['TIME', 'OUTCOME', 'REASON', 'TOOL', 'VERSION', 'WORKSPACE', 'PRINCIPAL', 'REQUEST'], rows.map((e) => [time(e.created_at), e.outcome, e.reason ?? '-', e.tool, e.secret_version === null ? '-' : String(e.secret_version), e.workspace_id, `${e.principal.type}:${e.principal.id}`, e.request_id ?? '-']));
|
|
148
|
+
}
|
|
149
|
+
export function bindingsText(b) {
|
|
150
|
+
if (b.secrets.length === 0)
|
|
151
|
+
return 'No secrets are bound to this workspace.\n';
|
|
152
|
+
return table(['NAME', 'STATUS', 'SCOPE', 'SECRET'], b.secrets.map((s) => [s.name, s.status, s.scope ?? '-', s.secret_id ?? '-']));
|
|
153
|
+
}
|
|
100
154
|
function isObject(v) {
|
|
101
155
|
return typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
102
156
|
}
|
|
@@ -175,3 +229,80 @@ export function errorText(e, hint) {
|
|
|
175
229
|
: '';
|
|
176
230
|
return `error: ${e.message}${reason} [${meta.join(' ')}]\n${issues}${hint ? `hint: ${hint}\n` : ''}`;
|
|
177
231
|
}
|
|
232
|
+
// ---- Templates v2 (contracts §19) ---------------------------------------------------------------
|
|
233
|
+
export function templateTable(rows) {
|
|
234
|
+
return table(['SLUG', 'OWNER', 'OPEN VERSION', 'LAYOUTS', 'FILES', 'DRAFT', 'ARCHIVED'], rows.map((t) => {
|
|
235
|
+
const v = t.open_version;
|
|
236
|
+
return [
|
|
237
|
+
t.slug,
|
|
238
|
+
t.owner,
|
|
239
|
+
v ? `v${v.version}` : '-',
|
|
240
|
+
v?.disk_layouts?.join(',') ?? '-',
|
|
241
|
+
v?.files ? v.files.state : '-',
|
|
242
|
+
t.draft ? `${t.draft.workspace_key} (${t.draft.observed_state})` : '-',
|
|
243
|
+
time(t.archived_at),
|
|
244
|
+
];
|
|
245
|
+
}));
|
|
246
|
+
}
|
|
247
|
+
/** Permission bits as the four-digit octal `ls -l` users read (e.g. 0755). */
|
|
248
|
+
export function octal(mode) {
|
|
249
|
+
return mode.toString(8).padStart(4, '0');
|
|
250
|
+
}
|
|
251
|
+
export function templateFileTable(rows) {
|
|
252
|
+
return table(['TYPE', 'MODE', 'OWNER', 'SIZE', 'NAME'], rows.map((e) => [e.type, octal(e.mode), `${e.uid}:${e.gid}`, e.type === 'dir' ? `${e.child_count ?? 0} entries` : String(e.size_bytes), e.link_target ? `${e.name} -> ${e.link_target}` : e.name]));
|
|
253
|
+
}
|
|
254
|
+
export function templateEntryText(e) {
|
|
255
|
+
return kv([
|
|
256
|
+
['path', e.path],
|
|
257
|
+
['type', e.type],
|
|
258
|
+
['size', `${e.size_bytes} bytes`],
|
|
259
|
+
['mode', octal(e.mode)],
|
|
260
|
+
['owner', `${e.uid}:${e.gid}`],
|
|
261
|
+
['sha256', e.sha256 ?? '-'],
|
|
262
|
+
['link target', e.link_target ?? '-'],
|
|
263
|
+
['hardlink of', e.hardlink_of ?? '-'],
|
|
264
|
+
...(e.type === 'dir' ? [['entries', String(e.child_count ?? 0)]] : []),
|
|
265
|
+
]);
|
|
266
|
+
}
|
|
267
|
+
function side(x) {
|
|
268
|
+
if (!x)
|
|
269
|
+
return '-';
|
|
270
|
+
return `${x.type} ${x.type === 'symlink' ? `-> ${x.link_target ?? '?'}` : `${x.size_bytes} B`} ${octal(x.mode)} ${x.uid}:${x.gid}`;
|
|
271
|
+
}
|
|
272
|
+
export function diffTable(rows) {
|
|
273
|
+
return table(['CHANGE', 'BEFORE', 'AFTER', 'PATH'], rows.map((d) => [d.change, side(d.before), side(d.after), d.path]));
|
|
274
|
+
}
|
|
275
|
+
export function diffSummaryText(summary) {
|
|
276
|
+
if (typeof summary !== 'object' || summary === null)
|
|
277
|
+
return '';
|
|
278
|
+
const s = summary;
|
|
279
|
+
return `added ${s.added}, removed ${s.removed}, changed ${s.changed}, type changed ${s.type_changed}, metadata ${s.metadata}; +${s.bytes_added} / -${s.bytes_removed} bytes\n`;
|
|
280
|
+
}
|
|
281
|
+
export function changesTable(rows) {
|
|
282
|
+
return table(['CHANGE', 'TYPE', 'SIZE', 'MODE', 'PATH'], rows.map((c) => [c.change, c.type, String(c.size_bytes), c.mode, c.link_target ? `${c.path} -> ${c.link_target}` : c.path]));
|
|
283
|
+
}
|
|
284
|
+
export function draftText(d) {
|
|
285
|
+
const w = d.workspace;
|
|
286
|
+
return kv([
|
|
287
|
+
['workspace', `${w.workspace_key} (${w.id})`],
|
|
288
|
+
['state', `${w.observed_state} (desired ${w.desired_state})`],
|
|
289
|
+
['base version', `${d.base_version.slug} v${d.base_version.version} (${d.base_version.id})`],
|
|
290
|
+
['states', String(d.states_count)],
|
|
291
|
+
['latest state', d.latest_state ? `${d.latest_state.checkpoint_id}${d.latest_state.label ? ` "${d.latest_state.label}"` : ''} ${time(d.latest_state.captured_at)}` : '-'],
|
|
292
|
+
['test instances', `${d.test_instances_live} live`],
|
|
293
|
+
['created', `${time(d.created_at)} by ${d.created_by.type}${d.created_by.id ? `:${d.created_by.id}` : ''}`],
|
|
294
|
+
]);
|
|
295
|
+
}
|
|
296
|
+
export function draftStateTable(rows) {
|
|
297
|
+
return table(['STATE', 'LABEL', 'CAPTURED', 'BYTES', 'TEST INSTANCES'], rows.map((st) => [st.checkpoint_id, st.label ?? '-', time(st.captured_at), st.bytes === null ? '-' : String(st.bytes), String(st.test_instances)]));
|
|
298
|
+
}
|
|
299
|
+
export function buildText(b) {
|
|
300
|
+
return kv([
|
|
301
|
+
['build', b.id],
|
|
302
|
+
['template', `${b.template.slug} v${b.target_version ?? '?'}`],
|
|
303
|
+
['state', `${b.state}${b.failure ? ` (${b.failure.code}: ${b.failure.message})` : ''}`],
|
|
304
|
+
['registration', b.registration.state],
|
|
305
|
+
['source', b.source_kind === 'workspace' ? `workspace ${b.source_workspace_id ?? '-'}${b.source_checkpoint_id ? ` checkpoint ${b.source_checkpoint_id}` : ''}` : 'recipe'],
|
|
306
|
+
['version', b.template_version ? `${b.template_version.id}${b.template_version.published ? ' (published)' : ' (unpublished)'}` : '-'],
|
|
307
|
+
]) + (b.state === 'published' || b.state === 'failed' || b.state === 'canceled' ? '' : `the build continues server side (--wait follows it; GET /v1/organizations/${b.organization_id}/template-builds/${b.id} reports it)\n`);
|
|
308
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* The executable is src/bin.ts; `run()` drives the same code in-process.
|
|
4
4
|
*/
|
|
5
5
|
export { run, abortableSleep } from './main.js';
|
|
6
|
-
export { COMMANDS, GLOBAL_OPTIONS, CLI_VERSION, commandHelp, findCommand, normalizeWords, parseCommandLine, parseDuration, parseEnvPairs, parsePositiveInt, resolveApiUrl, topHelp } from './args.js';
|
|
6
|
+
export { COMMANDS, GLOBAL_OPTIONS, CLI_VERSION, commandHelp, findCommand, normalizeWords, parseCommandLine, parseDuration, parseEnvPairs, parsePositiveInt, resolveApiUrl, resolveWake, resolveWakeTimeout, topHelp } from './args.js';
|
|
7
7
|
export type { CommandSpec, OptionSpec, Parsed, PositionalSpec, Values } from './args.js';
|
|
8
8
|
export { HANDLERS, execExitCode, resolveWorkspace } from './commands.js';
|
|
9
9
|
export type { Ctx, Handler, Io, Writer } from './commands.js';
|
package/dist/index.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* The executable is src/bin.ts; `run()` drives the same code in-process.
|
|
4
4
|
*/
|
|
5
5
|
export { run, abortableSleep } from "./main.js";
|
|
6
|
-
export { COMMANDS, GLOBAL_OPTIONS, CLI_VERSION, commandHelp, findCommand, normalizeWords, parseCommandLine, parseDuration, parseEnvPairs, parsePositiveInt, resolveApiUrl, topHelp } from "./args.js";
|
|
6
|
+
export { COMMANDS, GLOBAL_OPTIONS, CLI_VERSION, commandHelp, findCommand, normalizeWords, parseCommandLine, parseDuration, parseEnvPairs, parsePositiveInt, resolveApiUrl, resolveWake, resolveWakeTimeout, topHelp } from "./args.js";
|
|
7
7
|
export { HANDLERS, execExitCode, resolveWorkspace } from "./commands.js";
|
|
8
8
|
export { EXIT, AuthConfigError, InterruptedError, NotFoundError, UsageError, describeError, exitCodeFor, redact } from "./errors.js";
|
|
9
9
|
export { errorText, json, table, time, usageText, workspaceDetail, workspaceTable } from "./format.js";
|
package/dist/main.js
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* the whole CLI can be driven by tests; bin.ts wires it to the real process.
|
|
6
6
|
*/
|
|
7
7
|
import { SDK_VERSION, Shardflux } from '@shardflux/sdk';
|
|
8
|
-
import { CLI_VERSION, parseCommandLine, resolveApiUrl } from "./args.js";
|
|
8
|
+
import { CLI_VERSION, parseCommandLine, resolveApiUrl, resolveWake, resolveWakeTimeout } from "./args.js";
|
|
9
9
|
import { HANDLERS } from "./commands.js";
|
|
10
10
|
import { AuthConfigError, EXIT, InterruptedError, UsageError, describeError, exitCodeFor, redact } from "./errors.js";
|
|
11
11
|
import { errorText, json } from "./format.js";
|
|
@@ -76,12 +76,16 @@ export async function run(argv, io, opts = {}) {
|
|
|
76
76
|
const agentLabel = parsed.global.agentLabel ?? io.env.SHARDFLUX_AGENT_LABEL ?? 'cli';
|
|
77
77
|
if (!isLabel(agentLabel))
|
|
78
78
|
throw new UsageError('--agent-label must be 1-100 printable characters');
|
|
79
|
+
const wake = resolveWake(parsed.global.wake, io.env.SHARDFLUX_NO_WAKE);
|
|
80
|
+
const wakeTimeoutMs = resolveWakeTimeout(parsed.global.wakeTimeout, io.env.SHARDFLUX_WAKE_TIMEOUT_MS);
|
|
79
81
|
let cloud;
|
|
80
82
|
ctx = {
|
|
81
83
|
io,
|
|
82
84
|
json: parsed.global.json,
|
|
83
85
|
apiUrl,
|
|
84
86
|
agentLabel,
|
|
87
|
+
wake,
|
|
88
|
+
wakeTimeoutMs,
|
|
85
89
|
signal,
|
|
86
90
|
cloud() {
|
|
87
91
|
if (cloud)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "shard: the Shardflux command line. Open, run commands in, move files to, suspend, resume and fork persistent agent workspaces.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -38,7 +38,7 @@
|
|
|
38
38
|
"access": "public"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@shardflux/sdk": "^0.
|
|
41
|
+
"@shardflux/sdk": "^0.6.0"
|
|
42
42
|
},
|
|
43
43
|
"devDependencies": {
|
|
44
44
|
"@eslint/js": "10.0.1",
|