@shardflux/cli 0.13.3 → 0.13.4
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 +8 -1
- package/README.md +7 -7
- package/dist/account-commands.js +6 -3
- package/dist/agent-commands.d.ts +1 -1
- package/dist/agent-commands.js +20 -5
- package/dist/agent-credentials.js +9 -2
- package/dist/args.d.ts +1 -1
- package/dist/args.js +1 -1
- package/dist/filesystem-commands.d.ts +4 -2
- package/dist/filesystem-commands.js +14 -3
- package/dist/setup-cache.d.ts +5 -0
- package/dist/setup-cache.js +11 -0
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.13.4
|
|
4
|
+
|
|
5
|
+
- Cloud Claude sign-in and reopening the machine editor preserve existing workspace labels while updating their own labels.
|
|
6
|
+
- `shard billing status` on the signup trial suggests `shard billing upgrade pro` when the live catalog offers Pro (the trial is 60 days of Pro from catalog live-v10); otherwise it points to `shard billing plans`.
|
|
7
|
+
- Reuse an account-private setup cache across projects after the agent starts; report cache scope with `shard template --json`.
|
|
8
|
+
- Machine and project filesystem status shows the current platform base and whether a rebase is pending.
|
|
9
|
+
|
|
3
10
|
## 0.13.3
|
|
4
11
|
|
|
5
12
|
- `shard ./` seeds the workspace repository with the checked-out branch and its upstream only, not every local branch, tag and stash. When that history is still above 256 MiB, the workspace fetches the branch from origin with its own Git credentials and receives only the commits origin does not have yet. The workspace branch tracks its origin branch.
|
|
@@ -13,7 +20,7 @@
|
|
|
13
20
|
- Git history above the first copy's 256 MiB limit fails with `repository_too_large` and its next step in the hint, not `internal_error`.
|
|
14
21
|
- A `revision_mismatch` suggests `--expected-revision` only for the file commands that take it; elsewhere it says to run the same command again.
|
|
15
22
|
- Config sync reports project entries keyed by paths on this computer (Codex `config.projects.*`) as one summary line.
|
|
16
|
-
- The command center does not show a setup step that `.shardflux/setup.yaml` marks `required: false` as the setup failure while the template saves, and reads skipped steps (coding template
|
|
23
|
+
- The command center does not show a setup step that `.shardflux/setup.yaml` marks `required: false` as the setup failure while the template saves, and reads skipped steps (coding template 9+).
|
|
17
24
|
|
|
18
25
|
## 0.13.2
|
|
19
26
|
|
package/README.md
CHANGED
|
@@ -264,9 +264,9 @@ plan only when Stripe confirms the payment, so `--wait` polls until the subscrip
|
|
|
264
264
|
open). An organization that already has a subscription changes plans in the billing portal: the
|
|
265
265
|
command prints the portal URL instead (exit 0).
|
|
266
266
|
|
|
267
|
-
On the signup trial (60 days
|
|
267
|
+
On the signup trial (60 days of Pro, no card), `billing status` shows when it ends and
|
|
268
268
|
what follows (0.6.2+): `plan trial, ends 2026-11-29 14:03:00Z, then free (trial, catalog ...)`, then
|
|
269
|
-
`keep these limits after the trial: shard billing upgrade
|
|
269
|
+
`keep these limits after the trial: shard billing upgrade pro` (0.13.4+). A subscription outranks the
|
|
270
270
|
trial and ends it. The trial is granted, never bought, so `billing plans` leaves it out.
|
|
271
271
|
|
|
272
272
|
`billing overage` shows and changes opt-in overage (owners and billing members). With it on,
|
|
@@ -283,7 +283,7 @@ already charged this period (`spend_cap_below_charges`); a plan without overage
|
|
|
283
283
|
```text
|
|
284
284
|
$ shard billing overage on --cap 9
|
|
285
285
|
Acme: overage is on, spend cap $9.00 per billing period ($1.00 to $9.00, the plan price).
|
|
286
|
-
Rates past the allowances: $0.
|
|
286
|
+
Rates past the allowances: $0.015 per RAM GiB-hour, $0.04 per CPU-hour, charged on the next invoice (this period: shard usage).
|
|
287
287
|
Saved (policy version 1).
|
|
288
288
|
```
|
|
289
289
|
|
|
@@ -828,12 +828,12 @@ amount:
|
|
|
828
828
|
```text
|
|
829
829
|
overage accruing (usage past the allowances is charged on the next invoice)
|
|
830
830
|
spend cap $9.00 (at most $9.00, the plan price)
|
|
831
|
-
overage charges $
|
|
831
|
+
overage charges $1.10 of $9.00 (12.22%), $7.90 left; billed on the next invoice
|
|
832
832
|
cap reached 2026-09-29 15:02:46Z (projected at this period's average use)
|
|
833
833
|
|
|
834
|
-
OVERAGE UNIT PAST ALLOWANCE BILLED RATE
|
|
835
|
-
ram_gib_hours gib_hours 70 60 $0.
|
|
836
|
-
cpu_hours hours 5 5 $0.
|
|
834
|
+
OVERAGE UNIT PAST ALLOWANCE BILLED RATE AMOUNT
|
|
835
|
+
ram_gib_hours gib_hours 70 60 $0.015 $0.90
|
|
836
|
+
cpu_hours hours 5 5 $0.04 $0.20
|
|
837
837
|
```
|
|
838
838
|
|
|
839
839
|
`--json` prints the API's summary, with `spend_cap` and `exhausted_reason`. Owners and billing members turn overage on
|
package/dist/account-commands.js
CHANGED
|
@@ -1403,12 +1403,15 @@ export const ACCOUNT_HANDLERS = {
|
|
|
1403
1403
|
const account = ctx.account();
|
|
1404
1404
|
const org = await resolveOrg(ctx, account, values);
|
|
1405
1405
|
const s = await account.billing.subscription(org.id);
|
|
1406
|
-
// The trial has
|
|
1406
|
+
// The trial has Pro's limits and allowances (catalog live-v10): Pro keeps them (a subscription outranks the trial and ends it).
|
|
1407
|
+
// Name Pro only while the live catalog sells it; a catalog without Pro gets the general hint.
|
|
1408
|
+
const trialToPro = !s.subscription && s.purchase_available && s.plan?.plan_key === 'trial'
|
|
1409
|
+
&& (await fetchBillingCatalog({ baseUrl: ctx.apiUrl, fetch: ctx.accountOptions().fetch ?? fetch }).catch(() => null))?.plans?.some((p) => p.plan_key === 'pro') === true;
|
|
1407
1410
|
const next = s.subscription
|
|
1408
1411
|
? 'manage it: shard billing portal'
|
|
1409
1412
|
: s.purchase_available
|
|
1410
|
-
?
|
|
1411
|
-
? 'keep these limits after the trial: shard billing upgrade
|
|
1413
|
+
? trialToPro
|
|
1414
|
+
? 'keep these limits after the trial: shard billing upgrade pro'
|
|
1412
1415
|
: 'buy a plan: shard billing plans, then shard billing upgrade <plan>'
|
|
1413
1416
|
: '';
|
|
1414
1417
|
out(ctx, `${billingStatusText(s, org)}${next ? `${next}\n` : ''}`, s);
|
package/dist/agent-commands.d.ts
CHANGED
|
@@ -3,6 +3,6 @@ import type { Ctx, Handler } from './commands.js';
|
|
|
3
3
|
/** `<slug>-<6 hex of sha256(identity)>`: the same repository gets the same key on every machine. */
|
|
4
4
|
export declare function disambiguatedSlug(slug: string, identity: string): string;
|
|
5
5
|
export declare function projectTemplate(ctx: Ctx, remote: string | null): Promise<string>;
|
|
6
|
-
export declare function agentPty(cell: CellClient, app: 'claude' | 'codex', agent: string, approvals: boolean, term: string, signal?: AbortSignal, project?: string, setupHash?: string, cwd?: string, ptyName?: string, onLaunch?: () => void): Promise<PtySession>;
|
|
6
|
+
export declare function agentPty(cell: CellClient, app: 'claude' | 'codex', agent: string, approvals: boolean, term: string, signal?: AbortSignal, project?: string, setupHash?: string, cwd?: string, ptyName?: string, onLaunch?: () => void, deferSetup?: boolean): Promise<PtySession>;
|
|
7
7
|
export declare const launchProject: Handler;
|
|
8
8
|
export declare const AGENT_HANDLERS: Record<string, Handler>;
|
package/dist/agent-commands.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { prepareSetupCache } from "./setup-cache.js";
|
|
1
2
|
import { agentReachability } from "./agent-reachability.js";
|
|
2
3
|
import { configureCredentialFill, watchCredentialPrompts } from "./credential-prompts.js";
|
|
3
4
|
import { createHash } from 'node:crypto';
|
|
@@ -39,11 +40,11 @@ export async function projectTemplate(ctx, remote) {
|
|
|
39
40
|
throw error;
|
|
40
41
|
}
|
|
41
42
|
}
|
|
42
|
-
export async function agentPty(cell, app, agent, approvals, term, signal, project, setupHash, cwd, ptyName, onLaunch) {
|
|
43
|
+
export async function agentPty(cell, app, agent, approvals, term, signal, project, setupHash, cwd, ptyName, onLaunch, deferSetup = false) {
|
|
43
44
|
const id = `shard-${ptyName || agent}-${app}`;
|
|
44
45
|
// Arrival and changed locks queue the same durable guest job even when the
|
|
45
46
|
// native agent is already alive. Setup never becomes its first user turn.
|
|
46
|
-
if (setupHash) {
|
|
47
|
+
if (setupHash && !deferSetup) {
|
|
47
48
|
const queued = await cell.exec.run(['sf-agent', 'setup-start', '--deps-hash', setupHash], { timeoutMs: 30_000, maxOutputBytes: 4096, signal, ...(cwd ? { cwd } : {}) });
|
|
48
49
|
if (queued.exitCode !== 0)
|
|
49
50
|
throw new Error(`project setup could not be queued: ${queued.stderr.trim() || `exit ${queued.exitCode}`}`);
|
|
@@ -59,7 +60,7 @@ export async function agentPty(cell, app, agent, approvals, term, signal, projec
|
|
|
59
60
|
}
|
|
60
61
|
onLaunch?.();
|
|
61
62
|
const launch = await cell.exec.run(['sf-agent', 'run', app, '--name', agent, '--detach', ...(ptyName ? ['--pty-name', ptyName] : []), ...(approvals ? ['--approvals'] : []), ...(setupHash ? ['--setup', '--setup-deps-hash', setupHash] : [])], {
|
|
62
|
-
env: { TERM: term, ...(project ? { SF_PROJECT_NAME: project } : {}) }, timeoutMs: 30_000, maxOutputBytes: 4096, signal, ...(cwd ? { cwd } : {}),
|
|
63
|
+
env: { TERM: term, ...(project ? { SF_PROJECT_NAME: project } : {}), ...(setupHash && deferSetup ? { SF_SETUP_DEFERRED: '1' } : {}) }, timeoutMs: 30_000, maxOutputBytes: 4096, signal, ...(cwd ? { cwd } : {}),
|
|
63
64
|
});
|
|
64
65
|
if (launch.exitCode !== 0)
|
|
65
66
|
throw new Error(`agent launch failed: ${launch.stderr.trim() || `exit ${launch.exitCode}`}`);
|
|
@@ -88,14 +89,25 @@ async function terminal(ctx, ws, app, agent, project, values, setupHash, cwd = w
|
|
|
88
89
|
try {
|
|
89
90
|
const credentialFill = await configureCredentialFill(ctx, ws);
|
|
90
91
|
// A cold launch is remembered before it starts, so the listing can show it before the projection does (agent-live.ts).
|
|
91
|
-
const session = await agentPty(cell, app, agent, values.approvals === true, ctx.io.env.TERM || 'xterm-256color', ctx.signal, ws.labels.shard_project || ws.key.split('/')[0], setupHash, cwd, ws.labels.agent_pty_name, () => { rememberLaunch(ctx, ws.id); progress(ctx, `Starting ${app === 'claude' ? 'Claude' : 'Codex'} in ${ws.key}…`); });
|
|
92
|
+
const session = await agentPty(cell, app, agent, values.approvals === true, ctx.io.env.TERM || 'xterm-256color', ctx.signal, ws.labels.shard_project || ws.key.split('/')[0], setupHash, cwd, ws.labels.agent_pty_name, () => { rememberLaunch(ctx, ws.id); progress(ctx, `Starting ${app === 'claude' ? 'Claude' : 'Codex'} in ${ws.key}…`); }, true);
|
|
93
|
+
// The real agent is running before any cache provisioning or mount wait.
|
|
94
|
+
const preparation = prepareSetupCache(ctx, ws).catch((error) => {
|
|
95
|
+
ctx.io.stderr.write(`Account setup cache unavailable: ${error instanceof Error ? error.message : String(error)}\n`);
|
|
96
|
+
}).then(async () => {
|
|
97
|
+
if (!setupHash)
|
|
98
|
+
return;
|
|
99
|
+
const queued = await cell.exec.run(['sf-agent', 'setup-start', '--deps-hash', setupHash], { cwd, timeoutMs: 30_000, maxOutputBytes: 4096, signal: ctx.signal });
|
|
100
|
+
if (queued.exitCode !== 0)
|
|
101
|
+
throw new Error(`project setup could not be queued: ${queued.stderr.trim() || `exit ${queued.exitCode}`}`);
|
|
102
|
+
});
|
|
92
103
|
store.save(project, app);
|
|
93
104
|
if (!attach) {
|
|
105
|
+
await preparation;
|
|
94
106
|
ctx.io.stdout.write(json({ workspace: ws.data, session }));
|
|
95
107
|
return 0;
|
|
96
108
|
}
|
|
97
109
|
ctx.io.stderr.write(`Shardflux · ${ws.key}\nDetach: Ctrl-] then d\n`);
|
|
98
|
-
|
|
110
|
+
const attached = attachTerminal({
|
|
99
111
|
...(credentialFill ? { credentialPrompts: (ask, signal) => watchCredentialPrompts(ctx, ws, ask, signal) } : {}),
|
|
100
112
|
io: ctx.io, signal: ctx.signal, connect: cellConnector(cell, session.session_id),
|
|
101
113
|
wake: (signal) => ws.wake({ timeoutMs: ctx.wakeTimeoutMs, signal }),
|
|
@@ -108,8 +120,11 @@ async function terminal(ctx, ws, app, agent, project, values, setupHash, cwd = w
|
|
|
108
120
|
done();
|
|
109
121
|
}),
|
|
110
122
|
});
|
|
123
|
+
const [code] = await Promise.all([attached, preparation]);
|
|
124
|
+
return code;
|
|
111
125
|
}
|
|
112
126
|
finally {
|
|
127
|
+
cell.close();
|
|
113
128
|
await tunnels?.stop();
|
|
114
129
|
}
|
|
115
130
|
}
|
|
@@ -76,8 +76,15 @@ export const AGENT_CREDENTIAL_HANDLERS = {
|
|
|
76
76
|
}
|
|
77
77
|
requireClaudeTerminal(ctx);
|
|
78
78
|
const project = projectIdentity('./');
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
let ws;
|
|
80
|
+
if (typeof values.workspace === 'string')
|
|
81
|
+
ws = await resolveWorkspace(ctx, values.workspace);
|
|
82
|
+
else {
|
|
83
|
+
const cloud = ctx.cloud();
|
|
84
|
+
const key = `${project.slug}/main`;
|
|
85
|
+
const current = await cloud.workspaces.findByKey(key, { includeDeleted: false, signal: ctx.signal });
|
|
86
|
+
ws = await cloud.workspaces.open({ key, template: 'coding', labels: { ...current?.labels, ...projectLabels(project, 'main', 'claude') }, wait: { signal: ctx.signal, timeoutMs: ctx.wakeTimeoutMs } });
|
|
87
|
+
}
|
|
81
88
|
output(ctx, await loginClaudeInWorkspace(ctx, ws));
|
|
82
89
|
return 0;
|
|
83
90
|
},
|
package/dist/args.d.ts
CHANGED
package/dist/args.js
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import { parseArgs } from 'node:util';
|
|
13
13
|
import { ACCOUNT_COMMANDS } from "./account-args.js";
|
|
14
14
|
import { UsageError } from "./errors.js";
|
|
15
|
-
export const CLI_VERSION = '0.13.
|
|
15
|
+
export const CLI_VERSION = '0.13.4';
|
|
16
16
|
export const GLOBAL_OPTIONS = {
|
|
17
17
|
'api-url': { type: 'string', value: '<url>', description: 'API base URL (default $SHARDFLUX_API_URL, else https://api.shardflux.dev).' },
|
|
18
18
|
json: { type: 'boolean', description: 'Print machine-readable JSON on stdout (errors as JSON on stderr).' },
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { AgentFilesystem } from '@shardflux/sdk';
|
|
2
2
|
import type { Handler } from './commands.js';
|
|
3
|
-
/** One line per status: what is in use and what is saving or failed. */
|
|
4
|
-
export declare function filesystemLine(state: AgentFilesystem
|
|
3
|
+
/** One line per status: what is in use and what is saving or failed, its platform base and setup cache. */
|
|
4
|
+
export declare function filesystemLine(state: AgentFilesystem & {
|
|
5
|
+
cache_scope?: string;
|
|
6
|
+
}): string;
|
|
5
7
|
export declare const FILESYSTEM_HANDLERS: Record<string, Handler>;
|
|
@@ -2,14 +2,19 @@ import { waitTimeout } from "./commands.js";
|
|
|
2
2
|
import { UsageError } from "./errors.js";
|
|
3
3
|
import { projectIdentity, normalizeRemote } from "./project-identity.js";
|
|
4
4
|
import { AGENT_HANDLERS } from "./agent-commands.js";
|
|
5
|
+
import { readSetupProgress } from "./assisted-setup.js";
|
|
5
6
|
import { syncAgentConfig } from "./agent-config-sync.js";
|
|
6
7
|
import { progress } from "./progress.js";
|
|
7
8
|
function remote() {
|
|
8
9
|
const project = projectIdentity('./');
|
|
9
10
|
return { project, repoRemote: project.remote ?? normalizeRemote(`file://${project.folder}`) };
|
|
10
11
|
}
|
|
11
|
-
/** One line per status: what is in use and what is saving or failed. */
|
|
12
|
+
/** One line per status: what is in use and what is saving or failed, its platform base and setup cache. */
|
|
12
13
|
export function filesystemLine(state) {
|
|
14
|
+
const base = state.platform_base ? `; platform ${state.platform_base.slug}@${state.platform_base.version}${state.rebase_pending ? ', rebase pending' : ''}` : '';
|
|
15
|
+
return usageLine(state) + base + (state.cache_scope ? `; cache: ${state.cache_scope}` : '');
|
|
16
|
+
}
|
|
17
|
+
function usageLine(state) {
|
|
13
18
|
const current = state.version > 0 ? `version ${state.version}` : '';
|
|
14
19
|
if (state.state === 'building' && state.pending)
|
|
15
20
|
return `${state.level}: saving version ${state.pending.version}${current ? ` (${current} stays in use until it is ready)` : ''}`;
|
|
@@ -56,7 +61,7 @@ export const FILESYSTEM_HANDLERS = {
|
|
|
56
61
|
else if (existing.data.observed_state !== 'running')
|
|
57
62
|
progress(ctx, `Resuming your machine VM (${key})…`);
|
|
58
63
|
const ws = await cloud.workspaces.open({ key, template: state.template ? `${state.template.slug}@${state.template.version}` : 'coding',
|
|
59
|
-
labels: { shard_layer: 'machine', shard_machine: state.organization_id, repo_path: '/home/user', app: 'claude', agent: 'machine' },
|
|
64
|
+
labels: { ...existing?.labels, shard_layer: 'machine', shard_machine: state.organization_id, repo_path: '/home/user', app: 'claude', agent: 'machine' },
|
|
60
65
|
wait: { timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal } });
|
|
61
66
|
if (state.version === 0) {
|
|
62
67
|
progress(ctx, 'Syncing your Claude and Codex config into the machine…');
|
|
@@ -77,7 +82,13 @@ export const FILESYSTEM_HANDLERS = {
|
|
|
77
82
|
return output(ctx, await ctx.cloud().machine.rollback({ to: rollbackTo(values.to) }, { signal: ctx.signal }));
|
|
78
83
|
},
|
|
79
84
|
async template(ctx) {
|
|
80
|
-
|
|
85
|
+
const { project, repoRemote } = remote();
|
|
86
|
+
const state = await ctx.cloud().templates.getProject(repoRemote, { signal: ctx.signal });
|
|
87
|
+
const ws = await ctx.cloud().workspaces.findByKey(`${project.slug}/main`, { signal: ctx.signal });
|
|
88
|
+
const progress = ws ? await readSetupProgress(ctx, ws.data) : undefined;
|
|
89
|
+
const attachments = ws ? await ctx.cloud().volumes.listAttached(ws.id) : undefined;
|
|
90
|
+
const mounted = attachments?.data.some((a) => a.mount_path === '/mnt/shardflux-cache' && a.mounted && a.volume.name === 'shardflux-setup-cache' && a.volume.org_shared);
|
|
91
|
+
return output(ctx, { ...state, cache_scope: progress?.cache_scope ?? (mounted ? 'account' : 'workspace') });
|
|
81
92
|
},
|
|
82
93
|
async 'template save'(ctx, values) {
|
|
83
94
|
const { project, repoRemote } = remote();
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { Workspace } from '@shardflux/sdk';
|
|
2
|
+
import type { Ctx } from './commands.js';
|
|
3
|
+
export declare const SETUP_CACHE_PATH = "/mnt/shardflux-cache";
|
|
4
|
+
/** Uses ordinary fenced shared-volume operations, after the managed PTY starts. */
|
|
5
|
+
export declare function prepareSetupCache(ctx: Ctx, workspace: Workspace): Promise<void>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
const name = 'shardflux-setup-cache';
|
|
2
|
+
export const SETUP_CACHE_PATH = '/mnt/shardflux-cache';
|
|
3
|
+
/** Uses ordinary fenced shared-volume operations, after the managed PTY starts. */
|
|
4
|
+
export async function prepareSetupCache(ctx, workspace) {
|
|
5
|
+
const cloud = ctx.cloud();
|
|
6
|
+
const wait = { timeoutMs: ctx.wakeTimeoutMs, signal: ctx.signal };
|
|
7
|
+
const { volume } = await cloud.volumes.open({ name, projectId: workspace.data.project_id, wait });
|
|
8
|
+
if (volume.state !== 'available' || !volume.org_shared || volume.quota_gib !== 1)
|
|
9
|
+
throw new Error('account setup cache is not available');
|
|
10
|
+
await cloud.volumes.attach(workspace.id, { volumeId: volume.id, mountPath: SETUP_CACHE_PATH, wait });
|
|
11
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@shardflux/cli",
|
|
3
|
-
"version": "0.13.
|
|
3
|
+
"version": "0.13.4",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "shard: the Shardflux command line. Create an account, sign in, set up keys and billing, then open, run commands in, move files to, suspend, resume and fork serverless agent workspaces.",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"smol-toml": "1.9.0",
|
|
48
48
|
"snappyjs": "0.7.0",
|
|
49
49
|
"yaml": "2.9.1",
|
|
50
|
-
"@shardflux/sdk": "^0.18.
|
|
50
|
+
"@shardflux/sdk": "^0.18.2"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@eslint/js": "10.0.1",
|