@shardflux/cli 0.15.2 → 0.17.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 +144 -14
- package/README.md +92 -7
- package/dist/acp-models.js +5 -1
- package/dist/agent-commands.d.ts +15 -1
- package/dist/agent-commands.js +84 -23
- package/dist/agent-models.d.ts +61 -0
- package/dist/agent-models.js +79 -0
- package/dist/agent-state.d.ts +10 -0
- package/dist/agent-state.js +13 -0
- package/dist/agent-target-commands.d.ts +13 -3
- package/dist/agent-target-commands.js +129 -23
- package/dist/agent-targets.d.ts +5 -0
- package/dist/agent-targets.js +14 -5
- package/dist/agent-terminal.d.ts +8 -0
- package/dist/agent-terminal.js +35 -2
- package/dist/args.d.ts +5 -1
- package/dist/args.js +74 -11
- package/dist/cli-proxy-forward.d.ts +45 -0
- package/dist/cli-proxy-forward.js +432 -0
- package/dist/cli-proxy-hub.d.ts +273 -0
- package/dist/cli-proxy-hub.js +749 -0
- package/dist/cli-proxy.d.ts +325 -0
- package/dist/cli-proxy.js +1016 -0
- package/dist/codex-cloud.d.ts +3 -0
- package/dist/codex-cloud.js +17 -5
- package/dist/command-center-actions.js +25 -1
- package/dist/commands.d.ts +22 -0
- package/dist/commands.js +54 -14
- package/dist/conversation-commands.js +6 -0
- package/dist/conversation-local.js +2 -2
- package/dist/conversation-stash.js +4 -3
- package/dist/conversation-terminal.d.ts +5 -0
- package/dist/conversation-terminal.js +54 -6
- package/dist/conversations.d.ts +31 -3
- package/dist/conversations.js +53 -10
- package/dist/device-approvals.d.ts +12 -0
- package/dist/device-approvals.js +73 -0
- package/dist/device-helper.d.ts +3 -1
- package/dist/device-helper.js +14 -4
- package/dist/errors.d.ts +7 -0
- package/dist/errors.js +12 -2
- package/dist/files-mirror.d.ts +164 -0
- package/dist/files-mirror.js +761 -0
- package/dist/handlers.js +5 -1
- package/dist/index.d.ts +4 -0
- package/dist/index.js +3 -0
- package/dist/linux-helper.d.ts +12 -0
- package/dist/linux-helper.js +88 -0
- package/dist/linux-terminal.d.ts +11 -0
- package/dist/linux-terminal.js +42 -0
- package/dist/main.js +38 -8
- package/dist/onboarding.d.ts +2 -0
- package/dist/onboarding.js +53 -5
- package/dist/registry.d.ts +2 -42
- package/dist/registry.js +6 -90
- package/dist/t3-launch.d.ts +37 -0
- package/dist/t3-launch.js +133 -0
- package/dist/t3-mode.d.ts +121 -0
- package/dist/t3-mode.js +654 -0
- package/dist/t3-phone.d.ts +17 -0
- package/dist/t3-phone.js +15 -0
- package/dist/t3-thread-script.d.ts +18 -0
- package/dist/t3-thread-script.js +245 -0
- package/dist/terminal-onboarding.d.ts +11 -0
- package/dist/terminal-onboarding.js +75 -0
- package/dist/thread-on-computer.d.ts +35 -0
- package/dist/thread-on-computer.js +206 -0
- package/dist/update.d.ts +1 -1
- package/dist/update.js +12 -1
- package/dist/wait-status.d.ts +22 -0
- package/dist/wait-status.js +59 -0
- package/dist/worktree-twin-engine.d.ts +125 -0
- package/dist/worktree-twin-engine.js +615 -0
- package/dist/worktree-twin-land.py +331 -0
- package/dist/worktree-twin.d.ts +313 -0
- package/dist/worktree-twin.js +843 -0
- package/package.json +3 -3
package/dist/handlers.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { DEVICE_HANDLERS } from "./device-approvals.js";
|
|
1
2
|
import { CREDENTIAL_HANDLERS } from "./credential-prompts.js";
|
|
2
3
|
import { CONVERSATION_HANDLERS } from "./conversation-commands.js";
|
|
3
4
|
/**
|
|
@@ -15,5 +16,8 @@ import { MENUBAR_HANDLERS } from "./menubar.js";
|
|
|
15
16
|
import { UPDATE_HANDLERS } from "./update.js";
|
|
16
17
|
import { FILESYSTEM_HANDLERS } from "./filesystem-commands.js";
|
|
17
18
|
import { CONVERSATION_HANDLERS as LOCAL_CONVERSATION_HANDLERS } from "./conversation-local.js";
|
|
19
|
+
import { TWIN_HANDLERS } from "./worktree-twin-engine.js";
|
|
18
20
|
import { CODEX_CLOUD_HANDLERS } from "./codex-cloud.js";
|
|
19
|
-
|
|
21
|
+
import { THREAD_HANDLERS } from "./thread-on-computer.js";
|
|
22
|
+
import { CLI_PROXY_HANDLERS } from "./cli-proxy.js";
|
|
23
|
+
export const HANDLERS = { ...DEVICE_HANDLERS, ...CODEX_CLOUD_HANDLERS, ...CLI_PROXY_HANDLERS, ...THREAD_HANDLERS, ...CONVERSATION_HANDLERS, ...LOCAL_CONVERSATION_HANDLERS, ...TWIN_HANDLERS, ...CREDENTIAL_HANDLERS, ...AGENT_CREDENTIAL_HANDLERS, ...KEY_HANDLERS, ...ACCOUNT_HANDLERS, ...AGENT_HANDLERS, ...CENTER_HANDLERS, ...TARGET_HANDLERS, ...AGENT_GIT_HANDLERS, ...MENUBAR_HANDLERS, ...FILESYSTEM_HANDLERS, ...UPDATE_HANDLERS };
|
package/dist/index.d.ts
CHANGED
|
@@ -24,4 +24,8 @@ export type { ErrorInfo } from './errors.js';
|
|
|
24
24
|
export { changedText, errorText, executionJson, executionSummary, feedbackCommand, json, suspendRequestText, suspendWhenIdleText, table, time, usageText, workspaceDetail, workspaceTable } from './format.js';
|
|
25
25
|
export { TimingRecorder, coldBootNotice, hostLostNotice, lostSuspendNotice, notices, timingJson, timingText } from './timing.js';
|
|
26
26
|
export { readAgentCredentials, syncAgentCredentials } from './agent-credentials.js';
|
|
27
|
+
export { detectLocalCliProxy, importCliProxy } from './cli-proxy.js';
|
|
28
|
+
export type { ImportOptions, ImportResult, LocalCliProxy, LocalLogin } from './cli-proxy.js';
|
|
29
|
+
export { ensureCliProxyHub, readHubInfo } from './cli-proxy-hub.js';
|
|
30
|
+
export type { EnsureOptions, EnsureResult, HubInfo } from './cli-proxy-hub.js';
|
|
27
31
|
export type { CredentialSources } from './agent-credentials.js';
|
package/dist/index.js
CHANGED
|
@@ -16,3 +16,6 @@ export { EXIT, AuthConfigError, CheckoutEndedError, ExecutionFailedError, Execut
|
|
|
16
16
|
export { changedText, errorText, executionJson, executionSummary, feedbackCommand, json, suspendRequestText, suspendWhenIdleText, table, time, usageText, workspaceDetail, workspaceTable } from "./format.js";
|
|
17
17
|
export { TimingRecorder, coldBootNotice, hostLostNotice, lostSuspendNotice, notices, timingJson, timingText } from "./timing.js";
|
|
18
18
|
export { readAgentCredentials, syncAgentCredentials } from "./agent-credentials.js";
|
|
19
|
+
// Hosted CLIProxyAPI (onboarding offers the import when detectLocalCliProxy() finds logins).
|
|
20
|
+
export { detectLocalCliProxy, importCliProxy } from "./cli-proxy.js";
|
|
21
|
+
export { ensureCliProxyHub, readHubInfo } from "./cli-proxy-hub.js";
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Ctx } from './commands.js';
|
|
2
|
+
export declare const HELPER_UNIT = "shardflux-helper.service";
|
|
3
|
+
export declare const helperUnitPath: (home: string) => string;
|
|
4
|
+
/** No credentials in the unit: the helper reads the same private store as shard. */
|
|
5
|
+
export declare function helperUnit(ctx: Pick<Ctx, 'runtime' | 'store' | 'apiUrl' | 'io'>): string;
|
|
6
|
+
export type Systemctl = (args: string[]) => boolean;
|
|
7
|
+
export declare function installLinuxHelper(ctx: Ctx, deps?: {
|
|
8
|
+
platform?: NodeJS.Platform;
|
|
9
|
+
run?: Systemctl;
|
|
10
|
+
}): boolean;
|
|
11
|
+
/** The caller owns fallback lifetime; never leave an unmanaged background process behind. */
|
|
12
|
+
export declare function startLinuxHelper(ctx: Ctx): () => Promise<void>;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
4
|
+
import { dirname, join } from 'node:path';
|
|
5
|
+
import { CLI_VERSION } from "./args.js";
|
|
6
|
+
import { writePrivateFile } from "./credentials.js";
|
|
7
|
+
import { runDeviceHelper } from "./device-helper.js";
|
|
8
|
+
export const HELPER_UNIT = 'shardflux-helper.service';
|
|
9
|
+
export const helperUnitPath = (home) => join(home, '.config/systemd/user', HELPER_UNIT);
|
|
10
|
+
const quote = (s) => '"' + s.replaceAll('\\', '\\\\').replaceAll('"', '\\"').replaceAll('%', '%%').replaceAll('\n', '\\n').replaceAll('\r', '\\r') + '"';
|
|
11
|
+
/** No credentials in the unit: the helper reads the same private store as shard. */
|
|
12
|
+
export function helperUnit(ctx) {
|
|
13
|
+
if (!ctx.runtime)
|
|
14
|
+
throw new Error('An installed shard is required.');
|
|
15
|
+
let script = ctx.runtime.script;
|
|
16
|
+
try {
|
|
17
|
+
script = realpathSync(script);
|
|
18
|
+
}
|
|
19
|
+
catch { /* preserve the supplied installed path */ }
|
|
20
|
+
const env = { HOME: ctx.io.env.HOME, PATH: ctx.io.env.PATH ?? '/usr/local/bin:/usr/bin:/bin', SHARDFLUX_CONFIG_DIR: ctx.store.dir,
|
|
21
|
+
SHARDFLUX_API_URL: ctx.apiUrl, SHARDFLUX_NO_UPDATE_CHECK: '1',
|
|
22
|
+
...Object.fromEntries(['DISPLAY', 'WAYLAND_DISPLAY', 'DBUS_SESSION_BUS_ADDRESS', 'XDG_RUNTIME_DIR', 'CODEX_HOME', 'CLAUDE_CONFIG_DIR'].flatMap(k => ctx.io.env[k] ? [[k, ctx.io.env[k]]] : [])) };
|
|
23
|
+
return `# Shardflux CLI ${CLI_VERSION}\n[Unit]\nDescription=Shardflux background helper\nAfter=network-online.target\n\n[Service]\nType=simple\nExecStart=${[ctx.runtime.node, script, 'device-helper', '--service'].map(s => quote(s.replaceAll('$', '$$'))).join(' ')}\n${Object.entries(env).map(([k, v]) => `Environment=${quote(`${k}=${v}`)}`).join('\n')}\nRestart=on-failure\nRestartSec=5\nTimeoutStopSec=10\nUMask=0077\n\n[Install]\nWantedBy=default.target\n`;
|
|
24
|
+
}
|
|
25
|
+
export function installLinuxHelper(ctx, deps = {}) {
|
|
26
|
+
if ((deps.platform ?? process.platform) !== 'linux' || !ctx.runtime || !ctx.io.env.HOME)
|
|
27
|
+
return false;
|
|
28
|
+
const run = deps.run ?? (args => spawnSync('systemctl', ['--user', ...args], { env: ctx.io.env, stdio: 'ignore', timeout: 5000 }).status === 0);
|
|
29
|
+
// Probe before writing: containers without a user manager use the command-lifetime helper.
|
|
30
|
+
if (!run(['show-environment']))
|
|
31
|
+
return false;
|
|
32
|
+
const file = helperUnitPath(ctx.io.env.HOME), text = helperUnit(ctx);
|
|
33
|
+
const receipt = join(ctx.store.dir, 'linux-helper-started'), digest = createHash('sha256').update(text).digest('hex');
|
|
34
|
+
let before = '', started = '';
|
|
35
|
+
try {
|
|
36
|
+
before = readFileSync(file, 'utf8');
|
|
37
|
+
}
|
|
38
|
+
catch { /* first sign-in */ }
|
|
39
|
+
try {
|
|
40
|
+
started = readFileSync(receipt, 'utf8');
|
|
41
|
+
}
|
|
42
|
+
catch { /* no successful start yet */ }
|
|
43
|
+
if (before !== text) {
|
|
44
|
+
writePrivateFile(dirname(file), file, text);
|
|
45
|
+
if (!run(['daemon-reload']) || !run(['enable', '--now', HELPER_UNIT]))
|
|
46
|
+
return false;
|
|
47
|
+
if (before && !run(['restart', HELPER_UNIT]))
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
else if (!run(['is-active', '--quiet', HELPER_UNIT])) {
|
|
51
|
+
if (!run(['enable', '--now', HELPER_UNIT]))
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
else if (started !== digest && !run(['restart', HELPER_UNIT]))
|
|
55
|
+
return false;
|
|
56
|
+
// A failed restart must be retried by the next command even though the new unit was already written.
|
|
57
|
+
if (started !== digest)
|
|
58
|
+
writePrivateFile(ctx.store.dir, receipt, digest);
|
|
59
|
+
return true;
|
|
60
|
+
}
|
|
61
|
+
/** The caller owns fallback lifetime; never leave an unmanaged background process behind. */
|
|
62
|
+
export function startLinuxHelper(ctx) {
|
|
63
|
+
const noop = () => Promise.resolve();
|
|
64
|
+
if (process.platform !== 'linux' || !ctx.runtime)
|
|
65
|
+
return noop;
|
|
66
|
+
try {
|
|
67
|
+
ctx.apiKey();
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return noop;
|
|
71
|
+
}
|
|
72
|
+
try {
|
|
73
|
+
if (installLinuxHelper(ctx))
|
|
74
|
+
return noop;
|
|
75
|
+
}
|
|
76
|
+
catch { /* use the command-lifetime helper */ }
|
|
77
|
+
const notice = join(ctx.store.dir, 'linux-helper-notice');
|
|
78
|
+
try {
|
|
79
|
+
readFileSync(notice);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
ctx.io.stderr.write('Shardflux helper runs while shard commands are open on this computer.\n');
|
|
83
|
+
writePrivateFile(ctx.store.dir, notice, '1\n');
|
|
84
|
+
}
|
|
85
|
+
const abort = new AbortController();
|
|
86
|
+
const work = runDeviceHelper({ ...ctx, signal: AbortSignal.any([ctx.signal, abort.signal]) }, { service: true }).catch(() => undefined);
|
|
87
|
+
return async () => { abort.abort(); await work; };
|
|
88
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { Ctx } from './commands.js';
|
|
2
|
+
import type { Values } from './args.js';
|
|
3
|
+
/** Linux terminals use the existing native Codex harness; explicit app routes keep Cloud. */
|
|
4
|
+
export declare function linuxTerminalSession(ctx: Pick<Ctx, 'io' | 'json'>, values: Values, platform?: NodeJS.Platform): boolean;
|
|
5
|
+
export declare const shellWord: (s: string) => string;
|
|
6
|
+
export declare function linuxTerminalCommand(argv: string[], env: Record<string, string | undefined>): {
|
|
7
|
+
command: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
} | null;
|
|
10
|
+
/** Invoked before cloud work: the new terminal owns the attach, credentials and any prompts. */
|
|
11
|
+
export declare function openLinuxTerminal(ctx: Ctx, ref: string, app?: string): Promise<number>;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { accessSync, constants } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
/** Linux terminals use the existing native Codex harness; explicit app routes keep Cloud. */
|
|
5
|
+
export function linuxTerminalSession(ctx, values, platform = process.platform) {
|
|
6
|
+
return platform === 'linux' && (values.in === 'terminal' || (values.in === undefined && !!ctx.io.stdin.isTTY && !ctx.json));
|
|
7
|
+
}
|
|
8
|
+
export const shellWord = (s) => `'${s.replaceAll("'", "'\\''")}'`;
|
|
9
|
+
export function linuxTerminalCommand(argv, env) {
|
|
10
|
+
const executable = (name) => name.includes('/') ? [name] : (env.PATH ?? '').split(':').filter(Boolean).map(p => join(p, name));
|
|
11
|
+
const exists = (name) => executable(name).some(p => { try {
|
|
12
|
+
accessSync(p, constants.X_OK);
|
|
13
|
+
return true;
|
|
14
|
+
}
|
|
15
|
+
catch {
|
|
16
|
+
return false;
|
|
17
|
+
} });
|
|
18
|
+
// The shell keeps the window open on an attach error so the user can read and retry it.
|
|
19
|
+
const attach = ['sh', '-c', `${argv.map(shellWord).join(' ')}; status=$?; if [ "$status" -ne 0 ]; then printf '\nPress Enter to close.'; read answer; fi; exit "$status"`];
|
|
20
|
+
for (const command of [...new Set([env.TERMINAL?.trim(), 'x-terminal-emulator', 'gnome-terminal'].filter((s) => !!s))]) {
|
|
21
|
+
if (exists(command))
|
|
22
|
+
return { command, args: [command === 'gnome-terminal' ? '--' : '-e', ...attach] };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
/** Invoked before cloud work: the new terminal owns the attach, credentials and any prompts. */
|
|
27
|
+
export async function openLinuxTerminal(ctx, ref, app) {
|
|
28
|
+
const words = ['agents', 'open', ref, '--in', 'terminal', ...(app ? ['--app', app] : [])];
|
|
29
|
+
const argv = ctx.runtime ? [ctx.runtime.node, ctx.runtime.script, '--api-url', ctx.apiUrl, ...words] : ['shard', ...words];
|
|
30
|
+
const choice = linuxTerminalCommand(argv, ctx.io.env);
|
|
31
|
+
if (choice) {
|
|
32
|
+
const child = spawn(choice.command, choice.args, { env: ctx.io.env, cwd: process.cwd(), detached: true, stdio: 'ignore' });
|
|
33
|
+
try {
|
|
34
|
+
await new Promise((resolve, reject) => { child.once('spawn', resolve); child.once('error', reject); });
|
|
35
|
+
child.unref();
|
|
36
|
+
return 0;
|
|
37
|
+
}
|
|
38
|
+
catch { /* print a runnable command if the terminal could not start */ }
|
|
39
|
+
}
|
|
40
|
+
ctx.io.stdout.write(`${['shard', '--api-url', ctx.apiUrl, ...words].map(shellWord).join(' ')}\n`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
package/dist/main.js
CHANGED
|
@@ -13,12 +13,15 @@
|
|
|
13
13
|
import { homedir, hostname } from 'node:os';
|
|
14
14
|
import { statSync } from 'node:fs';
|
|
15
15
|
import { SDK_VERSION, Shardflux, ShardfluxAccount, isSessionToken } from '@shardflux/sdk';
|
|
16
|
-
import { CLI_VERSION, CENTER_COMMAND, PROJECT_COMMAND, commandHelp, parseCommandLine, resolveApiUrl, resolveWake, resolveWakeTimeout } from "./args.js";
|
|
16
|
+
import { CLI_VERSION, CENTER_COMMAND, ONBOARDING_COMMAND, PROJECT_COMMAND, commandHelp, parseCommandLine, resolveApiUrl, resolveWake, resolveWakeTimeout } from "./args.js";
|
|
17
17
|
import { commandCenter } from "./command-center.js";
|
|
18
|
-
import {
|
|
18
|
+
import { launchProjectCommand } from "./conversation-terminal.js";
|
|
19
19
|
import { runAcp } from "./acp.js";
|
|
20
20
|
import { registerInstalledT3 } from "./t3-provider.js";
|
|
21
|
+
import { refreshForwarderAgent } from "./cli-proxy.js";
|
|
21
22
|
import { runAcpLogin } from "./acp-login.js";
|
|
23
|
+
import { terminalOnboarding } from "./terminal-onboarding.js";
|
|
24
|
+
import { startLinuxHelper } from "./linux-helper.js";
|
|
22
25
|
import { runDeviceHelper } from "./device-helper.js";
|
|
23
26
|
import { housekeeping, startupHousekeeping } from "./agent-git-commands.js";
|
|
24
27
|
import { CredentialStore, configDir } from "./credentials.js";
|
|
@@ -31,6 +34,7 @@ import { HANDLERS } from "./handlers.js";
|
|
|
31
34
|
import { makeFetch } from "./http.js";
|
|
32
35
|
import { Input } from "./input.js";
|
|
33
36
|
import { TimingRecorder, notices, timingJson, timingText } from "./timing.js";
|
|
37
|
+
import { waitStatusListener } from "./wait-status.js";
|
|
34
38
|
import { CLI_DISTRIBUTION, NOTICE_WAIT_MS, UpdateCheck, noticeOf, updateCheckDisabled, updateView } from "./update-check.js";
|
|
35
39
|
const KEY_SHAPE = /^sfk_[a-z2-7]{16}_[A-Za-z0-9]+$/;
|
|
36
40
|
// Printable (no C0 controls or DEL), as the API's AgentLabel pattern.
|
|
@@ -126,7 +130,7 @@ function hintFor(info, cred, file, command = '') {
|
|
|
126
130
|
if (reason === 'edit_not_text')
|
|
127
131
|
return 'the file is not UTF-8 text; use files write for binary data';
|
|
128
132
|
if (reason === 'host_capacity')
|
|
129
|
-
return
|
|
133
|
+
return "the workspace's host is making room to wake it and the call did not run (it was already retried); run the command again";
|
|
130
134
|
if (reason === 'host_feature_unavailable')
|
|
131
135
|
return hostFeatureHint(info.details?.feature);
|
|
132
136
|
// Inbound ports (0.9.0): the next step names the command.
|
|
@@ -534,6 +538,7 @@ export async function run(argv, io, opts = {}) {
|
|
|
534
538
|
agentLabel,
|
|
535
539
|
wake,
|
|
536
540
|
wakeTimeoutMs,
|
|
541
|
+
wakeTimeoutExplicit: parsed.global.wakeTimeout !== undefined || Boolean(io.env.SHARDFLUX_WAKE_TIMEOUT_MS?.trim()),
|
|
537
542
|
signal,
|
|
538
543
|
timing: showTiming,
|
|
539
544
|
traces,
|
|
@@ -569,13 +574,18 @@ export async function run(argv, io, opts = {}) {
|
|
|
569
574
|
if (cloud)
|
|
570
575
|
return cloud;
|
|
571
576
|
const key = apiKey();
|
|
577
|
+
// Timings, and (0.16.0+) where a wait stands: a queued start's position, a call waiting for room (stderr).
|
|
578
|
+
const waitStatus = waitStatusListener(io.stderr, parsed.global.json);
|
|
572
579
|
cloud = new Shardflux({
|
|
573
580
|
apiKey: key.token,
|
|
574
581
|
baseUrl: apiUrl,
|
|
575
582
|
userAgent,
|
|
576
583
|
fetch: watchedFetch,
|
|
577
584
|
sleep,
|
|
578
|
-
onProgress:
|
|
585
|
+
onProgress: (e) => {
|
|
586
|
+
traces.listener(e);
|
|
587
|
+
waitStatus(e);
|
|
588
|
+
},
|
|
579
589
|
versionCheck: false,
|
|
580
590
|
});
|
|
581
591
|
return cloud;
|
|
@@ -653,7 +663,7 @@ export async function run(argv, io, opts = {}) {
|
|
|
653
663
|
refreshMenubarInBackground({ dir: store.dir, home: io.env.HOME || homedir(), runtime: opts.runtime, env: { ...process.env, ...io.env } });
|
|
654
664
|
}
|
|
655
665
|
// The update check runs next to the command (not for `version`, which reports it itself).
|
|
656
|
-
if (!updateCheckDisabled(io.env) && !['version', 'update', 'ssh-proxy', 'acp', 'device-helper'].includes(parsed.command.path[0]) && command !== 'menubar refresh') {
|
|
666
|
+
if (!updateCheckDisabled(io.env) && !['version', 'update', 'ssh-proxy', 'acp', 'device-helper'].includes(parsed.command.path[0]) && command !== 'menubar refresh' && command !== 'cli-proxy forward') {
|
|
657
667
|
try {
|
|
658
668
|
updates = new UpdateCheck({ dir: store.dir, apiUrl, distribution, fetch: base, userAgent, registry: npmRegistry(io.env) });
|
|
659
669
|
updates.start();
|
|
@@ -666,11 +676,28 @@ export async function run(argv, io, opts = {}) {
|
|
|
666
676
|
catch (err) {
|
|
667
677
|
return report(err);
|
|
668
678
|
}
|
|
669
|
-
|
|
679
|
+
let stopHelper;
|
|
680
|
+
const startHelper = () => {
|
|
681
|
+
if (stopHelper)
|
|
682
|
+
return;
|
|
683
|
+
try {
|
|
684
|
+
ctx.apiKey();
|
|
685
|
+
}
|
|
686
|
+
catch {
|
|
687
|
+
return;
|
|
688
|
+
}
|
|
689
|
+
stopHelper = startLinuxHelper(ctx);
|
|
690
|
+
};
|
|
691
|
+
const handler = parsed.command.path[0] === 'acp' ? (ctx) => parsed.values.login === true ? runAcpLogin(ctx) : runAcp(ctx) : parsed.command.path[0] === 'device-helper' ? (ctx) => runDeviceHelper(ctx, { service: parsed.values.service === true }) : command === 'menubar refresh' ? runMenubarRefresh : parsed.command === PROJECT_COMMAND ? launchProjectCommand : parsed.command === ONBOARDING_COMMAND ? (ctx, values) => terminalOnboarding(ctx, values, { startHelper }) : parsed.command === CENTER_COMMAND ? commandCenter : HANDLERS[parsed.command.path.join(' ')];
|
|
670
692
|
if (!handler)
|
|
671
693
|
return report(new UsageError(`unknown command "${parsed.command.path.join(' ')}"`));
|
|
672
|
-
|
|
694
|
+
// Long-running helpers other programs start (an editor, launchd, the menu bar) skip the per-command housekeeping.
|
|
695
|
+
if (!['acp', 'device-helper'].includes(parsed.command.path[0]) && command !== 'menubar refresh' && command !== 'cli-proxy forward') {
|
|
696
|
+
// An updated shard restarts this computer's CLIProxyAPI forwarder on itself (a no-op unless the version changed).
|
|
697
|
+
await refreshForwarderAgent(io.env, ctx.store.dir).catch(() => false);
|
|
673
698
|
await housekeeping(ctx);
|
|
699
|
+
startHelper();
|
|
700
|
+
}
|
|
674
701
|
const running = handler(ctx, parsed.values, parsed.positionals).then((code) => ({ code }), (err) => ({ err }));
|
|
675
702
|
// On Ctrl-C give the command a moment to clean up (exec sends a cancel), then stop regardless.
|
|
676
703
|
const interrupted = new Promise((resolve) => {
|
|
@@ -705,6 +732,9 @@ export async function run(argv, io, opts = {}) {
|
|
|
705
732
|
if (notice)
|
|
706
733
|
io.stderr.write(notice);
|
|
707
734
|
}
|
|
735
|
+
if (!['acp', 'device-helper'].includes(parsed.command.path[0]) && command !== 'menubar refresh' && command !== 'cli-proxy forward')
|
|
736
|
+
startHelper();
|
|
737
|
+
await stopHelper?.();
|
|
708
738
|
return code;
|
|
709
739
|
}
|
|
710
740
|
/**
|
|
@@ -731,7 +761,7 @@ export async function main(opts = {}) {
|
|
|
731
761
|
const runtime = { node: process.execPath, script: process.argv[1] ?? '' };
|
|
732
762
|
// The menu bar's update button runs this shard; it finds it here (macOS only, written when it changes).
|
|
733
763
|
if (process.platform === 'darwin' && runtime.script)
|
|
734
|
-
recordInstall(configDir(process.env), runtime, opts.distribution ?? CLI_DISTRIBUTION);
|
|
764
|
+
recordInstall(configDir(process.env), runtime, opts.distribution ?? CLI_DISTRIBUTION, process.env);
|
|
735
765
|
const code = await run(process.argv.slice(2), { stdout: process.stdout, stderr: process.stderr, stdin: process.stdin, env: process.env }, { signal: controller.signal, runtime, ...(opts.distribution ? { distribution: opts.distribution } : {}) });
|
|
736
766
|
await Promise.all([flush(process.stdout), flush(process.stderr)]);
|
|
737
767
|
process.exit(code);
|
package/dist/onboarding.d.ts
CHANGED
|
@@ -26,10 +26,12 @@ type CredentialRow = {
|
|
|
26
26
|
checked_at: string;
|
|
27
27
|
};
|
|
28
28
|
};
|
|
29
|
+
/** `url`: a page the person opens (the T3 Connect sign-in), instead of a command. */
|
|
29
30
|
type Action = {
|
|
30
31
|
title: string;
|
|
31
32
|
command?: string[];
|
|
32
33
|
retry?: boolean;
|
|
34
|
+
url?: string;
|
|
33
35
|
};
|
|
34
36
|
export interface Connection {
|
|
35
37
|
id: 'account' | AgentApp | 't3';
|
package/dist/onboarding.js
CHANGED
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
import { readFile, stat } from 'node:fs/promises';
|
|
2
2
|
import { delimiter, join, resolve, basename } from 'node:path';
|
|
3
3
|
import { homedir } from 'node:os';
|
|
4
|
+
import { ShardfluxApiError } from '@shardflux/sdk';
|
|
4
5
|
import { resolveWorkspace } from "./commands.js";
|
|
6
|
+
import { AgentStateStore } from "./agent-state.js";
|
|
7
|
+
import { describeError } from "./errors.js";
|
|
8
|
+
import { t3PhoneLink } from "./t3-phone.js";
|
|
5
9
|
import { readAgentCredentials, syncAgentCredentials } from "./agent-credentials.js";
|
|
6
10
|
import { getConversation } from "./conversations.js";
|
|
7
11
|
import { codexHealth, readCodexConnector, restartCodexConnector } from "./codex-cloud.js";
|
|
@@ -79,15 +83,59 @@ export async function t3Connection(ctx, signedIn, appDirs = applicationDirs(ctx.
|
|
|
79
83
|
return { ...base, mode: 'provider', state: 'action', fact: error instanceof Error ? error.message : 'T3 Code’s settings could not be updated.', action: { title: 'Check again' } };
|
|
80
84
|
}
|
|
81
85
|
}
|
|
82
|
-
// Stable T3: an SSH environment per project; the newest one this API set up is the one to add.
|
|
86
|
+
// Stable T3: an SSH environment per project; the newest one this API set up is the one to add. They are recorded in
|
|
87
|
+
// agents.json (targets.json keeps T3 out of its manifest); a manifest entry from an older CLI still counts.
|
|
88
|
+
const recorded = new AgentStateStore(ctx.store.dir).t3Environments(ctx.apiUrl).at(-1);
|
|
83
89
|
const manifest = await jsonFile(join(ctx.store.dir, 'targets.json'));
|
|
84
90
|
const entries = Array.isArray(manifest?.entries) ? manifest.entries : [];
|
|
85
|
-
const
|
|
86
|
-
|
|
87
|
-
|
|
91
|
+
const legacy = entries.filter(e => e.api_url === ctx.apiUrl && Array.isArray(e.targets) && e.targets.includes('t3') && typeof e.host === 'string').at(-1);
|
|
92
|
+
const host = recorded?.host ?? legacy?.host;
|
|
93
|
+
const workspaceKey = recorded?.workspace ?? (typeof legacy?.workspace === 'string' ? legacy.workspace : undefined);
|
|
94
|
+
if (host) {
|
|
95
|
+
const desktop = `In T3 Code: Add environment → SSH → ${host}`;
|
|
96
|
+
const phone = await t3Phone(ctx, workspaceKey);
|
|
97
|
+
if (!phone)
|
|
98
|
+
return { ...base, mode: 'ssh', state: 'verified', fact: desktop };
|
|
99
|
+
return { ...base, mode: 'ssh', ...phone, fact: phone.state === 'verified' ? `${desktop}. On your phone: ${phone.fact}` : phone.fact };
|
|
100
|
+
}
|
|
88
101
|
// An SSH environment is per project (shardflux-<project>-main): it is set up once a project is picked.
|
|
89
102
|
return { ...base, mode: 'ssh', state: 'info', fact: 'Available after you pick a project.', next: shard(['agents', 'new', '--in', 't3', '--path', '<project>']) };
|
|
90
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* The T3 app on the phone (T3 Connect, 0.17.0+): the workspace is linked with the person's one T3 Connect sign-in. The
|
|
106
|
+
* sign-in is a page to open once; afterwards every project appears in the T3 app by itself. Undefined when the API has
|
|
107
|
+
* no T3 Connect: the desktop route is the whole card then.
|
|
108
|
+
*/
|
|
109
|
+
async function t3Phone(ctx, key) {
|
|
110
|
+
if (!key)
|
|
111
|
+
return undefined;
|
|
112
|
+
try {
|
|
113
|
+
const ws = await resolveWorkspace(ctx, key);
|
|
114
|
+
const cloud = ctx.cloud();
|
|
115
|
+
const known = (await cloud.t3.status()).links.find(l => l.workspace_id === ws.id);
|
|
116
|
+
// A workspace connected to T3 before T3 Connect, or one whose link failed, is linked now: it is the person's choice.
|
|
117
|
+
const { link, signIn, needsLogin } = !known || known.status === 'error' || known.status === 'needs_sign_in'
|
|
118
|
+
? await t3PhoneLink(ctx, ws.id) : { link: known, signIn: undefined, needsLogin: false };
|
|
119
|
+
if (link.status === 'linked')
|
|
120
|
+
return { state: 'verified', fact: `${link.label} in the T3 app.` };
|
|
121
|
+
if (signIn) {
|
|
122
|
+
return { state: 'action', fact: `To use it from the T3 app on your phone, confirm ${signIn.user_code} once.`,
|
|
123
|
+
action: { title: 'Connect phone', url: signIn.verification_uri } };
|
|
124
|
+
}
|
|
125
|
+
if (needsLogin)
|
|
126
|
+
return { state: 'info', fact: 'Sign in to Shardflux on this Mac to use it from the T3 app.' };
|
|
127
|
+
return { state: 'pending', fact: `Adding ${link.label} to the T3 app on your phone…` };
|
|
128
|
+
}
|
|
129
|
+
catch (error) {
|
|
130
|
+
// No T3 Connect on this API, or nothing to ask it: the desktop route stands on its own.
|
|
131
|
+
if (!(error instanceof ShardfluxApiError) || describeError(error).code === 'not_found')
|
|
132
|
+
return undefined;
|
|
133
|
+
// Another member's T3 Connect sign-in links this project: it is in their T3 app, not something to fix here.
|
|
134
|
+
if (error.details?.reason === 't3_linked_by_other_member')
|
|
135
|
+
return { state: 'info', fact: 'In the T3 app of the member who linked it.' };
|
|
136
|
+
return { state: 'unknown', fact: 'The T3 app link could not be checked just now.', action: { title: 'Retry', retry: true } };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
91
139
|
const shard = (words) => ['shard', ...words];
|
|
92
140
|
/** Claude: the account login is ready and Claude itself accepts it right now. */
|
|
93
141
|
export function claudeConnection(row) {
|
|
@@ -105,7 +153,7 @@ export function codexConnection(local, synced, connector, health) {
|
|
|
105
153
|
const enable = { title: 'Connect Codex', command: shard(['agents', 'codex', 'enable']) };
|
|
106
154
|
if (!synced) {
|
|
107
155
|
return local ? { state: 'action', fact: 'Available on web and phone once connected.', action: enable }
|
|
108
|
-
: { state: 'action', fact: 'Sign in to Codex on this
|
|
156
|
+
: { state: 'action', fact: 'Sign in to Codex on this computer first.', action: { title: 'Sign in to Codex', command: ['codex', 'login'] } };
|
|
109
157
|
}
|
|
110
158
|
const retry = { title: 'Retry', retry: true, command: shard(['setup', '--inspect', '--apps', 'codex', '--connect', '--retry-codex']) };
|
|
111
159
|
switch (health) {
|
package/dist/registry.d.ts
CHANGED
|
@@ -1,48 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
export declare const REGISTRY_TIMEOUT_MS = 3000;
|
|
1
|
+
import type { RegistryOptions } from '@shardflux/sdk';
|
|
2
|
+
export { NPM_REGISTRY, npmLatest, withRegistryLatest } from '@shardflux/sdk';
|
|
4
3
|
/** The npm packages whose versions `shard update` compares. */
|
|
5
4
|
export declare const NPM_CLIENTS: readonly ["shardflux", "@shardflux/cli", "@shardflux/sdk", "@shardflux/mcp", "@shardflux/menubar-darwin"];
|
|
6
|
-
export interface RegistryAnswer {
|
|
7
|
-
latest: string | null;
|
|
8
|
-
/** The registry's validator for the next request (npm's dist-tags endpoint sends none today). */
|
|
9
|
-
etag: string | null;
|
|
10
|
-
/** 304: what the caller cached under `etag` is still current. */
|
|
11
|
-
notModified: boolean;
|
|
12
|
-
}
|
|
13
|
-
export interface RegistryOptions {
|
|
14
|
-
fetch: typeof fetch;
|
|
15
|
-
/** Default https://registry.npmjs.org (SHARDFLUX_NPM_REGISTRY overrides it for the CLI). */
|
|
16
|
-
registry?: string;
|
|
17
|
-
etag?: string | null;
|
|
18
|
-
timeoutMs?: number;
|
|
19
|
-
signal?: AbortSignal;
|
|
20
|
-
userAgent?: string;
|
|
21
|
-
}
|
|
22
5
|
/** The registry to ask: SHARDFLUX_NPM_REGISTRY, else npm's public registry. */
|
|
23
6
|
export declare function npmRegistry(env: Record<string, string | undefined>): string;
|
|
24
|
-
/** `latest` of an npm package from `<registry>/-/package/<name>/dist-tags`, or null when the registry did not answer. */
|
|
25
|
-
export declare function npmLatest(pkg: string, opts: RegistryOptions): Promise<RegistryAnswer | null>;
|
|
26
7
|
/** `latest` of every package in `packages`, asked in parallel; a package the registry did not answer is absent. */
|
|
27
8
|
export declare function npmLatestAll(packages: readonly string[], opts: RegistryOptions): Promise<Record<string, string>>;
|
|
28
|
-
export interface CatalogEntry {
|
|
29
|
-
package: string;
|
|
30
|
-
ecosystem: string;
|
|
31
|
-
latest: string | null;
|
|
32
|
-
minimum_supported: string | null;
|
|
33
|
-
upgrade_command: string | null;
|
|
34
|
-
release_notes_url: string | null;
|
|
35
|
-
hold?: string | null;
|
|
36
|
-
[key: string]: unknown;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* A GET /v1/client-versions body whose npm entries carry the registry's `latest` (`registry`: package -> version; an
|
|
40
|
-
* absent package keeps the catalog's). Works with every catalog shape the API has served: no `hold` field, `latest`
|
|
41
|
-
* null, a package missing from the list, or no catalog at all (`catalog` null: the API did not answer).
|
|
42
|
-
*
|
|
43
|
-
* `hold`: when the registry's latest is that version, it is not recommended; the catalog's own `latest` is, when it is
|
|
44
|
-
* older, else `minimum_supported` (so "no longer supported" still works and nobody is told to update).
|
|
45
|
-
*/
|
|
46
|
-
export declare function withRegistryLatest(catalog: unknown, registry: Record<string, string | null | undefined>): {
|
|
47
|
-
clients: CatalogEntry[];
|
|
48
|
-
};
|
package/dist/registry.js
CHANGED
|
@@ -1,102 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The latest version of each Shardflux client comes from
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* The API catalog (GET /v1/client-versions) still says which versions it serves (`minimum_supported`) and may name a
|
|
7
|
-
* version to stop recommending (`hold`, an emergency brake). Its `latest` is only the fallback when the registry does
|
|
8
|
-
* not answer. Every request here is bounded and never throws: offline means "no answer".
|
|
2
|
+
* The latest version of each Shardflux client comes from npm (0.15.2+; the SDK's registry helpers): its `latest`
|
|
3
|
+
* dist-tag is what `npm i -g shardflux` and `shard update` install. The API catalog adds `minimum_supported` and
|
|
4
|
+
* `hold` (see withRegistryLatest).
|
|
9
5
|
*/
|
|
10
|
-
import {
|
|
11
|
-
export
|
|
12
|
-
/** Each registry request's timeout (ms). */
|
|
13
|
-
export const REGISTRY_TIMEOUT_MS = 3_000;
|
|
6
|
+
import { npmLatest, registryFromEnv } from '@shardflux/sdk';
|
|
7
|
+
export { NPM_REGISTRY, npmLatest, withRegistryLatest } from '@shardflux/sdk';
|
|
14
8
|
/** The npm packages whose versions `shard update` compares. */
|
|
15
9
|
export const NPM_CLIENTS = ['shardflux', '@shardflux/cli', '@shardflux/sdk', '@shardflux/mcp', '@shardflux/menubar-darwin'];
|
|
16
|
-
const str = (v) => (typeof v === 'string' && v.length > 0 ? v : null);
|
|
17
|
-
const isObj = (v) => typeof v === 'object' && v !== null && !Array.isArray(v);
|
|
18
10
|
/** The registry to ask: SHARDFLUX_NPM_REGISTRY, else npm's public registry. */
|
|
19
11
|
export function npmRegistry(env) {
|
|
20
|
-
|
|
21
|
-
return own ? own.replace(/\/+$/, '') : NPM_REGISTRY;
|
|
22
|
-
}
|
|
23
|
-
/** `latest` of an npm package from `<registry>/-/package/<name>/dist-tags`, or null when the registry did not answer. */
|
|
24
|
-
export async function npmLatest(pkg, opts) {
|
|
25
|
-
try {
|
|
26
|
-
const timeout = AbortSignal.timeout(opts.timeoutMs ?? REGISTRY_TIMEOUT_MS);
|
|
27
|
-
const headers = { accept: 'application/json' };
|
|
28
|
-
if (opts.userAgent)
|
|
29
|
-
headers['user-agent'] = opts.userAgent;
|
|
30
|
-
if (opts.etag)
|
|
31
|
-
headers['if-none-match'] = opts.etag;
|
|
32
|
-
const res = await opts.fetch(`${(opts.registry ?? NPM_REGISTRY).replace(/\/+$/, '')}/-/package/${encodeURIComponent(pkg)}/dist-tags`, {
|
|
33
|
-
method: 'GET',
|
|
34
|
-
headers,
|
|
35
|
-
signal: opts.signal ? AbortSignal.any([timeout, opts.signal]) : timeout,
|
|
36
|
-
});
|
|
37
|
-
const etag = res.headers.get('etag');
|
|
38
|
-
if (res.status === 304) {
|
|
39
|
-
await res.body?.cancel().catch(() => undefined);
|
|
40
|
-
return { latest: null, etag: etag ?? opts.etag ?? null, notModified: true };
|
|
41
|
-
}
|
|
42
|
-
if (!res.ok) {
|
|
43
|
-
await res.body?.cancel().catch(() => undefined);
|
|
44
|
-
return null;
|
|
45
|
-
}
|
|
46
|
-
const body = JSON.parse(await res.text());
|
|
47
|
-
const latest = isObj(body) ? str(body.latest) : null;
|
|
48
|
-
// A tag that is not a version (a broken registry mirror) is no answer.
|
|
49
|
-
if (latest === null || compareVersions(latest, latest) === null)
|
|
50
|
-
return null;
|
|
51
|
-
return { latest, etag, notModified: false };
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return null;
|
|
55
|
-
}
|
|
12
|
+
return registryFromEnv(env, 'npm');
|
|
56
13
|
}
|
|
57
14
|
/** `latest` of every package in `packages`, asked in parallel; a package the registry did not answer is absent. */
|
|
58
15
|
export async function npmLatestAll(packages, opts) {
|
|
59
16
|
const answers = await Promise.all(packages.map(async (pkg) => [pkg, await npmLatest(pkg, { ...opts, etag: null })]));
|
|
60
17
|
return Object.fromEntries(answers.flatMap(([pkg, a]) => (a?.latest ? [[pkg, a.latest]] : [])));
|
|
61
18
|
}
|
|
62
|
-
const DEFAULT_UPGRADE = {
|
|
63
|
-
shardflux: 'npm i -g shardflux',
|
|
64
|
-
'@shardflux/cli': 'npm i -g shardflux',
|
|
65
|
-
'@shardflux/menubar-darwin': 'shard update',
|
|
66
|
-
};
|
|
67
|
-
/**
|
|
68
|
-
* A GET /v1/client-versions body whose npm entries carry the registry's `latest` (`registry`: package -> version; an
|
|
69
|
-
* absent package keeps the catalog's). Works with every catalog shape the API has served: no `hold` field, `latest`
|
|
70
|
-
* null, a package missing from the list, or no catalog at all (`catalog` null: the API did not answer).
|
|
71
|
-
*
|
|
72
|
-
* `hold`: when the registry's latest is that version, it is not recommended; the catalog's own `latest` is, when it is
|
|
73
|
-
* older, else `minimum_supported` (so "no longer supported" still works and nobody is told to update).
|
|
74
|
-
*/
|
|
75
|
-
export function withRegistryLatest(catalog, registry) {
|
|
76
|
-
const listed = isObj(catalog) && Array.isArray(catalog.clients) ? catalog.clients.filter(isObj) : [];
|
|
77
|
-
const entries = listed.map((c) => ({
|
|
78
|
-
...c,
|
|
79
|
-
package: String(c.package),
|
|
80
|
-
ecosystem: String(c.ecosystem),
|
|
81
|
-
latest: str(c.latest),
|
|
82
|
-
minimum_supported: str(c.minimum_supported),
|
|
83
|
-
upgrade_command: str(c.upgrade_command),
|
|
84
|
-
release_notes_url: str(c.release_notes_url),
|
|
85
|
-
hold: str(c.hold),
|
|
86
|
-
}));
|
|
87
|
-
for (const pkg of Object.keys(registry)) {
|
|
88
|
-
if (!entries.some((e) => e.ecosystem === 'npm' && e.package === pkg)) {
|
|
89
|
-
entries.push({ package: pkg, ecosystem: 'npm', latest: null, minimum_supported: null, upgrade_command: DEFAULT_UPGRADE[pkg] ?? null, release_notes_url: `https://www.npmjs.com/package/${pkg}?activeTab=versions`, hold: null });
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
return {
|
|
93
|
-
clients: entries.map((e) => {
|
|
94
|
-
const fromRegistry = e.ecosystem === 'npm' ? str(registry[e.package]) : null;
|
|
95
|
-
let latest = fromRegistry ?? e.latest;
|
|
96
|
-
if (latest !== null && e.hold && compareVersions(latest, e.hold) === 0) {
|
|
97
|
-
latest = e.latest !== null && compareVersions(e.latest, e.hold) === -1 ? e.latest : e.minimum_supported;
|
|
98
|
-
}
|
|
99
|
-
return { ...e, latest };
|
|
100
|
-
}),
|
|
101
|
-
};
|
|
102
|
-
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The CLI side of T3 Code mode (0.17.0+): `shard agents new --in t3 [--prompt…] [--on <ref>]`, `shard ./` and
|
|
3
|
+
* `--in auto` when T3 is where new conversations open, and `shard agents open t3 <conversation|workspace>`.
|
|
4
|
+
* --json results (contract shared with the menu bar):
|
|
5
|
+
* new: { target: "t3", mode, project, t3_thread_id, conversation_id, workspace: { workspace_key, id } | null,
|
|
6
|
+
* opened: true, focused: false, model, effort, ... }
|
|
7
|
+
* open: { opened: true, t3_thread_id, focused: false, ... }
|
|
8
|
+
* `focused` is false: T3 has no way to show a given thread from outside, so T3 is brought to the front instead.
|
|
9
|
+
*/
|
|
10
|
+
import type { Workspace } from '@shardflux/sdk';
|
|
11
|
+
import type { Ctx } from './commands.js';
|
|
12
|
+
import type { Values } from './args.js';
|
|
13
|
+
import type { AgentApp, T3Install, T3ThreadRequest, T3ThreadResult } from './t3-mode.js';
|
|
14
|
+
/**
|
|
15
|
+
* A `t3_*` refusal raised once the workspace, conversation or thread exists names them (details.workspace_key,
|
|
16
|
+
* conversation_id, t3_thread_id), so a client opens what exists instead of starting it again.
|
|
17
|
+
*/
|
|
18
|
+
export declare function withT3Context(error: unknown, ref: {
|
|
19
|
+
workspace_key?: string | null;
|
|
20
|
+
conversation_id?: string | null;
|
|
21
|
+
t3_thread_id?: string | null;
|
|
22
|
+
}): unknown;
|
|
23
|
+
/** The thread to start, when the command carries a first message (`--prompt`, `--prompt-file`, `--image`, `--model`, `--effort`). */
|
|
24
|
+
export declare function t3ThreadRequest(ctx: Ctx, values: Values, app: AgentApp): Promise<T3ThreadRequest | undefined>;
|
|
25
|
+
/**
|
|
26
|
+
* `agents new --in t3`: with a first message in ACP mode, the thread starts on T3's Shardflux provider (its own VM);
|
|
27
|
+
* otherwise the project VM is opened as T3's SSH environment (with its phone link), and a first message becomes a
|
|
28
|
+
* thread on the T3 server in that VM. `--on <ref>` uses that existing workspace instead of the project VM.
|
|
29
|
+
*/
|
|
30
|
+
export declare function launchT3(ctx: Ctx, values: Values, path: string, explicitApp: string | undefined): Promise<number>;
|
|
31
|
+
/** The T3 fields of an SSH-mode result (the thread, when one was started in the VM). */
|
|
32
|
+
export declare function t3Fields(ws: Workspace, project: string, install: T3Install | undefined, started: T3ThreadResult | undefined, conversation?: string | null): Record<string, unknown>;
|
|
33
|
+
/**
|
|
34
|
+
* `agents open t3 <conversation>` for a conversation that runs on T3's Shardflux provider (ACP mode): T3 is brought to
|
|
35
|
+
* the front with that thread's id. A conversation of another route opens its VM as T3's SSH environment.
|
|
36
|
+
*/
|
|
37
|
+
export declare function openT3Conversation(ctx: Ctx, conversationId: string, values?: Values): Promise<number>;
|