@shardflux/cli 0.20.0 → 0.23.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/CHANGELOG.md +312 -1
- package/README.md +242 -75
- package/dist/account-args.js +7 -2
- package/dist/account-commands.js +56 -106
- package/dist/account-pool.d.ts +19 -15
- package/dist/account-pool.js +47 -96
- package/dist/account-scope.d.ts +65 -0
- package/dist/account-scope.js +246 -0
- package/dist/acp-journal.js +58 -6
- package/dist/acp-login.d.ts +13 -5
- package/dist/acp-login.js +37 -8
- package/dist/acp-models.d.ts +6 -1
- package/dist/acp-models.js +29 -19
- package/dist/acp.js +120 -14
- package/dist/agent-commands.d.ts +4 -1
- package/dist/agent-commands.js +110 -42
- package/dist/agent-config-return.d.ts +46 -2
- package/dist/agent-config-return.js +110 -4
- package/dist/agent-credentials.js +7 -3
- package/dist/agent-repository.d.ts +16 -3
- package/dist/agent-repository.js +149 -74
- package/dist/agent-setup.d.ts +14 -3
- package/dist/agent-setup.js +78 -36
- package/dist/agent-target-commands.d.ts +20 -1
- package/dist/agent-target-commands.js +73 -31
- package/dist/agent-targets.js +6 -1
- package/dist/args.d.ts +1 -1
- package/dist/args.js +95 -54
- package/dist/bin.js +5 -1
- package/dist/browser.d.ts +13 -2
- package/dist/browser.js +30 -14
- package/dist/claude-cloud-login.d.ts +24 -2
- package/dist/claude-cloud-login.js +151 -11
- package/dist/cli-proxy-hub.d.ts +61 -25
- package/dist/cli-proxy-hub.js +202 -54
- package/dist/cli-proxy.d.ts +44 -1
- package/dist/cli-proxy.js +177 -28
- package/dist/client-settings.d.ts +39 -0
- package/dist/client-settings.js +91 -0
- package/dist/codex-cloud.d.ts +14 -4
- package/dist/codex-cloud.js +135 -31
- package/dist/codex-linux.d.ts +14 -0
- package/dist/codex-linux.js +69 -0
- package/dist/command-center-actions.js +18 -5
- package/dist/command-center-model.d.ts +2 -0
- package/dist/command-center-model.js +6 -1
- package/dist/command-center.js +3 -3
- package/dist/commands.js +173 -24
- package/dist/conversation-commands.d.ts +5 -0
- package/dist/conversation-commands.js +8 -5
- package/dist/conversation-terminal.d.ts +10 -2
- package/dist/conversation-terminal.js +78 -13
- package/dist/conversations.d.ts +86 -1
- package/dist/conversations.js +306 -23
- package/dist/desktop-linux.js +16 -3
- package/dist/device-bridge.d.ts +1 -1
- package/dist/device-bridge.js +6 -4
- package/dist/device-helper.js +43 -22
- package/dist/errors.d.ts +29 -2
- package/dist/errors.js +50 -7
- package/dist/filesystem-commands.d.ts +16 -9
- package/dist/filesystem-commands.js +133 -57
- package/dist/format.d.ts +2 -1
- package/dist/format.js +10 -1
- package/dist/handlers.js +3 -1
- package/dist/initial-prompt.d.ts +8 -0
- package/dist/initial-prompt.js +43 -1
- package/dist/large-media.d.ts +14 -0
- package/dist/large-media.js +100 -0
- package/dist/linux-terminal.d.ts +5 -3
- package/dist/linux-terminal.js +6 -20
- package/dist/local-sessions.d.ts +26 -2
- package/dist/local-sessions.js +113 -24
- package/dist/machine-setup.d.ts +11 -1
- package/dist/machine-setup.js +69 -22
- package/dist/machine-status.d.ts +67 -0
- package/dist/machine-status.js +140 -0
- package/dist/main.js +11 -13
- package/dist/menubar.d.ts +6 -5
- package/dist/menubar.js +38 -28
- package/dist/move-worktrees-restore.py +144 -0
- package/dist/move-worktrees.d.ts +69 -0
- package/dist/move-worktrees.js +252 -0
- package/dist/move.d.ts +10 -1
- package/dist/move.js +111 -16
- package/dist/onboarding.d.ts +11 -7
- package/dist/onboarding.js +63 -29
- package/dist/prepared-repository.d.ts +204 -0
- package/dist/prepared-repository.js +860 -0
- package/dist/prepared-repository.py +1307 -0
- package/dist/project-preparation.d.ts +62 -0
- package/dist/project-preparation.js +316 -0
- package/dist/project-sync-manifest.d.ts +4 -0
- package/dist/project-sync-manifest.js +5 -1
- package/dist/project-sync-transfer.js +1 -1
- package/dist/project-sync.d.ts +22 -4
- package/dist/project-sync.js +34 -8
- package/dist/project-template-setup.d.ts +33 -1
- package/dist/project-template-setup.js +59 -2
- package/dist/secret-ask.d.ts +72 -0
- package/dist/secret-ask.js +351 -0
- package/dist/seed-phases.d.ts +8 -1
- package/dist/seed-phases.js +17 -2
- package/dist/setup-cache.d.ts +7 -0
- package/dist/setup-cache.js +32 -0
- package/dist/t3-launch.d.ts +2 -1
- package/dist/t3-launch.js +12 -6
- package/dist/t3-mode.d.ts +8 -2
- package/dist/t3-mode.js +19 -4
- package/dist/t3-phone.d.ts +4 -2
- package/dist/t3-phone.js +12 -4
- package/dist/t3-proxy.d.ts +81 -0
- package/dist/t3-proxy.js +325 -0
- package/dist/terminal-apps.d.ts +168 -0
- package/dist/terminal-apps.js +574 -0
- package/dist/terminal-onboarding.js +6 -1
- package/dist/this-computer.d.ts +11 -0
- package/dist/this-computer.js +36 -0
- package/dist/thread-on-computer.js +4 -1
- package/dist/update.d.ts +2 -0
- package/dist/update.js +43 -9
- package/dist/worktree-twin.d.ts +10 -2
- package/dist/worktree-twin.js +27 -9
- package/package.json +5 -5
|
@@ -1,15 +1,21 @@
|
|
|
1
|
+
import { StringDecoder } from 'node:string_decoder';
|
|
1
2
|
import { isDeepStrictEqual } from 'node:util';
|
|
2
3
|
import { ShardfluxApiError } from '@shardflux/sdk';
|
|
3
|
-
import { UsageError } from "./errors.js";
|
|
4
|
+
import { CodedError, UsageError } from "./errors.js";
|
|
4
5
|
import { openUrl } from "./browser.js";
|
|
5
|
-
import { attachTerminal, cellConnector, PtyOutputCursor, workspaceAsleep } from "./agent-terminal.js";
|
|
6
|
+
import { attachTerminal, cellConnector, PtyOutputCursor, reattachDelayMs, retryableAttach, workspaceAsleep } from "./agent-terminal.js";
|
|
6
7
|
import { codingAgentShell } from "./agent-setup.js";
|
|
7
|
-
|
|
8
|
-
const
|
|
8
|
+
import { announce, askInBrowser, openInBrowser } from "./secret-ask.js";
|
|
9
|
+
const NEED_TERMINAL = 'Claude sign-in needed: run shard agents credentials login claude, once for the account (it opens the claude.ai sign-in and a page for its code).';
|
|
10
|
+
const NEED_TERMINAL_HINT = 'a coding agent runs shard agents credentials login claude --json itself; its user signs in and pastes the code into the page it opens';
|
|
9
11
|
const VERSION = '2.1.289';
|
|
10
|
-
|
|
12
|
+
/** Someone types into this terminal: not --json, a TTY with raw mode, and not a coding agent's shell. */
|
|
13
|
+
export function claudeTerminal(ctx) {
|
|
11
14
|
// A coding agent's shell (0.17.1+) may be a PTY, but the person who reads the code is not typing into it.
|
|
12
|
-
|
|
15
|
+
return !ctx.json && ctx.io.stdin.isTTY === true && typeof ctx.io.stdin.setRawMode === 'function' && typeof ctx.io.stdin.on === 'function' && !codingAgentShell(ctx.io.env);
|
|
16
|
+
}
|
|
17
|
+
export function requireClaudeTerminal(ctx) {
|
|
18
|
+
if (!claudeTerminal(ctx))
|
|
13
19
|
throw new UsageError(NEED_TERMINAL, NEED_TERMINAL_HINT);
|
|
14
20
|
}
|
|
15
21
|
export async function cloudClaudeLogin(ctx) {
|
|
@@ -109,7 +115,7 @@ export async function runWorkspaceLogin(ctx, ws, cell, login) {
|
|
|
109
115
|
for (const url of login.urls(output))
|
|
110
116
|
if (!opened.has(url)) {
|
|
111
117
|
opened.add(url);
|
|
112
|
-
openUrl(url, ctx.io.env);
|
|
118
|
+
void openUrl(url, ctx.io.env);
|
|
113
119
|
}
|
|
114
120
|
}
|
|
115
121
|
yield message;
|
|
@@ -121,9 +127,141 @@ export async function runWorkspaceLogin(ctx, ws, cell, login) {
|
|
|
121
127
|
const ended = await cell.pty.get(login.session);
|
|
122
128
|
return code === 0 && ended.state === 'exited' && ended.exit_code === 0;
|
|
123
129
|
}
|
|
124
|
-
/**
|
|
125
|
-
|
|
126
|
-
|
|
130
|
+
/** How long Claude may take to exchange the pasted code and exit. */
|
|
131
|
+
const CODE_EXCHANGE_MS = 60_000;
|
|
132
|
+
/**
|
|
133
|
+
* The same sign-in without an attached terminal (0.20.1+: a coding agent's shell, --json, no TTY). The workspace PTY
|
|
134
|
+
* runs the login exactly as runWorkspaceLogin does; shard reads its output, opens the claude.ai link it prints, and
|
|
135
|
+
* opens a page on this computer where the person pastes the code claude.ai shows (the agent never sees it). The code
|
|
136
|
+
* and Enter go into the PTY; the page says "Signed in" once the login exited 0. True when it did.
|
|
137
|
+
*/
|
|
138
|
+
export async function runWorkspaceLoginHeadless(ctx, cell, login, timeoutMs) {
|
|
139
|
+
await cell.pty.open({ session_id: login.session, argv: login.argv, env: login.env, cwd: login.cwd, rows: 24, cols: 100 });
|
|
140
|
+
const deadline = Date.now() + timeoutMs;
|
|
141
|
+
const stop = new AbortController();
|
|
142
|
+
const signal = AbortSignal.any([ctx.signal, stop.signal]);
|
|
143
|
+
// The PTY ended (or could not be read) before the code went in: the page closes with that.
|
|
144
|
+
const ended = new AbortController();
|
|
145
|
+
const connect = cellConnector(cell, login.session);
|
|
146
|
+
const cursor = new PtyOutputCursor();
|
|
147
|
+
const decoder = new StringDecoder('utf8');
|
|
148
|
+
const waiters = new Set();
|
|
149
|
+
const notify = () => { for (const wake of waiters)
|
|
150
|
+
wake(); waiters.clear(); };
|
|
151
|
+
let output = '';
|
|
152
|
+
let exit;
|
|
153
|
+
let failure;
|
|
154
|
+
let reading = true;
|
|
155
|
+
let codeSent = false;
|
|
156
|
+
const early = () => new CodedError('claude_sign_in_ended', 'The Claude sign-in ended before its code arrived.', 'run shard agents credentials login claude again');
|
|
157
|
+
const reader = (async () => {
|
|
158
|
+
let failures = 0;
|
|
159
|
+
while (!signal.aborted && exit === undefined) {
|
|
160
|
+
let connection;
|
|
161
|
+
try {
|
|
162
|
+
connection = await connect(cursor.offset, signal);
|
|
163
|
+
for await (const message of connection.messages()) {
|
|
164
|
+
failures = 0;
|
|
165
|
+
const bytes = cursor.consume(message);
|
|
166
|
+
if (bytes?.length)
|
|
167
|
+
output = (output + decoder.write(bytes)).slice(-16384);
|
|
168
|
+
if (message.type === 'exit') {
|
|
169
|
+
exit = message.session?.exit_code ?? null;
|
|
170
|
+
if (!codeSent)
|
|
171
|
+
ended.abort(early());
|
|
172
|
+
}
|
|
173
|
+
notify();
|
|
174
|
+
if (exit !== undefined || signal.aborted)
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
catch (error) {
|
|
179
|
+
if (signal.aborted)
|
|
180
|
+
return;
|
|
181
|
+
if (!retryableAttach(error))
|
|
182
|
+
throw error;
|
|
183
|
+
failures += 1;
|
|
184
|
+
await new Promise((resolve) => {
|
|
185
|
+
const done = () => { clearTimeout(timer); signal.removeEventListener('abort', done); resolve(); };
|
|
186
|
+
const timer = setTimeout(done, reattachDelayMs(error, failures));
|
|
187
|
+
signal.addEventListener('abort', done, { once: true });
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
await connection?.close().catch(() => undefined);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
})().catch((error) => {
|
|
195
|
+
failure = error;
|
|
196
|
+
if (!codeSent)
|
|
197
|
+
ended.abort(error instanceof Error ? error : early());
|
|
198
|
+
}).finally(() => { reading = false; notify(); });
|
|
199
|
+
/** Until `ready()`, at most `ms`: false when it did not happen (time out, or the reader stopped). */
|
|
200
|
+
const until = async (ready, ms) => {
|
|
201
|
+
const end = Date.now() + ms;
|
|
202
|
+
for (;;) {
|
|
203
|
+
if (ready())
|
|
204
|
+
return true;
|
|
205
|
+
if (failure !== undefined)
|
|
206
|
+
throw failure instanceof Error ? failure : new Error('The Claude sign-in terminal failed.');
|
|
207
|
+
if (!reading || Date.now() >= end)
|
|
208
|
+
return false;
|
|
209
|
+
await new Promise((resolve) => {
|
|
210
|
+
const timer = setTimeout(wake, end - Date.now());
|
|
211
|
+
function wake() { clearTimeout(timer); waiters.delete(wake); resolve(); }
|
|
212
|
+
waiters.add(wake);
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
try {
|
|
217
|
+
await until(() => login.urls(output).length > 0 || exit !== undefined, deadline - Date.now());
|
|
218
|
+
const url = login.urls(output)[0];
|
|
219
|
+
if (url === undefined)
|
|
220
|
+
return false;
|
|
221
|
+
const opened = await openInBrowser(ctx, url);
|
|
222
|
+
announce(ctx, { event: 'open', url, opened }, opened ? `Opened the Claude sign-in in your browser (or open: ${url})` : `Open the Claude sign-in: ${url}`);
|
|
223
|
+
await askInBrowser(ctx, {
|
|
224
|
+
kind: 'code',
|
|
225
|
+
title: 'Sign in to Claude',
|
|
226
|
+
purpose: 'Paste the code claude.ai shows after you sign in. Your coding agent never sees it.',
|
|
227
|
+
label: 'Code',
|
|
228
|
+
submit: 'Sign in',
|
|
229
|
+
done: 'Signed in to Claude',
|
|
230
|
+
failed: 'Not signed in',
|
|
231
|
+
link: { href: url, text: 'Open the Claude sign-in again' },
|
|
232
|
+
doing: 'to paste the Claude code',
|
|
233
|
+
event: 'ask',
|
|
234
|
+
maxBytes: 4096,
|
|
235
|
+
timeoutMs: Math.max(1_000, deadline - Date.now()),
|
|
236
|
+
signal: ended.signal,
|
|
237
|
+
// Once the code went in, the login either finishes or ends: there is nothing to retry on the page.
|
|
238
|
+
retry: () => false,
|
|
239
|
+
use: async (code) => {
|
|
240
|
+
if (exit !== undefined)
|
|
241
|
+
throw early();
|
|
242
|
+
codeSent = true;
|
|
243
|
+
await cell.pty.input(login.session, `${code}\r`);
|
|
244
|
+
if (!await until(() => exit !== undefined, CODE_EXCHANGE_MS))
|
|
245
|
+
throw new CodedError('claude_sign_in_timeout', 'Claude did not finish the sign-in within a minute of the code.', 'run shard agents credentials login claude again');
|
|
246
|
+
if (exit !== 0)
|
|
247
|
+
throw new CodedError('claude_code_refused', `Claude did not accept the code (exit ${exit ?? 'by signal'}).`, 'run shard agents credentials login claude again for a new code');
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
}
|
|
251
|
+
finally {
|
|
252
|
+
stop.abort();
|
|
253
|
+
await reader;
|
|
254
|
+
}
|
|
255
|
+
const session = await cell.pty.get(login.session);
|
|
256
|
+
return session.state === 'exited' && session.exit_code === 0;
|
|
257
|
+
}
|
|
258
|
+
/**
|
|
259
|
+
* A transient Linux profile holds the initial pair; it is removed before an agent starts. `headless` (0.20.1+): the
|
|
260
|
+
* sign-in runs through the pages shard opens (runWorkspaceLoginHeadless) instead of an attached terminal.
|
|
261
|
+
*/
|
|
262
|
+
export async function loginClaudeInWorkspace(ctx, ws, opts = {}) {
|
|
263
|
+
if (!opts.headless)
|
|
264
|
+
requireClaudeTerminal(ctx);
|
|
127
265
|
const api = ctx.cloud().agentCredentials;
|
|
128
266
|
let claim;
|
|
129
267
|
try {
|
|
@@ -147,7 +285,9 @@ export async function loginClaudeInWorkspace(ctx, ws) {
|
|
|
147
285
|
const version = await cell.exec.run(['claude', '--version'], { env, cwd: root, signal: ctx.signal, timeoutMs: 10_000 });
|
|
148
286
|
if (version.exitCode !== 0 || version.stdout.trim() !== `${VERSION} (Claude Code)`)
|
|
149
287
|
throw new UsageError(`Cloud Claude sign-in requires Claude Code ${VERSION} in the workspace image.`);
|
|
150
|
-
|
|
288
|
+
const login = { session, argv: ['claude', 'auth', 'login', '--claudeai'], env, cwd: root, urls: claudeLoginUrls };
|
|
289
|
+
const signedIn = opts.headless ? await runWorkspaceLoginHeadless(ctx, cell, login, opts.headless.timeoutMs) : await runWorkspaceLogin(ctx, ws, cell, login);
|
|
290
|
+
if (!signedIn)
|
|
151
291
|
throw new UsageError('Claude cloud sign-in was interrupted. Run shard agents credentials login claude again.');
|
|
152
292
|
const credential = JSON.parse(await cell.files.readText(`${root}/.claude/.credentials.json`));
|
|
153
293
|
const config = JSON.parse(await cell.files.readText(`${root}/.claude/.claude.json`));
|
package/dist/cli-proxy-hub.d.ts
CHANGED
|
@@ -32,24 +32,14 @@ export declare const HUB_TEMPLATE = "codex-connector";
|
|
|
32
32
|
/**
|
|
33
33
|
* The codex-connector version a hub runs at least: the first with the hub's boot service and an sf-agent that serves
|
|
34
34
|
* the VM port (cli-proxy-filter). New hubs are created on it; an older hub is upgraded in place (its home, and so its
|
|
35
|
-
* logins and settings, stay) before VMs get the hub's address.
|
|
35
|
+
* logins and settings, stay) before VMs get the hub's address. A hub on a newer version keeps it (never downgraded).
|
|
36
36
|
*/
|
|
37
37
|
export declare const HUB_TEMPLATE_VERSION = 4;
|
|
38
|
-
/**
|
|
39
|
-
* The codex-connector version whose sf-agent runs the account pool router: a hub set up for pooled accounts is created
|
|
40
|
-
* on it, or upgraded to it. HUB_TEMPLATE_VERSION moves to it once it is registered in every environment.
|
|
41
|
-
*/
|
|
42
|
-
export declare const POOL_HUB_TEMPLATE_VERSION = 5;
|
|
43
38
|
export declare const HUB_CAPS: Caps;
|
|
44
39
|
export declare const HUB_INFO_FILE = "cli-proxy.json";
|
|
45
40
|
/** Paths in the hub workspace. */
|
|
46
41
|
export declare const HUB_DIR = "/home/user/.cli-proxy-hub";
|
|
47
42
|
export declare const HUB_AUTH_DIR = "/home/user/.cli-proxy-api";
|
|
48
|
-
/** Pool mode: the router reads the API URL and the hub token (`sfh_…`) from these hub files (0600). */
|
|
49
|
-
export declare const HUB_POOL_FILES: {
|
|
50
|
-
readonly apiUrl: "/home/user/.cli-proxy-hub/api-url";
|
|
51
|
-
readonly apiToken: "/home/user/.cli-proxy-hub/api-token";
|
|
52
|
-
};
|
|
53
43
|
export interface HubInfo {
|
|
54
44
|
version: 1;
|
|
55
45
|
api_url: string;
|
|
@@ -112,7 +102,10 @@ export interface HubWorkspace {
|
|
|
112
102
|
write(path: string, data: string, mode: '0600' | '0700'): Promise<void>;
|
|
113
103
|
/** Names in a directory, or null when it does not exist. */
|
|
114
104
|
list(dir: string): Promise<string[] | null>;
|
|
115
|
-
/**
|
|
105
|
+
/**
|
|
106
|
+
* The hub's auth files, as paths relative to its auth directory (subdirectories included, as CLIProxyAPI reads them),
|
|
107
|
+
* oldest first: by modification time, then by path.
|
|
108
|
+
*/
|
|
116
109
|
authFiles(): Promise<string[]>;
|
|
117
110
|
/** Exposes the port (idempotent): when its callback URL was created, null without one. */
|
|
118
111
|
exposePort(port: number): Promise<{
|
|
@@ -167,6 +160,8 @@ export declare function sdkHubCloud(cloud: Shardflux, opts?: {
|
|
|
167
160
|
agentLabel?: string;
|
|
168
161
|
homeProject?: (organizationId: string) => Promise<HomeProject | null>;
|
|
169
162
|
}): HubCloud;
|
|
163
|
+
/** The hub's auth files, oldest first (modification time, then path in byte order), one relative path per line. */
|
|
164
|
+
export declare const AUTH_FILES_SCRIPT = "set -euo pipefail\n[ -d /home/user/.cli-proxy-api ] || exit 0; cd /home/user/.cli-proxy-api\nfind . -type f -name '*.json' -printf '%T@ %P\\n' | LC_ALL=C sort -t ' ' -k1,1n -k2 | cut -d ' ' -f 2-\n";
|
|
170
165
|
/** Installs the pinned release once: download, verify its SHA-256, then move the binary into place. */
|
|
171
166
|
export declare const INSTALL_SCRIPT: string;
|
|
172
167
|
/**
|
|
@@ -228,15 +223,6 @@ export interface EnsureOptions {
|
|
|
228
223
|
progress?: (message: string) => void;
|
|
229
224
|
/** Random management key (tests). */
|
|
230
225
|
randomKey?: () => string;
|
|
231
|
-
/**
|
|
232
|
-
* Pool mode (account pool): the hub's router gets the API URL and a hub token, and CLIProxyAPI passes the providers'
|
|
233
|
-
* rate-limit headers through so the router reads usage on every answer. `hubToken` mints a token for the hub
|
|
234
|
-
* workspace (`POST /v1/agent-pool/hub-token`); it is called when the hub has none, or always with `renew`.
|
|
235
|
-
*/
|
|
236
|
-
pool?: {
|
|
237
|
-
hubToken: (workspaceId: string) => Promise<string>;
|
|
238
|
-
renew?: boolean;
|
|
239
|
-
};
|
|
240
226
|
}
|
|
241
227
|
export interface EnsureResult {
|
|
242
228
|
info: HubInfo;
|
|
@@ -246,6 +232,8 @@ export interface EnsureResult {
|
|
|
246
232
|
callbackReplaced: boolean;
|
|
247
233
|
/** The config written now; null when the hub kept its own. */
|
|
248
234
|
config: HubConfig | null;
|
|
235
|
+
/** The hub's logins, each enabled or disabled as the user left it. */
|
|
236
|
+
logins: HubLogin[];
|
|
249
237
|
}
|
|
250
238
|
/** The hub could not be made ready (exit 1, code `cli_proxy_hub`). */
|
|
251
239
|
export declare class HubError extends CodedError {
|
|
@@ -258,11 +246,59 @@ export declare class HubError extends CodedError {
|
|
|
258
246
|
*/
|
|
259
247
|
export declare function ensureCliProxyHub(cloud: Shardflux | HubCloud, opts: EnsureOptions): Promise<EnsureResult>;
|
|
260
248
|
/**
|
|
261
|
-
*
|
|
262
|
-
*
|
|
263
|
-
*
|
|
249
|
+
* A login in the hub: one auth file. Only its provider, email and state are ever shown, never its tokens.
|
|
250
|
+
*
|
|
251
|
+
* The hub is the user's own CLIProxyAPI: it routes requests across the enabled logins exactly as its config says
|
|
252
|
+
* (round-robin, fill-first, session affinity, retries), the user's routing and their choice. A login with
|
|
253
|
+
* `"disabled": true` stays in the hub and is never used (CLIProxyAPI 8.0.23); `shard cli-proxy use` switches logins on
|
|
254
|
+
* and off.
|
|
255
|
+
*/
|
|
256
|
+
export interface HubLogin {
|
|
257
|
+
/** Path relative to the auth directory. */
|
|
258
|
+
file: string;
|
|
259
|
+
/** The auth file's `type` (claude, codex, gemini…), lowercase; null when it has none. */
|
|
260
|
+
provider: string | null;
|
|
261
|
+
email: string | null;
|
|
262
|
+
/** The file carries `"disabled": true`. */
|
|
263
|
+
disabled: boolean;
|
|
264
|
+
/** It serves requests: enabled, with a provider, and the user's (not a file the retired account pool router left). */
|
|
265
|
+
active: boolean;
|
|
266
|
+
}
|
|
267
|
+
/** The hub's logins, oldest first, as they are (nothing changes). Files the retired account pool router left are not listed. */
|
|
268
|
+
export declare function listHubLogins(ws: HubWorkspace): Promise<HubLogin[]>;
|
|
269
|
+
/** The auth file's text with `disabled` set (true) or removed (false), every other field kept; null when unchanged. */
|
|
270
|
+
export declare function withDisabled(text: string, disabled: boolean): string | null;
|
|
271
|
+
/**
|
|
272
|
+
* Replaces one auth file atomically (0600, a temporary file in its folder then rename), only while it still holds what
|
|
273
|
+
* the device read ($2, its SHA-256): CLIProxyAPI rewrites a file when it refreshes the login, and a newer token must
|
|
274
|
+
* never be overwritten. The modification time stays, so the order of the hub's logins (oldest first) does not change.
|
|
275
|
+
* $1 is the path relative to the auth directory, the new content comes on standard input. Prints `written`, `changed`
|
|
276
|
+
* or `gone`.
|
|
277
|
+
*/
|
|
278
|
+
export declare const SET_AUTH_SCRIPT = "set -euo pipefail\na=/home/user/.cli-proxy-api; rel=$1; want=$2\ncase \"$rel\" in ''|/*|*..*) echo \"not a path inside the auth folder\" >&2; exit 2 ;; esac\nf=\"$a/$rel\"\n[ -f \"$f\" ] || { echo gone; exit 0; }\ntmp=$(mktemp \"$(dirname \"$f\")/.writing.XXXXXX\"); trap 'rm -f \"$tmp\"' EXIT\nchmod 0600 \"$tmp\"; cat > \"$tmp\"; touch -r \"$f\" \"$tmp\"\nif [ \"$(sha256sum < \"$f\" | cut -d' ' -f1)\" != \"$want\" ]; then echo changed; exit 0; fi\nmv -f \"$tmp\" \"$f\"; trap - EXIT\necho written\n";
|
|
279
|
+
/**
|
|
280
|
+
* Under the hub's `logins` lock: the retired account pool router's files stay disabled (they were never the user's);
|
|
281
|
+
* every other login keeps the state its owner gave it. Returns the logins as they are now, oldest first.
|
|
282
|
+
*/
|
|
283
|
+
export declare function settleHubLogins(ws: HubWorkspace): Promise<HubLogin[]>;
|
|
284
|
+
/**
|
|
285
|
+
* The user's choice of which logins serve requests (`shard cli-proxy use`; call it under the hub's `logins` lock).
|
|
286
|
+
* `only[provider]` names the one login of that provider that stays enabled (it is enabled first, then the provider's
|
|
287
|
+
* other logins are disabled, so the provider is never without one); `all` enables every login of the hub, which then
|
|
288
|
+
* routes across them as its config says. Providers the choice does not name are left as they are. Returns the logins
|
|
289
|
+
* as they are now, oldest first.
|
|
264
290
|
*/
|
|
265
|
-
export declare function
|
|
291
|
+
export declare function useHubLogins(ws: HubWorkspace, choice: {
|
|
292
|
+
only?: Record<string, string>;
|
|
293
|
+
all?: boolean;
|
|
294
|
+
}): Promise<HubLogin[]>;
|
|
295
|
+
/** Where CLI 0.22.0 reads each provider's one login ({"<provider>": "<auth file>"}); written atomically (0600). */
|
|
296
|
+
export declare const LEGACY_ACTIVE_FILE = "/home/user/.cli-proxy-hub/active.json";
|
|
297
|
+
export declare const LEGACY_ACTIVE_SCRIPT = "set -euo pipefail\ndir=/home/user/.cli-proxy-hub; mkdir -p \"$dir\"; umask 077\ntmp=$(mktemp \"$dir/.active.XXXXXX\"); trap 'rm -f \"$tmp\"' EXIT\ncat > \"$tmp\"; mv -f \"$tmp\" \"$dir/active.json\"; trap - EXIT\necho written\n";
|
|
298
|
+
/** Removes the router's files; prints `retired` when its hub token was there (its sf-agent then starts again without it). */
|
|
299
|
+
export declare const RETIRE_POOL_SCRIPT: string;
|
|
300
|
+
/** The config without the router's own client key (`api-keys`, or v8 `access.api-keys`); the text itself when absent. */
|
|
301
|
+
export declare function withoutClientKey(text: string, key: string): string;
|
|
266
302
|
/** The callback URL as the hub stores it (`callback.json`, 0600): its secret is shown only when it is created. */
|
|
267
303
|
export interface StoredCallback {
|
|
268
304
|
callback_url: string;
|