@standardagents/code 0.9.4 → 0.9.5
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 +123 -13
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2352,6 +2352,23 @@ var ExecutionSession = class {
|
|
|
2352
2352
|
}
|
|
2353
2353
|
};
|
|
2354
2354
|
|
|
2355
|
+
// src/theme.ts
|
|
2356
|
+
function detectLight() {
|
|
2357
|
+
const forced = (process.env.STANDARD_CODE_THEME || "").toLowerCase();
|
|
2358
|
+
if (forced === "light") return true;
|
|
2359
|
+
if (forced === "dark") return false;
|
|
2360
|
+
const fgbg = process.env.COLORFGBG;
|
|
2361
|
+
if (fgbg) {
|
|
2362
|
+
const parts = fgbg.split(";");
|
|
2363
|
+
const bg = Number(parts[parts.length - 1]);
|
|
2364
|
+
if (!Number.isNaN(bg)) return bg === 7 || bg === 15 || bg >= 230;
|
|
2365
|
+
}
|
|
2366
|
+
return false;
|
|
2367
|
+
}
|
|
2368
|
+
var isLightTheme = detectLight();
|
|
2369
|
+
var themeWhite = isLightTheme ? "\x1B[30m" : "\x1B[97m";
|
|
2370
|
+
var themeGray = isLightTheme ? "\x1B[38;5;244m" : "\x1B[90m";
|
|
2371
|
+
|
|
2355
2372
|
// src/stream.ts
|
|
2356
2373
|
var MessageStream = class {
|
|
2357
2374
|
constructor(api, threadId, hooks) {
|
|
@@ -3071,7 +3088,7 @@ var C = {
|
|
|
3071
3088
|
red: "\x1B[31m",
|
|
3072
3089
|
blue: "\x1B[34m",
|
|
3073
3090
|
magenta: "\x1B[35m",
|
|
3074
|
-
gray:
|
|
3091
|
+
gray: themeGray,
|
|
3075
3092
|
teal: "\x1B[38;5;37m"
|
|
3076
3093
|
};
|
|
3077
3094
|
var FRAMES = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FB", "\u28FD", "\u28FE"];
|
|
@@ -3225,6 +3242,11 @@ var Tui = class _Tui {
|
|
|
3225
3242
|
/** Enter while the `[⚙ n bg]` badge is selected — opens the bg process panel. */
|
|
3226
3243
|
onBgBadge = () => {
|
|
3227
3244
|
};
|
|
3245
|
+
/** Fired (deduped) when the input draft text changes — index.ts debounce-writes
|
|
3246
|
+
* it into the shared cross-surface presence buffer. */
|
|
3247
|
+
onDraftChange = () => {
|
|
3248
|
+
};
|
|
3249
|
+
lastDraftSeen = "";
|
|
3228
3250
|
onQuit = () => process.exit(0);
|
|
3229
3251
|
levelListeners = [];
|
|
3230
3252
|
/**
|
|
@@ -3935,6 +3957,13 @@ var Tui = class _Tui {
|
|
|
3935
3957
|
*/
|
|
3936
3958
|
renderBottom() {
|
|
3937
3959
|
if (!this.started || this.takeoverHandler) return;
|
|
3960
|
+
if (this.inputBuffer !== this.lastDraftSeen) {
|
|
3961
|
+
this.lastDraftSeen = this.inputBuffer;
|
|
3962
|
+
try {
|
|
3963
|
+
this.onDraftChange(this.inputBuffer);
|
|
3964
|
+
} catch {
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3938
3967
|
if (this.resizePending) return;
|
|
3939
3968
|
const cols2 = process.stdout.columns || 80;
|
|
3940
3969
|
this.moveToRegionTop();
|
|
@@ -5088,6 +5117,7 @@ function machineIdFromDaemonClientId(clientId) {
|
|
|
5088
5117
|
var KEY_PREFIX = "standardcode.machine.";
|
|
5089
5118
|
var CMD_SUFFIX = ".cmd";
|
|
5090
5119
|
var NAME_SUFFIX = ".name";
|
|
5120
|
+
var ICON_SUFFIX = ".icon";
|
|
5091
5121
|
var FSREQ_SUFFIX = ".fsreq";
|
|
5092
5122
|
var FSRES_SUFFIX = ".fsres";
|
|
5093
5123
|
var DAEMON_ONLINE_WINDOW_MS = 90 * 1e3;
|
|
@@ -5100,6 +5130,9 @@ function commandKey(machineId) {
|
|
|
5100
5130
|
function nameKey(machineId) {
|
|
5101
5131
|
return `${KEY_PREFIX}${machineId}${NAME_SUFFIX}`;
|
|
5102
5132
|
}
|
|
5133
|
+
function iconKey(machineId) {
|
|
5134
|
+
return `${KEY_PREFIX}${machineId}${ICON_SUFFIX}`;
|
|
5135
|
+
}
|
|
5103
5136
|
function fsRequestKey(machineId) {
|
|
5104
5137
|
return `${KEY_PREFIX}${machineId}${FSREQ_SUFFIX}`;
|
|
5105
5138
|
}
|
|
@@ -5109,11 +5142,13 @@ function fsResponseKey(machineId) {
|
|
|
5109
5142
|
function parseFsRequest(value) {
|
|
5110
5143
|
if (!value || typeof value !== "object") return null;
|
|
5111
5144
|
const r = value;
|
|
5112
|
-
|
|
5145
|
+
const op = r.op === "mkdir" ? "mkdir" : r.op === "list" ? "list" : null;
|
|
5146
|
+
if (typeof r.nonce !== "string" || !op || typeof r.path !== "string") return null;
|
|
5113
5147
|
return {
|
|
5114
5148
|
nonce: r.nonce,
|
|
5115
|
-
op
|
|
5149
|
+
op,
|
|
5116
5150
|
path: r.path,
|
|
5151
|
+
name: typeof r.name === "string" ? r.name : void 0,
|
|
5117
5152
|
show_hidden: r.show_hidden === true,
|
|
5118
5153
|
requested_at: typeof r.requested_at === "number" ? r.requested_at : Date.now()
|
|
5119
5154
|
};
|
|
@@ -5124,6 +5159,12 @@ async function readFsRequest(api, machineId) {
|
|
|
5124
5159
|
async function writeFsResponse(api, machineId, res) {
|
|
5125
5160
|
await api.userKvSet(fsResponseKey(machineId), { ...res, responded_at: Date.now() });
|
|
5126
5161
|
}
|
|
5162
|
+
function machineIcon(record) {
|
|
5163
|
+
if (record.icon && record.icon.trim()) return record.icon.trim();
|
|
5164
|
+
if (record.platform === "darwin") return "\u{1F4BB}";
|
|
5165
|
+
if (record.platform === "win32") return "\u{1F5A5}\uFE0F";
|
|
5166
|
+
return "\u{1F5B3}";
|
|
5167
|
+
}
|
|
5127
5168
|
function machineDisplayName(record) {
|
|
5128
5169
|
return record.name?.trim() || "" || (record.hostname || "") || (record.id || "") || "machine";
|
|
5129
5170
|
}
|
|
@@ -5159,28 +5200,45 @@ async function loadMachines(api) {
|
|
|
5159
5200
|
const entries = await api.userKvList(KEY_PREFIX);
|
|
5160
5201
|
const records = [];
|
|
5161
5202
|
const names = /* @__PURE__ */ new Map();
|
|
5203
|
+
const icons = /* @__PURE__ */ new Map();
|
|
5162
5204
|
for (const e of entries) {
|
|
5163
5205
|
const rest = e.key.slice(KEY_PREFIX.length);
|
|
5164
|
-
if (rest.endsWith(CMD_SUFFIX)) continue;
|
|
5206
|
+
if (rest.endsWith(CMD_SUFFIX) || rest.endsWith(FSREQ_SUFFIX) || rest.endsWith(FSRES_SUFFIX)) continue;
|
|
5165
5207
|
if (rest.endsWith(NAME_SUFFIX)) {
|
|
5166
5208
|
const id = rest.slice(0, -NAME_SUFFIX.length);
|
|
5167
5209
|
if (typeof e.value === "string" && e.value.trim()) names.set(id, e.value.trim());
|
|
5168
5210
|
continue;
|
|
5169
5211
|
}
|
|
5212
|
+
if (rest.endsWith(ICON_SUFFIX)) {
|
|
5213
|
+
const id = rest.slice(0, -ICON_SUFFIX.length);
|
|
5214
|
+
if (typeof e.value === "string" && e.value.trim()) icons.set(id, e.value.trim());
|
|
5215
|
+
continue;
|
|
5216
|
+
}
|
|
5170
5217
|
const rec = parseMachineRecord(e.value);
|
|
5171
5218
|
if (rec) records.push(rec);
|
|
5172
5219
|
}
|
|
5173
5220
|
for (const rec of records) {
|
|
5174
5221
|
const override = names.get(rec.id);
|
|
5175
5222
|
if (override) rec.name = override;
|
|
5223
|
+
const icon = icons.get(rec.id);
|
|
5224
|
+
if (icon) rec.icon = icon;
|
|
5176
5225
|
}
|
|
5177
5226
|
return records;
|
|
5178
5227
|
}
|
|
5228
|
+
async function getMachineIcon(api, machineId) {
|
|
5229
|
+
const v = await api.userKvGet(iconKey(machineId));
|
|
5230
|
+
return typeof v === "string" && v.trim() ? v.trim() : null;
|
|
5231
|
+
}
|
|
5232
|
+
async function setMachineIcon(api, machineId, icon) {
|
|
5233
|
+
const trimmed = icon.trim();
|
|
5234
|
+
await api.userKvSet(iconKey(machineId), trimmed || null);
|
|
5235
|
+
}
|
|
5179
5236
|
async function loadMachine(api, machineId) {
|
|
5180
5237
|
const rec = await loadRawMachine(api, machineId);
|
|
5181
5238
|
if (!rec) return null;
|
|
5182
|
-
const override = await getMachineName(api, machineId);
|
|
5239
|
+
const [override, icon] = await Promise.all([getMachineName(api, machineId), getMachineIcon(api, machineId)]);
|
|
5183
5240
|
if (override) rec.name = override;
|
|
5241
|
+
if (icon) rec.icon = icon;
|
|
5184
5242
|
return rec;
|
|
5185
5243
|
}
|
|
5186
5244
|
function daemonOnline(record, now = Date.now()) {
|
|
@@ -5448,6 +5506,26 @@ function browseDirectory(input3, opts = {}) {
|
|
|
5448
5506
|
const truncated = all.length > MAX_ENTRIES2;
|
|
5449
5507
|
return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES2) : all, truncated };
|
|
5450
5508
|
}
|
|
5509
|
+
function mkdirDirectory(parentInput, name, opts = {}) {
|
|
5510
|
+
const parent = resolveBrowsePath(parentInput);
|
|
5511
|
+
const clean2 = (name ?? "").trim();
|
|
5512
|
+
const listParent = () => browseDirectory(parent, opts);
|
|
5513
|
+
if (!clean2 || clean2 === "." || clean2 === ".." || clean2.includes("/") || clean2.includes("\\") || clean2.includes("\0")) {
|
|
5514
|
+
return { ...listParent(), error: "Invalid folder name." };
|
|
5515
|
+
}
|
|
5516
|
+
const target = path3.join(parent, clean2);
|
|
5517
|
+
if (path3.dirname(target) !== parent) {
|
|
5518
|
+
return { ...listParent(), error: "Invalid folder name." };
|
|
5519
|
+
}
|
|
5520
|
+
try {
|
|
5521
|
+
fs4.mkdirSync(target, { recursive: false });
|
|
5522
|
+
} catch (err) {
|
|
5523
|
+
const code = err?.code;
|
|
5524
|
+
const message = code === "EEXIST" ? "A folder with that name already exists." : code === "EACCES" || code === "EPERM" ? "Permission denied." : code === "ENOENT" ? "The parent folder no longer exists." : "Could not create the folder.";
|
|
5525
|
+
return { ...listParent(), error: message };
|
|
5526
|
+
}
|
|
5527
|
+
return listParent();
|
|
5528
|
+
}
|
|
5451
5529
|
var PKG_NAME = "@standardagents/code";
|
|
5452
5530
|
var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
|
|
5453
5531
|
var CACHE_REL_DIR = ".config/standardagents-cli";
|
|
@@ -5960,7 +6038,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
|
|
|
5960
6038
|
let result;
|
|
5961
6039
|
let error;
|
|
5962
6040
|
try {
|
|
5963
|
-
result = browseDirectory(req.path, { showHidden: req.show_hidden });
|
|
6041
|
+
result = req.op === "mkdir" ? mkdirDirectory(req.path, req.name ?? "", { showHidden: req.show_hidden }) : browseDirectory(req.path, { showHidden: req.show_hidden });
|
|
5964
6042
|
} catch (err) {
|
|
5965
6043
|
ok = false;
|
|
5966
6044
|
error = err instanceof Error ? err.message : "browse failed";
|
|
@@ -6425,10 +6503,10 @@ var c3 = {
|
|
|
6425
6503
|
reset: "\x1B[0m",
|
|
6426
6504
|
dim: "\x1B[2m",
|
|
6427
6505
|
bold: "\x1B[1m",
|
|
6428
|
-
white:
|
|
6506
|
+
white: themeWhite,
|
|
6429
6507
|
cyan: "\x1B[36m",
|
|
6430
6508
|
green: "\x1B[32m",
|
|
6431
|
-
gray:
|
|
6509
|
+
gray: themeGray,
|
|
6432
6510
|
magenta: "\x1B[35m",
|
|
6433
6511
|
yellow: "\x1B[33m",
|
|
6434
6512
|
red: "\x1B[31m",
|
|
@@ -6832,9 +6910,9 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
|
|
|
6832
6910
|
const where = await tui.select(
|
|
6833
6911
|
`${c3.bold}${gradientText("Where should this session run?")}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
|
|
6834
6912
|
[
|
|
6835
|
-
{ label:
|
|
6913
|
+
{ label: `\u{1F5A5}\uFE0F This machine \u2014 ${shortDir}`, hint: "tools run locally", value: null },
|
|
6836
6914
|
...remoteTargets.map((m) => ({
|
|
6837
|
-
label: m.name
|
|
6915
|
+
label: `${machineIcon(m)} ${m.name}`,
|
|
6838
6916
|
hint: `${m.hostname} \xB7 daemon online${m.daemon ? ` \xB7 v${m.daemon.version}` : ""}`,
|
|
6839
6917
|
value: m
|
|
6840
6918
|
}))
|
|
@@ -7570,7 +7648,24 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
7570
7648
|
]);
|
|
7571
7649
|
const history = await loadHistory(api, threadId, historySeedThreadId);
|
|
7572
7650
|
tui.setHistory(history);
|
|
7651
|
+
const PRESENCE_KEY = "presence.latest_draft";
|
|
7652
|
+
const presenceSurfaceId = `cli:${Math.random().toString(36).slice(2, 10)}`;
|
|
7653
|
+
let presenceTimer;
|
|
7654
|
+
const clearPresence = () => {
|
|
7655
|
+
if (presenceTimer) clearTimeout(presenceTimer);
|
|
7656
|
+
void api.kvSet(threadId, PRESENCE_KEY, { text: "", images: [], surface: "cli", surface_id: presenceSurfaceId, updated_at: Date.now() }).catch(() => {
|
|
7657
|
+
});
|
|
7658
|
+
};
|
|
7659
|
+
tui.onDraftChange = (textVal) => {
|
|
7660
|
+
if (presenceTimer) clearTimeout(presenceTimer);
|
|
7661
|
+
presenceTimer = setTimeout(() => {
|
|
7662
|
+
const t = textVal.trim();
|
|
7663
|
+
void api.kvSet(threadId, PRESENCE_KEY, { text: t ? textVal : "", images: [], surface: "cli", surface_id: presenceSurfaceId, updated_at: Date.now() }).catch(() => {
|
|
7664
|
+
});
|
|
7665
|
+
}, 400);
|
|
7666
|
+
};
|
|
7573
7667
|
tui.onSubmit = (text, images) => {
|
|
7668
|
+
clearPresence();
|
|
7574
7669
|
const trimmed = text.trimStart();
|
|
7575
7670
|
if (trimmed.startsWith("!") && !trimmed.startsWith("!!")) {
|
|
7576
7671
|
const command = trimmed.slice(1).trim();
|
|
@@ -7898,7 +7993,7 @@ async function runMachinesMenu(tui, api, self) {
|
|
|
7898
7993
|
const daemonBit = m.daemon ? online ? "daemon online" : "daemon offline" : "no daemon";
|
|
7899
7994
|
const nproj = Object.keys(m.projects).length;
|
|
7900
7995
|
return {
|
|
7901
|
-
label: `${m.name}${isSelf ? " (this machine)" : ""}`,
|
|
7996
|
+
label: `${machineIcon(m)} ${m.name}${isSelf ? " (this machine)" : ""}`,
|
|
7902
7997
|
hint: `${m.hostname} \xB7 ${m.platform}/${m.arch} \xB7 v${m.version ?? "?"} \xB7 ${daemonBit} \xB7 ${nproj} project${nproj === 1 ? "" : "s"}`,
|
|
7903
7998
|
value: m.id
|
|
7904
7999
|
};
|
|
@@ -7913,7 +8008,8 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
7913
8008
|
const online = daemonOnline(machine);
|
|
7914
8009
|
const canRunCommands = isSelf || !!machine.daemon;
|
|
7915
8010
|
const options = [
|
|
7916
|
-
{ label: "Rename", value: "rename" }
|
|
8011
|
+
{ label: "Rename", value: "rename" },
|
|
8012
|
+
{ label: "Set icon", hint: machine.icon || "default", value: "icon" }
|
|
7917
8013
|
];
|
|
7918
8014
|
if (canRunCommands) {
|
|
7919
8015
|
options.push(
|
|
@@ -7931,8 +8027,22 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
7931
8027
|
`${c3.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c3.reset}`
|
|
7932
8028
|
);
|
|
7933
8029
|
}
|
|
7934
|
-
const action = await tui.select(`${c3.bold}${machine.name}${c3.reset}`, options);
|
|
8030
|
+
const action = await tui.select(`${c3.bold}${machineIcon(machine)} ${machine.name}${c3.reset}`, options);
|
|
7935
8031
|
if (!action || action === "back") return;
|
|
8032
|
+
if (action === "icon") {
|
|
8033
|
+
const current = machine.icon ?? "";
|
|
8034
|
+
const emoji = await tui.prompt(
|
|
8035
|
+
`${c3.bold}Icon for ${machine.name}${c3.reset} ${c3.dim}(paste an emoji, blank to reset)${c3.reset}`,
|
|
8036
|
+
current
|
|
8037
|
+
);
|
|
8038
|
+
if (emoji !== null) {
|
|
8039
|
+
const trimmed = emoji.trim();
|
|
8040
|
+
await setMachineIcon(api, machine.id, trimmed);
|
|
8041
|
+
machine.icon = trimmed || void 0;
|
|
8042
|
+
tui.print(`${c3.green}\u2713${c3.reset} icon ${trimmed ? `set to ${trimmed}` : "reset"} for ${machine.name}`);
|
|
8043
|
+
}
|
|
8044
|
+
return manageMachine(tui, api, self, machine);
|
|
8045
|
+
}
|
|
7936
8046
|
const dispatch = async (kind, args) => {
|
|
7937
8047
|
if (isSelf) {
|
|
7938
8048
|
await applyMachineCommand(api, self, { kind, args});
|