@shardflux/cli 0.3.0 → 0.3.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 +19 -1
- package/README.md +21 -4
- package/dist/args.d.ts +1 -1
- package/dist/args.js +4 -4
- package/dist/errors.d.ts +1 -1
- package/dist/errors.js +7 -2
- package/dist/format.d.ts +4 -1
- package/dist/format.js +18 -2
- package/dist/main.js +3 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -3,7 +3,25 @@
|
|
|
3
3
|
Every command and option the README shows is available from the version named here. Below 1.0, a minor release may
|
|
4
4
|
break compatibility; breaking changes are marked **Breaking**.
|
|
5
5
|
|
|
6
|
-
## 0.3.
|
|
6
|
+
## 0.3.1 (not yet published; npm `latest` is 0.3.0)
|
|
7
|
+
|
|
8
|
+
Needs `@shardflux/sdk` 0.6.2 or later.
|
|
9
|
+
|
|
10
|
+
The API no longer lets a start (open, resume, fork) wait in `capacity_pending` forever: one that no host could admit
|
|
11
|
+
15 minutes after it began fails with `capacity_unavailable` (retryable; nothing was started, a suspended workspace
|
|
12
|
+
stays suspended).
|
|
13
|
+
|
|
14
|
+
- A failed operation's error has `retryable` (the operation error's own flag): `retryable=true|false` in the error line,
|
|
15
|
+
`"retryable"` in the `--json` error document, next to `details.error_code`. `details.reason` carries the operation
|
|
16
|
+
error's reason (for `capacity_unavailable`, why no host fit, e.g. `no_ready_hosts`), printed after the message.
|
|
17
|
+
- A retryable failure gets a hint to retry later. Exit codes are unchanged (6 for any failed operation); `shard` does
|
|
18
|
+
not retry.
|
|
19
|
+
- A `--timeout` that ends while the start waits for capacity names the time it gives up (the SDK's message).
|
|
20
|
+
- Help and README: `--timeout` and exit codes 5 and 6 describe the 15-minute limit.
|
|
21
|
+
- `shard workspaces get` names the origin of a template version test instance (`template <id> version <n>`, contracts
|
|
22
|
+
§24.6) instead of reading it as a draft state.
|
|
23
|
+
|
|
24
|
+
## 0.3.0 (2026-09-28)
|
|
7
25
|
|
|
8
26
|
Needs `@shardflux/sdk` 0.6.0 or later.
|
|
9
27
|
|
package/README.md
CHANGED
|
@@ -106,6 +106,21 @@ never runs twice, because the workspace refused it before doing anything.
|
|
|
106
106
|
is still queued after 120000 ms`). The operation continues server side:
|
|
107
107
|
`shard operations wait <id>` keeps waiting.
|
|
108
108
|
- A resume or open that fails exits 6.
|
|
109
|
+
|
|
110
|
+
## Starts that wait for capacity
|
|
111
|
+
|
|
112
|
+
An open, resume or fork that no host can admit yet waits in `capacity_pending` for at most 15
|
|
113
|
+
minutes. If `--timeout` ends first, the timeout error names the time it gives up. A start still
|
|
114
|
+
pending then fails with `capacity_unavailable`: nothing was started, and a suspended
|
|
115
|
+
workspace stays suspended. `shard` exits 6 as for any failed operation, and the error says
|
|
116
|
+
`retryable=true` (with `--json`, `"retryable": true`) with a hint to retry later:
|
|
117
|
+
|
|
118
|
+
```text
|
|
119
|
+
error: Operation 01a0df37-b3a9-7a14-a3e7-11395c349c39 (open) failed: capacity_unavailable (no_ready_hosts) [code=operation_failed operation_id=01a0df37-b3a9-7a14-a3e7-11395c349c39 retryable=true]
|
|
120
|
+
hint: no host could admit it before its deadline; nothing was started. Retry later.
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
`shard` does not retry it for you. A definitive failure has `retryable=false`.
|
|
109
124
|
- `--no-wake` (or `SHARDFLUX_NO_WAKE=1`) turns this off. A workspace that is not running is then
|
|
110
125
|
refused at once (exit 1, `workspace_not_running`).
|
|
111
126
|
|
|
@@ -179,7 +194,7 @@ printf %s "$NEW_KEY" | shard secrets rotate OPENAI_API_KEY
|
|
|
179
194
|
|
|
180
195
|
- **Human (default):** aligned tables and key/value blocks on stdout. Errors go to stderr as
|
|
181
196
|
`error: <message> (<reason>) [code=... status=... request_id=... operation_id=...]`, with a
|
|
182
|
-
hint when there is one.
|
|
197
|
+
hint when there is one. A failed operation adds `retryable=true|false` (0.3.1+).
|
|
183
198
|
- **`--json`:** stdout carries the API's own objects:
|
|
184
199
|
- `workspaces open`/`get` print the workspace view; `list` prints `{data, next_cursor}`.
|
|
185
200
|
- Lifecycle commands print the operation; `fork` prints `{operation, workspace}`.
|
|
@@ -190,7 +205,9 @@ printf %s "$NEW_KEY" | shard secrets rotate OPENAI_API_KEY
|
|
|
190
205
|
print `{data, next_cursor}`; `rotate` prints `{secret, version}`; `delete` prints
|
|
191
206
|
`{deleted, id, name, scope}`; `workspaces secrets` prints `{workspace_id, names, secrets}`.
|
|
192
207
|
|
|
193
|
-
Errors go to stderr as `{"error": {code, message, status, request_id, ...}}`.
|
|
208
|
+
Errors go to stderr as `{"error": {code, message, status, request_id, ...}}`. A failed operation is
|
|
209
|
+
`{"error": {"code": "operation_failed", "message", "operation_id", "retryable", "details": {"error_code",
|
|
210
|
+
"reason"}, "operation"}}`; `retryable` (0.3.1+) is the operation error's own flag.
|
|
194
211
|
- **`--timing`:** the timing goes to stderr after the output. With `--json` it is a `timing` field instead
|
|
195
212
|
(see "Timing").
|
|
196
213
|
|
|
@@ -203,8 +220,8 @@ printf %s "$NEW_KEY" | shard secrets rotate OPENAI_API_KEY
|
|
|
203
220
|
| 2 | usage error: bad arguments, or input the API rejected as `validation_failed` |
|
|
204
221
|
| 3 | not found: workspace, operation or route |
|
|
205
222
|
| 4 | authentication or authorization: missing, malformed, revoked or insufficient key |
|
|
206
|
-
| 5 | timeout: waiting gave up (`--timeout`, or `--wake-timeout` while waking a workspace); the operation continues server side (`shard operations wait <id>`) |
|
|
207
|
-
| 6 | the awaited operation ended `failed` or `canceled` (also a resume or open that failed while waking a workspace) |
|
|
223
|
+
| 5 | timeout: waiting gave up (`--timeout`, or `--wake-timeout` while waking a workspace); the operation continues server side (`shard operations wait <id>`), a start waiting for capacity for at most 15 minutes |
|
|
224
|
+
| 6 | the awaited operation ended `failed` or `canceled` (also a resume or open that failed while waking a workspace). `retryable=true` in the error means it may succeed later (`capacity_unavailable`) |
|
|
208
225
|
| 130 | interrupted (Ctrl-C). Waits stop at once, and exec sends a cancel to the session. |
|
|
209
226
|
| exec | the command's own code: 124 if it timed out, 128+N if killed by signal N. Failures before the command ran use the codes above; use `--json` to tell them apart. |
|
|
210
227
|
|
package/dist/args.d.ts
CHANGED
package/dist/args.js
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*/
|
|
11
11
|
import { parseArgs } from 'node:util';
|
|
12
12
|
import { UsageError } from "./errors.js";
|
|
13
|
-
export const CLI_VERSION = '0.3.
|
|
13
|
+
export const CLI_VERSION = '0.3.1';
|
|
14
14
|
export const GLOBAL_OPTIONS = {
|
|
15
15
|
'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
|
|
16
16
|
json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
|
|
@@ -38,7 +38,7 @@ const WAKE_TIMING = {
|
|
|
38
38
|
};
|
|
39
39
|
const WAIT = {
|
|
40
40
|
wait: { type: 'boolean', description: 'Wait until the operation finishes.' },
|
|
41
|
-
timeout: { type: 'string', value: '<duration>', description: 'Give up waiting after this long (e.g. 90s, 5m; default 5m). The operation continues server side.' },
|
|
41
|
+
timeout: { type: 'string', value: '<duration>', description: 'Give up waiting after this long (e.g. 90s, 5m; default 5m). The operation continues server side (a start waiting for capacity until its deadline, 15 minutes after it began; then it fails with capacity_unavailable).' },
|
|
42
42
|
...TIMING,
|
|
43
43
|
};
|
|
44
44
|
const REF = { name: 'id|key', required: true };
|
|
@@ -111,7 +111,7 @@ export const COMMANDS = [
|
|
|
111
111
|
},
|
|
112
112
|
...CAPS,
|
|
113
113
|
wait: { type: 'boolean', description: 'Wait until ready (default; --no-wait returns at once).' },
|
|
114
|
-
timeout: { type: 'string', value: '<duration>', description: 'Give up waiting after this long (default 5m). The start continues server side.' },
|
|
114
|
+
timeout: { type: 'string', value: '<duration>', description: 'Give up waiting after this long (default 5m). The start continues server side; one waiting for capacity gives up 15 minutes after it began (capacity_unavailable, retryable).' },
|
|
115
115
|
...TIMING,
|
|
116
116
|
},
|
|
117
117
|
examples: [
|
|
@@ -691,7 +691,7 @@ export const EXIT_CODE_HELP = [
|
|
|
691
691
|
' 3 not found (workspace, operation or route)',
|
|
692
692
|
' 4 authentication/authorization (missing, malformed, revoked or insufficient key)',
|
|
693
693
|
' 5 timeout (waiting gave up; the operation continues server side)',
|
|
694
|
-
' 6 operation failed or canceled',
|
|
694
|
+
' 6 operation failed or canceled (retryable=true in the error: it may succeed later, e.g. capacity_unavailable)',
|
|
695
695
|
' 130 interrupted (Ctrl-C)',
|
|
696
696
|
' exec: the command’s own exit code (124 if it timed out, 128+N if killed by signal N)',
|
|
697
697
|
];
|
package/dist/errors.d.ts
CHANGED
|
@@ -10,7 +10,7 @@ export declare const EXIT: {
|
|
|
10
10
|
readonly auth: 4;
|
|
11
11
|
/** Waiting ran out of time (the operation continues server side), or a request timed out. */
|
|
12
12
|
readonly timeout: 5;
|
|
13
|
-
/** The awaited operation ended `failed` or `canceled
|
|
13
|
+
/** The awaited operation ended `failed` or `canceled` (the error's `retryable` says whether a retry may succeed). */
|
|
14
14
|
readonly operationFailed: 6;
|
|
15
15
|
/** SIGINT (Ctrl-C). */
|
|
16
16
|
readonly interrupted: 130;
|
package/dist/errors.js
CHANGED
|
@@ -16,7 +16,7 @@ export const EXIT = {
|
|
|
16
16
|
auth: 4,
|
|
17
17
|
/** Waiting ran out of time (the operation continues server side), or a request timed out. */
|
|
18
18
|
timeout: 5,
|
|
19
|
-
/** The awaited operation ended `failed` or `canceled
|
|
19
|
+
/** The awaited operation ended `failed` or `canceled` (the error's `retryable` says whether a retry may succeed). */
|
|
20
20
|
operationFailed: 6,
|
|
21
21
|
/** SIGINT (Ctrl-C). */
|
|
22
22
|
interrupted: 130,
|
|
@@ -69,7 +69,12 @@ export function describeError(err) {
|
|
|
69
69
|
if (err instanceof OperationTimeoutError)
|
|
70
70
|
return { code: 'timeout', message: err.message, operation_id: err.operationId, last_state: err.lastState };
|
|
71
71
|
if (err instanceof OperationFailedError) {
|
|
72
|
-
|
|
72
|
+
// retryable: the operation error's own flag (true for capacity_unavailable: nothing was started, retry later).
|
|
73
|
+
// details.reason: the operation error's reason (e.g. the scheduler's no_ready_hosts), printed after the message.
|
|
74
|
+
const opDetails = err.operation.error?.details;
|
|
75
|
+
const reason = typeof opDetails === 'object' && opDetails !== null ? opDetails.reason : undefined;
|
|
76
|
+
const details = { ...(err.errorCode ? { error_code: err.errorCode } : {}), ...(typeof reason === 'string' ? { reason } : {}) };
|
|
77
|
+
return { code: 'operation_failed', message: err.message, operation_id: err.operationId, retryable: err.retryable, ...(Object.keys(details).length ? { details } : {}), operation: err.operation };
|
|
73
78
|
}
|
|
74
79
|
if (err instanceof UsageError)
|
|
75
80
|
return { code: 'usage', message: err.message };
|
package/dist/format.d.ts
CHANGED
|
@@ -36,7 +36,10 @@ export declare function bindingsText(b: WorkspaceSecretBindings): string;
|
|
|
36
36
|
* freshness, then meters and allowances as tables. Columns absent from the response are omitted.
|
|
37
37
|
*/
|
|
38
38
|
export declare function usageText(summary: unknown): string;
|
|
39
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* One stderr line: `error: <message> [code=... status=... request_id=...]` plus a hint when there is one. A failed
|
|
41
|
+
* operation also shows `retryable=true|false`.
|
|
42
|
+
*/
|
|
40
43
|
export declare function errorText(e: ErrorInfo, hint?: string): string;
|
|
41
44
|
export declare function templateTable(rows: readonly TemplateSummary[]): string;
|
|
42
45
|
/** Permission bits as the four-digit octal `ls -l` users read (e.g. 0755). */
|
package/dist/format.js
CHANGED
|
@@ -67,6 +67,17 @@ export function workspaceTable(ws) {
|
|
|
67
67
|
export function isReady(w) {
|
|
68
68
|
return w.observed_state === 'running' && w.desired_state === 'running' && w.deleted_at === null;
|
|
69
69
|
}
|
|
70
|
+
/** A workspace's origin: a fork, a draft state (draft test instance) or a template version (version test instance). */
|
|
71
|
+
function originText(o) {
|
|
72
|
+
switch (o.kind) {
|
|
73
|
+
case 'fork':
|
|
74
|
+
return `fork of ${o.workspace_id}`;
|
|
75
|
+
case 'draft_state':
|
|
76
|
+
return `draft state ${o.checkpoint_id ?? '(capturing)'} of ${o.draft_workspace_id}`;
|
|
77
|
+
default:
|
|
78
|
+
return `template ${o.template_id} version ${o.version}`;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
70
81
|
export function workspaceDetail(w) {
|
|
71
82
|
const c = w.ceilings;
|
|
72
83
|
return kv([
|
|
@@ -87,7 +98,7 @@ export function workspaceDetail(w) {
|
|
|
87
98
|
['lifetime', w.lifetime === 'session' ? `session (ends after ${w.idle_timeout_seconds ?? '-'} s idle, or on close)` : 'persistent'],
|
|
88
99
|
['purpose', w.purpose],
|
|
89
100
|
['disk layout', w.disk_layout],
|
|
90
|
-
...(w.origin ? [['origin', w.origin
|
|
101
|
+
...(w.origin ? [['origin', originText(w.origin)]] : []),
|
|
91
102
|
...(w.ended_reason ? [['ended', w.ended_reason]] : []),
|
|
92
103
|
]
|
|
93
104
|
: []),
|
|
@@ -212,7 +223,10 @@ export function usageText(summary) {
|
|
|
212
223
|
out += `\n${allowances}`;
|
|
213
224
|
return out || json(summary);
|
|
214
225
|
}
|
|
215
|
-
/**
|
|
226
|
+
/**
|
|
227
|
+
* One stderr line: `error: <message> [code=... status=... request_id=...]` plus a hint when there is one. A failed
|
|
228
|
+
* operation also shows `retryable=true|false`.
|
|
229
|
+
*/
|
|
216
230
|
export function errorText(e, hint) {
|
|
217
231
|
const meta = [`code=${e.code}`];
|
|
218
232
|
if (e.status !== undefined)
|
|
@@ -223,6 +237,8 @@ export function errorText(e, hint) {
|
|
|
223
237
|
meta.push(`operation_id=${e.operation_id}`);
|
|
224
238
|
if (e.source === 'cell')
|
|
225
239
|
meta.push('source=cell');
|
|
240
|
+
if (e.code === 'operation_failed' && e.retryable !== undefined)
|
|
241
|
+
meta.push(`retryable=${e.retryable}`);
|
|
226
242
|
const reason = e.details && typeof e.details.reason === 'string' ? ` (${e.details.reason})` : '';
|
|
227
243
|
const issues = Array.isArray(e.details?.issues)
|
|
228
244
|
? e.details.issues.filter(isObject).map((i) => ` ${scalar(i.path)}: ${scalar(i.message)}\n`).join('')
|
package/dist/main.js
CHANGED
|
@@ -31,6 +31,9 @@ export function abortableSleep(signal) {
|
|
|
31
31
|
function hintFor(info) {
|
|
32
32
|
if (info.code === 'timeout' && info.operation_id)
|
|
33
33
|
return `the operation continues server side; keep waiting with: shard operations wait ${info.operation_id}`;
|
|
34
|
+
if (info.code === 'operation_failed' && info.retryable) {
|
|
35
|
+
return info.details?.error_code === 'capacity_unavailable' ? 'no host could admit it before its deadline; nothing was started. Retry later.' : 'the operation may succeed if you retry it later.';
|
|
36
|
+
}
|
|
34
37
|
if (info.code === 'interrupted' && info.operation_id)
|
|
35
38
|
return `keep waiting with: shard operations wait ${info.operation_id}`;
|
|
36
39
|
if (info.code === 'unauthenticated' && info.status === undefined)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.1",
|
|
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",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@shardflux/sdk": "^0.6.
|
|
42
|
+
"@shardflux/sdk": "^0.6.2"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@eslint/js": "10.0.1",
|