@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 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: "\x1B[90m",
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
- if (typeof r.nonce !== "string" || r.op !== "list" || typeof r.path !== "string") return null;
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: "list",
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: "\x1B[97m",
6506
+ white: themeWhite,
6429
6507
  cyan: "\x1B[36m",
6430
6508
  green: "\x1B[32m",
6431
- gray: "\x1B[90m",
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: `This machine \u2014 ${shortDir}`, hint: "tools run locally", value: null },
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});