@standardagents/code 0.11.5 → 0.11.7
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 +464 -247
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -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 {
|
|
@@ -1565,8 +1595,9 @@ var Bridge = class {
|
|
|
1565
1595
|
} finally {
|
|
1566
1596
|
this.hooks.onStatus?.(callKey, null);
|
|
1567
1597
|
}
|
|
1568
|
-
|
|
1569
|
-
|
|
1598
|
+
const ledgerKey = req.toolCallId ?? req.id;
|
|
1599
|
+
if (ledgerKey) {
|
|
1600
|
+
this.ledger.record(ledgerKey, {
|
|
1570
1601
|
ok: result.ok,
|
|
1571
1602
|
result: result.ok ? result.result ?? "" : void 0,
|
|
1572
1603
|
error: result.ok ? void 0 : result.error
|
|
@@ -2689,7 +2720,7 @@ var McpManager = class {
|
|
|
2689
2720
|
}
|
|
2690
2721
|
}
|
|
2691
2722
|
closeAll() {
|
|
2692
|
-
for (const [,
|
|
2723
|
+
for (const [, c6] of this.clients) c6.close();
|
|
2693
2724
|
this.clients.clear();
|
|
2694
2725
|
}
|
|
2695
2726
|
get(name) {
|
|
@@ -2700,13 +2731,13 @@ var McpManager = class {
|
|
|
2700
2731
|
}
|
|
2701
2732
|
toolCount() {
|
|
2702
2733
|
let n = 0;
|
|
2703
|
-
for (const [,
|
|
2734
|
+
for (const [, c6] of this.clients) n += c6.tools.length;
|
|
2704
2735
|
return n;
|
|
2705
2736
|
}
|
|
2706
2737
|
/** A JSON-serializable catalog of every connected server for the KV/context. */
|
|
2707
2738
|
catalog() {
|
|
2708
2739
|
return {
|
|
2709
|
-
servers: Array.from(this.clients.values()).map((
|
|
2740
|
+
servers: Array.from(this.clients.values()).map((c6) => c6.catalogEntry()),
|
|
2710
2741
|
generatedAt: Date.now()
|
|
2711
2742
|
};
|
|
2712
2743
|
}
|
|
@@ -2796,9 +2827,9 @@ function flattenContent(content, structured) {
|
|
|
2796
2827
|
}
|
|
2797
2828
|
function flattenResourceContents(contents) {
|
|
2798
2829
|
const parts = [];
|
|
2799
|
-
for (const
|
|
2800
|
-
if (typeof
|
|
2801
|
-
else if (typeof
|
|
2830
|
+
for (const c6 of contents || []) {
|
|
2831
|
+
if (typeof c6.text === "string") parts.push(c6.text);
|
|
2832
|
+
else if (typeof c6.blob === "string") parts.push(`[binary resource ${String(c6.uri ?? "")} (${c6.blob.length} b64 chars)]`);
|
|
2802
2833
|
}
|
|
2803
2834
|
return parts.join("\n").trim();
|
|
2804
2835
|
}
|
|
@@ -3299,6 +3330,7 @@ function parseToolCalls(message) {
|
|
|
3299
3330
|
var EMPTY_BOUNDARY_STALE_MS = 15e4;
|
|
3300
3331
|
var WAITING_FOR_CLIENT_MARKER = "Waiting for your machine to reconnect";
|
|
3301
3332
|
var PENDING_TAIL_STALE_MS = 10 * 6e4;
|
|
3333
|
+
var FORWARDED_TOOL_STALE_MS = 6.5 * 6e4;
|
|
3302
3334
|
var BUSY_TAIL_STALE_MS = 30 * 6e4;
|
|
3303
3335
|
function toMs(raw) {
|
|
3304
3336
|
return raw > 1e14 ? raw / 1e3 : raw < 1e12 ? raw * 1e3 : raw;
|
|
@@ -3328,7 +3360,17 @@ function deriveSessionActivity(messages, nowMs = Date.now()) {
|
|
|
3328
3360
|
return { busy: true, currentTool: { ...tool ?? { id: last.tool_call_id ?? "", name: "forwarded", arguments: {} }, waitingForHost: true } };
|
|
3329
3361
|
}
|
|
3330
3362
|
const currentTool = unresolvedTool(visible);
|
|
3331
|
-
if (currentTool)
|
|
3363
|
+
if (currentTool) {
|
|
3364
|
+
const carrier = [...visible].reverse().find((message) => {
|
|
3365
|
+
const raw = message.tool_calls;
|
|
3366
|
+
return typeof raw === "string" && raw.includes(currentTool.id);
|
|
3367
|
+
});
|
|
3368
|
+
const carrierMs = carrier ? toMs(createdAt(carrier)) : 0;
|
|
3369
|
+
if (carrierMs > 0 && nowMs - carrierMs > FORWARDED_TOOL_STALE_MS) {
|
|
3370
|
+
return { busy: false, currentTool: null, stalled: true };
|
|
3371
|
+
}
|
|
3372
|
+
return { busy: true, currentTool };
|
|
3373
|
+
}
|
|
3332
3374
|
if (last.role === "user" || last.role === "tool") {
|
|
3333
3375
|
const lastMs = toMs(createdAt(last));
|
|
3334
3376
|
if (lastMs > 0 && nowMs - lastMs > BUSY_TAIL_STALE_MS) {
|
|
@@ -3837,17 +3879,17 @@ function wrapBlock(out, cols2, leadFirst, leadRest, leadWidth, text) {
|
|
|
3837
3879
|
function renderTable(rows) {
|
|
3838
3880
|
const cols2 = Math.max(...rows.map((r) => r.length));
|
|
3839
3881
|
const widths = [];
|
|
3840
|
-
for (let
|
|
3841
|
-
widths[
|
|
3882
|
+
for (let c6 = 0; c6 < cols2; c6++) {
|
|
3883
|
+
widths[c6] = Math.max(...rows.map((r) => visibleWidth(inline(r[c6] ?? ""))));
|
|
3842
3884
|
}
|
|
3843
3885
|
const sep = `${GRAY} \u2502 ${R}`;
|
|
3844
3886
|
const out = [];
|
|
3845
3887
|
rows.forEach((r, ri) => {
|
|
3846
3888
|
const cells = [];
|
|
3847
|
-
for (let
|
|
3848
|
-
const raw = r[
|
|
3889
|
+
for (let c6 = 0; c6 < cols2; c6++) {
|
|
3890
|
+
const raw = r[c6] ?? "";
|
|
3849
3891
|
const styled = ri === 0 ? `${BOLD}${inline(raw)}${R}` : inline(raw);
|
|
3850
|
-
cells.push(padEndVisible(styled, widths[
|
|
3892
|
+
cells.push(padEndVisible(styled, widths[c6]));
|
|
3851
3893
|
}
|
|
3852
3894
|
out.push((" " + cells.join(sep)).replace(/\s+$/, ""));
|
|
3853
3895
|
if (ri === 0) {
|
|
@@ -4319,7 +4361,7 @@ function buildInputBoxRows(opts) {
|
|
|
4319
4361
|
let di = 0;
|
|
4320
4362
|
for (let ci = 0; ci < plainChars.length; ci++) {
|
|
4321
4363
|
const ch = plainChars[ci];
|
|
4322
|
-
const
|
|
4364
|
+
const c6 = colorAt(perimeterIndex("top", xi++, W, bodyH));
|
|
4323
4365
|
if (ci >= agentSpanStart && ci < agentSpanEnd) {
|
|
4324
4366
|
top += `\x1B[2m${themeGray}${ch}\x1B[0m`;
|
|
4325
4367
|
continue;
|
|
@@ -4328,9 +4370,9 @@ function buildInputBoxRows(opts) {
|
|
|
4328
4370
|
const levelN = Math.max(1, Math.min(5, level));
|
|
4329
4371
|
const filled = di < levelN;
|
|
4330
4372
|
di++;
|
|
4331
|
-
top += (filled ? levelColor ||
|
|
4373
|
+
top += (filled ? levelColor || c6 : "\x1B[38;5;240m") + (filled ? "\u25CF" : "\u25CB") + reset;
|
|
4332
4374
|
} else {
|
|
4333
|
-
top +=
|
|
4375
|
+
top += c6 + ch + reset;
|
|
4334
4376
|
}
|
|
4335
4377
|
}
|
|
4336
4378
|
}
|
|
@@ -4351,10 +4393,10 @@ function buildInputBoxRows(opts) {
|
|
|
4351
4393
|
let bottom = "";
|
|
4352
4394
|
for (let x = 0; x < W; x++) {
|
|
4353
4395
|
const idx = perimeterIndex("bottom", W - 1 - x, W, bodyH);
|
|
4354
|
-
const
|
|
4355
|
-
if (x === 0) bottom +=
|
|
4356
|
-
else if (x === W - 1) bottom +=
|
|
4357
|
-
else bottom +=
|
|
4396
|
+
const c6 = colorAt(idx);
|
|
4397
|
+
if (x === 0) bottom += c6 + "\u2570" + reset;
|
|
4398
|
+
else if (x === W - 1) bottom += c6 + "\u256F" + reset;
|
|
4399
|
+
else bottom += c6 + "\u2500" + reset;
|
|
4358
4400
|
}
|
|
4359
4401
|
return [pad + top, ...bodyRows, pad + bottom];
|
|
4360
4402
|
}
|
|
@@ -4464,7 +4506,7 @@ var Tui = class _Tui {
|
|
|
4464
4506
|
// The `[⚙ n bg]` prompt badge can be selected with ← from the start of the
|
|
4465
4507
|
// input (rendered inverted); Enter then opens the background-process panel.
|
|
4466
4508
|
bgBadgeSelected = false;
|
|
4467
|
-
|
|
4509
|
+
queuedMessages = [];
|
|
4468
4510
|
subagents = [];
|
|
4469
4511
|
// active subagents (one line each)
|
|
4470
4512
|
subagentColorByID = /* @__PURE__ */ new Map();
|
|
@@ -5089,7 +5131,7 @@ var Tui = class _Tui {
|
|
|
5089
5131
|
const q = this.inputBuffer.slice(1).trim().toLowerCase();
|
|
5090
5132
|
if (q === "") return this.commands;
|
|
5091
5133
|
return this.commands.filter(
|
|
5092
|
-
(
|
|
5134
|
+
(c6) => c6.name.startsWith(q) || c6.name.includes(q) || c6.label.toLowerCase().includes(q)
|
|
5093
5135
|
);
|
|
5094
5136
|
}
|
|
5095
5137
|
runCommand(cmd) {
|
|
@@ -5139,11 +5181,10 @@ var Tui = class _Tui {
|
|
|
5139
5181
|
}
|
|
5140
5182
|
/** The prompt line prefix (with ANSI colour) that precedes the typed text. */
|
|
5141
5183
|
promptPrefix() {
|
|
5142
|
-
const q = this.queuedCount > 0 ? `${C.yellow}[\u23F3 ${this.queuedCount} queued]${C.reset} ` : "";
|
|
5143
5184
|
const attachments2 = this.externalAttachmentNames.length > 0 ? `${C.gray}[\u{1F4CE} ${this.externalAttachmentNames.length}]${C.reset} ` : "";
|
|
5144
5185
|
const bgText = `[\u2699 ${this.bgCount} bg]`;
|
|
5145
5186
|
const bg = this.bgCount > 0 ? this.bgBadgeSelected ? `${C.cyan}\x1B[7m${bgText}\x1B[27m${C.reset} ` : `${C.cyan}${bgText}${C.reset} ` : "";
|
|
5146
|
-
return `${
|
|
5187
|
+
return `${attachments2}${bg}${this.levelColor()}\u276F${C.reset} `;
|
|
5147
5188
|
}
|
|
5148
5189
|
visibleWidth(s) {
|
|
5149
5190
|
let w = 0;
|
|
@@ -5331,6 +5372,13 @@ var Tui = class _Tui {
|
|
|
5331
5372
|
}
|
|
5332
5373
|
const goalLines = this.goalLines(workCols);
|
|
5333
5374
|
for (const line of goalLines) writeHudRow(workPad + line);
|
|
5375
|
+
for (const [i, text] of this.queuedMessages.entries()) {
|
|
5376
|
+
const hint = i === 0 ? ` ${C.dim}esc to steer \xB7 /queue to edit${C.reset}` : "";
|
|
5377
|
+
const budget = workCols - 3 - (i === 0 ? 30 : 0);
|
|
5378
|
+
let msg = text.replace(/\s+/g, " ").trim();
|
|
5379
|
+
if (budget > 1 && msg.length > budget) msg = msg.slice(0, budget - 1) + "\u2026";
|
|
5380
|
+
writeHudRow(workPad + `${C.yellow}\u23F3${C.reset} ${C.gray}${msg}${C.reset}${hint}`);
|
|
5381
|
+
}
|
|
5334
5382
|
const prefix = this.promptPrefix();
|
|
5335
5383
|
const pw = this.visibleWidth(prefix);
|
|
5336
5384
|
const borderAnimating = this.working;
|
|
@@ -5684,9 +5732,9 @@ var Tui = class _Tui {
|
|
|
5684
5732
|
if (n === 0) this.bgBadgeSelected = false;
|
|
5685
5733
|
this.renderBottom();
|
|
5686
5734
|
}
|
|
5687
|
-
|
|
5688
|
-
if (
|
|
5689
|
-
this.
|
|
5735
|
+
setQueuedMessages(texts) {
|
|
5736
|
+
if (texts.length === this.queuedMessages.length && texts.every((t, i) => t === this.queuedMessages[i])) return;
|
|
5737
|
+
this.queuedMessages = texts;
|
|
5690
5738
|
this.renderBottom();
|
|
5691
5739
|
}
|
|
5692
5740
|
setConnected(connected) {
|
|
@@ -6442,7 +6490,7 @@ function readVersion() {
|
|
|
6442
6490
|
if (typeof pkg.version === "string" && pkg.version) return pkg.version;
|
|
6443
6491
|
} catch {
|
|
6444
6492
|
}
|
|
6445
|
-
return "0.11.
|
|
6493
|
+
return "0.11.7" ;
|
|
6446
6494
|
}
|
|
6447
6495
|
function isLocalHost(host) {
|
|
6448
6496
|
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);
|
|
@@ -6801,7 +6849,7 @@ async function clearDaemon(api, identity) {
|
|
|
6801
6849
|
function parseCommands(value) {
|
|
6802
6850
|
if (!Array.isArray(value)) return [];
|
|
6803
6851
|
return value.filter(
|
|
6804
|
-
(
|
|
6852
|
+
(c6) => !!c6 && typeof c6 === "object" && typeof c6.id === "string" && typeof c6.kind === "string"
|
|
6805
6853
|
);
|
|
6806
6854
|
}
|
|
6807
6855
|
async function enqueueMachineCommand(api, machineId, kind, args) {
|
|
@@ -6824,7 +6872,7 @@ async function clearMachineCommands(api, machineId, appliedIds) {
|
|
|
6824
6872
|
if (appliedIds.length === 0) return;
|
|
6825
6873
|
const key = commandKey(machineId);
|
|
6826
6874
|
const remaining = parseCommands(await api.userKvGet(key)).filter(
|
|
6827
|
-
(
|
|
6875
|
+
(c6) => !appliedIds.includes(c6.id)
|
|
6828
6876
|
);
|
|
6829
6877
|
await api.userKvSet(key, remaining.length ? remaining : null);
|
|
6830
6878
|
}
|
|
@@ -7210,7 +7258,7 @@ var HubSocket = class {
|
|
|
7210
7258
|
}
|
|
7211
7259
|
openSocket() {
|
|
7212
7260
|
if (this.closed) return;
|
|
7213
|
-
let url = `${this.api.wsEndpoint}/api/users/me/
|
|
7261
|
+
let url = `${this.api.wsEndpoint}/api/users/me/stream?token=${encodeURIComponent(this.api.bearer)}&client_id=${encodeURIComponent(this.clientId)}&client_kind=daemon`;
|
|
7214
7262
|
if (this.clientName) url += `&client_name=${encodeURIComponent(this.clientName)}`;
|
|
7215
7263
|
let ws;
|
|
7216
7264
|
try {
|
|
@@ -7239,8 +7287,15 @@ var HubSocket = class {
|
|
|
7239
7287
|
} catch {
|
|
7240
7288
|
return;
|
|
7241
7289
|
}
|
|
7242
|
-
if (msg
|
|
7243
|
-
|
|
7290
|
+
if (!msg || typeof msg !== "object") return;
|
|
7291
|
+
const frame = msg;
|
|
7292
|
+
if (frame.event === "standardagents.wake") {
|
|
7293
|
+
const threadId = frame.data?.thread_id;
|
|
7294
|
+
if (typeof threadId === "string") this.hooks.onWake(threadId);
|
|
7295
|
+
return;
|
|
7296
|
+
}
|
|
7297
|
+
if (frame.type === "wake" && typeof frame.threadId === "string") {
|
|
7298
|
+
this.hooks.onWake(frame.threadId);
|
|
7244
7299
|
}
|
|
7245
7300
|
}
|
|
7246
7301
|
handleDrop(ws) {
|
|
@@ -8285,8 +8340,127 @@ async function runDaemonCommand(argv) {
|
|
|
8285
8340
|
}
|
|
8286
8341
|
}
|
|
8287
8342
|
|
|
8288
|
-
// src/
|
|
8343
|
+
// src/auth-cli.ts
|
|
8289
8344
|
var c4 = {
|
|
8345
|
+
reset: "\x1B[0m",
|
|
8346
|
+
dim: "\x1B[2m",
|
|
8347
|
+
bold: "\x1B[1m",
|
|
8348
|
+
green: "\x1B[32m",
|
|
8349
|
+
red: "\x1B[31m",
|
|
8350
|
+
teal: "\x1B[38;5;43m"
|
|
8351
|
+
};
|
|
8352
|
+
function parseAuthArgs(argv) {
|
|
8353
|
+
let endpoint = null;
|
|
8354
|
+
let token = null;
|
|
8355
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
8356
|
+
const arg = argv[i];
|
|
8357
|
+
if (arg === "--endpoint" || arg === "-e") {
|
|
8358
|
+
endpoint = argv[++i] ?? "";
|
|
8359
|
+
if (!endpoint) throw new Error("--endpoint requires a URL");
|
|
8360
|
+
} else if (arg.startsWith("--endpoint=")) {
|
|
8361
|
+
endpoint = arg.slice("--endpoint=".length);
|
|
8362
|
+
} else if (arg === "--token") {
|
|
8363
|
+
token = argv[++i] ?? "";
|
|
8364
|
+
if (!token) throw new Error("--token requires a value");
|
|
8365
|
+
} else if (arg.startsWith("--token=")) {
|
|
8366
|
+
token = arg.slice("--token=".length);
|
|
8367
|
+
} else {
|
|
8368
|
+
throw new Error(`Unknown option for login/logout: ${arg}`);
|
|
8369
|
+
}
|
|
8370
|
+
}
|
|
8371
|
+
const endpointExplicit = endpoint !== null;
|
|
8372
|
+
const resolved = normalizeEndpoint(endpoint ?? defaultEndpoint() ?? PRODUCTION_ENDPOINT);
|
|
8373
|
+
return { endpoint: resolved, endpointExplicit, token };
|
|
8374
|
+
}
|
|
8375
|
+
function hostLabel(endpoint) {
|
|
8376
|
+
return endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
8377
|
+
}
|
|
8378
|
+
async function signedInLabel(endpoint, token) {
|
|
8379
|
+
try {
|
|
8380
|
+
const res = await fetch(`${endpoint}/api/auth/me`, {
|
|
8381
|
+
headers: { Authorization: `Bearer ${token}` },
|
|
8382
|
+
signal: AbortSignal.timeout(1e4)
|
|
8383
|
+
});
|
|
8384
|
+
if (!res.ok) return null;
|
|
8385
|
+
const body = await res.json();
|
|
8386
|
+
return body.user?.email || body.user?.username || null;
|
|
8387
|
+
} catch {
|
|
8388
|
+
return null;
|
|
8389
|
+
}
|
|
8390
|
+
}
|
|
8391
|
+
async function runAuthCommand(command, argv) {
|
|
8392
|
+
let args;
|
|
8393
|
+
try {
|
|
8394
|
+
args = parseAuthArgs(argv);
|
|
8395
|
+
} catch (error) {
|
|
8396
|
+
process.stdout.write(`${c4.red}error:${c4.reset} ${error instanceof Error ? error.message : String(error)}
|
|
8397
|
+
`);
|
|
8398
|
+
process.stdout.write(`${c4.dim}usage: standardcode ${command} [--endpoint <url>]${command === "login" ? " [--token <key>]" : ""}${c4.reset}
|
|
8399
|
+
`);
|
|
8400
|
+
process.exitCode = 1;
|
|
8401
|
+
return;
|
|
8402
|
+
}
|
|
8403
|
+
const host = hostLabel(args.endpoint);
|
|
8404
|
+
relaxTlsForLocalEndpoint(args.endpoint);
|
|
8405
|
+
const existing = getCredential(args.endpoint);
|
|
8406
|
+
if (command === "logout") {
|
|
8407
|
+
if (!existing) {
|
|
8408
|
+
process.stdout.write(`You're not signed in to ${c4.teal}${host}${c4.reset} \u2014 nothing to do.
|
|
8409
|
+
`);
|
|
8410
|
+
return;
|
|
8411
|
+
}
|
|
8412
|
+
deleteCredential(args.endpoint);
|
|
8413
|
+
process.stdout.write(`${c4.green}\u2713${c4.reset} Signed out of ${c4.teal}${host}${c4.reset}.
|
|
8414
|
+
`);
|
|
8415
|
+
process.stdout.write(`${c4.dim}Run \`standardcode login\` to sign in again \u2014 with any account.${c4.reset}
|
|
8416
|
+
`);
|
|
8417
|
+
return;
|
|
8418
|
+
}
|
|
8419
|
+
if (existing) {
|
|
8420
|
+
const who2 = await signedInLabel(args.endpoint, existing.access_token);
|
|
8421
|
+
process.stdout.write(
|
|
8422
|
+
`${c4.dim}Currently signed in to ${host}${who2 ? ` as ${who2}` : ""} \u2014 replacing that sign-in.${c4.reset}
|
|
8423
|
+
`
|
|
8424
|
+
);
|
|
8425
|
+
}
|
|
8426
|
+
let token = args.token;
|
|
8427
|
+
if (!token) {
|
|
8428
|
+
process.stdout.write(`${c4.bold}Sign in to Standard Code${c4.reset}
|
|
8429
|
+
`);
|
|
8430
|
+
if (`https://${host}` !== PRODUCTION_ENDPOINT && `http://${host}` !== PRODUCTION_ENDPOINT) {
|
|
8431
|
+
process.stdout.write(`${c4.dim}Connecting to${c4.reset} ${c4.teal}${host}${c4.reset}
|
|
8432
|
+
`);
|
|
8433
|
+
}
|
|
8434
|
+
try {
|
|
8435
|
+
token = await deviceLogin(args.endpoint);
|
|
8436
|
+
} catch (error) {
|
|
8437
|
+
process.stdout.write(`${c4.red}\u2717${c4.reset} ${error instanceof Error ? error.message : String(error)}
|
|
8438
|
+
`);
|
|
8439
|
+
process.exitCode = 1;
|
|
8440
|
+
return;
|
|
8441
|
+
}
|
|
8442
|
+
}
|
|
8443
|
+
const api = new ApiClient(args.endpoint, token);
|
|
8444
|
+
const check = await api.verifyDetailed();
|
|
8445
|
+
if (!check.ok) {
|
|
8446
|
+
process.stdout.write(`${c4.red}\u2717${c4.reset} Sign-in didn't verify: ${check.reason}
|
|
8447
|
+
`);
|
|
8448
|
+
if (check.hint) process.stdout.write(` ${c4.dim}${check.hint}${c4.reset}
|
|
8449
|
+
`);
|
|
8450
|
+
process.exitCode = 1;
|
|
8451
|
+
return;
|
|
8452
|
+
}
|
|
8453
|
+
saveCredential(
|
|
8454
|
+
{ endpoint: args.endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
|
|
8455
|
+
{ updateDefault: !args.endpointExplicit }
|
|
8456
|
+
);
|
|
8457
|
+
const who = await signedInLabel(args.endpoint, token);
|
|
8458
|
+
process.stdout.write(`${c4.green}\u2713${c4.reset} Signed in to ${c4.teal}${host}${c4.reset}${who ? ` as ${c4.bold}${who}${c4.reset}` : ""}.
|
|
8459
|
+
`);
|
|
8460
|
+
}
|
|
8461
|
+
|
|
8462
|
+
// src/index.ts
|
|
8463
|
+
var c5 = {
|
|
8290
8464
|
reset: "\x1B[0m",
|
|
8291
8465
|
dim: "\x1B[2m",
|
|
8292
8466
|
bold: "\x1B[1m",
|
|
@@ -8315,10 +8489,16 @@ function printUsage() {
|
|
|
8315
8489
|
stdout.write(
|
|
8316
8490
|
[
|
|
8317
8491
|
"",
|
|
8318
|
-
`${
|
|
8492
|
+
`${c5.bold}Usage${c5.reset}`,
|
|
8319
8493
|
" standardcode [options] [dir]",
|
|
8494
|
+
" standardcode login [--endpoint <url>] [--token <key>]",
|
|
8495
|
+
" Sign in (or switch accounts \u2014 always re-runs the flow).",
|
|
8496
|
+
" standardcode logout [--endpoint <url>]",
|
|
8497
|
+
" Sign out of the saved endpoint.",
|
|
8498
|
+
" standardcode daemon <install|run|status|uninstall>",
|
|
8499
|
+
" Manage this machine's headless execution daemon.",
|
|
8320
8500
|
"",
|
|
8321
|
-
`${
|
|
8501
|
+
`${c5.bold}Options${c5.reset}`,
|
|
8322
8502
|
" -v, --version Print the Standard Code version and exit.",
|
|
8323
8503
|
" -e, --endpoint [url] Use a different Standard Agents instance for this run",
|
|
8324
8504
|
" (default: https://api.standardcode.ai).",
|
|
@@ -8387,15 +8567,15 @@ function parseArgs2(args) {
|
|
|
8387
8567
|
}
|
|
8388
8568
|
function printCommandBlock(tui, command, output4, ok, where) {
|
|
8389
8569
|
tui.print("");
|
|
8390
|
-
const note = where ? ` ${
|
|
8391
|
-
tui.print(`${
|
|
8570
|
+
const note = where ? ` ${c5.dim}(ran on ${where})${c5.reset}` : "";
|
|
8571
|
+
tui.print(`${c5.magenta}!${c5.reset} ${c5.bold}${command}${c5.reset}${note}`);
|
|
8392
8572
|
const body = (output4 ?? "").replace(/\s+$/, "");
|
|
8393
8573
|
if (body) {
|
|
8394
8574
|
for (const line of body.split("\n")) {
|
|
8395
|
-
tui.print(` ${ok ?
|
|
8575
|
+
tui.print(` ${ok ? c5.dim : c5.red}${line}${c5.reset}`);
|
|
8396
8576
|
}
|
|
8397
8577
|
} else {
|
|
8398
|
-
tui.print(` ${
|
|
8578
|
+
tui.print(` ${c5.dim}(no output)${c5.reset}`);
|
|
8399
8579
|
}
|
|
8400
8580
|
tui.print("");
|
|
8401
8581
|
}
|
|
@@ -8406,7 +8586,7 @@ function printAssistant(tui, text) {
|
|
|
8406
8586
|
let dotted = false;
|
|
8407
8587
|
for (const line of renderStreamingMarkdown(text, cols2)) {
|
|
8408
8588
|
if (!dotted && line.trim()) {
|
|
8409
|
-
tui.print(`${
|
|
8589
|
+
tui.print(`${c5.gray}\u2022${c5.reset} ${line}`);
|
|
8410
8590
|
dotted = true;
|
|
8411
8591
|
} else {
|
|
8412
8592
|
tui.print(` ${line}`);
|
|
@@ -8421,7 +8601,7 @@ function startLoader(label) {
|
|
|
8421
8601
|
const draw = () => {
|
|
8422
8602
|
const now = Date.now();
|
|
8423
8603
|
const f = frames[Math.floor(now / 70) % frames.length];
|
|
8424
|
-
stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${
|
|
8604
|
+
stdout.write(`\r\x1B[K${pad}${brandCycleColor(now)}${f}${c5.reset} ${c5.dim}${label}\u2026${c5.reset}`);
|
|
8425
8605
|
};
|
|
8426
8606
|
draw();
|
|
8427
8607
|
const timer = setInterval(draw, 70);
|
|
@@ -8437,12 +8617,12 @@ function farewell(stoppedProcs = 0) {
|
|
|
8437
8617
|
if (stoppedProcs > 0) {
|
|
8438
8618
|
stdout.write(
|
|
8439
8619
|
`
|
|
8440
|
-
${
|
|
8620
|
+
${c5.cyan}\u2699${c5.reset} Stopped ${stoppedProcs} background process${stoppedProcs === 1 ? "" : "es"}.
|
|
8441
8621
|
`
|
|
8442
8622
|
);
|
|
8443
8623
|
}
|
|
8444
8624
|
stdout.write(`
|
|
8445
|
-
${
|
|
8625
|
+
${c5.teal}\u25C7${c5.reset} ${c5.dim}Standard Code \u2014 see you soon.${c5.reset}
|
|
8446
8626
|
`);
|
|
8447
8627
|
}
|
|
8448
8628
|
function printWelcome(endpoint, projectDir) {
|
|
@@ -8456,10 +8636,10 @@ function printWelcome(endpoint, projectDir) {
|
|
|
8456
8636
|
const metaWidth = Math.max(1, terminalColumns - pad.length - markWidth - 3 - 1);
|
|
8457
8637
|
const displayDir = truncateMiddle(dir2, metaWidth);
|
|
8458
8638
|
const meta = [
|
|
8459
|
-
`${
|
|
8460
|
-
`${
|
|
8461
|
-
...endpoint === PRODUCTION_ENDPOINT ? [] : [`${
|
|
8462
|
-
`${
|
|
8639
|
+
`${c5.bold}${gradientText("Standard Code")}${c5.reset}${version ? ` ${c5.dim}v${version}${c5.reset}` : ""}`,
|
|
8640
|
+
`${c5.dim}terminal coding agent${c5.reset}`,
|
|
8641
|
+
...endpoint === PRODUCTION_ENDPOINT ? [] : [`${c5.teal}${host}${c5.reset}`],
|
|
8642
|
+
`${c5.dim}${displayDir}${c5.reset}`
|
|
8463
8643
|
];
|
|
8464
8644
|
const metaTop = Math.floor((LOGO_MARK.length - meta.length) / 2);
|
|
8465
8645
|
stdout.write("\n");
|
|
@@ -8473,11 +8653,11 @@ function printWelcome(endpoint, projectDir) {
|
|
|
8473
8653
|
}
|
|
8474
8654
|
function colorActivity(line) {
|
|
8475
8655
|
const m = line.match(/^(\s*)([✓✗⛔])\s?([\s\S]*)$/);
|
|
8476
|
-
if (!m) return `${
|
|
8656
|
+
if (!m) return `${c5.dim}${line}${c5.reset}`;
|
|
8477
8657
|
const [, indent, glyph, rest] = m;
|
|
8478
8658
|
if (glyph === "\u2713") {
|
|
8479
|
-
const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${
|
|
8480
|
-
return `${indent}${
|
|
8659
|
+
const body = rest.replace(/\s(\([^()]*\))\s*$/, ` ${c5.dim}$1${c5.reset}`);
|
|
8660
|
+
return `${indent}${c5.green}\u2713${c5.reset} ${body}`;
|
|
8481
8661
|
}
|
|
8482
8662
|
if (glyph === "\u2717") {
|
|
8483
8663
|
const ERR_MAX_LINES = 7;
|
|
@@ -8485,15 +8665,15 @@ function colorActivity(line) {
|
|
|
8485
8665
|
const shown = lines.slice(0, ERR_MAX_LINES);
|
|
8486
8666
|
const hidden = lines.length - shown.length;
|
|
8487
8667
|
const body = shown.map(
|
|
8488
|
-
(l, i) => i === 0 ? `${indent}${
|
|
8668
|
+
(l, i) => i === 0 ? `${indent}${c5.red}\u2717 ${l}${c5.reset}` : `${indent}${c5.red}${c5.dim}${l}${c5.reset}`
|
|
8489
8669
|
).join("\n");
|
|
8490
8670
|
if (hidden > 0) {
|
|
8491
8671
|
return `${body}
|
|
8492
|
-
${indent}${
|
|
8672
|
+
${indent}${c5.dim}\u2026 +${hidden} more line${hidden === 1 ? "" : "s"}${c5.reset}`;
|
|
8493
8673
|
}
|
|
8494
8674
|
return body;
|
|
8495
8675
|
}
|
|
8496
|
-
return `${indent}${
|
|
8676
|
+
return `${indent}${c5.yellow}\u26D4 ${rest}${c5.reset}`;
|
|
8497
8677
|
}
|
|
8498
8678
|
async function main() {
|
|
8499
8679
|
if (process.argv.slice(2).some((arg) => arg === "-v" || arg === "--version")) {
|
|
@@ -8505,11 +8685,15 @@ async function main() {
|
|
|
8505
8685
|
await runDaemonCommand(process.argv.slice(3));
|
|
8506
8686
|
return;
|
|
8507
8687
|
}
|
|
8688
|
+
if (process.argv[2] === "login" || process.argv[2] === "logout") {
|
|
8689
|
+
await runAuthCommand(process.argv[2], process.argv.slice(3));
|
|
8690
|
+
return;
|
|
8691
|
+
}
|
|
8508
8692
|
let cliArgs;
|
|
8509
8693
|
try {
|
|
8510
8694
|
cliArgs = parseArgs2(process.argv.slice(2));
|
|
8511
8695
|
} catch (error) {
|
|
8512
|
-
stdout.write(`${
|
|
8696
|
+
stdout.write(`${c5.red}error:${c5.reset} ${error instanceof Error ? error.message : String(error)}
|
|
8513
8697
|
`);
|
|
8514
8698
|
printUsage();
|
|
8515
8699
|
process.exit(1);
|
|
@@ -8536,7 +8720,7 @@ async function main() {
|
|
|
8536
8720
|
}
|
|
8537
8721
|
preflightArmed = true;
|
|
8538
8722
|
stdout.write(`
|
|
8539
|
-
${
|
|
8723
|
+
${c5.dim}Press Control-C again to exit${c5.reset}
|
|
8540
8724
|
`);
|
|
8541
8725
|
preflightTimer = setTimeout(() => {
|
|
8542
8726
|
preflightArmed = false;
|
|
@@ -8558,10 +8742,10 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8558
8742
|
const askEndpoint = async () => {
|
|
8559
8743
|
for (; ; ) {
|
|
8560
8744
|
const answer = (await ask(
|
|
8561
|
-
`${
|
|
8745
|
+
`${c5.cyan}Standard Agents instance URL${c5.reset} (e.g. http://localhost:5178): `
|
|
8562
8746
|
)).trim();
|
|
8563
8747
|
if (answer) return answer;
|
|
8564
|
-
stdout.write(`${
|
|
8748
|
+
stdout.write(`${c5.dim}An endpoint URL is required.${c5.reset}
|
|
8565
8749
|
`);
|
|
8566
8750
|
}
|
|
8567
8751
|
};
|
|
@@ -8576,7 +8760,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8576
8760
|
const tlsRelaxed = relaxTlsForLocalEndpoint(endpoint);
|
|
8577
8761
|
printWelcome(endpoint, projectDir);
|
|
8578
8762
|
if (tlsRelaxed) {
|
|
8579
|
-
stdout.write(`${
|
|
8763
|
+
stdout.write(`${c5.dim} TLS verification relaxed for local endpoint.${c5.reset}
|
|
8580
8764
|
|
|
8581
8765
|
`);
|
|
8582
8766
|
}
|
|
@@ -8588,7 +8772,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8588
8772
|
loading.stop();
|
|
8589
8773
|
const applied = consumeAppliedUpdate(version);
|
|
8590
8774
|
if (applied) {
|
|
8591
|
-
stdout.write(` ${
|
|
8775
|
+
stdout.write(` ${c5.green}\u2713${c5.reset} ${c5.dim}Standard Code updated to v${version}.${c5.reset}
|
|
8592
8776
|
|
|
8593
8777
|
`);
|
|
8594
8778
|
}
|
|
@@ -8597,21 +8781,21 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8597
8781
|
const decision = decideAutoUpdate(updateAvailable, { state: readAutoUpdateState(), pm });
|
|
8598
8782
|
if (decision === "start" && pm && startBackgroundUpdate(updateAvailable.latest, pm)) {
|
|
8599
8783
|
stdout.write(
|
|
8600
|
-
` ${
|
|
8784
|
+
` ${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}
|
|
8601
8785
|
|
|
8602
8786
|
`
|
|
8603
8787
|
);
|
|
8604
8788
|
} else if (decision === "in_flight") {
|
|
8605
8789
|
stdout.write(
|
|
8606
|
-
` ${
|
|
8790
|
+
` ${c5.teal}\u27F3${c5.reset} ${c5.dim}Standard Code v${updateAvailable.latest} is still installing in the background.${c5.reset}
|
|
8607
8791
|
|
|
8608
8792
|
`
|
|
8609
8793
|
);
|
|
8610
8794
|
} else {
|
|
8611
8795
|
const display = updateCommand(pm ?? "npm").display;
|
|
8612
8796
|
stdout.write(
|
|
8613
|
-
` ${
|
|
8614
|
-
${
|
|
8797
|
+
` ${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}
|
|
8798
|
+
${c5.dim}Run ${c5.reset}${c5.bold}${display}${c5.reset}${c5.dim} to update${c5.reset}
|
|
8615
8799
|
|
|
8616
8800
|
`
|
|
8617
8801
|
);
|
|
@@ -8629,39 +8813,39 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8629
8813
|
if (!api || !storedCheck?.ok) {
|
|
8630
8814
|
const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
8631
8815
|
if (storedCheck && !storedCheck.ok) {
|
|
8632
|
-
stdout.write(`${
|
|
8816
|
+
stdout.write(`${c5.red}\u2717${c5.reset} ${c5.dim}Saved sign-in for this endpoint failed:${c5.reset} ${storedCheck.reason}
|
|
8633
8817
|
`);
|
|
8634
|
-
if (storedCheck.hint) stdout.write(` ${
|
|
8818
|
+
if (storedCheck.hint) stdout.write(` ${c5.dim}${storedCheck.hint}${c5.reset}
|
|
8635
8819
|
`);
|
|
8636
8820
|
stdout.write("\n");
|
|
8637
8821
|
}
|
|
8638
8822
|
const explainFailure = (result, prefix) => {
|
|
8639
|
-
stdout.write(`${
|
|
8823
|
+
stdout.write(`${c5.red}\u2717${c5.reset} ${prefix}${result.reason}
|
|
8640
8824
|
`);
|
|
8641
|
-
if (result.hint) stdout.write(` ${
|
|
8825
|
+
if (result.hint) stdout.write(` ${c5.dim}${result.hint}${c5.reset}
|
|
8642
8826
|
`);
|
|
8643
8827
|
};
|
|
8644
|
-
stdout.write(`${
|
|
8828
|
+
stdout.write(`${c5.bold}Sign in to ${gradientText("Standard Code")}${c5.reset}
|
|
8645
8829
|
`);
|
|
8646
8830
|
if (`https://${host}` !== PRODUCTION_ENDPOINT) {
|
|
8647
|
-
stdout.write(`${
|
|
8831
|
+
stdout.write(`${c5.dim}Connecting to${c5.reset} ${c5.teal}${host}${c5.reset}
|
|
8648
8832
|
`);
|
|
8649
8833
|
}
|
|
8650
8834
|
stdout.write(
|
|
8651
|
-
`${
|
|
8835
|
+
`${c5.dim}You'll only need to do this once on this machine.${c5.reset}
|
|
8652
8836
|
|
|
8653
8837
|
`
|
|
8654
8838
|
);
|
|
8655
8839
|
stdout.write(
|
|
8656
|
-
`${
|
|
8840
|
+
`${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}
|
|
8657
8841
|
|
|
8658
8842
|
`
|
|
8659
8843
|
);
|
|
8660
8844
|
for (; ; ) {
|
|
8661
|
-
const token = (await ask(`${
|
|
8845
|
+
const token = (await ask(`${c5.teal}\u276F${c5.reset} `)).trim();
|
|
8662
8846
|
if (!token) {
|
|
8663
8847
|
const got = await deviceLogin(endpoint).catch((e) => {
|
|
8664
|
-
stdout.write(`${
|
|
8848
|
+
stdout.write(`${c5.red}\u2717${c5.reset} ${c5.dim}${e instanceof Error ? e.message : String(e)}${c5.reset}
|
|
8665
8849
|
`);
|
|
8666
8850
|
return null;
|
|
8667
8851
|
});
|
|
@@ -8675,7 +8859,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8675
8859
|
{ endpoint, access_token: got, token_type: "Bearer", saved_at: Date.now() },
|
|
8676
8860
|
{ updateDefault: !endpointOverride }
|
|
8677
8861
|
);
|
|
8678
|
-
stdout.write(`${
|
|
8862
|
+
stdout.write(`${c5.green}\u2713${c5.reset} Connected to ${c5.teal}${host}${c5.reset}
|
|
8679
8863
|
`);
|
|
8680
8864
|
break;
|
|
8681
8865
|
}
|
|
@@ -8691,7 +8875,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8691
8875
|
{ endpoint, access_token: token, token_type: "Bearer", saved_at: Date.now() },
|
|
8692
8876
|
{ updateDefault: !endpointOverride }
|
|
8693
8877
|
);
|
|
8694
|
-
stdout.write(`${
|
|
8878
|
+
stdout.write(`${c5.green}\u2713${c5.reset} Connected to ${c5.teal}${host}${c5.reset}
|
|
8695
8879
|
`);
|
|
8696
8880
|
break;
|
|
8697
8881
|
}
|
|
@@ -8715,7 +8899,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8715
8899
|
} else if (["unlimited", "standard", "unlimited_one", AGENT_ID].includes(wanted)) {
|
|
8716
8900
|
agentOverride = AGENT_ID;
|
|
8717
8901
|
} else {
|
|
8718
|
-
stdout.write(`${
|
|
8902
|
+
stdout.write(`${c5.red}error:${c5.reset} Unknown agent "${cliArgs.agent}". Use unlimited or sama.
|
|
8719
8903
|
`);
|
|
8720
8904
|
process.exit(1);
|
|
8721
8905
|
}
|
|
@@ -8753,7 +8937,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8753
8937
|
flow: for (; ; ) {
|
|
8754
8938
|
if (step === "machine") {
|
|
8755
8939
|
const picked = await tui.select(
|
|
8756
|
-
`${
|
|
8940
|
+
`${c5.bold}Where should this session run?${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
8757
8941
|
[
|
|
8758
8942
|
{
|
|
8759
8943
|
label: `This machine \u2014 ${self ? machineDisplayName(self) : machine}`,
|
|
@@ -8837,7 +9021,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8837
9021
|
items.push({ label: "\uFF0B Start a new session", value: null });
|
|
8838
9022
|
const whereLabel = session.mode === "remote" && session.runner ? `${session.runner.name}:${shortenPath(session.remotePath ?? "")}` : shortDir;
|
|
8839
9023
|
const picked = await tui.select(
|
|
8840
|
-
`${
|
|
9024
|
+
`${c5.bold}Resume a session${c5.reset} ${c5.gray}${whereLabel}${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
8841
9025
|
items
|
|
8842
9026
|
);
|
|
8843
9027
|
if (picked === void 0) {
|
|
@@ -8863,7 +9047,7 @@ ${c4.dim}Press Control-C again to exit${c4.reset}
|
|
|
8863
9047
|
} else {
|
|
8864
9048
|
if (!agentOverride) {
|
|
8865
9049
|
const picked = await tui.select(
|
|
8866
|
-
`${
|
|
9050
|
+
`${c5.bold}Which agent?${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
8867
9051
|
AGENT_CHOICES.map((choice) => ({
|
|
8868
9052
|
label: choice.title,
|
|
8869
9053
|
hint: choice.description,
|
|
@@ -8913,7 +9097,7 @@ async function ensureSamaAuth(tui, api) {
|
|
|
8913
9097
|
checking.stop();
|
|
8914
9098
|
if (already) return true;
|
|
8915
9099
|
const picked = await tui.select(
|
|
8916
|
-
`${
|
|
9100
|
+
`${c5.bold}Authenticate with ChatGPT${c5.reset} ${c5.dim}Sama One runs on OpenAI using your own ChatGPT Pro account${c5.reset}`,
|
|
8917
9101
|
[
|
|
8918
9102
|
{
|
|
8919
9103
|
label: "Continue with ChatGPT",
|
|
@@ -8926,7 +9110,7 @@ async function ensureSamaAuth(tui, api) {
|
|
|
8926
9110
|
if (picked !== "continue") return false;
|
|
8927
9111
|
openUrl("https://standardcode.ai/app?connect=sama");
|
|
8928
9112
|
tui.print(
|
|
8929
|
-
`${
|
|
9113
|
+
`${c5.dim}Finish connecting ChatGPT in the browser \u2014 waiting here for the authorization to land on your account\u2026${c5.reset}`
|
|
8930
9114
|
);
|
|
8931
9115
|
const waiting = startLoader("Waiting for your ChatGPT authorization");
|
|
8932
9116
|
const deadline = Date.now() + 5 * 60 * 1e3;
|
|
@@ -8935,20 +9119,20 @@ async function ensureSamaAuth(tui, api) {
|
|
|
8935
9119
|
if (await api.openSamaStatus().catch(() => false)) {
|
|
8936
9120
|
waiting.stop();
|
|
8937
9121
|
tui.print(
|
|
8938
|
-
`${
|
|
9122
|
+
`${c5.green}\u2713${c5.reset} ChatGPT connected \u2014 Sama One is now unlocked on your account (terminal, web, and macOS app).`
|
|
8939
9123
|
);
|
|
8940
9124
|
return true;
|
|
8941
9125
|
}
|
|
8942
9126
|
}
|
|
8943
9127
|
waiting.stop();
|
|
8944
9128
|
tui.print(
|
|
8945
|
-
`${
|
|
9129
|
+
`${c5.yellow}Still not connected.${c5.reset} Finish the flow at ${c5.teal}standardcode.ai/app${c5.reset} and pick Sama One again.`
|
|
8946
9130
|
);
|
|
8947
9131
|
return false;
|
|
8948
9132
|
}
|
|
8949
9133
|
async function runAgentSwitchMenu(tui, api, threadId) {
|
|
8950
9134
|
const picked = await tui.select(
|
|
8951
|
-
`${
|
|
9135
|
+
`${c5.bold}Switch agent${c5.reset} ${c5.dim}takes effect on the next message${c5.reset}`,
|
|
8952
9136
|
AGENT_CHOICES.map((choice) => ({
|
|
8953
9137
|
label: choice.title,
|
|
8954
9138
|
hint: choice.description,
|
|
@@ -8965,10 +9149,10 @@ async function runAgentSwitchMenu(tui, api, threadId) {
|
|
|
8965
9149
|
try {
|
|
8966
9150
|
await api.setThreadAgent(threadId, picked);
|
|
8967
9151
|
tui.setAgentLabel(title);
|
|
8968
|
-
tui.print(`${
|
|
9152
|
+
tui.print(`${c5.green}\u2713${c5.reset} Session handed to ${c5.bold}${title}${c5.reset} \u2014 applies from your next message.`);
|
|
8969
9153
|
} catch (e) {
|
|
8970
9154
|
tui.print(
|
|
8971
|
-
`${
|
|
9155
|
+
`${c5.red}\u2717 couldn't switch agent:${c5.reset} ${c5.gray}${e instanceof Error ? e.message : String(e)}${c5.reset}`
|
|
8972
9156
|
);
|
|
8973
9157
|
}
|
|
8974
9158
|
}
|
|
@@ -8993,7 +9177,7 @@ async function pickLocalProject(tui, api, self, cwd, machineName) {
|
|
|
8993
9177
|
{ label: "\uFF0B New project", hint: "browse or create a directory", value: NEW }
|
|
8994
9178
|
];
|
|
8995
9179
|
const picked = await tui.select(
|
|
8996
|
-
`${
|
|
9180
|
+
`${c5.bold}Project on ${label}${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
8997
9181
|
items,
|
|
8998
9182
|
{ spaced: true }
|
|
8999
9183
|
);
|
|
@@ -9017,7 +9201,7 @@ async function pickRemoteProject(tui, api, runner) {
|
|
|
9017
9201
|
);
|
|
9018
9202
|
items.push({ label: "\uFF0B New project", hint: "browse or create a directory", value: ENTER_PATH });
|
|
9019
9203
|
const picked = await tui.select(
|
|
9020
|
-
`${
|
|
9204
|
+
`${c5.bold}Project on ${runner.name}${c5.reset} ${c5.dim}\u2191\u2193 \xB7 enter \xB7 esc${c5.reset}`,
|
|
9021
9205
|
items,
|
|
9022
9206
|
{ spaced: true }
|
|
9023
9207
|
);
|
|
@@ -9050,7 +9234,7 @@ async function summarizeThreads(api, threads) {
|
|
|
9050
9234
|
}
|
|
9051
9235
|
function subagentLabel(s, titles) {
|
|
9052
9236
|
const agentName = (s.agent_name || "").trim();
|
|
9053
|
-
const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (
|
|
9237
|
+
const title = (s.title || "").trim() || titles.get(agentName) || (agentName ? agentName.replace(/[_-]+/g, " ").replace(/\b\w/g, (c6) => c6.toUpperCase()) : "Subagent");
|
|
9054
9238
|
const tagged = (s.threadName || "").trim();
|
|
9055
9239
|
return tagged ? `${title} \xB7 ${tagged}` : title;
|
|
9056
9240
|
}
|
|
@@ -9073,8 +9257,8 @@ async function printHistory(api, threadId, tui) {
|
|
|
9073
9257
|
).sort((a, b) => (a.created_at ?? 0) - (b.created_at ?? 0));
|
|
9074
9258
|
if (!convo.length) return;
|
|
9075
9259
|
const shown = convo.slice(-24);
|
|
9076
|
-
tui.print(`${
|
|
9077
|
-
if (shown.length < convo.length) tui.print(`${
|
|
9260
|
+
tui.print(`${c5.dim}\u2500\u2500 resuming session \xB7 ${convo.length} message${convo.length === 1 ? "" : "s"} \u2500\u2500${c5.reset}`);
|
|
9261
|
+
if (shown.length < convo.length) tui.print(`${c5.dim} \u2026 earlier messages omitted${c5.reset}`);
|
|
9078
9262
|
for (const m of shown) {
|
|
9079
9263
|
if (m.metadata?.user_command) {
|
|
9080
9264
|
printCommandBlock(
|
|
@@ -9135,6 +9319,7 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
|
|
|
9135
9319
|
let editingPendingId = null;
|
|
9136
9320
|
let reconcileSharedMessaging = async () => {
|
|
9137
9321
|
};
|
|
9322
|
+
let applySharedMessagingEvent = () => false;
|
|
9138
9323
|
let refreshSessionProjection = async () => {
|
|
9139
9324
|
};
|
|
9140
9325
|
const shownIds = /* @__PURE__ */ new Set();
|
|
@@ -9192,21 +9377,21 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
|
|
|
9192
9377
|
if (isOwner) {
|
|
9193
9378
|
bridge?.setClaim("takeover");
|
|
9194
9379
|
exec?.writeSessionInfo();
|
|
9195
|
-
if (wasKnown && moved) tui.print(`${
|
|
9380
|
+
if (wasKnown && moved) tui.print(`${c5.dim}Tool execution moved to this terminal.${c5.reset}`);
|
|
9196
9381
|
} else if (wasKnown && moved) {
|
|
9197
|
-
tui.print(`${
|
|
9382
|
+
tui.print(`${c5.dim}Tool execution moved to ${describeExecOwner(owner2)}.${c5.reset}`);
|
|
9198
9383
|
}
|
|
9199
9384
|
},
|
|
9200
9385
|
onClaimRefused: (reason) => {
|
|
9201
9386
|
if (reason === "in_flight") {
|
|
9202
9387
|
tui.print(
|
|
9203
|
-
`${
|
|
9388
|
+
`${c5.dim}The session's current client is mid-operation \u2014 execution stays there until it finishes.${c5.reset}`
|
|
9204
9389
|
);
|
|
9205
9390
|
}
|
|
9206
9391
|
},
|
|
9207
9392
|
onSuperseded: () => {
|
|
9208
9393
|
tui.print(
|
|
9209
|
-
`${
|
|
9394
|
+
`${c5.yellow}\u26A0 Another Standard Code process on this machine took over this session \u2014 this terminal is watching.${c5.reset}`
|
|
9210
9395
|
);
|
|
9211
9396
|
},
|
|
9212
9397
|
onStatus: (id, summary) => {
|
|
@@ -9268,7 +9453,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9268
9453
|
} else if (eventType === "goal_updated" && data) {
|
|
9269
9454
|
tui.setGoal(data);
|
|
9270
9455
|
} else if (eventType === SHARED_MESSAGING_EVENT) {
|
|
9271
|
-
void reconcileSharedMessaging(true);
|
|
9456
|
+
if (!applySharedMessagingEvent(data)) void reconcileSharedMessaging(true);
|
|
9272
9457
|
}
|
|
9273
9458
|
},
|
|
9274
9459
|
// A failed turn whose message is the lease service's at-limit denial → offer
|
|
@@ -9364,7 +9549,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9364
9549
|
const logout = async () => {
|
|
9365
9550
|
deleteCredential(api.origin);
|
|
9366
9551
|
const instanceHost = api.origin.replace(/^https?:\/\//, "");
|
|
9367
|
-
tui.print(`${
|
|
9552
|
+
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}`);
|
|
9368
9553
|
await quit();
|
|
9369
9554
|
};
|
|
9370
9555
|
const bgMgr = {
|
|
@@ -9372,7 +9557,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9372
9557
|
stop: async (id) => {
|
|
9373
9558
|
if (!host) {
|
|
9374
9559
|
tui.print(
|
|
9375
|
-
`${
|
|
9560
|
+
`${c5.gray}That process runs on ${runnerName} \u2014 ask the agent to stop it (it manages processes there).${c5.reset}`
|
|
9376
9561
|
);
|
|
9377
9562
|
return;
|
|
9378
9563
|
}
|
|
@@ -9435,7 +9620,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9435
9620
|
sharedMessaging = firstSnapshot ? snapshot : mergeSharedMessagingSnapshot(sharedMessaging, snapshot);
|
|
9436
9621
|
sharedMessagingReady = true;
|
|
9437
9622
|
sharedMessagingUnavailableShown = false;
|
|
9438
|
-
tui.
|
|
9623
|
+
tui.setQueuedMessages(sharedMessaging.pending.items.map((item) => item.content));
|
|
9439
9624
|
if (mirrorDraft && (firstSnapshot || sharedMessaging.draft.revision > previousDraftRevision) && (firstSnapshot || sharedMessaging.draft.originClientId !== messagingOrigin.originClientId)) {
|
|
9440
9625
|
mirroredDraftRefs = sharedMessaging.draft.attachments.filter(isSharedAttachmentRef);
|
|
9441
9626
|
tui.setExternalAttachmentNames(mirroredDraftRefs.map((attachment) => attachment.name));
|
|
@@ -9453,17 +9638,25 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9453
9638
|
} catch (error) {
|
|
9454
9639
|
if (!sharedMessagingReady && !sharedMessagingUnavailableShown) {
|
|
9455
9640
|
sharedMessagingUnavailableShown = true;
|
|
9456
|
-
tui.print(`${
|
|
9641
|
+
tui.print(`${c5.dim}shared messaging unavailable: ${error instanceof Error ? error.message : String(error)}${c5.reset}`);
|
|
9457
9642
|
}
|
|
9458
9643
|
}
|
|
9459
9644
|
};
|
|
9645
|
+
applySharedMessagingEvent = (data) => {
|
|
9646
|
+
if (!sharedMessagingReady) return false;
|
|
9647
|
+
const event = parseMessagingChangedEvent(data);
|
|
9648
|
+
if (!event || !event.draft || event.draftOmitted) return false;
|
|
9649
|
+
if (event.pendingRevision > sharedMessaging.pending.revision) return false;
|
|
9650
|
+
applySharedMessaging({ version: 1, pending: sharedMessaging.pending, draft: event.draft }, true);
|
|
9651
|
+
return true;
|
|
9652
|
+
};
|
|
9460
9653
|
const onTerminalResume = () => void reconcileSharedMessaging(true);
|
|
9461
9654
|
process.on("SIGCONT", onTerminalResume);
|
|
9462
9655
|
const applySharedMutation = (promise) => promise.then((snapshot) => {
|
|
9463
9656
|
applySharedMessaging(snapshot, false);
|
|
9464
9657
|
return true;
|
|
9465
9658
|
}).catch((error) => {
|
|
9466
|
-
tui.print(`${
|
|
9659
|
+
tui.print(`${c5.dim}shared messaging failed: ${error instanceof Error ? error.message : String(error)}${c5.reset}`);
|
|
9467
9660
|
return false;
|
|
9468
9661
|
});
|
|
9469
9662
|
const appendSharedPending = (text, images, refs = []) => applySharedMutation(api.appendPendingInput(threadId, {
|
|
@@ -9512,7 +9705,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9512
9705
|
busy = false;
|
|
9513
9706
|
optimisticBusyUntil = 0;
|
|
9514
9707
|
tui.setWorking(false);
|
|
9515
|
-
tui.print(`${
|
|
9708
|
+
tui.print(`${c5.dim}failed to send: ${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
9516
9709
|
return false;
|
|
9517
9710
|
}
|
|
9518
9711
|
return true;
|
|
@@ -9521,11 +9714,11 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9521
9714
|
let bangRunning = false;
|
|
9522
9715
|
const runBangCommand = async (command) => {
|
|
9523
9716
|
if (bangRunning) {
|
|
9524
|
-
tui.print(`${
|
|
9717
|
+
tui.print(`${c5.dim}a command is already running \u2014 one at a time.${c5.reset}`);
|
|
9525
9718
|
return;
|
|
9526
9719
|
}
|
|
9527
9720
|
bangRunning = true;
|
|
9528
|
-
tui.print(`${
|
|
9721
|
+
tui.print(`${c5.magenta}!${c5.reset} ${c5.dim}running on ${whereLabel}\u2026${c5.reset}`);
|
|
9529
9722
|
try {
|
|
9530
9723
|
const res = await api.runCommand(threadId, command);
|
|
9531
9724
|
if (res.messageId) shownIds.add(res.messageId);
|
|
@@ -9539,7 +9732,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9539
9732
|
const openPendingMenu = async () => {
|
|
9540
9733
|
const items = sharedMessaging.pending.items;
|
|
9541
9734
|
if (!items.length) {
|
|
9542
|
-
tui.print(`${
|
|
9735
|
+
tui.print(`${c5.dim}No pending messages.${c5.reset}`);
|
|
9543
9736
|
return;
|
|
9544
9737
|
}
|
|
9545
9738
|
const picked = await tui.select("Pending messages", items.map((item, index) => ({
|
|
@@ -9569,16 +9762,16 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9569
9762
|
try {
|
|
9570
9763
|
await api.compact(threadId);
|
|
9571
9764
|
} catch (err) {
|
|
9572
|
-
tui.print(`${
|
|
9765
|
+
tui.print(`${c5.red}\u2717${c5.reset} couldn't start compaction: ${err.message}`);
|
|
9573
9766
|
}
|
|
9574
9767
|
};
|
|
9575
9768
|
const runAccountCommand = async () => {
|
|
9576
|
-
tui.print(`${
|
|
9769
|
+
tui.print(`${c5.gray}Opening your account\u2026${c5.reset}`);
|
|
9577
9770
|
const link = await api.accountLink(threadId).catch(() => null);
|
|
9578
9771
|
const target = link?.url ?? "https://standardcode.ai/account";
|
|
9579
9772
|
openUrl(target);
|
|
9580
9773
|
tui.print(
|
|
9581
|
-
link?.preauthed ? `${
|
|
9774
|
+
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}`
|
|
9582
9775
|
);
|
|
9583
9776
|
};
|
|
9584
9777
|
const ordinal = (n) => {
|
|
@@ -9589,24 +9782,24 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9589
9782
|
const renderUpgradePanel = (q) => {
|
|
9590
9783
|
const dots = [];
|
|
9591
9784
|
for (let i = 0; i < q.max; i++) {
|
|
9592
|
-
if (i < q.current) dots.push(`${
|
|
9593
|
-
else if (i === q.current) dots.push(`${
|
|
9594
|
-
else dots.push(`${
|
|
9785
|
+
if (i < q.current) dots.push(`${c5.teal}\u25CF${c5.reset}`);
|
|
9786
|
+
else if (i === q.current) dots.push(`${c5.bold}${gradientText("\uFF0B")}${c5.reset}`);
|
|
9787
|
+
else dots.push(`${c5.dim}\xB7${c5.reset}`);
|
|
9595
9788
|
}
|
|
9596
9789
|
const cost = fmtCost(q);
|
|
9597
9790
|
const lines = [
|
|
9598
9791
|
"",
|
|
9599
|
-
`${
|
|
9792
|
+
`${c5.bold}\u2726 Add a parallel session${c5.reset}`,
|
|
9600
9793
|
"",
|
|
9601
|
-
`${dots.join(" ")} ${
|
|
9794
|
+
`${dots.join(" ")} ${c5.dim}${q.current} of ${q.current} session${q.current === 1 ? "" : "s"} in use${c5.reset}`
|
|
9602
9795
|
];
|
|
9603
9796
|
if (q.ends_trial) {
|
|
9604
9797
|
lines.push(
|
|
9605
|
-
`${
|
|
9606
|
-
`${
|
|
9798
|
+
`${c5.yellow}Your $5 trial covers 1 session. Adding a ${ordinal(q.sessions)} ends the trial${c5.reset}`,
|
|
9799
|
+
`${c5.yellow}and starts your $49/mo plan now${cost ? ` \u2014 ${c5.bold}${cost} charged today${c5.reset}${c5.yellow}` : ""}.${c5.reset}`
|
|
9607
9800
|
);
|
|
9608
9801
|
} else if (cost) {
|
|
9609
|
-
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${
|
|
9802
|
+
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 ${c5.bold}${cost} charged now${c5.reset}.`);
|
|
9610
9803
|
} else {
|
|
9611
9804
|
lines.push(`Add a ${ordinal(q.sessions)} session at $49/mo \u2014 prorated on your next invoice.`);
|
|
9612
9805
|
}
|
|
@@ -9619,7 +9812,7 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9619
9812
|
try {
|
|
9620
9813
|
if (opts.auto) {
|
|
9621
9814
|
tui.print(
|
|
9622
|
-
`${
|
|
9815
|
+
`${c5.yellow}You're out of simultaneous sessions \u2014 another Standard Code session is using your slot.${c5.reset}`
|
|
9623
9816
|
);
|
|
9624
9817
|
}
|
|
9625
9818
|
const quote = await api.sessionsQuote(threadId);
|
|
@@ -9627,16 +9820,16 @@ why: ${req.requestPermission}` : ""}`,
|
|
|
9627
9820
|
const link = await api.accountLink(threadId).catch(() => null);
|
|
9628
9821
|
const target = link?.url ?? "https://standardcode.ai/account";
|
|
9629
9822
|
tui.print(
|
|
9630
|
-
`${
|
|
9823
|
+
`${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}`
|
|
9631
9824
|
);
|
|
9632
9825
|
openUrl(target);
|
|
9633
|
-
tui.print(`${
|
|
9826
|
+
tui.print(`${c5.gray}\u2192 opened ${target} to manage your plan${c5.reset}`);
|
|
9634
9827
|
return;
|
|
9635
9828
|
}
|
|
9636
9829
|
if (quote.current >= quote.max) {
|
|
9637
9830
|
tui.print(
|
|
9638
|
-
`${
|
|
9639
|
-
${
|
|
9831
|
+
`${c5.yellow}You're at the maximum of ${quote.max} parallel session${quote.max === 1 ? "" : "s"}.${c5.reset}
|
|
9832
|
+
${c5.gray}Close another session (its slot frees within ~90s), then resend your message.${c5.reset}`
|
|
9640
9833
|
);
|
|
9641
9834
|
return;
|
|
9642
9835
|
}
|
|
@@ -9648,25 +9841,25 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9648
9841
|
{ label: "Not now", value: "no" }
|
|
9649
9842
|
]);
|
|
9650
9843
|
if (choice !== "go") {
|
|
9651
|
-
tui.print(`${
|
|
9844
|
+
tui.print(`${c5.gray}No change made \u2014 you can upgrade anytime with /upgrade.${c5.reset}`);
|
|
9652
9845
|
return;
|
|
9653
9846
|
}
|
|
9654
|
-
tui.print(`${
|
|
9847
|
+
tui.print(`${c5.gray}Applying\u2026${c5.reset}`);
|
|
9655
9848
|
let applied;
|
|
9656
9849
|
try {
|
|
9657
9850
|
applied = await api.sessionsUpgrade(threadId, quote.sessions);
|
|
9658
9851
|
} catch (e) {
|
|
9659
|
-
tui.print(`${
|
|
9852
|
+
tui.print(`${c5.red}\u2717${c5.reset} Upgrade failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
9660
9853
|
return;
|
|
9661
9854
|
}
|
|
9662
9855
|
if (!applied?.ok) {
|
|
9663
|
-
tui.print(`${
|
|
9856
|
+
tui.print(`${c5.red}\u2717${c5.reset} Upgrade failed: ${applied?.error ?? "unknown error"}`);
|
|
9664
9857
|
return;
|
|
9665
9858
|
}
|
|
9666
9859
|
const n = applied.sessions ?? quote.sessions;
|
|
9667
|
-
tui.print(`${
|
|
9860
|
+
tui.print(`${c5.green}\u2713${c5.reset} ${c5.bold}${gradientText(`You now have ${n} parallel session${n === 1 ? "" : "s"}.`)}${c5.reset}`);
|
|
9668
9861
|
if (opts.auto && lastSent) {
|
|
9669
|
-
tui.print(`${
|
|
9862
|
+
tui.print(`${c5.gray}Continuing\u2026${c5.reset}`);
|
|
9670
9863
|
await sendNow(lastSent.text, lastSent.images, lastSent.refs);
|
|
9671
9864
|
}
|
|
9672
9865
|
} finally {
|
|
@@ -9777,38 +9970,50 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9777
9970
|
const history = await loadHistory(api, threadId, historySeedThreadId);
|
|
9778
9971
|
tui.setHistory(history);
|
|
9779
9972
|
await reconcileSharedMessaging(true);
|
|
9780
|
-
let
|
|
9781
|
-
|
|
9782
|
-
|
|
9783
|
-
|
|
9784
|
-
|
|
9785
|
-
|
|
9786
|
-
|
|
9787
|
-
|
|
9788
|
-
|
|
9789
|
-
|
|
9790
|
-
|
|
9791
|
-
|
|
9792
|
-
if (sharedMessagingReady) {
|
|
9793
|
-
const hasDraft = !!textVal.trim() || images.length > 0 || mirroredDraftRefs.length > 0;
|
|
9973
|
+
let latestDraftPayload = null;
|
|
9974
|
+
let draftDirty = false;
|
|
9975
|
+
let draftInFlight = false;
|
|
9976
|
+
const pumpComposerDraft = async () => {
|
|
9977
|
+
if (draftInFlight) return;
|
|
9978
|
+
draftInFlight = true;
|
|
9979
|
+
try {
|
|
9980
|
+
while (draftDirty) {
|
|
9981
|
+
draftDirty = false;
|
|
9982
|
+
const payload = latestDraftPayload;
|
|
9983
|
+
if (!payload || !sharedMessagingReady) continue;
|
|
9984
|
+
const hasDraft = !!payload.text.trim() || payload.images.length > 0 || mirroredDraftRefs.length > 0;
|
|
9794
9985
|
const mutation = {
|
|
9795
|
-
content:
|
|
9986
|
+
content: payload.text,
|
|
9796
9987
|
attachments: [
|
|
9797
9988
|
...hasDraft ? mirroredDraftRefs : [],
|
|
9798
|
-
...toSharedAttachments(images)
|
|
9989
|
+
...toSharedAttachments(payload.images)
|
|
9799
9990
|
],
|
|
9800
9991
|
...messagingOrigin
|
|
9801
9992
|
};
|
|
9802
|
-
|
|
9993
|
+
await applySharedMutation(
|
|
9803
9994
|
hasDraft ? api.putSharedDraft(threadId, mutation) : api.clearSharedDraft(threadId, messagingOrigin)
|
|
9804
9995
|
);
|
|
9805
9996
|
}
|
|
9806
|
-
}
|
|
9997
|
+
} finally {
|
|
9998
|
+
draftInFlight = false;
|
|
9999
|
+
}
|
|
10000
|
+
};
|
|
10001
|
+
const clearComposerDraft = () => {
|
|
10002
|
+
mirroredDraftRefs = [];
|
|
10003
|
+
tui.setExternalAttachmentNames([]);
|
|
10004
|
+
latestDraftPayload = { text: "", images: [] };
|
|
10005
|
+
draftDirty = true;
|
|
10006
|
+
void pumpComposerDraft();
|
|
10007
|
+
};
|
|
10008
|
+
tui.onDraftChange = (textVal, images) => {
|
|
10009
|
+
latestDraftPayload = { text: textVal, images };
|
|
10010
|
+
draftDirty = true;
|
|
10011
|
+
void pumpComposerDraft();
|
|
9807
10012
|
};
|
|
9808
10013
|
const submitComposer = async (text, images, steer) => {
|
|
9809
10014
|
const draftRefs = mirroredDraftRefs;
|
|
9810
10015
|
if (!sharedMessagingReady && (busy || steer || editingPendingId !== null)) {
|
|
9811
|
-
tui.print(`${
|
|
10016
|
+
tui.print(`${c5.dim}Restoring shared message state \u2014 try again in a moment.${c5.reset}`);
|
|
9812
10017
|
tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
|
|
9813
10018
|
tui.setInput(text, images);
|
|
9814
10019
|
return;
|
|
@@ -9831,7 +10036,7 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9831
10036
|
const item = sharedMessaging.pending.items.find((candidate) => candidate.id === pendingId);
|
|
9832
10037
|
editingPendingId = null;
|
|
9833
10038
|
if (!item) {
|
|
9834
|
-
tui.print(`${
|
|
10039
|
+
tui.print(`${c5.dim}That pending message was already dispatched or dismissed.${c5.reset}`);
|
|
9835
10040
|
return;
|
|
9836
10041
|
}
|
|
9837
10042
|
const updated = await editSharedPending(item, text, images, draftRefs);
|
|
@@ -9844,7 +10049,7 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9844
10049
|
return;
|
|
9845
10050
|
}
|
|
9846
10051
|
if (steer) {
|
|
9847
|
-
tui.print(`${
|
|
10052
|
+
tui.print(`${c5.yellow}\u21AA steering now \u2014 stopping the current step${c5.reset}`);
|
|
9848
10053
|
await Promise.all([
|
|
9849
10054
|
api.stopThread(threadId).catch(() => {
|
|
9850
10055
|
}),
|
|
@@ -9859,9 +10064,7 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9859
10064
|
return;
|
|
9860
10065
|
}
|
|
9861
10066
|
if (busy) {
|
|
9862
|
-
if (await appendSharedPending(text, images, draftRefs)) {
|
|
9863
|
-
tui.print(`${c4.gray}\u23F3 pending:${c4.reset} ${text} ${c4.dim}(/queue to edit, steer, or dismiss)${c4.reset}`);
|
|
9864
|
-
} else {
|
|
10067
|
+
if (await appendSharedPending(text, images, draftRefs)) ; else {
|
|
9865
10068
|
mirroredDraftRefs = draftRefs;
|
|
9866
10069
|
tui.setExternalAttachmentNames(draftRefs.map((attachment) => attachment.name));
|
|
9867
10070
|
tui.setInput(text, images);
|
|
@@ -9883,13 +10086,18 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9883
10086
|
tui.onInterrupt = () => {
|
|
9884
10087
|
const firstPending = sharedMessaging.pending.items[0];
|
|
9885
10088
|
if (!busy && firstPending) {
|
|
9886
|
-
tui.print(`${
|
|
10089
|
+
tui.print(`${c5.yellow}\u21AA steering the first pending message\u2026${c5.reset}`);
|
|
9887
10090
|
void promoteSharedPending(firstPending);
|
|
9888
10091
|
return;
|
|
9889
10092
|
}
|
|
9890
10093
|
if (busy) {
|
|
9891
|
-
const
|
|
9892
|
-
|
|
10094
|
+
const head = sharedMessaging.pending.items[0];
|
|
10095
|
+
if (head) {
|
|
10096
|
+
tui.print(`${c5.yellow}\u21AA steering \u2014 stopping the current step to run the queued message${c5.reset}`);
|
|
10097
|
+
void promoteSharedPending(head);
|
|
10098
|
+
return;
|
|
10099
|
+
}
|
|
10100
|
+
tui.print(`${c5.yellow}\u25A0 stopping now${c5.reset}`);
|
|
9893
10101
|
const stops = [api.stopThread(threadId).catch(() => {
|
|
9894
10102
|
})];
|
|
9895
10103
|
for (const childId of activeSubagents.keys()) {
|
|
@@ -9919,7 +10127,7 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9919
10127
|
void api.getGoal(threadId).then((g) => tui.setGoal(g)).catch(() => {
|
|
9920
10128
|
});
|
|
9921
10129
|
attaching.stop();
|
|
9922
|
-
const header = `${
|
|
10130
|
+
const header = `${c5.bold}${c5.magenta}Standard Code${c5.reset} ${c5.dim}\u2014 ${agentTitle}${c5.reset}`;
|
|
9923
10131
|
const remoteDaemonV = session.runner?.daemon?.version;
|
|
9924
10132
|
const owner = currentOwner;
|
|
9925
10133
|
let localOwnerDesc = ownsExecution ? "this terminal" : describeExecOwner(owner);
|
|
@@ -9927,24 +10135,24 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9927
10135
|
const selfRec = await loadMachine(api, session.identity.machine_id).catch(() => null);
|
|
9928
10136
|
if (selfRec?.daemon?.version) localOwnerDesc = `the daemon on this machine (v${selfRec.daemon.version})`;
|
|
9929
10137
|
}
|
|
9930
|
-
const execLine = remote ? `${
|
|
10138
|
+
const execLine = remote ? `${c5.gray}tool execution:${c5.reset} daemon${remoteDaemonV ? ` v${remoteDaemonV}` : ""} on ${runnerName}` : `${c5.gray}tool execution:${c5.reset} ${localOwnerDesc}`;
|
|
9931
10139
|
tui.setAgentLabel(agentTitle);
|
|
9932
10140
|
tui.banner(
|
|
9933
10141
|
remote ? [
|
|
9934
10142
|
header,
|
|
9935
|
-
`${
|
|
9936
|
-
`${
|
|
10143
|
+
`${c5.gray}project:${c5.reset} ${session.remotePath ?? "?"} ${c5.teal}on ${runnerName}${c5.reset}`,
|
|
10144
|
+
`${c5.gray}machine:${c5.reset} ${runnerName} ${c5.gray}thread:${c5.reset} ${threadId.slice(0, 8)}`,
|
|
9937
10145
|
execLine
|
|
9938
10146
|
] : [
|
|
9939
10147
|
header,
|
|
9940
|
-
`${
|
|
9941
|
-
`${
|
|
10148
|
+
`${c5.gray}project:${c5.reset} ${projectDir}`,
|
|
10149
|
+
`${c5.gray}machine:${c5.reset} ${machine} ${c5.gray}thread:${c5.reset} ${threadId.slice(0, 8)}`,
|
|
9942
10150
|
execLine
|
|
9943
10151
|
]
|
|
9944
10152
|
);
|
|
9945
10153
|
if (!remote && session.suggestDaemonInstall && process.platform !== "win32" && !serviceStatus().installed) {
|
|
9946
10154
|
tui.print(
|
|
9947
|
-
`${
|
|
10155
|
+
`${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}`
|
|
9948
10156
|
);
|
|
9949
10157
|
}
|
|
9950
10158
|
if (resumed) await printHistory(api, threadId, tui);
|
|
@@ -9955,17 +10163,17 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9955
10163
|
const runningProcs = (await registry.list()).filter((p) => p.status === "running");
|
|
9956
10164
|
if (runningProcs.length) {
|
|
9957
10165
|
tui.print(
|
|
9958
|
-
`${
|
|
10166
|
+
`${c5.cyan}\u2699 ${runningProcs.length} background process${runningProcs.length === 1 ? "" : "es"} running:${c5.reset}`
|
|
9959
10167
|
);
|
|
9960
|
-
for (const p of runningProcs) tui.print(`${
|
|
10168
|
+
for (const p of runningProcs) tui.print(`${c5.gray} ${p.id} ${p.description || p.command}${c5.reset}`);
|
|
9961
10169
|
}
|
|
9962
10170
|
refreshBgCount();
|
|
9963
10171
|
if (exec && ownsExecution) {
|
|
9964
10172
|
for (const res of await exec.connectEnabledMcpServers()) {
|
|
9965
10173
|
if (res.ok) {
|
|
9966
|
-
tui.print(`${
|
|
10174
|
+
tui.print(`${c5.cyan}\u26A1 MCP "${res.name}" connected${c5.reset} ${c5.gray}(${res.tools} tool${res.tools === 1 ? "" : "s"})${c5.reset}`);
|
|
9967
10175
|
} else {
|
|
9968
|
-
tui.print(`${
|
|
10176
|
+
tui.print(`${c5.red}\u26A0 MCP "${res.name}" failed:${c5.reset} ${c5.gray}${res.error}${c5.reset}`);
|
|
9969
10177
|
}
|
|
9970
10178
|
}
|
|
9971
10179
|
}
|
|
@@ -9981,8 +10189,8 @@ ${c4.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
9981
10189
|
try {
|
|
9982
10190
|
const { choice, reason } = await tui.approval(
|
|
9983
10191
|
`${request.summary}${request.permission ? `
|
|
9984
|
-
${
|
|
9985
|
-
${
|
|
10192
|
+
${c5.bold}why: ${request.permission}${c5.reset}` : ""}
|
|
10193
|
+
${c5.dim}runs on ${request.machine || runnerName}${c5.reset}`,
|
|
9986
10194
|
request.risk
|
|
9987
10195
|
);
|
|
9988
10196
|
answeredApprovals.add(request.tool_call_id);
|
|
@@ -9996,6 +10204,7 @@ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
|
|
|
9996
10204
|
approvalPromptOpen = false;
|
|
9997
10205
|
}
|
|
9998
10206
|
};
|
|
10207
|
+
let stalledNoticeShown = false;
|
|
9999
10208
|
const poll = async () => {
|
|
10000
10209
|
let msgs;
|
|
10001
10210
|
let serverBusy = null;
|
|
@@ -10005,6 +10214,14 @@ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
|
|
|
10005
10214
|
msgs = snapshot.messages.slice(-60);
|
|
10006
10215
|
serverBusy = snapshot.busy;
|
|
10007
10216
|
serverTool = snapshot.current_tool;
|
|
10217
|
+
if (snapshot.stalled && !stalledNoticeShown) {
|
|
10218
|
+
stalledNoticeShown = true;
|
|
10219
|
+
tui.print(
|
|
10220
|
+
`${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}`
|
|
10221
|
+
);
|
|
10222
|
+
} else if (!snapshot.stalled && stalledNoticeShown && snapshot.busy) {
|
|
10223
|
+
stalledNoticeShown = false;
|
|
10224
|
+
}
|
|
10008
10225
|
} catch {
|
|
10009
10226
|
try {
|
|
10010
10227
|
msgs = await api.getMessages(threadId, 60);
|
|
@@ -10033,7 +10250,7 @@ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
|
|
|
10033
10250
|
continue;
|
|
10034
10251
|
}
|
|
10035
10252
|
if (m.role === "assistant" && text) printAssistant(tui, text);
|
|
10036
|
-
else if (m.role === "system" && text) tui.print(`${
|
|
10253
|
+
else if (m.role === "system" && text) tui.print(`${c5.dim}${text}${c5.reset}`);
|
|
10037
10254
|
else if (m.role === "user" && text) {
|
|
10038
10255
|
const pending = pendingSent.get(text) ?? 0;
|
|
10039
10256
|
if (pending > 0) {
|
|
@@ -10122,21 +10339,21 @@ ${c4.dim}runs on ${request.machine || runnerName}${c4.reset}`,
|
|
|
10122
10339
|
tui.setWorking(false);
|
|
10123
10340
|
tui.setSubagents([]);
|
|
10124
10341
|
tui.setGoal(null);
|
|
10125
|
-
tui.
|
|
10342
|
+
tui.setQueuedMessages([]);
|
|
10126
10343
|
tui.setContextPct(null);
|
|
10127
10344
|
tui.setStep(null, 0);
|
|
10128
10345
|
tui.setBackgroundCount(0);
|
|
10129
10346
|
if (killed > 0) {
|
|
10130
|
-
tui.print(`${
|
|
10347
|
+
tui.print(`${c5.cyan}\u2699${c5.reset} Stopped ${killed} background process${killed === 1 ? "" : "es"}.`);
|
|
10131
10348
|
}
|
|
10132
|
-
tui.print(`${
|
|
10349
|
+
tui.print(`${c5.dim}\u2500\u2500 conversation cleared \u2014 starting a fresh session \u2500\u2500${c5.reset}`);
|
|
10133
10350
|
}
|
|
10134
10351
|
async function runSkillsMenu(tui, skills) {
|
|
10135
10352
|
let list;
|
|
10136
10353
|
try {
|
|
10137
10354
|
list = await skills.list();
|
|
10138
10355
|
} catch (e) {
|
|
10139
|
-
tui.print(`${
|
|
10356
|
+
tui.print(`${c5.red}\u2717 couldn't load skills:${c5.reset} ${c5.gray}${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
10140
10357
|
return;
|
|
10141
10358
|
}
|
|
10142
10359
|
const INSTALL = "__install__";
|
|
@@ -10147,7 +10364,7 @@ async function runSkillsMenu(tui, skills) {
|
|
|
10147
10364
|
}));
|
|
10148
10365
|
items.push({ label: "\uFF0B Install a skill\u2026", hint: "find & install", value: INSTALL });
|
|
10149
10366
|
const picked = await tui.select(
|
|
10150
|
-
`${
|
|
10367
|
+
`${c5.bold}Agent skills${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c5.reset}`,
|
|
10151
10368
|
items
|
|
10152
10369
|
);
|
|
10153
10370
|
if (!picked) return;
|
|
@@ -10160,8 +10377,8 @@ async function runSkillsMenu(tui, skills) {
|
|
|
10160
10377
|
return;
|
|
10161
10378
|
}
|
|
10162
10379
|
const skill = list.find((s) => s.name === picked);
|
|
10163
|
-
tui.print(`${
|
|
10164
|
-
const action = await tui.select(`${
|
|
10380
|
+
tui.print(`${c5.cyan}${skill.name}${c5.reset}${skill.version ? ` ${c5.dim}v${skill.version}${c5.reset}` : ""} ${c5.gray}\u2014 ${skill.description}${c5.reset}`);
|
|
10381
|
+
const action = await tui.select(`${c5.bold}${picked}${c5.reset}`, [
|
|
10165
10382
|
skill.enabled ? { label: "Disable (hide from the agent)", value: "disable" } : { label: "Enable", value: "enable" },
|
|
10166
10383
|
{ label: "View files", value: "files" },
|
|
10167
10384
|
{ label: "Remove this skill", value: "remove" },
|
|
@@ -10170,20 +10387,20 @@ async function runSkillsMenu(tui, skills) {
|
|
|
10170
10387
|
try {
|
|
10171
10388
|
if (action === "enable" || action === "disable") {
|
|
10172
10389
|
await skills.setEnabled(picked, action === "enable");
|
|
10173
|
-
tui.print(`${
|
|
10390
|
+
tui.print(`${c5.gray}${action}d ${picked}${c5.reset}`);
|
|
10174
10391
|
} else if (action === "files") {
|
|
10175
|
-
for (const f of skill.files) tui.print(` ${
|
|
10392
|
+
for (const f of skill.files) tui.print(` ${c5.gray}${f}${c5.reset}`);
|
|
10176
10393
|
} else if (action === "remove") {
|
|
10177
10394
|
await skills.remove(picked);
|
|
10178
|
-
tui.print(`${
|
|
10395
|
+
tui.print(`${c5.gray}removed ${picked}${c5.reset}`);
|
|
10179
10396
|
}
|
|
10180
10397
|
} catch (e) {
|
|
10181
|
-
tui.print(`${
|
|
10398
|
+
tui.print(`${c5.red}\u2717 ${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
10182
10399
|
}
|
|
10183
10400
|
}
|
|
10184
10401
|
async function runLevelMenu(tui, perm) {
|
|
10185
10402
|
const picked = await tui.select(
|
|
10186
|
-
`${
|
|
10403
|
+
`${c5.bold}Auto-accept level${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 shift-tab cycles)${c5.reset}`,
|
|
10187
10404
|
LEVELS.map((l) => ({
|
|
10188
10405
|
label: levelLabel(l),
|
|
10189
10406
|
hint: l === tui.level ? "current" : "",
|
|
@@ -10200,16 +10417,16 @@ async function runMachinesMenu(tui, api, self) {
|
|
|
10200
10417
|
try {
|
|
10201
10418
|
machines = await loadMachines(api);
|
|
10202
10419
|
} catch (e) {
|
|
10203
|
-
tui.print(`${
|
|
10420
|
+
tui.print(`${c5.red}\u2717 couldn't load machines:${c5.reset} ${c5.gray}${e instanceof Error ? e.message : String(e)}${c5.reset}`);
|
|
10204
10421
|
return;
|
|
10205
10422
|
}
|
|
10206
10423
|
if (!machines.length) {
|
|
10207
|
-
tui.print(`${
|
|
10424
|
+
tui.print(`${c5.gray}No machines registered yet. Run standardcode on a machine (or install its daemon) to register it.${c5.reset}`);
|
|
10208
10425
|
return;
|
|
10209
10426
|
}
|
|
10210
10427
|
machines.sort((a, b) => (b.updated_at ?? 0) - (a.updated_at ?? 0));
|
|
10211
10428
|
const picked = await tui.select(
|
|
10212
|
-
`${
|
|
10429
|
+
`${c5.bold}Your machines${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 esc)${c5.reset}`,
|
|
10213
10430
|
machines.map((m) => {
|
|
10214
10431
|
const isSelf = m.id === self.machine_id;
|
|
10215
10432
|
const online = daemonOnline(m);
|
|
@@ -10243,26 +10460,26 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
10243
10460
|
options.push({ label: "Back", value: "back" });
|
|
10244
10461
|
if (!isSelf && !machine.daemon) {
|
|
10245
10462
|
tui.print(
|
|
10246
|
-
`${
|
|
10463
|
+
`${c5.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c5.reset}`
|
|
10247
10464
|
);
|
|
10248
10465
|
} else if (!isSelf && machine.daemon && !online) {
|
|
10249
10466
|
tui.print(
|
|
10250
|
-
`${
|
|
10467
|
+
`${c5.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c5.reset}`
|
|
10251
10468
|
);
|
|
10252
10469
|
}
|
|
10253
|
-
const action = await tui.select(`${
|
|
10470
|
+
const action = await tui.select(`${c5.bold}${machineIcon(machine)} ${machine.name}${c5.reset}`, options);
|
|
10254
10471
|
if (!action || action === "back") return;
|
|
10255
10472
|
if (action === "icon") {
|
|
10256
10473
|
const current = machine.icon ?? "";
|
|
10257
10474
|
const emoji = await tui.prompt(
|
|
10258
|
-
`${
|
|
10475
|
+
`${c5.bold}Icon for ${machine.name}${c5.reset} ${c5.dim}(paste an emoji, blank to reset)${c5.reset}`,
|
|
10259
10476
|
current
|
|
10260
10477
|
);
|
|
10261
10478
|
if (emoji !== null) {
|
|
10262
10479
|
const trimmed = emoji.trim();
|
|
10263
10480
|
await setMachineIcon(api, machine.id, trimmed);
|
|
10264
10481
|
machine.icon = trimmed || void 0;
|
|
10265
|
-
tui.print(`${
|
|
10482
|
+
tui.print(`${c5.green}\u2713${c5.reset} icon ${trimmed ? `set to ${trimmed}` : "reset"} for ${machine.name}`);
|
|
10266
10483
|
}
|
|
10267
10484
|
return manageMachine(tui, api, self, machine);
|
|
10268
10485
|
}
|
|
@@ -10278,7 +10495,7 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
10278
10495
|
const name = await tui.prompt(`New name for ${machine.name}`, machine.name);
|
|
10279
10496
|
if (name === null || !name.trim()) return;
|
|
10280
10497
|
await setMachineName(api, machine.id, name.trim());
|
|
10281
|
-
tui.print(`${
|
|
10498
|
+
tui.print(`${c5.green}\u2713${c5.reset} Renamed ${c5.bold}${machine.name}${c5.reset} \u2192 ${c5.bold}${name.trim()}${c5.reset}.`);
|
|
10282
10499
|
} else if (action === "update") {
|
|
10283
10500
|
if (isSelf) {
|
|
10284
10501
|
await runUpdateCommand(tui);
|
|
@@ -10289,7 +10506,7 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
10289
10506
|
]);
|
|
10290
10507
|
if (go !== "yes") return;
|
|
10291
10508
|
await dispatch("update");
|
|
10292
|
-
tui.print(`${
|
|
10509
|
+
tui.print(`${c5.green}\u2713${c5.reset} Update ${c5.gray}${applyNote} (its daemon updates and restarts on the new version).${c5.reset}`);
|
|
10293
10510
|
}
|
|
10294
10511
|
} else if (action === "projects") {
|
|
10295
10512
|
await manageMachineProjects(tui, api, self, machine, dispatch, applyNote);
|
|
@@ -10300,7 +10517,7 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
|
|
|
10300
10517
|
const paths = Object.keys(machine.projects).sort();
|
|
10301
10518
|
const projLabels = projectDisplayLabels(machine.projects);
|
|
10302
10519
|
const picked = await tui.select(
|
|
10303
|
-
`${
|
|
10520
|
+
`${c5.bold}Projects on ${machine.name}${c5.reset} ${c5.dim}(\u2191\u2193 \xB7 enter \xB7 esc)${c5.reset}`,
|
|
10304
10521
|
[
|
|
10305
10522
|
...paths.map((p) => ({
|
|
10306
10523
|
label: projLabels.get(p) ?? projectDisplayName(p, machine.projects[p]),
|
|
@@ -10321,12 +10538,12 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
|
|
|
10321
10538
|
});
|
|
10322
10539
|
if (!chosen) return;
|
|
10323
10540
|
await dispatch("add_project", { path: chosen });
|
|
10324
|
-
tui.print(`${
|
|
10541
|
+
tui.print(`${c5.green}\u2713${c5.reset} Add ${chosen} ${c5.gray}${applyNote}.${c5.reset}`);
|
|
10325
10542
|
return;
|
|
10326
10543
|
}
|
|
10327
10544
|
const project = machine.projects[picked];
|
|
10328
10545
|
const displayName = projectDisplayName(picked, project);
|
|
10329
|
-
const action = await tui.select(`${
|
|
10546
|
+
const action = await tui.select(`${c5.bold}${displayName}${c5.reset} ${c5.dim}${shortenPath(picked, 48)}${c5.reset}`, [
|
|
10330
10547
|
{ label: "Rename", hint: "display name only \u2014 the directory is untouched", value: "rename" },
|
|
10331
10548
|
{ label: "Remove from this machine's projects", hint: "doesn't delete the directory", value: "remove" },
|
|
10332
10549
|
{ label: "Back", value: "back" }
|
|
@@ -10341,60 +10558,60 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
|
|
|
10341
10558
|
await setProjectName(api, machine.id, picked, name);
|
|
10342
10559
|
const now = name.trim() || projectDisplayName(picked, null);
|
|
10343
10560
|
if (project) project.name = now;
|
|
10344
|
-
tui.print(`${
|
|
10561
|
+
tui.print(`${c5.green}\u2713${c5.reset} Renamed ${c5.bold}${displayName}${c5.reset} \u2192 ${c5.bold}${now}${c5.reset}.`);
|
|
10345
10562
|
} else {
|
|
10346
10563
|
await dispatch("remove_project", { path: picked });
|
|
10347
|
-
tui.print(`${
|
|
10564
|
+
tui.print(`${c5.green}\u2713${c5.reset} Remove ${picked} ${c5.gray}${applyNote}.${c5.reset}`);
|
|
10348
10565
|
}
|
|
10349
10566
|
}
|
|
10350
10567
|
function showDaemonInfo(tui, session) {
|
|
10351
10568
|
if (session.mode === "remote" && session.runner) {
|
|
10352
10569
|
tui.print(
|
|
10353
|
-
`${
|
|
10570
|
+
`${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}`
|
|
10354
10571
|
);
|
|
10355
10572
|
} else {
|
|
10356
|
-
tui.print(`${
|
|
10573
|
+
tui.print(`${c5.gray}This session runs on this machine.${c5.reset}`);
|
|
10357
10574
|
}
|
|
10358
10575
|
const status = serviceStatus();
|
|
10359
10576
|
tui.print(
|
|
10360
|
-
`${
|
|
10577
|
+
`${c5.gray}Daemon on this machine:${c5.reset} ${status.installed ? status.detail : "not installed"}`
|
|
10361
10578
|
);
|
|
10362
10579
|
if (!status.installed) {
|
|
10363
10580
|
tui.print(
|
|
10364
|
-
`${
|
|
10581
|
+
`${c5.gray}Install it to start sessions on this machine from anywhere:${c5.reset} ${c5.bold}standardcode daemon install${c5.reset}`
|
|
10365
10582
|
);
|
|
10366
10583
|
tui.print(
|
|
10367
|
-
`${
|
|
10584
|
+
`${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}`
|
|
10368
10585
|
);
|
|
10369
10586
|
} else {
|
|
10370
|
-
tui.print(`${
|
|
10587
|
+
tui.print(`${c5.dim}Manage it with standardcode daemon status | uninstall | add-project <path>.${c5.reset}`);
|
|
10371
10588
|
}
|
|
10372
10589
|
}
|
|
10373
10590
|
function showKeybindings(tui) {
|
|
10374
|
-
tui.print(`${
|
|
10375
|
-
tui.print(`${
|
|
10376
|
-
tui.print(`${
|
|
10377
|
-
tui.print(`${
|
|
10378
|
-
tui.print(`${
|
|
10379
|
-
tui.print(`${
|
|
10380
|
-
tui.print(`${
|
|
10381
|
-
tui.print(`${
|
|
10382
|
-
tui.print(`${
|
|
10383
|
-
tui.print(`${
|
|
10591
|
+
tui.print(`${c5.gray}shortcuts:${c5.reset}`);
|
|
10592
|
+
tui.print(`${c5.gray} shift-tab${c5.reset} cycle auto-accept level (1\u20135)`);
|
|
10593
|
+
tui.print(`${c5.gray} shift-\u23CE${c5.reset} insert a newline (multiline input)`);
|
|
10594
|
+
tui.print(`${c5.gray} option-\u23CE${c5.reset} steer now \u2014 stops the current step and plays your message`);
|
|
10595
|
+
tui.print(`${c5.gray} /${c5.reset} open the command palette (type to filter)`);
|
|
10596
|
+
tui.print(`${c5.gray} !cmd${c5.reset} run a shell command on the session's machine (e.g. !ls); !! to send a literal !`);
|
|
10597
|
+
tui.print(`${c5.gray} ctrl-v${c5.reset} paste an image from the clipboard ([#Image 1])`);
|
|
10598
|
+
tui.print(`${c5.gray} \u2191 / \u2193${c5.reset} cycle past messages (on the input's top line)`);
|
|
10599
|
+
tui.print(`${c5.gray} \u2190${c5.reset} from the start of the input: select the [\u2699 n bg] badge (enter opens it)`);
|
|
10600
|
+
tui.print(`${c5.gray} ctrl-c${c5.reset} quit`);
|
|
10384
10601
|
}
|
|
10385
10602
|
async function runUpdateCommand(tui) {
|
|
10386
10603
|
const version = readVersion();
|
|
10387
10604
|
const result = await forceCheckForUpdate(version);
|
|
10388
10605
|
if (!result) {
|
|
10389
|
-
tui.print(`${
|
|
10606
|
+
tui.print(`${c5.green}\u2713${c5.reset} ${c5.gray}@standardagents/code${c5.reset} is up to date (v${version})`);
|
|
10390
10607
|
return;
|
|
10391
10608
|
}
|
|
10392
10609
|
const { latest } = result;
|
|
10393
10610
|
tui.print(`
|
|
10394
|
-
${
|
|
10611
|
+
${c5.yellow}\u27F3${c5.reset} Update available: ${c5.gray}v${version}${c5.reset} \u2192 ${c5.green}v${latest}${c5.reset}`);
|
|
10395
10612
|
const pm = detectPackageManager();
|
|
10396
10613
|
if (!pm) {
|
|
10397
|
-
tui.print(` ${
|
|
10614
|
+
tui.print(` ${c5.gray}This is a source checkout \u2014 pull the repo to update.${c5.reset}`);
|
|
10398
10615
|
return;
|
|
10399
10616
|
}
|
|
10400
10617
|
const { display } = updateCommand(pm);
|
|
@@ -10403,28 +10620,28 @@ async function runUpdateCommand(tui) {
|
|
|
10403
10620
|
{ label: "No, skip", value: "no" }
|
|
10404
10621
|
]);
|
|
10405
10622
|
if (choice === "yes") {
|
|
10406
|
-
tui.print(` ${
|
|
10623
|
+
tui.print(` ${c5.gray}Running ${display}\u2026${c5.reset}`);
|
|
10407
10624
|
const { ok, output: pmOutput } = await runUpdate(pm);
|
|
10408
10625
|
if (ok) {
|
|
10409
|
-
tui.print(` ${
|
|
10626
|
+
tui.print(` ${c5.green}\u2713${c5.reset} Updated to v${latest}. Restart to use the new version.`);
|
|
10410
10627
|
} else {
|
|
10411
|
-
tui.print(` ${
|
|
10628
|
+
tui.print(` ${c5.red}\u2717${c5.reset} Update failed:`);
|
|
10412
10629
|
for (const line of pmOutput.trim().split("\n").slice(-6)) {
|
|
10413
|
-
tui.print(` ${
|
|
10630
|
+
tui.print(` ${c5.dim}${line}${c5.reset}`);
|
|
10414
10631
|
}
|
|
10415
10632
|
}
|
|
10416
10633
|
} else {
|
|
10417
|
-
tui.print(` ${
|
|
10634
|
+
tui.print(` ${c5.gray}Skipped. Run /update later.${c5.reset}`);
|
|
10418
10635
|
}
|
|
10419
10636
|
}
|
|
10420
10637
|
async function runProcessMenu(tui, bg) {
|
|
10421
10638
|
const procs = await bg.list();
|
|
10422
10639
|
if (!procs.length) {
|
|
10423
|
-
tui.print(`${
|
|
10640
|
+
tui.print(`${c5.gray}No background processes for this session.${c5.reset}`);
|
|
10424
10641
|
return;
|
|
10425
10642
|
}
|
|
10426
10643
|
const items = procs.map((p) => {
|
|
10427
|
-
const status = p.status === "running" ? `${
|
|
10644
|
+
const status = p.status === "running" ? `${c5.green}running${c5.reset}` : `${c5.gray}${p.status}${typeof p.exitCode === "number" ? ` (exit ${p.exitCode})` : ""}${c5.reset}`;
|
|
10428
10645
|
return {
|
|
10429
10646
|
label: `${p.description || p.command}`,
|
|
10430
10647
|
hint: `${p.id} \xB7 ${status}`,
|
|
@@ -10432,22 +10649,22 @@ async function runProcessMenu(tui, bg) {
|
|
|
10432
10649
|
};
|
|
10433
10650
|
});
|
|
10434
10651
|
const picked = await tui.select(
|
|
10435
|
-
`${
|
|
10652
|
+
`${c5.bold}Background processes${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter to manage \xB7 esc to close)${c5.reset}`,
|
|
10436
10653
|
items
|
|
10437
10654
|
);
|
|
10438
10655
|
if (!picked) return;
|
|
10439
10656
|
const proc = procs.find((p) => p.id === picked);
|
|
10440
10657
|
if (!proc || proc.status !== "running") {
|
|
10441
|
-
tui.print(`${
|
|
10658
|
+
tui.print(`${c5.gray}${picked} is not running.${c5.reset}`);
|
|
10442
10659
|
return;
|
|
10443
10660
|
}
|
|
10444
|
-
const action = await tui.select(`${
|
|
10661
|
+
const action = await tui.select(`${c5.bold}${proc.description || proc.command}${c5.reset}`, [
|
|
10445
10662
|
{ label: "Stop this process", value: "stop" },
|
|
10446
10663
|
{ label: "Leave it running", value: "leave" }
|
|
10447
10664
|
]);
|
|
10448
10665
|
if (action === "stop") {
|
|
10449
10666
|
await bg.stop(picked);
|
|
10450
|
-
tui.print(`${
|
|
10667
|
+
tui.print(`${c5.gray}stopped ${picked}${c5.reset}`);
|
|
10451
10668
|
}
|
|
10452
10669
|
}
|
|
10453
10670
|
async function runApprovalsMenu(tui, perm, save) {
|
|
@@ -10455,7 +10672,7 @@ async function runApprovalsMenu(tui, perm, save) {
|
|
|
10455
10672
|
const risks = Array.from(perm.allowRisk).sort((a, b) => a - b);
|
|
10456
10673
|
if (!tools.length && !risks.length) {
|
|
10457
10674
|
tui.print(
|
|
10458
|
-
`${
|
|
10675
|
+
`${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}`
|
|
10459
10676
|
);
|
|
10460
10677
|
return;
|
|
10461
10678
|
}
|
|
@@ -10465,22 +10682,22 @@ async function runApprovalsMenu(tui, perm, save) {
|
|
|
10465
10682
|
{ label: "Clear all approvals", hint: "", value: "clear" }
|
|
10466
10683
|
];
|
|
10467
10684
|
const picked = await tui.select(
|
|
10468
|
-
`${
|
|
10685
|
+
`${c5.bold}Approved commands${c5.reset} ${c5.dim}(enter to revoke \xB7 esc to close)${c5.reset}`,
|
|
10469
10686
|
items
|
|
10470
10687
|
);
|
|
10471
10688
|
if (!picked) return;
|
|
10472
10689
|
if (picked === "clear") {
|
|
10473
10690
|
perm.alwaysAllow.clear();
|
|
10474
10691
|
perm.allowRisk.clear();
|
|
10475
|
-
tui.print(`${
|
|
10692
|
+
tui.print(`${c5.gray}cleared all approvals${c5.reset}`);
|
|
10476
10693
|
} else if (picked.startsWith("tool:")) {
|
|
10477
10694
|
const t = picked.slice(5);
|
|
10478
10695
|
perm.alwaysAllow.delete(t);
|
|
10479
|
-
tui.print(`${
|
|
10696
|
+
tui.print(`${c5.gray}revoked tool ${t}${c5.reset}`);
|
|
10480
10697
|
} else if (picked.startsWith("risk:")) {
|
|
10481
10698
|
const r = Number(picked.slice(5));
|
|
10482
10699
|
perm.allowRisk.delete(r);
|
|
10483
|
-
tui.print(`${
|
|
10700
|
+
tui.print(`${c5.gray}revoked level ${r}${c5.reset}`);
|
|
10484
10701
|
}
|
|
10485
10702
|
save();
|
|
10486
10703
|
}
|
|
@@ -10498,7 +10715,7 @@ async function runMcpMenu(tui, mcp) {
|
|
|
10498
10715
|
items.push({ label: "\uFF0B Install a new MCP server\u2026", hint: "find & install", value: INSTALL });
|
|
10499
10716
|
items.push({ label: "Add manually (name: command)\u2026", hint: "advanced", value: ADD_MANUAL });
|
|
10500
10717
|
const picked = await tui.select(
|
|
10501
|
-
`${
|
|
10718
|
+
`${c5.bold}MCP servers${c5.reset} ${c5.dim}(\u2191/\u2193 \xB7 enter \xB7 esc to close)${c5.reset}`,
|
|
10502
10719
|
items
|
|
10503
10720
|
);
|
|
10504
10721
|
if (!picked) return;
|
|
@@ -10512,7 +10729,7 @@ async function runMcpMenu(tui, mcp) {
|
|
|
10512
10729
|
}
|
|
10513
10730
|
const server = configured.find((s) => s.name === picked);
|
|
10514
10731
|
const isConnected = connected.has(picked);
|
|
10515
|
-
const action = await tui.select(`${
|
|
10732
|
+
const action = await tui.select(`${c5.bold}${picked}${c5.reset}`, [
|
|
10516
10733
|
{ label: "View tools", value: "tools" },
|
|
10517
10734
|
isConnected ? { label: "Disconnect", value: "disconnect" } : { label: "Connect", value: "connect" },
|
|
10518
10735
|
server.enabled ? { label: "Disable (don't auto-connect)", value: "disable" } : { label: "Enable (auto-connect on start)", value: "enable" },
|
|
@@ -10522,29 +10739,29 @@ async function runMcpMenu(tui, mcp) {
|
|
|
10522
10739
|
if (action === "tools") {
|
|
10523
10740
|
const entry = mcp.catalog().servers.find((e) => e.name === picked);
|
|
10524
10741
|
if (!entry || entry.status !== "connected") {
|
|
10525
|
-
tui.print(`${
|
|
10742
|
+
tui.print(`${c5.gray}${picked} is not connected \u2014 connect it to list tools.${c5.reset}`);
|
|
10526
10743
|
return;
|
|
10527
10744
|
}
|
|
10528
|
-
if (!entry.tools.length) tui.print(`${
|
|
10529
|
-
for (const t of entry.tools) tui.print(` ${
|
|
10530
|
-
if (entry.resources.length) tui.print(` ${
|
|
10745
|
+
if (!entry.tools.length) tui.print(`${c5.gray}${picked} exposes no tools.${c5.reset}`);
|
|
10746
|
+
for (const t of entry.tools) tui.print(` ${c5.cyan}${t.name}${c5.reset}${t.description ? ` ${c5.gray}\u2014 ${t.description}${c5.reset}` : ""}`);
|
|
10747
|
+
if (entry.resources.length) tui.print(` ${c5.gray}${entry.resources.length} resource(s)${c5.reset}`);
|
|
10531
10748
|
} else if (action === "connect") {
|
|
10532
10749
|
const res = await mcp.connect(server);
|
|
10533
|
-
tui.print(res.ok ? `${
|
|
10750
|
+
tui.print(res.ok ? `${c5.cyan}\u26A1 connected (${res.tools} tools)${c5.reset}` : `${c5.red}\u26A0 ${res.error}${c5.reset}`);
|
|
10534
10751
|
} else if (action === "disconnect") {
|
|
10535
10752
|
mcp.disconnect(picked);
|
|
10536
|
-
tui.print(`${
|
|
10753
|
+
tui.print(`${c5.gray}disconnected ${picked}${c5.reset}`);
|
|
10537
10754
|
} else if (action === "enable") {
|
|
10538
10755
|
mcp.setEnabled(picked, true);
|
|
10539
10756
|
const res = await mcp.connect(server);
|
|
10540
|
-
tui.print(res.ok ? `${
|
|
10757
|
+
tui.print(res.ok ? `${c5.cyan}\u26A1 enabled + connected (${res.tools} tools)${c5.reset}` : `${c5.red}\u26A0 enabled but failed: ${res.error}${c5.reset}`);
|
|
10541
10758
|
} else if (action === "disable") {
|
|
10542
10759
|
mcp.setEnabled(picked, false);
|
|
10543
10760
|
mcp.disconnect(picked);
|
|
10544
|
-
tui.print(`${
|
|
10761
|
+
tui.print(`${c5.gray}disabled + disconnected ${picked}${c5.reset}`);
|
|
10545
10762
|
} else if (action === "remove") {
|
|
10546
10763
|
mcp.remove(picked);
|
|
10547
|
-
tui.print(`${
|
|
10764
|
+
tui.print(`${c5.gray}removed ${picked}${c5.reset}`);
|
|
10548
10765
|
}
|
|
10549
10766
|
}
|
|
10550
10767
|
async function addMcpServer(tui, mcp) {
|
|
@@ -10555,13 +10772,13 @@ async function addMcpServer(tui, mcp) {
|
|
|
10555
10772
|
if (!spec) return;
|
|
10556
10773
|
const cfg = parseServerSpec(spec);
|
|
10557
10774
|
if (!cfg) {
|
|
10558
|
-
tui.print(`${
|
|
10775
|
+
tui.print(`${c5.yellow}couldn't parse that. Use name: command [args]${c5.reset}`);
|
|
10559
10776
|
return;
|
|
10560
10777
|
}
|
|
10561
|
-
tui.print(`${
|
|
10778
|
+
tui.print(`${c5.gray}connecting MCP "${cfg.name}" (${cfg.command} ${cfg.args.join(" ")})\u2026${c5.reset}`);
|
|
10562
10779
|
const res = await mcp.add(cfg);
|
|
10563
|
-
if (res.ok) tui.print(`${
|
|
10564
|
-
else tui.print(`${
|
|
10780
|
+
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}`);
|
|
10781
|
+
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}`);
|
|
10565
10782
|
}
|
|
10566
10783
|
async function installMcpServerFlow(tui, mcp) {
|
|
10567
10784
|
const query = await tui.prompt(
|