@standardagents/code 0.11.6 → 0.11.8
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/dist/index.js +492 -245
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import os8, { homedir } from 'os';
|
|
3
3
|
import path4 from 'path';
|
|
4
|
-
import
|
|
4
|
+
import readline3 from 'readline/promises';
|
|
5
5
|
import { stdout, stdin } from 'process';
|
|
6
6
|
import net from 'net';
|
|
7
7
|
import fs5 from 'fs';
|
|
@@ -135,6 +135,36 @@ function parseSharedMessagingSnapshot(value) {
|
|
|
135
135
|
}
|
|
136
136
|
};
|
|
137
137
|
}
|
|
138
|
+
function parseComposerDraft(value) {
|
|
139
|
+
const draft = record(value);
|
|
140
|
+
if (!draft || draft.version !== 1 || typeof draft.content !== "string") {
|
|
141
|
+
throw new TypeError("unsupported or malformed shared draft");
|
|
142
|
+
}
|
|
143
|
+
return {
|
|
144
|
+
version: 1,
|
|
145
|
+
revision: finiteNumber(draft.revision, "draft revision"),
|
|
146
|
+
content: draft.content,
|
|
147
|
+
attachments: attachments(draft.attachments),
|
|
148
|
+
updatedAt: finiteNumber(draft.updatedAt, "draft updatedAt"),
|
|
149
|
+
...origin(draft)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function parseMessagingChangedEvent(value) {
|
|
153
|
+
const event = record(value);
|
|
154
|
+
if (!event || event.version !== 1) return null;
|
|
155
|
+
try {
|
|
156
|
+
return {
|
|
157
|
+
version: 1,
|
|
158
|
+
pendingRevision: finiteNumber(event.pendingRevision, "event pending revision"),
|
|
159
|
+
draftRevision: finiteNumber(event.draftRevision, "event draft revision"),
|
|
160
|
+
...event.draft !== void 0 ? { draft: parseComposerDraft(event.draft) } : {},
|
|
161
|
+
...event.draftOmitted === true ? { draftOmitted: true } : {},
|
|
162
|
+
...origin(event)
|
|
163
|
+
};
|
|
164
|
+
} catch {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
138
168
|
|
|
139
169
|
// src/api.ts
|
|
140
170
|
var ApiHttpError = class extends Error {
|
|
@@ -908,6 +938,7 @@ var TunnelManager = class {
|
|
|
908
938
|
let opened = false;
|
|
909
939
|
const socket = net.connect({ host, port });
|
|
910
940
|
socket.setNoDelay(true);
|
|
941
|
+
socket.setKeepAlive(true, 15e3);
|
|
911
942
|
this.tunnels.set(id, { socket });
|
|
912
943
|
socket.on("connect", () => {
|
|
913
944
|
opened = true;
|
|
@@ -2690,7 +2721,7 @@ var McpManager = class {
|
|
|
2690
2721
|
}
|
|
2691
2722
|
}
|
|
2692
2723
|
closeAll() {
|
|
2693
|
-
for (const [,
|
|
2724
|
+
for (const [, c6] of this.clients) c6.close();
|
|
2694
2725
|
this.clients.clear();
|
|
2695
2726
|
}
|
|
2696
2727
|
get(name) {
|
|
@@ -2701,13 +2732,13 @@ var McpManager = class {
|
|
|
2701
2732
|
}
|
|
2702
2733
|
toolCount() {
|
|
2703
2734
|
let n = 0;
|
|
2704
|
-
for (const [,
|
|
2735
|
+
for (const [, c6] of this.clients) n += c6.tools.length;
|
|
2705
2736
|
return n;
|
|
2706
2737
|
}
|
|
2707
2738
|
/** A JSON-serializable catalog of every connected server for the KV/context. */
|
|
2708
2739
|
catalog() {
|
|
2709
2740
|
return {
|
|
2710
|
-
servers: Array.from(this.clients.values()).map((
|
|
2741
|
+
servers: Array.from(this.clients.values()).map((c6) => c6.catalogEntry()),
|
|
2711
2742
|
generatedAt: Date.now()
|
|
2712
2743
|
};
|
|
2713
2744
|
}
|
|
@@ -2797,9 +2828,9 @@ function flattenContent(content, structured) {
|
|
|
2797
2828
|
}
|
|
2798
2829
|
function flattenResourceContents(contents) {
|
|
2799
2830
|
const parts = [];
|
|
2800
|
-
for (const
|
|
2801
|
-
if (typeof
|
|
2802
|
-
else if (typeof
|
|
2831
|
+
for (const c6 of contents || []) {
|
|
2832
|
+
if (typeof c6.text === "string") parts.push(c6.text);
|
|
2833
|
+
else if (typeof c6.blob === "string") parts.push(`[binary resource ${String(c6.uri ?? "")} (${c6.blob.length} b64 chars)]`);
|
|
2803
2834
|
}
|
|
2804
2835
|
return parts.join("\n").trim();
|
|
2805
2836
|
}
|
|
@@ -3849,17 +3880,17 @@ function wrapBlock(out, cols2, leadFirst, leadRest, leadWidth, text) {
|
|
|
3849
3880
|
function renderTable(rows) {
|
|
3850
3881
|
const cols2 = Math.max(...rows.map((r) => r.length));
|
|
3851
3882
|
const widths = [];
|
|
3852
|
-
for (let
|
|
3853
|
-
widths[
|
|
3883
|
+
for (let c6 = 0; c6 < cols2; c6++) {
|
|
3884
|
+
widths[c6] = Math.max(...rows.map((r) => visibleWidth(inline(r[c6] ?? ""))));
|
|
3854
3885
|
}
|
|
3855
3886
|
const sep = `${GRAY} \u2502 ${R}`;
|
|
3856
3887
|
const out = [];
|
|
3857
3888
|
rows.forEach((r, ri) => {
|
|
3858
3889
|
const cells = [];
|
|
3859
|
-
for (let
|
|
3860
|
-
const raw = r[
|
|
3890
|
+
for (let c6 = 0; c6 < cols2; c6++) {
|
|
3891
|
+
const raw = r[c6] ?? "";
|
|
3861
3892
|
const styled = ri === 0 ? `${BOLD}${inline(raw)}${R}` : inline(raw);
|
|
3862
|
-
cells.push(padEndVisible(styled, widths[
|
|
3893
|
+
cells.push(padEndVisible(styled, widths[c6]));
|
|
3863
3894
|
}
|
|
3864
3895
|
out.push((" " + cells.join(sep)).replace(/\s+$/, ""));
|
|
3865
3896
|
if (ri === 0) {
|
|
@@ -4331,7 +4362,7 @@ function buildInputBoxRows(opts) {
|
|
|
4331
4362
|
let di = 0;
|
|
4332
4363
|
for (let ci = 0; ci < plainChars.length; ci++) {
|
|
4333
4364
|
const ch = plainChars[ci];
|
|
4334
|
-
const
|
|
4365
|
+
const c6 = colorAt(perimeterIndex("top", xi++, W, bodyH));
|
|
4335
4366
|
if (ci >= agentSpanStart && ci < agentSpanEnd) {
|
|
4336
4367
|
top += `\x1B[2m${themeGray}${ch}\x1B[0m`;
|
|
4337
4368
|
continue;
|
|
@@ -4340,9 +4371,9 @@ function buildInputBoxRows(opts) {
|
|
|
4340
4371
|
const levelN = Math.max(1, Math.min(5, level));
|
|
4341
4372
|
const filled = di < levelN;
|
|
4342
4373
|
di++;
|
|
4343
|
-
top += (filled ? levelColor ||
|
|
4374
|
+
top += (filled ? levelColor || c6 : "\x1B[38;5;240m") + (filled ? "\u25CF" : "\u25CB") + reset;
|
|
4344
4375
|
} else {
|
|
4345
|
-
top +=
|
|
4376
|
+
top += c6 + ch + reset;
|
|
4346
4377
|
}
|
|
4347
4378
|
}
|
|
4348
4379
|
}
|
|
@@ -4363,10 +4394,10 @@ function buildInputBoxRows(opts) {
|
|
|
4363
4394
|
let bottom = "";
|
|
4364
4395
|
for (let x = 0; x < W; x++) {
|
|
4365
4396
|
const idx = perimeterIndex("bottom", W - 1 - x, W, bodyH);
|
|
4366
|
-
const
|
|
4367
|
-
if (x === 0) bottom +=
|
|
4368
|
-
else if (x === W - 1) bottom +=
|
|
4369
|
-
else bottom +=
|
|
4397
|
+
const c6 = colorAt(idx);
|
|
4398
|
+
if (x === 0) bottom += c6 + "\u2570" + reset;
|
|
4399
|
+
else if (x === W - 1) bottom += c6 + "\u256F" + reset;
|
|
4400
|
+
else bottom += c6 + "\u2500" + reset;
|
|
4370
4401
|
}
|
|
4371
4402
|
return [pad + top, ...bodyRows, pad + bottom];
|
|
4372
4403
|
}
|
|
@@ -5101,7 +5132,7 @@ var Tui = class _Tui {
|
|
|
5101
5132
|
const q = this.inputBuffer.slice(1).trim().toLowerCase();
|
|
5102
5133
|
if (q === "") return this.commands;
|
|
5103
5134
|
return this.commands.filter(
|
|
5104
|
-
(
|
|
5135
|
+
(c6) => c6.name.startsWith(q) || c6.name.includes(q) || c6.label.toLowerCase().includes(q)
|
|
5105
5136
|
);
|
|
5106
5137
|
}
|
|
5107
5138
|
runCommand(cmd) {
|
|
@@ -6460,7 +6491,7 @@ function readVersion() {
|
|
|
6460
6491
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
6461
6492
|
} catch {
|
|
6462
6493
|
}
|
|
6463
|
-
return "0.11.
|
|
6494
|
+
return "0.11.8" ;
|
|
6464
6495
|
}
|
|
6465
6496
|
function isLocalHost(host) {
|
|
6466
6497
|
return host === "localhost" || host === "127.0.0.1" || host === "::1" || host.endsWith(".local") || host.endsWith(".localhost") || /^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host);
|
|
@@ -6819,7 +6850,7 @@ async function clearDaemon(api, identity) {
|
|
|
6819
6850
|
function parseCommands(value) {
|
|
6820
6851
|
if (!Array.isArray(value)) return [];
|
|
6821
6852
|
return value.filter(
|
|
6822
|
-
(
|
|
6853
|
+
(c6) => !!c6 && typeof c6 === "object" && typeof c6.id === "string" && typeof c6.kind === "string"
|
|
6823
6854
|
);
|
|
6824
6855
|
}
|
|
6825
6856
|
async function enqueueMachineCommand(api, machineId, kind, args) {
|
|
@@ -6842,23 +6873,23 @@ async function clearMachineCommands(api, machineId, appliedIds) {
|
|
|
6842
6873
|
if (appliedIds.length === 0) return;
|
|
6843
6874
|
const key = commandKey(machineId);
|
|
6844
6875
|
const remaining = parseCommands(await api.userKvGet(key)).filter(
|
|
6845
|
-
(
|
|
6876
|
+
(c6) => !appliedIds.includes(c6.id)
|
|
6846
6877
|
);
|
|
6847
6878
|
await api.userKvSet(key, remaining.length ? remaining : null);
|
|
6848
6879
|
}
|
|
6849
6880
|
async function applyMachineCommand(api, identity, cmd) {
|
|
6850
6881
|
switch (cmd.kind) {
|
|
6851
6882
|
case "add_project": {
|
|
6852
|
-
const
|
|
6853
|
-
if (!
|
|
6854
|
-
await registerProject(api, identity,
|
|
6855
|
-
return `added project ${
|
|
6883
|
+
const path16 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
|
|
6884
|
+
if (!path16) return "add_project: ignored (no path)";
|
|
6885
|
+
await registerProject(api, identity, path16);
|
|
6886
|
+
return `added project ${path16}`;
|
|
6856
6887
|
}
|
|
6857
6888
|
case "remove_project": {
|
|
6858
|
-
const
|
|
6859
|
-
if (!
|
|
6860
|
-
await unregisterProject(api, identity,
|
|
6861
|
-
return `removed project ${
|
|
6889
|
+
const path16 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
|
|
6890
|
+
if (!path16) return "remove_project: ignored (no path)";
|
|
6891
|
+
await unregisterProject(api, identity, path16);
|
|
6892
|
+
return `removed project ${path16}`;
|
|
6862
6893
|
}
|
|
6863
6894
|
case "update":
|
|
6864
6895
|
return "update requested";
|
|
@@ -7228,7 +7259,7 @@ var HubSocket = class {
|
|
|
7228
7259
|
}
|
|
7229
7260
|
openSocket() {
|
|
7230
7261
|
if (this.closed) return;
|
|
7231
|
-
let url = `${this.api.wsEndpoint}/api/users/me/
|
|
7262
|
+
let url = `${this.api.wsEndpoint}/api/users/me/stream?token=${encodeURIComponent(this.api.bearer)}&client_id=${encodeURIComponent(this.clientId)}&client_kind=daemon`;
|
|
7232
7263
|
if (this.clientName) url += `&client_name=${encodeURIComponent(this.clientName)}`;
|
|
7233
7264
|
let ws;
|
|
7234
7265
|
try {
|
|
@@ -7257,8 +7288,15 @@ var HubSocket = class {
|
|
|
7257
7288
|
} catch {
|
|
7258
7289
|
return;
|
|
7259
7290
|
}
|
|
7260
|
-
if (msg
|
|
7261
|
-
|
|
7291
|
+
if (!msg || typeof msg !== "object") return;
|
|
7292
|
+
const frame = msg;
|
|
7293
|
+
if (frame.event === "standardagents.wake") {
|
|
7294
|
+
const threadId = frame.data?.thread_id;
|
|
7295
|
+
if (typeof threadId === "string") this.hooks.onWake(threadId);
|
|
7296
|
+
return;
|
|
7297
|
+
}
|
|
7298
|
+
if (frame.type === "wake" && typeof frame.threadId === "string") {
|
|
7299
|
+
this.hooks.onWake(frame.threadId);
|
|
7262
7300
|
}
|
|
7263
7301
|
}
|
|
7264
7302
|
handleDrop(ws) {
|
|
@@ -8144,7 +8182,7 @@ async function installCommand(endpointFlag) {
|
|
|
8144
8182
|
const identity = loadMachineIdentity();
|
|
8145
8183
|
const existing = await loadMachine(api, identity.machine_id).catch(() => null);
|
|
8146
8184
|
const suggested = machineDisplayName(existing ?? { hostname: os8.hostname(), id: identity.machine_id });
|
|
8147
|
-
const rl =
|
|
8185
|
+
const rl = readline3.createInterface({ input: stdin, output: stdout });
|
|
8148
8186
|
const answer = (await rl.question(
|
|
8149
8187
|
`${c3.bold}Machine name${c3.reset} ${c3.dim}(shown in the session picker)${c3.reset} [${suggested}]: `
|
|
8150
8188
|
)).trim();
|
|
@@ -8302,9 +8340,151 @@ async function runDaemonCommand(argv) {
|
|
|
8302
8340
|
}
|
|
8303
8341
|
}
|
|
8304
8342
|
}
|
|
8343
|
+
var DIR2 = path4.join(os8.homedir(), ".standardagents");
|
|
8344
|
+
var FILE2 = path4.join(DIR2, "prefs.json");
|
|
8345
|
+
function loadPrefs(file2 = FILE2) {
|
|
8346
|
+
try {
|
|
8347
|
+
return JSON.parse(fs5.readFileSync(file2, "utf8"));
|
|
8348
|
+
} catch {
|
|
8349
|
+
return {};
|
|
8350
|
+
}
|
|
8351
|
+
}
|
|
8352
|
+
function savePrefs(update, file2 = FILE2) {
|
|
8353
|
+
const merged = { ...loadPrefs(file2), ...update };
|
|
8354
|
+
fs5.mkdirSync(path4.dirname(file2), { recursive: true });
|
|
8355
|
+
fs5.writeFileSync(file2, JSON.stringify(merged, null, 2));
|
|
8356
|
+
}
|
|
8357
|
+
function shouldOfferDaemonInstall(facts) {
|
|
8358
|
+
if (facts.optedOutEnv) return false;
|
|
8359
|
+
if (!facts.interactive) return false;
|
|
8360
|
+
if (facts.platform === "win32") return false;
|
|
8361
|
+
if (facts.serviceInstalled) return false;
|
|
8362
|
+
if (facts.daemonOnline) return false;
|
|
8363
|
+
if (facts.declinedAt !== void 0) return false;
|
|
8364
|
+
return true;
|
|
8365
|
+
}
|
|
8305
8366
|
|
|
8306
|
-
// src/
|
|
8367
|
+
// src/auth-cli.ts
|
|
8307
8368
|
var c4 = {
|
|
8369
|
+
reset: "\x1B[0m",
|
|
8370
|
+
dim: "\x1B[2m",
|
|
8371
|
+
bold: "\x1B[1m",
|
|
8372
|
+
green: "\x1B[32m",
|
|
8373
|
+
red: "\x1B[31m",
|
|
8374
|
+
teal: "\x1B[38;5;43m"
|
|
8375
|
+
};
|
|
8376
|
+
function parseAuthArgs(argv) {
|
|
8377
|
+
let endpoint = null;
|
|
8378
|
+
let token = null;
|
|
8379
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
8380
|
+
const arg = argv[i];
|
|
8381
|
+
if (arg === "--endpoint" || arg === "-e") {
|
|
8382
|
+
endpoint = argv[++i] ?? "";
|
|
8383
|
+
if (!endpoint) throw new Error("--endpoint requires a URL");
|
|
8384
|
+
} else if (arg.startsWith("--endpoint=")) {
|
|
8385
|
+
endpoint = arg.slice("--endpoint=".length);
|
|
8386
|
+
} else if (arg === "--token") {
|
|
8387
|
+
token = argv[++i] ?? "";
|
|
8388
|
+
if (!token) throw new Error("--token requires a value");
|
|
8389
|
+
} else if (arg.startsWith("--token=")) {
|
|
8390
|
+
token = arg.slice("--token=".length);
|
|
8391
|
+
} else {
|
|
8392
|
+
throw new Error(`Unknown option for login/logout: ${arg}`);
|
|
8393
|
+
}
|
|
8394
|
+
}
|
|
8395
|
+
const endpointExplicit = endpoint !== null;
|
|
8396
|
+
const resolved = normalizeEndpoint(endpoint ?? defaultEndpoint() ?? PRODUCTION_ENDPOINT);
|
|
8397
|
+
return { endpoint: resolved, endpointExplicit, token };
|
|
8398
|
+
}
|
|
8399
|
+
function hostLabel(endpoint) {
|
|
8400
|
+
return endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
8401
|
+
}
|
|
8402
|
+
async function signedInLabel(endpoint, token) {
|
|
8403
|
+
try {
|
|
8404
|
+
const res = await fetch(`${endpoint}/api/auth/me`, {
|
|
8405
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
8406
|
+
signal: AbortSignal.timeout(1e4)
|
|
8407
|
+
});
|
|
8408
|
+
if (!res.ok) return null;
|
|
8409
|
+
const body = await res.json();
|
|
8410
|
+
return body.user?.email || body.user?.username || null;
|
|
8411
|
+
} catch {
|
|
8412
|
+
return null;
|
|
8413
|
+
}
|
|
8414
|
+
}
|
|
8415
|
+
async function runAuthCommand(command, argv) {
|
|
8416
|
+
let args;
|
|
8417
|
+
try {
|
|
8418
|
+
args = parseAuthArgs(argv);
|
|
8419
|
+
} catch (error) {
|
|
8420
|
+
process.stdout.write(`${c4.red}error:${c4.reset} ${error instanceof Error ? error.message : String(error)}
|
|
8421
|
+
`);
|
|
8422
|
+
process.stdout.write(`${c4.dim}usage: standardcode ${command} [--endpoint <url>]${command === "login" ? " [--token <key>]" : ""}${c4.reset}
|
|
8423
|
+
`);
|
|
8424
|
+
process.exitCode = 1;
|
|
8425
|
+
return;
|
|
8426
|
+
}
|
|
8427
|
+
const host = hostLabel(args.endpoint);
|
|
8428
|
+
relaxTlsForLocalEndpoint(args.endpoint);
|
|
8429
|
+
const existing = getCredential(args.endpoint);
|
|
8430
|
+
if (command === "logout") {
|
|
8431
|
+
if (!existing) {
|
|
8432
|
+
process.stdout.write(`You're not signed in to ${c4.teal}${host}${c4.reset} \u2014 nothing to do.
|
|
8433
|
+
`);
|
|
8434
|
+
return;
|
|
8435
|
+
}
|
|
8436
|
+
deleteCredential(args.endpoint);
|
|
8437
|
+
process.stdout.write(`${c4.green}\u2713${c4.reset} Signed out of ${c4.teal}${host}${c4.reset}.
|
|
8438
|
+
`);
|
|
8439
|
+
process.stdout.write(`${c4.dim}Run \`standardcode login\` to sign in again \u2014 with any account.${c4.reset}
|
|
8440
|
+
`);
|
|
8441
|
+
return;
|
|
8442
|
+
}
|
|
8443
|
+
if (existing) {
|
|
8444
|
+
const who2 = await signedInLabel(args.endpoint, existing.access_token);
|
|
8445
|
+
process.stdout.write(
|
|
8446
|
+
`${c4.dim}Currently signed in to ${host}${who2 ? ` as ${who2}` : ""} \u2014 replacing that sign-in.${c4.reset}
|
|
8447
|
+
`
|
|
8448
|
+
);
|
|
8449
|
+
}
|
|
8450
|
+
let token = args.token;
|
|
8451
|
+
if (!token) {
|
|
8452
|
+
process.stdout.write(`${c4.bold}Sign in to Standard Code${c4.reset}
|
|
8453
|
+
`);
|
|
8454
|
+
if (`https://${host}` !== PRODUCTION_ENDPOINT && `http://${host}` !== PRODUCTION_ENDPOINT) {
|
|
8455
|
+
process.stdout.write(`${c4.dim}Connecting to${c4.reset} ${c4.teal}${host}${c4.reset}
|
|
8456
|
+
`);
|
|
8457
|
+
}
|
|
8458
|
+
try {
|
|
8459
|
+
token = await deviceLogin(args.endpoint);
|
|
8460
|
+
} catch (error) {
|
|
8461
|
+
process.stdout.write(`${c4.red}\u2717${c4.reset} ${error instanceof Error ? error.message : String(error)}
|
|
8462
|
+
`);
|
|
8463
|
+
process.exitCode = 1;
|
|
8464
|
+
return;
|
|
8465
|
+
}
|
|
8466
|
+
}
|
|
8467
|
+
const api = new ApiClient(args.endpoint, token);
|
|
8468
|
+
const check = await api.verifyDetailed();
|
|
8469
|
+
if (!check.ok) {
|
|
8470
|
+
process.stdout.write(`${c4.red}\u2717${c4.reset} Sign-in didn't verify: ${check.reason}
|
|
8471
|
+
`);
|
|
8472
|
+
if (check.hint) process.stdout.write(` ${c4.dim}${check.hint}${c4.reset}
|
|
8473
|
+
`);
|
|
8474
|
+
process.exitCode = 1;
|
|
8475
|
+
return;
|
|
8476
|
+
}
|
|
8477
|
+
saveCredential(
|
|
8478
|
+
{ endpoint: args.endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
|
|
8479
|
+
{ updateDefault: !args.endpointExplicit }
|
|
8480
|
+
);
|
|
8481
|
+
const who = await signedInLabel(args.endpoint, token);
|
|
8482
|
+
process.stdout.write(`${c4.green}\u2713${c4.reset} Signed in to ${c4.teal}${host}${c4.reset}${who ? ` as ${c4.bold}${who}${c4.reset}` : ""}.
|
|
8483
|
+
`);
|
|
8484
|
+
}
|
|
8485
|
+
|
|
8486
|
+
// src/index.ts
|
|
8487
|
+
var c5 = {
|
|
8308
8488
|
reset: "\x1B[0m",
|
|
8309
8489
|
dim: "\x1B[2m",
|
|
8310
8490
|
bold: "\x1B[1m",
|
|
@@ -8333,10 +8513,16 @@ function printUsage() {
|
|
|
8333
8513
|
stdout.write(
|
|
8334
8514
|
[
|
|
8335
8515
|
"",
|
|
8336
|
-
`${
|
|
8516
|
+
`${c5.bold}Usage${c5.reset}`,
|
|
8337
8517
|
" standardcode [options] [dir]",
|
|
8518
|
+
" standardcode login [--endpoint <url>] [--token <key>]",
|
|
8519
|
+
" Sign in (or switch accounts \u2014 always re-runs the flow).",
|
|
8520
|
+
" standardcode logout [--endpoint <url>]",
|
|
8521
|
+
" Sign out of the saved endpoint.",
|
|
8522
|
+
" standardcode daemon <install|run|status|uninstall>",
|
|
8523
|
+
" Manage this machine's headless execution daemon.",
|
|
8338
8524
|
"",
|
|
8339
|
-
`${
|
|
8525
|
+
`${c5.bold}Options${c5.reset}`,
|
|
8340
8526
|
" -v, --version Print the Standard Code version and exit.",
|
|
8341
8527
|
" -e, --endpoint [url] Use a different Standard Agents instance for this run",
|
|
8342
8528
|
" (default: https://api.standardcode.ai).",
|
|
@@ -8405,15 +8591,15 @@ function parseArgs2(args) {
|
|
|
8405
8591
|
}
|
|
8406
8592
|
function printCommandBlock(tui, command, output4, ok, where) {
|
|
8407
8593
|
tui.print("");
|
|
8408
|
-
const note = where ? ` ${
|
|
8409
|
-
tui.print(`${
|
|
8594
|
+
const note = where ? ` ${c5.dim}(ran on ${where})${c5.reset}` : "";
|
|
8595
|
+
tui.print(`${c5.magenta}!${c5.reset} ${c5.bold}${command}${c5.reset}${note}`);
|
|
8410
8596
|
const body = (output4 ?? "").replace(/\s+$/, "");
|
|
8411
8597
|
if (body) {
|
|
8412
8598
|
for (const line of body.split("\n")) {
|
|
8413
|
-
tui.print(` ${ok ?
|
|
8599
|
+
tui.print(` ${ok ? c5.dim : c5.red}${line}${c5.reset}`);
|
|
8414
8600
|
}
|
|
8415
8601
|
} else {
|
|
8416
|
-
tui.print(` ${
|
|
8602
|
+
tui.print(` ${c5.dim}(no output)${c5.reset}`);
|
|
8417
8603
|
}
|
|
8418
8604
|
tui.print("");
|
|
8419
8605
|
}
|
|
@@ -8424,7 +8610,7 @@ function printAssistant(tui, text) {
|
|
|
8424
8610
|
let dotted = false;
|
|
8425
8611
|
for (const line of renderStreamingMarkdown(text, cols2)) {
|
|
8426
8612
|
if (!dotted && line.trim()) {
|
|
8427
|
-
tui.print(`${
|
|
8613
|
+
tui.print(`${c5.gray}\u2022${c5.reset} ${line}`);
|
|
8428
8614
|
dotted = true;
|
|
8429
8615
|
} else {
|
|
8430
8616
|
tui.print(` ${line}`);
|
|
@@ -8439,7 +8625,7 @@ function startLoader(label) {
|
|
|
8439
8625
|
const draw = () => {
|
|
8440
8626
|
const now = Date.now();
|
|
8441
8627
|
const f = frames[Math.floor(now / 70) % frames.length];
|
|
8442
|
-
stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${
|
|
8628
|
+
stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c5.reset} ${c5.dim}${label}\u2026${c5.reset}`);
|
|
8443
8629
|
};
|
|
8444
8630
|
draw();
|
|
8445
8631
|
const timer = setInterval(draw, 70);
|
|
@@ -8455,12 +8641,12 @@ function farewell(stoppedProcs = 0) {
|
|
|
8455
8641
|
if (stoppedProcs > 0) {
|
|
8456
8642
|
stdout.write(
|
|
8457
8643
|
`
|
|
8458
|
-
${
|
|
8644
|
+
${c5.cyan}\u2699${c5.reset} Stopped ${stoppedProcs} background process${stoppedProcs === 1 ? "" : "es"}.
|
|
8459
8645
|
`
|
|
8460
8646
|
);
|
|
8461
8647
|
}
|
|
8462
8648
|
stdout.write(`
|
|
8463
|
-
${
|
|
8649
|
+
${c5.teal}\u25C7${c5.reset} ${c5.dim}Standard Code \u2014 see you soon.${c5.reset}
|
|
8464
8650
|
`);
|
|
8465
8651
|
}
|
|
8466
8652
|
function printWelcome(endpoint, projectDir) {
|
|
@@ -8474,10 +8660,10 @@ function printWelcome(endpoint, projectDir) {
|
|
|
8474
8660
|
const metaWidth = Math.max(1, terminalColumns - pad.length - markWidth - 3 - 1);
|
|
8475
8661
|
const displayDir = truncateMiddle(dir2, metaWidth);
|
|
8476
8662
|
const meta = [
|
|
8477
|
-
`${
|
|
8478
|
-
`${
|
|
8479
|
-
...endpoint === PRODUCTION_ENDPOINT ? [] : [`${
|
|
8480
|
-
`${
|
|
8663
|
+
`${c5.bold}${gradientText("Standard Code")}${c5.reset}${version ? ` ${c5.dim}v${version}${c5.reset}` : ""}`,
|
|
8664
|
+
`${c5.dim}terminal coding agent${c5.reset}`,
|
|
8665
|
+
...endpoint === PRODUCTION_ENDPOINT ? [] : [`${c5.teal}${host}${c5.reset}`],
|
|
8666
|
+
`${c5.dim}${displayDir}${c5.reset}`
|
|
8481
8667
|
];
|
|
8482
8668
|
const metaTop = Math.floor((LOGO_MARK.length - meta.length) / 2);
|
|
8483
8669
|
stdout.write("\n");
|
|
@@ -8491,11 +8677,11 @@ function printWelcome(endpoint, projectDir) {
|
|
|
8491
8677
|
}
|
|
8492
8678
|
function colorActivity(line) {
|
|
8493
8679
|
const m = line.match(/^(\s*)([✓✗⛔])\s?([\s\S]*)$/);
|
|
8494
|
-
if (!m) return `${
|
|
8680
|
+
if (!m) return `${c5.dim}${line}${c5.reset}`;
|
|
8495
8681
|
const [, indent, glyph, rest] = m;
|
|
8496
8682
|
if (glyph === "\u2713") {
|
|
8497
|
-
const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${
|
|
8498
|
-
return `${indent}${
|
|
8683
|
+
const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${c5.dim}$1${c5.reset}`);
|
|
8684
|
+
return `${indent}${c5.green}\u2713${c5.reset} ${body}`;
|
|
8499
8685
|
}
|
|
8500
8686
|
if (glyph === "\u2717") {
|
|
8501
8687
|
const ERR_MAX_LINES = 7;
|
|
@@ -8503,15 +8689,15 @@ function colorActivity(line) {
|
|
|
8503
8689
|
const shown = lines.slice(0, ERR_MAX_LINES);
|
|
8504
8690
|
const hidden = lines.length - shown.length;
|
|
8505
8691
|
const body = shown.map(
|
|
8506
|
-
(l, i) => i === 0 ? `${indent}${
|
|
8692
|
+
(l, i) => i === 0 ? `${indent}${c5.red}\u2717 ${l}${c5.reset}` : `${indent}${c5.red}${c5.dim}${l}${c5.reset}`
|
|
8507
8693
|
).join("\n");
|
|
8508
8694
|
if (hidden > 0) {
|
|
8509
8695
|
return `${body}
|
|
8510
|
-
${indent}${
|
|
8696
|
+
${indent}${c5.dim}\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}${c5.reset}`;
|
|
8511
8697
|
}
|
|
8512
8698
|
return body;
|
|
8513
8699
|
}
|
|
8514
|
-
return `${indent}${
|
|
8700
|
+
return `${indent}${c5.yellow}\u26D4 ${rest}${c5.reset}`;
|
|
8515
8701
|
}
|
|
8516
8702
|
async function main() {
|
|
8517
8703
|
if (process.argv.slice(2).some((arg) => arg === "-v" || arg === "--version")) {
|
|
@@ -8523,11 +8709,15 @@ async function main() {
|
|
|
8523
8709
|
await runDaemonCommand(process.argv.slice(3));
|
|
8524
8710
|
return;
|
|
8525
8711
|
}
|
|
8712
|
+
if (process.argv[2] === "login" || process.argv[2] === "logout") {
|
|
8713
|
+
await runAuthCommand(process.argv[2], process.argv.slice(3));
|
|
8714
|
+
return;
|
|
8715
|
+
}
|
|
8526
8716
|
let cliArgs;
|
|
8527
8717
|
try {
|
|
8528
8718
|
cliArgs = parseArgs2(process.argv.slice(2));
|
|
8529
8719
|
} catch (error) {
|
|
8530
|
-
stdout.write(`${
|
|
8720
|
+
stdout.write(`${c5.red}error:${c5.reset} ${error instanceof Error ? error.message : String(error)}
|
|
8531
8721
|
`);
|
|
8532
8722
|
printUsage();
|
|
8533
8723
|
process.exit(1);
|
|
@@ -8554,7 +8744,7 @@ async function main() {
|
|
|
8554
8744
|
}
|
|
8555
8745
|
preflightArmed = true;
|
|
8556
8746
|
stdout.write(`
|
|
8557
|
-
${
|
|
8747
|
+
${c5.dim}Press Control-C again to exit${c5.reset}
|
|
8558
8748
|
`);
|
|
8559
8749
|
preflightTimer = setTimeout(() => {
|
|
8560
8750
|
preflightArmed = false;
|
|
@@ -8563,7 +8753,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8563
8753
|
};
|
|
8564
8754
|
const ask = async (question) => {
|
|
8565
8755
|
if (!reader.rl) {
|
|
8566
|
-
reader.rl =
|
|
8756
|
+
reader.rl = readline3.createInterface({ input: stdin, output: stdout });
|
|
8567
8757
|
reader.rl.on("SIGINT", onPreflightSigint);
|
|
8568
8758
|
reader.rl.on("close", () => {
|
|
8569
8759
|
if (handoffClosing) return;
|
|
@@ -8576,10 +8766,10 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8576
8766
|
const askEndpoint = async () => {
|
|
8577
8767
|
for (; ; ) {
|
|
8578
8768
|
const answer = (await ask(
|
|
8579
|
-
`${
|
|
8769
|
+
`${c5.cyan}Standard Agents instance URL${c5.reset} (e.g. http://localhost:5178): `
|
|
8580
8770
|
)).trim();
|
|
8581
8771
|
if (answer) return answer;
|
|
8582
|
-
stdout.write(`${
|
|
8772
|
+
stdout.write(`${c5.dim}An endpoint URL is required.${c5.reset}
|
|
8583
8773
|
`);
|
|
8584
8774
|
}
|
|
8585
8775
|
};
|
|
@@ -8594,7 +8784,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8594
8784
|
const tlsRelaxed = relaxTlsForLocalEndpoint(endpoint);
|
|
8595
8785
|
printWelcome(endpoint, projectDir);
|
|
8596
8786
|
if (tlsRelaxed) {
|
|
8597
|
-
stdout.write(`${
|
|
8787
|
+
stdout.write(`${c5.dim} TLS verification relaxed for local endpoint.${c5.reset}
|
|
8598
8788
|
|
|
8599
8789
|
`);
|
|
8600
8790
|
}
|
|
@@ -8606,7 +8796,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8606
8796
|
loading.stop();
|
|
8607
8797
|
const applied = consumeAppliedUpdate(version);
|
|
8608
8798
|
if (applied) {
|
|
8609
|
-
stdout.write(` ${
|
|
8799
|
+
stdout.write(` ${c5.green}\u2713${c5.reset} ${c5.dim}Standard Code updated to v${version}.${c5.reset}
|
|
8610
8800
|
|
|
8611
8801
|
`);
|
|
8612
8802
|
}
|
|
@@ -8615,21 +8805,21 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8615
8805
|
const decision = decideAutoUpdate(updateAvailable, { state: readAutoUpdateState(), pm });
|
|
8616
8806
|
if (decision === "start" && pm && startBackgroundUpdate(updateAvailable.latest, pm)) {
|
|
8617
8807
|
stdout.write(
|
|
8618
|
-
` ${
|
|
8808
|
+
` ${c5.teal}\u27F3${c5.reset} ${c5.dim}Standard Code ${c5.reset}${c5.bold}v${updateAvailable.latest}${c5.reset}${c5.dim} is installing in the background \u2014 it applies on your next launch.${c5.reset}
|
|
8619
8809
|
|
|
8620
8810
|
`
|
|
8621
8811
|
);
|
|
8622
8812
|
} else if (decision === "in_flight") {
|
|
8623
8813
|
stdout.write(
|
|
8624
|
-
` ${
|
|
8814
|
+
` ${c5.teal}\u27F3${c5.reset} ${c5.dim}Standard Code v${updateAvailable.latest} is still installing in the background.${c5.reset}
|
|
8625
8815
|
|
|
8626
8816
|
`
|
|
8627
8817
|
);
|
|
8628
8818
|
} else {
|
|
8629
8819
|
const display = updateCommand(pm ?? "npm").display;
|
|
8630
8820
|
stdout.write(
|
|
8631
|
-
` ${
|
|
8632
|
-
${
|
|
8821
|
+
` ${c5.teal}\u25C7${c5.reset} ${c5.dim}Update available:${c5.reset} ${c5.dim}v${updateAvailable.current}${c5.reset} \u2192 ${c5.bold}v${updateAvailable.latest}${c5.reset}
|
|
8822
|
+
${c5.dim}Run ${c5.reset}${c5.bold}${display}${c5.reset}${c5.dim} to update${c5.reset}
|
|
8633
8823
|
|
|
8634
8824
|
`
|
|
8635
8825
|
);
|
|
@@ -8647,39 +8837,39 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8647
8837
|
if (!api || !storedCheck?.ok) {
|
|
8648
8838
|
const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
8649
8839
|
if (storedCheck && !storedCheck.ok) {
|
|
8650
|
-
stdout.write(`${
|
|
8840
|
+
stdout.write(`${c5.red}\u2717${c5.reset} ${c5.dim}Saved sign-in for this endpoint failed:${c5.reset} ${storedCheck.reason}
|
|
8651
8841
|
`);
|
|
8652
|
-
if (storedCheck.hint) stdout.write(` ${
|
|
8842
|
+
if (storedCheck.hint) stdout.write(` ${c5.dim}${storedCheck.hint}${c5.reset}
|
|
8653
8843
|
`);
|
|
8654
8844
|
stdout.write("\n");
|
|
8655
8845
|
}
|
|
8656
8846
|
const explainFailure = (result, prefix) => {
|
|
8657
|
-
stdout.write(`${
|
|
8847
|
+
stdout.write(`${c5.red}\u2717${c5.reset} ${prefix}${result.reason}
|
|
8658
8848
|
`);
|
|
8659
|
-
if (result.hint) stdout.write(` ${
|
|
8849
|
+
if (result.hint) stdout.write(` ${c5.dim}${result.hint}${c5.reset}
|
|
8660
8850
|
`);
|
|
8661
8851
|
};
|
|
8662
|
-
stdout.write(`${
|
|
8852
|
+
stdout.write(`${c5.bold}Sign in to ${gradientText("Standard Code")}${c5.reset}
|
|
8663
8853
|
`);
|
|
8664
8854
|
if (`https://${host}` !== PRODUCTION_ENDPOINT) {
|
|
8665
|
-
stdout.write(`${
|
|
8855
|
+
stdout.write(`${c5.dim}Connecting to${c5.reset} ${c5.teal}${host}${c5.reset}
|
|
8666
8856
|
`);
|
|
8667
8857
|
}
|
|
8668
8858
|
stdout.write(
|
|
8669
|
-
`${
|
|
8859
|
+
`${c5.dim}You'll only need to do this once on this machine.${c5.reset}
|
|
8670
8860
|
|
|
8671
8861
|
`
|
|
8672
8862
|
);
|
|
8673
8863
|
stdout.write(
|
|
8674
|
-
`${
|
|
8864
|
+
`${c5.white}Press ${c5.bold}Enter${c5.reset}${c5.white} to open your browser and sign in.${c5.reset} ${c5.dim}(or paste an API token)${c5.reset}
|
|
8675
8865
|
|
|
8676
8866
|
`
|
|
8677
8867
|
);
|
|
8678
8868
|
for (; ; ) {
|
|
8679
|
-
const token = (await ask(`${
|
|
8869
|
+
const token = (await ask(`${c5.teal}\u276F${c5.reset} `)).trim();
|
|
8680
8870
|
if (!token) {
|
|
8681
8871
|
const got = await deviceLogin(endpoint).catch((e) => {
|
|
8682
|
-
stdout.write(`${
|
|
8872
|
+
stdout.write(`${c5.red}\u2717${c5.reset} ${c5.dim}${e instanceof Error ? e.message : String(e)}${c5.reset}
|
|
8683
8873
|
`);
|
|
8684
8874
|
return null;
|
|
8685
8875
|
});
|
|
@@ -8693,7 +8883,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8693
8883
|
{ endpoint, access_token: got, token_type: "Bearer", saved_at: Date.now() },
|
|
8694
8884
|
{ updateDefault: !endpointOverride }
|
|
8695
8885
|
);
|
|
8696
|
-
stdout.write(`${
|
|
8886
|
+
stdout.write(`${c5.green}\u2713${c5.reset} Connected to ${c5.teal}${host}${c5.reset}
|
|
8697
8887
|
`);
|
|
8698
8888
|
break;
|
|
8699
8889
|
}
|
|
@@ -8709,7 +8899,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8709
8899
|
{ endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
|
|
8710
8900
|
{ updateDefault: !endpointOverride }
|
|
8711
8901
|
);
|
|
8712
|
-
stdout.write(`${
|
|
8902
|
+
stdout.write(`${c5.green}\u2713${c5.reset} Connected to ${c5.teal}${host}${c5.reset}
|
|
8713
8903
|
`);
|
|
8714
8904
|
break;
|
|
8715
8905
|
}
|
|
@@ -8733,7 +8923,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8733
8923
|
} else if (["unlimited", "standard", "unlimited_one", AGENT_ID].includes(wanted)) {
|
|
8734
8924
|
agentOverride = AGENT_ID;
|
|
8735
8925
|
} else {
|
|
8736
|
-
stdout.write(`${
|
|
8926
|
+
stdout.write(`${c5.red}error:${c5.reset} Unknown agent "${cliArgs.agent}". Use unlimited or sama.
|
|
8737
8927
|
`);
|
|
8738
8928
|
process.exit(1);
|
|
8739
8929
|
}
|
|
@@ -8746,6 +8936,42 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8746
8936
|
session.suggestDaemonInstall = machines.every((m) => !m.daemon);
|
|
8747
8937
|
const remoteTargets = machines.filter((m) => m.id !== identity.machine_id && daemonOnline(m));
|
|
8748
8938
|
const self = machines.find((m) => m.id === identity.machine_id);
|
|
8939
|
+
if (shouldOfferDaemonInstall({
|
|
8940
|
+
platform: process.platform,
|
|
8941
|
+
interactive: Boolean(stdin.isTTY && stdout.isTTY),
|
|
8942
|
+
serviceInstalled: serviceStatus().installed,
|
|
8943
|
+
daemonOnline: Boolean(self && daemonOnline(self)),
|
|
8944
|
+
declinedAt: loadPrefs().daemon_install_declined_at,
|
|
8945
|
+
optedOutEnv: Boolean(process.env.STANDARD_CODE_NO_DAEMON_PROMPT)
|
|
8946
|
+
})) {
|
|
8947
|
+
stdout.write(
|
|
8948
|
+
`
|
|
8949
|
+
${c5.bold}This machine has no always-on daemon${c5.reset} ${c5.dim}\u2014 sessions stop when this terminal closes.${c5.reset}
|
|
8950
|
+
`
|
|
8951
|
+
);
|
|
8952
|
+
const promptRl = readline3.createInterface({ input: stdin, output: stdout });
|
|
8953
|
+
let answer = "";
|
|
8954
|
+
try {
|
|
8955
|
+
answer = (await promptRl.question(
|
|
8956
|
+
`${c5.white}Install it now so sessions keep running and start from anywhere?${c5.reset} ${c5.dim}[Y/n]${c5.reset} `
|
|
8957
|
+
)).trim().toLowerCase();
|
|
8958
|
+
} catch {
|
|
8959
|
+
answer = "n";
|
|
8960
|
+
} finally {
|
|
8961
|
+
promptRl.close();
|
|
8962
|
+
}
|
|
8963
|
+
if (answer === "" || answer === "y" || answer === "yes") {
|
|
8964
|
+
await runDaemonCommand(endpointOverride ? ["install", "--endpoint", endpoint] : ["install"]);
|
|
8965
|
+
stdout.write("\n");
|
|
8966
|
+
} else {
|
|
8967
|
+
savePrefs({ daemon_install_declined_at: Date.now() });
|
|
8968
|
+
stdout.write(
|
|
8969
|
+
`${c5.dim}Okay \u2014 \`standardcode daemon install\` sets it up any time.${c5.reset}
|
|
8970
|
+
|
|
8971
|
+
`
|
|
8972
|
+
);
|
|
8973
|
+
}
|
|
8974
|
+
}
|
|
8749
8975
|
const launchDir = projectDir;
|
|
8750
8976
|
let tags = [];
|
|
8751
8977
|
let resumeTags = [];
|
|
@@ -8771,7 +8997,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8771
8997
|
flow: for (; ; ) {
|
|
8772
8998
|
if (step === "machine") {
|
|
8773
8999
|
const picked = await tui.select(
|
|
8774
|
-
`${
|
|
9000
|
+
`${c5.bold}Where should this session run?${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
8775
9001
|
[
|
|
8776
9002
|
{
|
|
8777
9003
|
label: `This machine \u2014 ${self ? machineDisplayName(self) : machine}`,
|
|
@@ -8855,7 +9081,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8855
9081
|
items.push({ label: "\uFF0B Start a new session", value: null });
|
|
8856
9082
|
const whereLabel = session.mode === "remote" && session.runner ? `${session.runner.name}:${shortenPath(session.remotePath ?? "")}` : shortDir;
|
|
8857
9083
|
const picked = await tui.select(
|
|
8858
|
-
`${
|
|
9084
|
+
`${c5.bold}Resume a session${c5.reset} ${c5.gray}${whereLabel}${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
8859
9085
|
items
|
|
8860
9086
|
);
|
|
8861
9087
|
if (picked === void 0) {
|
|
@@ -8881,7 +9107,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8881
9107
|
} else {
|
|
8882
9108
|
if (!agentOverride) {
|
|
8883
9109
|
const picked = await tui.select(
|
|
8884
|
-
`${
|
|
9110
|
+
`${c5.bold}Which agent?${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
8885
9111
|
AGENT_CHOICES.map((choice) => ({
|
|
8886
9112
|
label: choice.title,
|
|
8887
9113
|
hint: choice.description,
|
|
@@ -8931,7 +9157,7 @@ async function ensureSamaAuth(tui, api) {
|
|
|
8931
9157
|
checking.stop();
|
|
8932
9158
|
if (already) return true;
|
|
8933
9159
|
const picked = await tui.select(
|
|
8934
|
-
`${
|
|
9160
|
+
`${c5.bold}Authenticate with ChatGPT${c5.reset} ${c5.dim}Sama One runs on OpenAI using your own ChatGPT Pro account${c5.reset}`,
|
|
8935
9161
|
[
|
|
8936
9162
|
{
|
|
8937
9163
|
label: "Continue with ChatGPT",
|
|
@@ -8944,7 +9170,7 @@ async function ensureSamaAuth(tui, api) {
|
|
|
8944
9170
|
if (picked !== "continue") return false;
|
|
8945
9171
|
openUrl("https://standardcode.ai/app?connect=sama");
|
|
8946
9172
|
tui.print(
|
|
8947
|
-
`${
|
|
9173
|
+
`${c5.dim}Finish connecting ChatGPT in the browser \u2014 waiting here for the authorization to land on your account\u2026${c5.reset}`
|
|
8948
9174
|
);
|
|
8949
9175
|
const waiting = startLoader("Waiting for your ChatGPT authorization");
|
|
8950
9176
|
const deadline = Date.now() + 5 * 60 * 1e3;
|
|
@@ -8953,20 +9179,20 @@ async function ensureSamaAuth(tui, api) {
|
|
|
8953
9179
|
if (await api.openSamaStatus().catch(() => false)) {
|
|
8954
9180
|
waiting.stop();
|
|
8955
9181
|
tui.print(
|
|
8956
|
-
`${
|
|
9182
|
+
`${c5.green}\u2713${c5.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
|
|
8957
9183
|
);
|
|
8958
9184
|
return true;
|
|
8959
9185
|
}
|
|
8960
9186
|
}
|
|
8961
9187
|
waiting.stop();
|
|
8962
9188
|
tui.print(
|
|
8963
|
-
`${
|
|
9189
|
+
`${c5.yellow}Still not connected.${c5.reset} Finish the flow at ${c5.teal}standardcode.ai/app${c5.reset} and pick Sama One again.`
|
|
8964
9190
|
);
|
|
8965
9191
|
return false;
|
|
8966
9192
|
}
|
|
8967
9193
|
async function runAgentSwitchMenu(tui, api, threadId) {
|
|
8968
9194
|
const picked = await tui.select(
|
|
8969
|
-
`${
|
|
9195
|
+
`${c5.bold}Switch agent${c5.reset} ${c5.dim}takes effect on the next message${c5.reset}`,
|
|
8970
9196
|
AGENT_CHOICES.map((choice) => ({
|
|
8971
9197
|
label: choice.title,
|
|
8972
9198
|
hint: choice.description,
|
|
@@ -8983,10 +9209,10 @@ async function runAgentSwitchMenu(tui, api, threadId) {
|
|
|
8983
9209
|
try {
|
|
8984
9210
|
await api.setThreadAgent(threadId, picked);
|
|
8985
9211
|
tui.setAgentLabel(title);
|
|
8986
|
-
tui.print(`${
|
|
9212
|
+
tui.print(`${c5.green}\u2713${c5.reset} Session handed to ${c5.bold}${title}${c5.reset} \u2014 applies from your next message.`);
|
|
8987
9213
|
} catch (e) {
|
|
8988
9214
|
tui.print(
|
|
8989
|
-
`${
|
|
9215
|
+
`${c5.red}\u2717 couldn't switch agent:${c5.reset} ${c5.gray}${e instanceof Error ? e.message : String(e)}${c5.reset}`
|
|
8990
9216
|
);
|
|
8991
9217
|
}
|
|
8992
9218
|
}
|
|
@@ -9011,7 +9237,7 @@ async function pickLocalProject(tui, api, self, cwd, machineName) {
|
|
|
9011
9237
|
{ label: "\uFF0B New project", hint: "browse or create a directory", value: NEW }
|
|
9012
9238
|
];
|
|
9013
9239
|
const picked = await tui.select(
|
|
9014
|
-
`${
|
|
9240
|
+
`${c5.bold}Project on ${label}${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
9015
9241
|
items,
|
|
9016
9242
|
{ spaced: true }
|
|
9017
9243
|
);
|
|
@@ -9035,7 +9261,7 @@ async function pickRemoteProject(tui, api, runner) {
|
|
|
9035
9261
|
);
|
|
9036
9262
|
items.push({ label: "\uFF0B New project", hint: "browse or create a directory", value: ENTER_PATH });
|
|
9037
9263
|
const picked = await tui.select(
|
|
9038
|
-
`${
|
|
9264
|
+
`${c5.bold}Project on ${runner.name}${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
9039
9265
|
items,
|
|
9040
9266
|
{ spaced: true }
|
|
9041
9267
|
);
|
|
@@ -9068,7 +9294,7 @@ async function summarizeThreads(api, threads) {
|
|
|
9068
9294
|
}
|
|
9069
9295
|
function subagentLabel(s, titles) {
|
|
9070
9296
|
const agentName = (s.agent_name || "").trim();
|
|
9071
|
-
const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (
|
|
9297
|
+
const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (c6) => c6.toUpperCase()) : "Subagent");
|
|
9072
9298
|
const tagged = (s.threadName || "").trim();
|
|
9073
9299
|
return tagged ? `${title} \xB7 ${tagged}` : title;
|
|
9074
9300
|
}
|
|
@@ -9091,8 +9317,8 @@ async function printHistory(api, threadId, tui) {
|
|
|
9091
9317
|
).sort((a, b) => (a.created_at ?? 0) - (b.created_at ?? 0));
|
|
9092
9318
|
if (!convo.length) return;
|
|
9093
9319
|
const shown = convo.slice(-24);
|
|
9094
|
-
tui.print(`${
|
|
9095
|
-
if (shown.length < convo.length) tui.print(`${
|
|
9320
|
+
tui.print(`${c5.dim}\u2500\u2500 resuming session \xB7 ${convo.length} message${convo.length === 1 ? "" : "s"} \u2500\u2500${c5.reset}`);
|
|
9321
|
+
if (shown.length < convo.length) tui.print(`${c5.dim} \u2026 earlier messages omitted${c5.reset}`);
|
|
9096
9322
|
for (const m of shown) {
|
|
9097
9323
|
if (m.metadata?.user_command) {
|
|
9098
9324
|
printCommandBlock(
|
|
@@ -9153,6 +9379,7 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
|
|
|
9153
9379
|
let editingPendingId = null;
|
|
9154
9380
|
let reconcileSharedMessaging = async () => {
|
|
9155
9381
|
};
|
|
9382
|
+
let applySharedMessagingEvent = () => false;
|
|
9156
9383
|
let refreshSessionProjection = async () => {
|
|
9157
9384
|
};
|
|
9158
9385
|
const shownIds = /* @__PURE__ */ new Set();
|
|
@@ -9210,21 +9437,21 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
|
|
|
9210
9437
|
if (isOwner) {
|
|
9211
9438
|
bridge?.setClaim("takeover");
|
|
9212
9439
|
exec?.writeSessionInfo();
|
|
9213
|
-
if (wasKnown && moved) tui.print(`${
|
|
9440
|
+
if (wasKnown && moved) tui.print(`${c5.dim}Tool execution moved to this terminal.${c5.reset}`);
|
|
9214
9441
|
} else if (wasKnown && moved) {
|
|
9215
|
-
tui.print(`${
|
|
9442
|
+
tui.print(`${c5.dim}Tool execution moved to ${describeExecOwner(owner2)}.${c5.reset}`);
|
|
9216
9443
|
}
|
|
9217
9444
|
},
|
|
9218
9445
|
onClaimRefused: (reason) => {
|
|
9219
9446
|
if (reason === "in_flight") {
|
|
9220
9447
|
tui.print(
|
|
9221
|
-
`${
|
|
9448
|
+
`${c5.dim}The session's current client is mid-operation \u2014 execution stays there until it finishes.${c5.reset}`
|
|
9222
9449
|
);
|
|
9223
9450
|
}
|
|
9224
9451
|
},
|
|
9225
9452
|
onSuperseded: () => {
|
|
9226
9453
|
tui.print(
|
|
9227
|
-
`${
|
|
9454
|
+
`${c5.yellow}\u26A0 Another Standard Code process on this machine took over this session \u2014 this terminal is watching.${c5.reset}`
|
|
9228
9455
|
);
|
|
9229
9456
|
},
|
|
9230
9457
|
onStatus: (id, summary) => {
|
|
@@ -9286,7 +9513,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9286
9513
|
} else if (eventType === "goal_updated" && data) {
|
|
9287
9514
|
tui.setGoal(data);
|
|
9288
9515
|
} else if (eventType === SHARED_MESSAGING_EVENT) {
|
|
9289
|
-
void reconcileSharedMessaging(true);
|
|
9516
|
+
if (!applySharedMessagingEvent(data)) void reconcileSharedMessaging(true);
|
|
9290
9517
|
}
|
|
9291
9518
|
},
|
|
9292
9519
|
// A failed turn whose message is the lease service's at-limit denial → offer
|
|
@@ -9382,7 +9609,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9382
9609
|
const logout = async () => {
|
|
9383
9610
|
deleteCredential(api.origin);
|
|
9384
9611
|
const instanceHost = api.origin.replace(/^https?:\/\//, "");
|
|
9385
|
-
tui.print(`${
|
|
9612
|
+
tui.print(`${c5.gray}Signed out \u2014 removed the saved token for ${c5.teal}${instanceHost}${c5.reset}${c5.gray}. Run standardcode to sign in again.${c5.reset}`);
|
|
9386
9613
|
await quit();
|
|
9387
9614
|
};
|
|
9388
9615
|
const bgMgr = {
|
|
@@ -9390,7 +9617,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9390
9617
|
stop: async (id) => {
|
|
9391
9618
|
if (!host) {
|
|
9392
9619
|
tui.print(
|
|
9393
|
-
`${
|
|
9620
|
+
`${c5.gray}That process runs on ${runnerName} \u2014 ask the agent to stop it (it manages processes there).${c5.reset}`
|
|
9394
9621
|
);
|
|
9395
9622
|
return;
|
|
9396
9623
|
}
|
|
@@ -9471,17 +9698,25 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9471
9698
|
} catch (error) {
|
|
9472
9699
|
if (!sharedMessagingReady && !sharedMessagingUnavailableShown) {
|
|
9473
9700
|
sharedMessagingUnavailableShown = true;
|
|
9474
|
-
tui.print(`${
|
|
9701
|
+
tui.print(`${c5.dim}shared messaging unavailable: ${error instanceof Error ? error.message : String(error)}${c5.reset}`);
|
|
9475
9702
|
}
|
|
9476
9703
|
}
|
|
9477
9704
|
};
|
|
9705
|
+
applySharedMessagingEvent = (data) => {
|
|
9706
|
+
if (!sharedMessagingReady) return false;
|
|
9707
|
+
const event = parseMessagingChangedEvent(data);
|
|
9708
|
+
if (!event || !event.draft || event.draftOmitted) return false;
|
|
9709
|
+
if (event.pendingRevision > sharedMessaging.pending.revision) return false;
|
|
9710
|
+
applySharedMessaging({ version: 1, pending: sharedMessaging.pending, draft: event.draft }, true);
|
|
9711
|
+
return true;
|
|
9712
|
+
};
|
|
9478
9713
|
const onTerminalResume = () => void reconcileSharedMessaging(true);
|
|
9479
9714
|
process.on("SIGCONT", onTerminalResume);
|
|
9480
9715
|
const applySharedMutation = (promise) => promise.then((snapshot) => {
|
|
9481
9716
|
applySharedMessaging(snapshot, false);
|
|
9482
9717
|
return true;
|
|
9483
9718
|
}).catch((error) => {
|
|
9484
|
-
tui.print(`${
|
|
9719
|
+
tui.print(`${c5.dim}shared messaging failed: ${error instanceof Error ? error.message : String(error)}${c5.reset}`);
|
|
9485
9720
|
return false;
|
|
9486
9721
|
});
|
|
9487
9722
|
const appendSharedPending = (text, images, refs = []) => applySharedMutation(api.appendPendingInput(threadId, {
|
|
@@ -9530,7 +9765,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9530
9765
|
busy = false;
|
|
9531
9766
|
optimisticBusyUntil = 0;
|
|
9532
9767
|
tui.setWorking(false);
|
|
9533
|
-
tui.print(`${
|
|
9768
|
+
tui.print(`${c5.dim}failed to send: ${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
9534
9769
|
return false;
|
|
9535
9770
|
}
|
|
9536
9771
|
return true;
|
|
@@ -9539,11 +9774,11 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9539
9774
|
let bangRunning = false;
|
|
9540
9775
|
const runBangCommand = async (command) => {
|
|
9541
9776
|
if (bangRunning) {
|
|
9542
|
-
tui.print(`${
|
|
9777
|
+
tui.print(`${c5.dim}a command is already running \u2014 one at a time.${c5.reset}`);
|
|
9543
9778
|
return;
|
|
9544
9779
|
}
|
|
9545
9780
|
bangRunning = true;
|
|
9546
|
-
tui.print(`${
|
|
9781
|
+
tui.print(`${c5.magenta}!${c5.reset} ${c5.dim}running on ${whereLabel}\u2026${c5.reset}`);
|
|
9547
9782
|
try {
|
|
9548
9783
|
const res = await api.runCommand(threadId, command);
|
|
9549
9784
|
if (res.messageId) shownIds.add(res.messageId);
|
|
@@ -9557,7 +9792,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9557
9792
|
const openPendingMenu = async () => {
|
|
9558
9793
|
const items = sharedMessaging.pending.items;
|
|
9559
9794
|
if (!items.length) {
|
|
9560
|
-
tui.print(`${
|
|
9795
|
+
tui.print(`${c5.dim}No pending messages.${c5.reset}`);
|
|
9561
9796
|
return;
|
|
9562
9797
|
}
|
|
9563
9798
|
const picked = await tui.select("Pending messages", items.map((item, index) => ({
|
|
@@ -9587,16 +9822,16 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9587
9822
|
try {
|
|
9588
9823
|
await api.compact(threadId);
|
|
9589
9824
|
} catch (err) {
|
|
9590
|
-
tui.print(`${
|
|
9825
|
+
tui.print(`${c5.red}\u2717${c5.reset} couldn't start compaction: ${err.message}`);
|
|
9591
9826
|
}
|
|
9592
9827
|
};
|
|
9593
9828
|
const runAccountCommand = async () => {
|
|
9594
|
-
tui.print(`${
|
|
9829
|
+
tui.print(`${c5.gray}Opening your account\u2026${c5.reset}`);
|
|
9595
9830
|
const link = await api.accountLink(threadId).catch(() => null);
|
|
9596
9831
|
const target = link?.url ?? "https://standardcode.ai/account";
|
|
9597
9832
|
openUrl(target);
|
|
9598
9833
|
tui.print(
|
|
9599
|
-
link?.preauthed ? `${
|
|
9834
|
+
link?.preauthed ? `${c5.gray}\u2192 account dashboard opened in your browser (signed in)${c5.reset}` : `${c5.gray}\u2192 opened ${target} \u2014 sign in with your account email${c5.reset}`
|
|
9600
9835
|
);
|
|
9601
9836
|
};
|
|
9602
9837
|
const ordinal = (n) => {
|
|
@@ -9607,24 +9842,24 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9607
9842
|
const renderUpgradePanel = (q) => {
|
|
9608
9843
|
const dots = [];
|
|
9609
9844
|
for (let i = 0; i < q.max; i++) {
|
|
9610
|
-
if (i < q.current) dots.push(`${
|
|
9611
|
-
else if (i === q.current) dots.push(`${
|
|
9612
|
-
else dots.push(`${
|
|
9845
|
+
if (i < q.current) dots.push(`${c5.teal}\u25CF${c5.reset}`);
|
|
9846
|
+
else if (i === q.current) dots.push(`${c5.bold}${gradientText("\uFF0B")}${c5.reset}`);
|
|
9847
|
+
else dots.push(`${c5.dim}\xB7${c5.reset}`);
|
|
9613
9848
|
}
|
|
9614
9849
|
const cost = fmtCost(q);
|
|
9615
9850
|
const lines = [
|
|
9616
9851
|
"",
|
|
9617
|
-
`${
|
|
9852
|
+
`${c5.bold}\u2726 Add a parallel session${c5.reset}`,
|
|
9618
9853
|
"",
|
|
9619
|
-
`${dots.join(" ")} ${
|
|
9854
|
+
`${dots.join(" ")} ${c5.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c5.reset}`
|
|
9620
9855
|
];
|
|
9621
9856
|
if (q.ends_trial) {
|
|
9622
9857
|
lines.push(
|
|
9623
|
-
`${
|
|
9624
|
-
`${
|
|
9858
|
+
`${c5.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c5.reset}`,
|
|
9859
|
+
`${c5.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c5.bold}${cost} charged today${c5.reset}${c5.yellow}` : ""}.${c5.reset}`
|
|
9625
9860
|
);
|
|
9626
9861
|
} else if (cost) {
|
|
9627
|
-
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${
|
|
9862
|
+
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c5.bold}${cost} charged now${c5.reset}.`);
|
|
9628
9863
|
} else {
|
|
9629
9864
|
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 prorated on your next invoice.`);
|
|
9630
9865
|
}
|
|
@@ -9637,7 +9872,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9637
9872
|
try {
|
|
9638
9873
|
if (opts.auto) {
|
|
9639
9874
|
tui.print(
|
|
9640
|
-
`${
|
|
9875
|
+
`${c5.yellow}You're out of simultaneous sessions \u2014 another Standard Code session is using your slot.${c5.reset}`
|
|
9641
9876
|
);
|
|
9642
9877
|
}
|
|
9643
9878
|
const quote = await api.sessionsQuote(threadId);
|
|
@@ -9645,16 +9880,16 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9645
9880
|
const link = await api.accountLink(threadId).catch(() => null);
|
|
9646
9881
|
const target = link?.url ?? "https://standardcode.ai/account";
|
|
9647
9882
|
tui.print(
|
|
9648
|
-
`${
|
|
9883
|
+
`${c5.gray}Close the other session (its slot frees within ~90s) \u2014 or add another simultaneous session to your plan, then resend your message.${c5.reset}`
|
|
9649
9884
|
);
|
|
9650
9885
|
openUrl(target);
|
|
9651
|
-
tui.print(`${
|
|
9886
|
+
tui.print(`${c5.gray}\u2192 opened ${target} to manage your plan${c5.reset}`);
|
|
9652
9887
|
return;
|
|
9653
9888
|
}
|
|
9654
9889
|
if (quote.current >= quote.max) {
|
|
9655
9890
|
tui.print(
|
|
9656
|
-
`${
|
|
9657
|
-
${
|
|
9891
|
+
`${c5.yellow}You're at the maximum of ${quote.max} parallel session${quote.max === 1 ? "" : "s"}.${c5.reset}
|
|
9892
|
+
${c5.gray}Close another session (its slot frees within ~90s), then resend your message.${c5.reset}`
|
|
9658
9893
|
);
|
|
9659
9894
|
return;
|
|
9660
9895
|
}
|
|
@@ -9666,25 +9901,25 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9666
9901
|
{ label: "Not now", value: "no" }
|
|
9667
9902
|
]);
|
|
9668
9903
|
if (choice !== "go") {
|
|
9669
|
-
tui.print(`${
|
|
9904
|
+
tui.print(`${c5.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c5.reset}`);
|
|
9670
9905
|
return;
|
|
9671
9906
|
}
|
|
9672
|
-
tui.print(`${
|
|
9907
|
+
tui.print(`${c5.gray}Applying\u2026${c5.reset}`);
|
|
9673
9908
|
let applied;
|
|
9674
9909
|
try {
|
|
9675
9910
|
applied = await api.sessionsUpgrade(threadId, quote.sessions);
|
|
9676
9911
|
} catch (e) {
|
|
9677
|
-
tui.print(`${
|
|
9912
|
+
tui.print(`${c5.red}\u2717${c5.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
9678
9913
|
return;
|
|
9679
9914
|
}
|
|
9680
9915
|
if (!applied?.ok) {
|
|
9681
|
-
tui.print(`${
|
|
9916
|
+
tui.print(`${c5.red}\u2717${c5.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
|
|
9682
9917
|
return;
|
|
9683
9918
|
}
|
|
9684
9919
|
const n = applied.sessions ?? quote.sessions;
|
|
9685
|
-
tui.print(`${
|
|
9920
|
+
tui.print(`${c5.green}\u2713${c5.reset} ${c5.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c5.reset}`);
|
|
9686
9921
|
if (opts.auto && lastSent) {
|
|
9687
|
-
tui.print(`${
|
|
9922
|
+
tui.print(`${c5.gray}Continuing\u2026${c5.reset}`);
|
|
9688
9923
|
await sendNow(lastSent.text, lastSent.images, lastSent.refs);
|
|
9689
9924
|
}
|
|
9690
9925
|
} finally {
|
|
@@ -9795,38 +10030,50 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9795
10030
|
const history = await loadHistory(api, threadId, historySeedThreadId);
|
|
9796
10031
|
tui.setHistory(history);
|
|
9797
10032
|
await reconcileSharedMessaging(true);
|
|
9798
|
-
let
|
|
9799
|
-
|
|
9800
|
-
|
|
9801
|
-
|
|
9802
|
-
|
|
9803
|
-
|
|
9804
|
-
|
|
9805
|
-
|
|
9806
|
-
|
|
9807
|
-
|
|
9808
|
-
|
|
9809
|
-
|
|
9810
|
-
if (sharedMessagingReady) {
|
|
9811
|
-
const hasDraft = !!textVal.trim() || images.length > 0 || mirroredDraftRefs.length > 0;
|
|
10033
|
+
let latestDraftPayload = null;
|
|
10034
|
+
let draftDirty = false;
|
|
10035
|
+
let draftInFlight = false;
|
|
10036
|
+
const pumpComposerDraft = async () => {
|
|
10037
|
+
if (draftInFlight) return;
|
|
10038
|
+
draftInFlight = true;
|
|
10039
|
+
try {
|
|
10040
|
+
while (draftDirty) {
|
|
10041
|
+
draftDirty = false;
|
|
10042
|
+
const payload = latestDraftPayload;
|
|
10043
|
+
if (!payload || !sharedMessagingReady) continue;
|
|
10044
|
+
const hasDraft = !!payload.text.trim() || payload.images.length > 0 || mirroredDraftRefs.length > 0;
|
|
9812
10045
|
const mutation = {
|
|
9813
|
-
content:
|
|
10046
|
+
content: payload.text,
|
|
9814
10047
|
attachments: [
|
|
9815
10048
|
...hasDraft ? mirroredDraftRefs : [],
|
|
9816
|
-
...toSharedAttachments(images)
|
|
10049
|
+
...toSharedAttachments(payload.images)
|
|
9817
10050
|
],
|
|
9818
10051
|
...messagingOrigin
|
|
9819
10052
|
};
|
|
9820
|
-
|
|
10053
|
+
await applySharedMutation(
|
|
9821
10054
|
hasDraft ? api.putSharedDraft(threadId, mutation) : api.clearSharedDraft(threadId, messagingOrigin)
|
|
9822
10055
|
);
|
|
9823
10056
|
}
|
|
9824
|
-
}
|
|
10057
|
+
} finally {
|
|
10058
|
+
draftInFlight = false;
|
|
10059
|
+
}
|
|
10060
|
+
};
|
|
10061
|
+
const clearComposerDraft = () => {
|
|
10062
|
+
mirroredDraftRefs = [];
|
|
10063
|
+
tui.setExternalAttachmentNames([]);
|
|
10064
|
+
latestDraftPayload = { text: "", images: [] };
|
|
10065
|
+
draftDirty = true;
|
|
10066
|
+
void pumpComposerDraft();
|
|
10067
|
+
};
|
|
10068
|
+
tui.onDraftChange = (textVal, images) => {
|
|
10069
|
+
latestDraftPayload = { text: textVal, images };
|
|
10070
|
+
draftDirty = true;
|
|
10071
|
+
void pumpComposerDraft();
|
|
9825
10072
|
};
|
|
9826
10073
|
const submitComposer = async (text, images, steer) => {
|
|
9827
10074
|
const draftRefs = mirroredDraftRefs;
|
|
9828
10075
|
if (!sharedMessagingReady && (busy || steer || editingPendingId !== null)) {
|
|
9829
|
-
tui.print(`${
|
|
10076
|
+
tui.print(`${c5.dim}Restoring shared message state \u2014 try again in a moment.${c5.reset}`);
|
|
9830
10077
|
tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
|
|
9831
10078
|
tui.setInput(text, images);
|
|
9832
10079
|
return;
|
|
@@ -9849,7 +10096,7 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9849
10096
|
const item = sharedMessaging.pending.items.find((candidate) => candidate.id === pendingId);
|
|
9850
10097
|
editingPendingId = null;
|
|
9851
10098
|
if (!item) {
|
|
9852
|
-
tui.print(`${
|
|
10099
|
+
tui.print(`${c5.dim}That pending message was already dispatched or dismissed.${c5.reset}`);
|
|
9853
10100
|
return;
|
|
9854
10101
|
}
|
|
9855
10102
|
const updated = await editSharedPending(item, text, images, draftRefs);
|
|
@@ -9862,7 +10109,7 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9862
10109
|
return;
|
|
9863
10110
|
}
|
|
9864
10111
|
if (steer) {
|
|
9865
|
-
tui.print(`${
|
|
10112
|
+
tui.print(`${c5.yellow}\u21AA steering now \u2014 stopping the current step${c5.reset}`);
|
|
9866
10113
|
await Promise.all([
|
|
9867
10114
|
api.stopThread(threadId).catch(() => {
|
|
9868
10115
|
}),
|
|
@@ -9899,18 +10146,18 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9899
10146
|
tui.onInterrupt = () => {
|
|
9900
10147
|
const firstPending = sharedMessaging.pending.items[0];
|
|
9901
10148
|
if (!busy && firstPending) {
|
|
9902
|
-
tui.print(`${
|
|
10149
|
+
tui.print(`${c5.yellow}\u21AA steering the first pending message\u2026${c5.reset}`);
|
|
9903
10150
|
void promoteSharedPending(firstPending);
|
|
9904
10151
|
return;
|
|
9905
10152
|
}
|
|
9906
10153
|
if (busy) {
|
|
9907
10154
|
const head = sharedMessaging.pending.items[0];
|
|
9908
10155
|
if (head) {
|
|
9909
|
-
tui.print(`${
|
|
10156
|
+
tui.print(`${c5.yellow}\u21AA steering \u2014 stopping the current step to run the queued message${c5.reset}`);
|
|
9910
10157
|
void promoteSharedPending(head);
|
|
9911
10158
|
return;
|
|
9912
10159
|
}
|
|
9913
|
-
tui.print(`${
|
|
10160
|
+
tui.print(`${c5.yellow}\u25A0 stopping now${c5.reset}`);
|
|
9914
10161
|
const stops = [api.stopThread(threadId).catch(() => {
|
|
9915
10162
|
})];
|
|
9916
10163
|
for (const childId of activeSubagents.keys()) {
|
|
@@ -9940,7 +10187,7 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9940
10187
|
void api.getGoal(threadId).then((g) => tui.setGoal(g)).catch(() => {
|
|
9941
10188
|
});
|
|
9942
10189
|
attaching.stop();
|
|
9943
|
-
const header = `${
|
|
10190
|
+
const header = `${c5.bold}${c5.magenta}Standard Code${c5.reset} ${c5.dim}\u2014 ${agentTitle}${c5.reset}`;
|
|
9944
10191
|
const remoteDaemonV = session.runner?.daemon?.version;
|
|
9945
10192
|
const owner = currentOwner;
|
|
9946
10193
|
let localOwnerDesc = ownsExecution ? "this terminal" : describeExecOwner(owner);
|
|
@@ -9948,24 +10195,24 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9948
10195
|
const selfRec = await loadMachine(api, session.identity.machine_id).catch(() => null);
|
|
9949
10196
|
if (selfRec?.daemon?.version) localOwnerDesc = `the daemon on this machine (v${selfRec.daemon.version})`;
|
|
9950
10197
|
}
|
|
9951
|
-
const execLine = remote ? `${
|
|
10198
|
+
const execLine = remote ? `${c5.gray}tool execution:${c5.reset} daemon${remoteDaemonV ? ` v${remoteDaemonV}` : ""} on ${runnerName}` : `${c5.gray}tool execution:${c5.reset} ${localOwnerDesc}`;
|
|
9952
10199
|
tui.setAgentLabel(agentTitle);
|
|
9953
10200
|
tui.banner(
|
|
9954
10201
|
remote ? [
|
|
9955
10202
|
header,
|
|
9956
|
-
`${
|
|
9957
|
-
`${
|
|
10203
|
+
`${c5.gray}project:${c5.reset} ${session.remotePath ?? "?"} ${c5.teal}on ${runnerName}${c5.reset}`,
|
|
10204
|
+
`${c5.gray}machine:${c5.reset} ${runnerName} ${c5.gray}thread:${c5.reset} ${threadId.slice(0, 8)}`,
|
|
9958
10205
|
execLine
|
|
9959
10206
|
] : [
|
|
9960
10207
|
header,
|
|
9961
|
-
`${
|
|
9962
|
-
`${
|
|
10208
|
+
`${c5.gray}project:${c5.reset} ${projectDir}`,
|
|
10209
|
+
`${c5.gray}machine:${c5.reset} ${machine} ${c5.gray}thread:${c5.reset} ${threadId.slice(0, 8)}`,
|
|
9963
10210
|
execLine
|
|
9964
10211
|
]
|
|
9965
10212
|
);
|
|
9966
10213
|
if (!remote && session.suggestDaemonInstall && process.platform !== "win32" && !serviceStatus().installed) {
|
|
9967
10214
|
tui.print(
|
|
9968
|
-
`${
|
|
10215
|
+
`${c5.dim}Tip: install the always-on daemon (${c5.reset}standardcode daemon install${c5.dim}) to start sessions on this machine from anywhere.${c5.reset}`
|
|
9969
10216
|
);
|
|
9970
10217
|
}
|
|
9971
10218
|
if (resumed) await printHistory(api, threadId, tui);
|
|
@@ -9976,17 +10223,17 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9976
10223
|
const runningProcs = (await registry.list()).filter((p) => p.status === "running");
|
|
9977
10224
|
if (runningProcs.length) {
|
|
9978
10225
|
tui.print(
|
|
9979
|
-
`${
|
|
10226
|
+
`${c5.cyan}\u2699 ${runningProcs.length} background process${runningProcs.length === 1 ? "" : "es"} running:${c5.reset}`
|
|
9980
10227
|
);
|
|
9981
|
-
for (const p of runningProcs) tui.print(`${
|
|
10228
|
+
for (const p of runningProcs) tui.print(`${c5.gray} ${p.id} ${p.description || p.command}${c5.reset}`);
|
|
9982
10229
|
}
|
|
9983
10230
|
refreshBgCount();
|
|
9984
10231
|
if (exec && ownsExecution) {
|
|
9985
10232
|
for (const res of await exec.connectEnabledMcpServers()) {
|
|
9986
10233
|
if (res.ok) {
|
|
9987
|
-
tui.print(`${
|
|
10234
|
+
tui.print(`${c5.cyan}\u26A1 MCP "${res.name}" connected${c5.reset} ${c5.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c5.reset}`);
|
|
9988
10235
|
} else {
|
|
9989
|
-
tui.print(`${
|
|
10236
|
+
tui.print(`${c5.red}\u26A0 MCP "${res.name}" failed:${c5.reset} ${c5.gray}${res.error}${c5.reset}`);
|
|
9990
10237
|
}
|
|
9991
10238
|
}
|
|
9992
10239
|
}
|
|
@@ -10002,8 +10249,8 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
10002
10249
|
try {
|
|
10003
10250
|
const { choice, reason } = await tui.approval(
|
|
10004
10251
|
`${request.summary}${request.permission ? `
|
|
10005
|
-
${
|
|
10006
|
-
${
|
|
10252
|
+
${c5.bold}why: ${request.permission}${c5.reset}` : ""}
|
|
10253
|
+
${c5.dim}runs on ${request.machine || runnerName}${c5.reset}`,
|
|
10007
10254
|
request.risk
|
|
10008
10255
|
);
|
|
10009
10256
|
answeredApprovals.add(request.tool_call_id);
|
|
@@ -10030,7 +10277,7 @@ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
|
|
|
10030
10277
|
if (snapshot.stalled && !stalledNoticeShown) {
|
|
10031
10278
|
stalledNoticeShown = true;
|
|
10032
10279
|
tui.print(
|
|
10033
|
-
`${
|
|
10280
|
+
`${c5.yellow}\u26A0 session interrupted${c5.reset} ${c5.dim}\u2014 the last operation never finished (the runtime is attempting recovery). Send a message to continue.${c5.reset}`
|
|
10034
10281
|
);
|
|
10035
10282
|
} else if (!snapshot.stalled && stalledNoticeShown && snapshot.busy) {
|
|
10036
10283
|
stalledNoticeShown = false;
|
|
@@ -10063,7 +10310,7 @@ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
|
|
|
10063
10310
|
continue;
|
|
10064
10311
|
}
|
|
10065
10312
|
if (m.role === "assistant" && text) printAssistant(tui, text);
|
|
10066
|
-
else if (m.role === "system" && text) tui.print(`${
|
|
10313
|
+
else if (m.role === "system" && text) tui.print(`${c5.dim}${text}${c5.reset}`);
|
|
10067
10314
|
else if (m.role === "user" && text) {
|
|
10068
10315
|
const pending = pendingSent.get(text) ?? 0;
|
|
10069
10316
|
if (pending > 0) {
|
|
@@ -10157,16 +10404,16 @@ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
|
|
|
10157
10404
|
tui.setStep(null, 0);
|
|
10158
10405
|
tui.setBackgroundCount(0);
|
|
10159
10406
|
if (killed > 0) {
|
|
10160
|
-
tui.print(`${
|
|
10407
|
+
tui.print(`${c5.cyan}\u2699${c5.reset} Stopped ${killed} background process${killed === 1 ? "" : "es"}.`);
|
|
10161
10408
|
}
|
|
10162
|
-
tui.print(`${
|
|
10409
|
+
tui.print(`${c5.dim}\u2500\u2500 conversation cleared \u2014 starting a fresh session \u2500\u2500${c5.reset}`);
|
|
10163
10410
|
}
|
|
10164
10411
|
async function runSkillsMenu(tui, skills) {
|
|
10165
10412
|
let list;
|
|
10166
10413
|
try {
|
|
10167
10414
|
list = await skills.list();
|
|
10168
10415
|
} catch (e) {
|
|
10169
|
-
tui.print(`${
|
|
10416
|
+
tui.print(`${c5.red}\u2717 couldn't load skills:${c5.reset} ${c5.gray}${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
10170
10417
|
return;
|
|
10171
10418
|
}
|
|
10172
10419
|
const INSTALL = "__install__";
|
|
@@ -10177,7 +10424,7 @@ async function runSkillsMenu(tui, skills) {
|
|
|
10177
10424
|
}));
|
|
10178
10425
|
items.push({ label: "\uFF0B Install a skill\u2026", hint: "find & install", value: INSTALL });
|
|
10179
10426
|
const picked = await tui.select(
|
|
10180
|
-
`${
|
|
10427
|
+
`${c5.bold}Agent skills${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c5.reset}`,
|
|
10181
10428
|
items
|
|
10182
10429
|
);
|
|
10183
10430
|
if (!picked) return;
|
|
@@ -10190,8 +10437,8 @@ async function runSkillsMenu(tui, skills) {
|
|
|
10190
10437
|
return;
|
|
10191
10438
|
}
|
|
10192
10439
|
const skill = list.find((s) => s.name === picked);
|
|
10193
|
-
tui.print(`${
|
|
10194
|
-
const action = await tui.select(`${
|
|
10440
|
+
tui.print(`${c5.cyan}${skill.name}${c5.reset}${skill.version ? ` ${c5.dim}v${skill.version}${c5.reset}` : ""} ${c5.gray}\u2014 ${skill.description}${c5.reset}`);
|
|
10441
|
+
const action = await tui.select(`${c5.bold}${picked}${c5.reset}`, [
|
|
10195
10442
|
skill.enabled ? { label: "Disable (hide from the agent)", value: "disable" } : { label: "Enable", value: "enable" },
|
|
10196
10443
|
{ label: "View files", value: "files" },
|
|
10197
10444
|
{ label: "Remove this skill", value: "remove" },
|
|
@@ -10200,20 +10447,20 @@ async function runSkillsMenu(tui, skills) {
|
|
|
10200
10447
|
try {
|
|
10201
10448
|
if (action === "enable" || action === "disable") {
|
|
10202
10449
|
await skills.setEnabled(picked, action === "enable");
|
|
10203
|
-
tui.print(`${
|
|
10450
|
+
tui.print(`${c5.gray}${action}d ${picked}${c5.reset}`);
|
|
10204
10451
|
} else if (action === "files") {
|
|
10205
|
-
for (const f of skill.files) tui.print(` ${
|
|
10452
|
+
for (const f of skill.files) tui.print(` ${c5.gray}${f}${c5.reset}`);
|
|
10206
10453
|
} else if (action === "remove") {
|
|
10207
10454
|
await skills.remove(picked);
|
|
10208
|
-
tui.print(`${
|
|
10455
|
+
tui.print(`${c5.gray}removed ${picked}${c5.reset}`);
|
|
10209
10456
|
}
|
|
10210
10457
|
} catch (e) {
|
|
10211
|
-
tui.print(`${
|
|
10458
|
+
tui.print(`${c5.red}\u2717 ${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
10212
10459
|
}
|
|
10213
10460
|
}
|
|
10214
10461
|
async function runLevelMenu(tui, perm) {
|
|
10215
10462
|
const picked = await tui.select(
|
|
10216
|
-
`${
|
|
10463
|
+
`${c5.bold}Auto-accept level${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 shift-tab cycles)${c5.reset}`,
|
|
10217
10464
|
LEVELS.map((l) => ({
|
|
10218
10465
|
label: levelLabel(l),
|
|
10219
10466
|
hint: l === tui.level ? "current" : "",
|
|
@@ -10230,16 +10477,16 @@ async function runMachinesMenu(tui, api, self) {
|
|
|
10230
10477
|
try {
|
|
10231
10478
|
machines = await loadMachines(api);
|
|
10232
10479
|
} catch (e) {
|
|
10233
|
-
tui.print(`${
|
|
10480
|
+
tui.print(`${c5.red}\u2717 couldn't load machines:${c5.reset} ${c5.gray}${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
10234
10481
|
return;
|
|
10235
10482
|
}
|
|
10236
10483
|
if (!machines.length) {
|
|
10237
|
-
tui.print(`${
|
|
10484
|
+
tui.print(`${c5.gray}No machines registered yet. Run standardcode on a machine (or install its daemon) to register it.${c5.reset}`);
|
|
10238
10485
|
return;
|
|
10239
10486
|
}
|
|
10240
10487
|
machines.sort((a, b) => (b.updated_at ?? 0) - (a.updated_at ?? 0));
|
|
10241
10488
|
const picked = await tui.select(
|
|
10242
|
-
`${
|
|
10489
|
+
`${c5.bold}Your machines${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 esc)${c5.reset}`,
|
|
10243
10490
|
machines.map((m) => {
|
|
10244
10491
|
const isSelf = m.id === self.machine_id;
|
|
10245
10492
|
const online = daemonOnline(m);
|
|
@@ -10273,26 +10520,26 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
10273
10520
|
options.push({ label: "Back", value: "back" });
|
|
10274
10521
|
if (!isSelf && !machine.daemon) {
|
|
10275
10522
|
tui.print(
|
|
10276
|
-
`${
|
|
10523
|
+
`${c5.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c5.reset}`
|
|
10277
10524
|
);
|
|
10278
10525
|
} else if (!isSelf && machine.daemon && !online) {
|
|
10279
10526
|
tui.print(
|
|
10280
|
-
`${
|
|
10527
|
+
`${c5.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c5.reset}`
|
|
10281
10528
|
);
|
|
10282
10529
|
}
|
|
10283
|
-
const action = await tui.select(`${
|
|
10530
|
+
const action = await tui.select(`${c5.bold}${machineIcon(machine)} ${machine.name}${c5.reset}`, options);
|
|
10284
10531
|
if (!action || action === "back") return;
|
|
10285
10532
|
if (action === "icon") {
|
|
10286
10533
|
const current = machine.icon ?? "";
|
|
10287
10534
|
const emoji = await tui.prompt(
|
|
10288
|
-
`${
|
|
10535
|
+
`${c5.bold}Icon for ${machine.name}${c5.reset} ${c5.dim}(paste an emoji, blank to reset)${c5.reset}`,
|
|
10289
10536
|
current
|
|
10290
10537
|
);
|
|
10291
10538
|
if (emoji !== null) {
|
|
10292
10539
|
const trimmed = emoji.trim();
|
|
10293
10540
|
await setMachineIcon(api, machine.id, trimmed);
|
|
10294
10541
|
machine.icon = trimmed || void 0;
|
|
10295
|
-
tui.print(`${
|
|
10542
|
+
tui.print(`${c5.green}\u2713${c5.reset} icon ${trimmed ? `set to ${trimmed}` : "reset"} for ${machine.name}`);
|
|
10296
10543
|
}
|
|
10297
10544
|
return manageMachine(tui, api, self, machine);
|
|
10298
10545
|
}
|
|
@@ -10308,7 +10555,7 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
10308
10555
|
const name = await tui.prompt(`New name for ${machine.name}`, machine.name);
|
|
10309
10556
|
if (name === null || !name.trim()) return;
|
|
10310
10557
|
await setMachineName(api, machine.id, name.trim());
|
|
10311
|
-
tui.print(`${
|
|
10558
|
+
tui.print(`${c5.green}\u2713${c5.reset} Renamed ${c5.bold}${machine.name}${c5.reset} \u2192 ${c5.bold}${name.trim()}${c5.reset}.`);
|
|
10312
10559
|
} else if (action === "update") {
|
|
10313
10560
|
if (isSelf) {
|
|
10314
10561
|
await runUpdateCommand(tui);
|
|
@@ -10319,7 +10566,7 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
10319
10566
|
]);
|
|
10320
10567
|
if (go !== "yes") return;
|
|
10321
10568
|
await dispatch("update");
|
|
10322
|
-
tui.print(`${
|
|
10569
|
+
tui.print(`${c5.green}\u2713${c5.reset} Update ${c5.gray}${applyNote} (its daemon updates and restarts on the new version).${c5.reset}`);
|
|
10323
10570
|
}
|
|
10324
10571
|
} else if (action === "projects") {
|
|
10325
10572
|
await manageMachineProjects(tui, api, self, machine, dispatch, applyNote);
|
|
@@ -10330,7 +10577,7 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
|
|
|
10330
10577
|
const paths = Object.keys(machine.projects).sort();
|
|
10331
10578
|
const projLabels = projectDisplayLabels(machine.projects);
|
|
10332
10579
|
const picked = await tui.select(
|
|
10333
|
-
`${
|
|
10580
|
+
`${c5.bold}Projects on ${machine.name}${c5.reset} ${c5.dim}(\u2191\u2193 \xB7 enter \xB7 esc)${c5.reset}`,
|
|
10334
10581
|
[
|
|
10335
10582
|
...paths.map((p) => ({
|
|
10336
10583
|
label: projLabels.get(p) ?? projectDisplayName(p, machine.projects[p]),
|
|
@@ -10351,12 +10598,12 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
|
|
|
10351
10598
|
});
|
|
10352
10599
|
if (!chosen) return;
|
|
10353
10600
|
await dispatch("add_project", { path: chosen });
|
|
10354
|
-
tui.print(`${
|
|
10601
|
+
tui.print(`${c5.green}\u2713${c5.reset} Add ${chosen} ${c5.gray}${applyNote}.${c5.reset}`);
|
|
10355
10602
|
return;
|
|
10356
10603
|
}
|
|
10357
10604
|
const project = machine.projects[picked];
|
|
10358
10605
|
const displayName = projectDisplayName(picked, project);
|
|
10359
|
-
const action = await tui.select(`${
|
|
10606
|
+
const action = await tui.select(`${c5.bold}${displayName}${c5.reset} ${c5.dim}${shortenPath(picked, 48)}${c5.reset}`, [
|
|
10360
10607
|
{ label: "Rename", hint: "display name only \u2014 the directory is untouched", value: "rename" },
|
|
10361
10608
|
{ label: "Remove from this machine's projects", hint: "doesn't delete the directory", value: "remove" },
|
|
10362
10609
|
{ label: "Back", value: "back" }
|
|
@@ -10371,60 +10618,60 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
|
|
|
10371
10618
|
await setProjectName(api, machine.id, picked, name);
|
|
10372
10619
|
const now = name.trim() || projectDisplayName(picked, null);
|
|
10373
10620
|
if (project) project.name = now;
|
|
10374
|
-
tui.print(`${
|
|
10621
|
+
tui.print(`${c5.green}\u2713${c5.reset} Renamed ${c5.bold}${displayName}${c5.reset} \u2192 ${c5.bold}${now}${c5.reset}.`);
|
|
10375
10622
|
} else {
|
|
10376
10623
|
await dispatch("remove_project", { path: picked });
|
|
10377
|
-
tui.print(`${
|
|
10624
|
+
tui.print(`${c5.green}\u2713${c5.reset} Remove ${picked} ${c5.gray}${applyNote}.${c5.reset}`);
|
|
10378
10625
|
}
|
|
10379
10626
|
}
|
|
10380
10627
|
function showDaemonInfo(tui, session) {
|
|
10381
10628
|
if (session.mode === "remote" && session.runner) {
|
|
10382
10629
|
tui.print(
|
|
10383
|
-
`${
|
|
10630
|
+
`${c5.gray}This session runs on${c5.reset} ${c5.bold}${session.runner.name}${c5.reset} ${c5.gray}(${session.runner.hostname}) \u2014 its daemon executes the tools.${c5.reset}`
|
|
10384
10631
|
);
|
|
10385
10632
|
} else {
|
|
10386
|
-
tui.print(`${
|
|
10633
|
+
tui.print(`${c5.gray}This session runs on this machine.${c5.reset}`);
|
|
10387
10634
|
}
|
|
10388
10635
|
const status = serviceStatus();
|
|
10389
10636
|
tui.print(
|
|
10390
|
-
`${
|
|
10637
|
+
`${c5.gray}Daemon on this machine:${c5.reset} ${status.installed ? status.detail : "not installed"}`
|
|
10391
10638
|
);
|
|
10392
10639
|
if (!status.installed) {
|
|
10393
10640
|
tui.print(
|
|
10394
|
-
`${
|
|
10641
|
+
`${c5.gray}Install it to start sessions on this machine from anywhere:${c5.reset} ${c5.bold}standardcode daemon install${c5.reset}`
|
|
10395
10642
|
);
|
|
10396
10643
|
tui.print(
|
|
10397
|
-
`${
|
|
10644
|
+
`${c5.dim}The daemon keeps running after you close the terminal \u2014 it self-restarts, self-updates, and executes sessions you start from other machines.${c5.reset}`
|
|
10398
10645
|
);
|
|
10399
10646
|
} else {
|
|
10400
|
-
tui.print(`${
|
|
10647
|
+
tui.print(`${c5.dim}Manage it with standardcode daemon status | uninstall | add-project <path>.${c5.reset}`);
|
|
10401
10648
|
}
|
|
10402
10649
|
}
|
|
10403
10650
|
function showKeybindings(tui) {
|
|
10404
|
-
tui.print(`${
|
|
10405
|
-
tui.print(`${
|
|
10406
|
-
tui.print(`${
|
|
10407
|
-
tui.print(`${
|
|
10408
|
-
tui.print(`${
|
|
10409
|
-
tui.print(`${
|
|
10410
|
-
tui.print(`${
|
|
10411
|
-
tui.print(`${
|
|
10412
|
-
tui.print(`${
|
|
10413
|
-
tui.print(`${
|
|
10651
|
+
tui.print(`${c5.gray}shortcuts:${c5.reset}`);
|
|
10652
|
+
tui.print(`${c5.gray} shift-tab${c5.reset} cycle auto-accept level (1\u20135)`);
|
|
10653
|
+
tui.print(`${c5.gray} shift-\u23CE${c5.reset} insert a newline (multiline input)`);
|
|
10654
|
+
tui.print(`${c5.gray} option-\u23CE${c5.reset} steer now \u2014 stops the current step and plays your message`);
|
|
10655
|
+
tui.print(`${c5.gray} /${c5.reset} open the command palette (type to filter)`);
|
|
10656
|
+
tui.print(`${c5.gray} !cmd${c5.reset} run a shell command on the session's machine (e.g. !ls); !! to send a literal !`);
|
|
10657
|
+
tui.print(`${c5.gray} ctrl-v${c5.reset} paste an image from the clipboard ([#Image 1])`);
|
|
10658
|
+
tui.print(`${c5.gray} \u2191 / \u2193${c5.reset} cycle past messages (on the input's top line)`);
|
|
10659
|
+
tui.print(`${c5.gray} \u2190${c5.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
|
|
10660
|
+
tui.print(`${c5.gray} ctrl-c${c5.reset} quit`);
|
|
10414
10661
|
}
|
|
10415
10662
|
async function runUpdateCommand(tui) {
|
|
10416
10663
|
const version = readVersion();
|
|
10417
10664
|
const result = await forceCheckForUpdate(version);
|
|
10418
10665
|
if (!result) {
|
|
10419
|
-
tui.print(`${
|
|
10666
|
+
tui.print(`${c5.green}\u2713${c5.reset} ${c5.gray}@standardagents/code${c5.reset} is up to date (v${version})`);
|
|
10420
10667
|
return;
|
|
10421
10668
|
}
|
|
10422
10669
|
const { latest } = result;
|
|
10423
10670
|
tui.print(`
|
|
10424
|
-
${
|
|
10671
|
+
${c5.yellow}\u27F3${c5.reset} Update available: ${c5.gray}v${version}${c5.reset} \u2192 ${c5.green}v${latest}${c5.reset}`);
|
|
10425
10672
|
const pm = detectPackageManager();
|
|
10426
10673
|
if (!pm) {
|
|
10427
|
-
tui.print(` ${
|
|
10674
|
+
tui.print(` ${c5.gray}This is a source checkout \u2014 pull the repo to update.${c5.reset}`);
|
|
10428
10675
|
return;
|
|
10429
10676
|
}
|
|
10430
10677
|
const { display } = updateCommand(pm);
|
|
@@ -10433,28 +10680,28 @@ async function runUpdateCommand(tui) {
|
|
|
10433
10680
|
{ label: "No, skip", value: "no" }
|
|
10434
10681
|
]);
|
|
10435
10682
|
if (choice === "yes") {
|
|
10436
|
-
tui.print(` ${
|
|
10683
|
+
tui.print(` ${c5.gray}Running ${display}\u2026${c5.reset}`);
|
|
10437
10684
|
const { ok, output: pmOutput } = await runUpdate(pm);
|
|
10438
10685
|
if (ok) {
|
|
10439
|
-
tui.print(` ${
|
|
10686
|
+
tui.print(` ${c5.green}\u2713${c5.reset} Updated to v${latest}. Restart to use the new version.`);
|
|
10440
10687
|
} else {
|
|
10441
|
-
tui.print(` ${
|
|
10688
|
+
tui.print(` ${c5.red}\u2717${c5.reset} Update failed:`);
|
|
10442
10689
|
for (const line of pmOutput.trim().split("\n").slice(-6)) {
|
|
10443
|
-
tui.print(` ${
|
|
10690
|
+
tui.print(` ${c5.dim}${line}${c5.reset}`);
|
|
10444
10691
|
}
|
|
10445
10692
|
}
|
|
10446
10693
|
} else {
|
|
10447
|
-
tui.print(` ${
|
|
10694
|
+
tui.print(` ${c5.gray}Skipped. Run /update later.${c5.reset}`);
|
|
10448
10695
|
}
|
|
10449
10696
|
}
|
|
10450
10697
|
async function runProcessMenu(tui, bg) {
|
|
10451
10698
|
const procs = await bg.list();
|
|
10452
10699
|
if (!procs.length) {
|
|
10453
|
-
tui.print(`${
|
|
10700
|
+
tui.print(`${c5.gray}No background processes for this session.${c5.reset}`);
|
|
10454
10701
|
return;
|
|
10455
10702
|
}
|
|
10456
10703
|
const items = procs.map((p) => {
|
|
10457
|
-
const status = p.status === "running" ? `${
|
|
10704
|
+
const status = p.status === "running" ? `${c5.green}running${c5.reset}` : `${c5.gray}${p.status}${typeof p.exitCode === "number" ? ` (exit ${p.exitCode})` : ""}${c5.reset}`;
|
|
10458
10705
|
return {
|
|
10459
10706
|
label: `${p.description || p.command}`,
|
|
10460
10707
|
hint: `${p.id} \xB7 ${status}`,
|
|
@@ -10462,22 +10709,22 @@ async function runProcessMenu(tui, bg) {
|
|
|
10462
10709
|
};
|
|
10463
10710
|
});
|
|
10464
10711
|
const picked = await tui.select(
|
|
10465
|
-
`${
|
|
10712
|
+
`${c5.bold}Background processes${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter to manage \xB7 esc to close)${c5.reset}`,
|
|
10466
10713
|
items
|
|
10467
10714
|
);
|
|
10468
10715
|
if (!picked) return;
|
|
10469
10716
|
const proc = procs.find((p) => p.id === picked);
|
|
10470
10717
|
if (!proc || proc.status !== "running") {
|
|
10471
|
-
tui.print(`${
|
|
10718
|
+
tui.print(`${c5.gray}${picked} is not running.${c5.reset}`);
|
|
10472
10719
|
return;
|
|
10473
10720
|
}
|
|
10474
|
-
const action = await tui.select(`${
|
|
10721
|
+
const action = await tui.select(`${c5.bold}${proc.description || proc.command}${c5.reset}`, [
|
|
10475
10722
|
{ label: "Stop this process", value: "stop" },
|
|
10476
10723
|
{ label: "Leave it running", value: "leave" }
|
|
10477
10724
|
]);
|
|
10478
10725
|
if (action === "stop") {
|
|
10479
10726
|
await bg.stop(picked);
|
|
10480
|
-
tui.print(`${
|
|
10727
|
+
tui.print(`${c5.gray}stopped ${picked}${c5.reset}`);
|
|
10481
10728
|
}
|
|
10482
10729
|
}
|
|
10483
10730
|
async function runApprovalsMenu(tui, perm, save) {
|
|
@@ -10485,7 +10732,7 @@ async function runApprovalsMenu(tui, perm, save) {
|
|
|
10485
10732
|
const risks = Array.from(perm.allowRisk).sort((a, b) => a - b);
|
|
10486
10733
|
if (!tools.length && !risks.length) {
|
|
10487
10734
|
tui.print(
|
|
10488
|
-
`${
|
|
10735
|
+
`${c5.gray}No pre-approved commands. At a permission prompt, choose "Always allow this tool" or "Allow all level N" to add some.${c5.reset}`
|
|
10489
10736
|
);
|
|
10490
10737
|
return;
|
|
10491
10738
|
}
|
|
@@ -10495,22 +10742,22 @@ async function runApprovalsMenu(tui, perm, save) {
|
|
|
10495
10742
|
{ label: "Clear all approvals", hint: "", value: "clear" }
|
|
10496
10743
|
];
|
|
10497
10744
|
const picked = await tui.select(
|
|
10498
|
-
`${
|
|
10745
|
+
`${c5.bold}Approved commands${c5.reset} ${c5.dim}(enter to revoke \xB7 esc to close)${c5.reset}`,
|
|
10499
10746
|
items
|
|
10500
10747
|
);
|
|
10501
10748
|
if (!picked) return;
|
|
10502
10749
|
if (picked === "clear") {
|
|
10503
10750
|
perm.alwaysAllow.clear();
|
|
10504
10751
|
perm.allowRisk.clear();
|
|
10505
|
-
tui.print(`${
|
|
10752
|
+
tui.print(`${c5.gray}cleared all approvals${c5.reset}`);
|
|
10506
10753
|
} else if (picked.startsWith("tool:")) {
|
|
10507
10754
|
const t = picked.slice(5);
|
|
10508
10755
|
perm.alwaysAllow.delete(t);
|
|
10509
|
-
tui.print(`${
|
|
10756
|
+
tui.print(`${c5.gray}revoked tool ${t}${c5.reset}`);
|
|
10510
10757
|
} else if (picked.startsWith("risk:")) {
|
|
10511
10758
|
const r = Number(picked.slice(5));
|
|
10512
10759
|
perm.allowRisk.delete(r);
|
|
10513
|
-
tui.print(`${
|
|
10760
|
+
tui.print(`${c5.gray}revoked level ${r}${c5.reset}`);
|
|
10514
10761
|
}
|
|
10515
10762
|
save();
|
|
10516
10763
|
}
|
|
@@ -10528,7 +10775,7 @@ async function runMcpMenu(tui, mcp) {
|
|
|
10528
10775
|
items.push({ label: "\uFF0B Install a new MCP server\u2026", hint: "find & install", value: INSTALL });
|
|
10529
10776
|
items.push({ label: "Add manually (name: command)\u2026", hint: "advanced", value: ADD_MANUAL });
|
|
10530
10777
|
const picked = await tui.select(
|
|
10531
|
-
`${
|
|
10778
|
+
`${c5.bold}MCP servers${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c5.reset}`,
|
|
10532
10779
|
items
|
|
10533
10780
|
);
|
|
10534
10781
|
if (!picked) return;
|
|
@@ -10542,7 +10789,7 @@ async function runMcpMenu(tui, mcp) {
|
|
|
10542
10789
|
}
|
|
10543
10790
|
const server = configured.find((s) => s.name === picked);
|
|
10544
10791
|
const isConnected = connected.has(picked);
|
|
10545
|
-
const action = await tui.select(`${
|
|
10792
|
+
const action = await tui.select(`${c5.bold}${picked}${c5.reset}`, [
|
|
10546
10793
|
{ label: "View tools", value: "tools" },
|
|
10547
10794
|
isConnected ? { label: "Disconnect", value: "disconnect" } : { label: "Connect", value: "connect" },
|
|
10548
10795
|
server.enabled ? { label: "Disable (don't auto-connect)", value: "disable" } : { label: "Enable (auto-connect on start)", value: "enable" },
|
|
@@ -10552,29 +10799,29 @@ async function runMcpMenu(tui, mcp) {
|
|
|
10552
10799
|
if (action === "tools") {
|
|
10553
10800
|
const entry = mcp.catalog().servers.find((e) => e.name === picked);
|
|
10554
10801
|
if (!entry || entry.status !== "connected") {
|
|
10555
|
-
tui.print(`${
|
|
10802
|
+
tui.print(`${c5.gray}${picked} is not connected \u2014 connect it to list tools.${c5.reset}`);
|
|
10556
10803
|
return;
|
|
10557
10804
|
}
|
|
10558
|
-
if (!entry.tools.length) tui.print(`${
|
|
10559
|
-
for (const t of entry.tools) tui.print(` ${
|
|
10560
|
-
if (entry.resources.length) tui.print(` ${
|
|
10805
|
+
if (!entry.tools.length) tui.print(`${c5.gray}${picked} exposes no tools.${c5.reset}`);
|
|
10806
|
+
for (const t of entry.tools) tui.print(` ${c5.cyan}${t.name}${c5.reset}${t.description ? ` ${c5.gray}\u2014 ${t.description}${c5.reset}` : ""}`);
|
|
10807
|
+
if (entry.resources.length) tui.print(` ${c5.gray}${entry.resources.length} resource(s)${c5.reset}`);
|
|
10561
10808
|
} else if (action === "connect") {
|
|
10562
10809
|
const res = await mcp.connect(server);
|
|
10563
|
-
tui.print(res.ok ? `${
|
|
10810
|
+
tui.print(res.ok ? `${c5.cyan}\u26A1 connected (${res.tools} tools)${c5.reset}` : `${c5.red}\u26A0 ${res.error}${c5.reset}`);
|
|
10564
10811
|
} else if (action === "disconnect") {
|
|
10565
10812
|
mcp.disconnect(picked);
|
|
10566
|
-
tui.print(`${
|
|
10813
|
+
tui.print(`${c5.gray}disconnected ${picked}${c5.reset}`);
|
|
10567
10814
|
} else if (action === "enable") {
|
|
10568
10815
|
mcp.setEnabled(picked, true);
|
|
10569
10816
|
const res = await mcp.connect(server);
|
|
10570
|
-
tui.print(res.ok ? `${
|
|
10817
|
+
tui.print(res.ok ? `${c5.cyan}\u26A1 enabled + connected (${res.tools} tools)${c5.reset}` : `${c5.red}\u26A0 enabled but failed: ${res.error}${c5.reset}`);
|
|
10571
10818
|
} else if (action === "disable") {
|
|
10572
10819
|
mcp.setEnabled(picked, false);
|
|
10573
10820
|
mcp.disconnect(picked);
|
|
10574
|
-
tui.print(`${
|
|
10821
|
+
tui.print(`${c5.gray}disabled + disconnected ${picked}${c5.reset}`);
|
|
10575
10822
|
} else if (action === "remove") {
|
|
10576
10823
|
mcp.remove(picked);
|
|
10577
|
-
tui.print(`${
|
|
10824
|
+
tui.print(`${c5.gray}removed ${picked}${c5.reset}`);
|
|
10578
10825
|
}
|
|
10579
10826
|
}
|
|
10580
10827
|
async function addMcpServer(tui, mcp) {
|
|
@@ -10585,13 +10832,13 @@ async function addMcpServer(tui, mcp) {
|
|
|
10585
10832
|
if (!spec) return;
|
|
10586
10833
|
const cfg = parseServerSpec(spec);
|
|
10587
10834
|
if (!cfg) {
|
|
10588
|
-
tui.print(`${
|
|
10835
|
+
tui.print(`${c5.yellow}couldn't parse that. Use name: command [args]${c5.reset}`);
|
|
10589
10836
|
return;
|
|
10590
10837
|
}
|
|
10591
|
-
tui.print(`${
|
|
10838
|
+
tui.print(`${c5.gray}connecting MCP "${cfg.name}" (${cfg.command} ${cfg.args.join(" ")})\u2026${c5.reset}`);
|
|
10592
10839
|
const res = await mcp.add(cfg);
|
|
10593
|
-
if (res.ok) tui.print(`${
|
|
10594
|
-
else tui.print(`${
|
|
10840
|
+
if (res.ok) tui.print(`${c5.cyan}\u26A1 MCP "${cfg.name}" connected${c5.reset} ${c5.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c5.reset}`);
|
|
10841
|
+
else tui.print(`${c5.red}\u26A0 MCP "${cfg.name}" failed:${c5.reset} ${c5.gray}${res.error}${c5.reset} ${c5.dim}(saved; retry from the MCP menu)${c5.reset}`);
|
|
10595
10842
|
}
|
|
10596
10843
|
async function installMcpServerFlow(tui, mcp) {
|
|
10597
10844
|
const query = await tui.prompt(
|