@standardagents/code 0.9.2 → 0.9.4

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
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import os9, { homedir } from 'os';
2
+ import os10, { homedir } from 'os';
3
3
  import path3 from 'path';
4
4
  import readline2 from 'readline/promises';
5
5
  import { stdout, stdin } from 'process';
@@ -1112,7 +1112,7 @@ function detailSuffix(tool, result) {
1112
1112
  const lines = result.split("\n").length;
1113
1113
  return ` (${lines} line${lines === 1 ? "" : "s"})`;
1114
1114
  }
1115
- var LOG_DIR = path3.join(os9.homedir(), ".standardagents", "process-logs");
1115
+ var LOG_DIR = path3.join(os10.homedir(), ".standardagents", "process-logs");
1116
1116
  var KEY2 = "bg_processes";
1117
1117
  function isAlive(pid) {
1118
1118
  try {
@@ -1189,7 +1189,7 @@ var ProcessRegistry = class {
1189
1189
  }
1190
1190
  };
1191
1191
  function configFile() {
1192
- return process.env.STANDARDAGENTS_MCP_CONFIG || path3.join(os9.homedir(), ".standardagents", "mcp.json");
1192
+ return process.env.STANDARDAGENTS_MCP_CONFIG || path3.join(os10.homedir(), ".standardagents", "mcp.json");
1193
1193
  }
1194
1194
  function loadMcpConfig() {
1195
1195
  try {
@@ -1491,7 +1491,7 @@ var HostTools = class {
1491
1491
  }
1492
1492
  }
1493
1493
  const hash = crypto.createHash("sha256").update(JSON.stringify(files)).digest("hex").slice(0, 12);
1494
- const skillDir = path3.join(os9.tmpdir(), "standardcode-skills", `${skill}-${hash}`);
1494
+ const skillDir = path3.join(os10.tmpdir(), "standardcode-skills", `${skill}-${hash}`);
1495
1495
  for (const f of files) {
1496
1496
  const dest = path3.resolve(skillDir, f.path);
1497
1497
  if (path3.relative(skillDir, dest).startsWith("..")) {
@@ -2816,7 +2816,7 @@ function fromFile(filePath) {
2816
2816
  }
2817
2817
  }
2818
2818
  async function readDarwin() {
2819
- const tmp = path3.join(os9.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
2819
+ const tmp = path3.join(os10.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
2820
2820
  const script = [
2821
2821
  `set d to the clipboard as \xABclass PNGf\xBB`,
2822
2822
  `set f to open for access POSIX file "${tmp}" with write permission`,
@@ -2853,7 +2853,7 @@ async function readLinux() {
2853
2853
  return null;
2854
2854
  }
2855
2855
  async function readWindows() {
2856
- const tmp = path3.join(os9.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
2856
+ const tmp = path3.join(os10.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
2857
2857
  const ps = [
2858
2858
  "Add-Type -AssemblyName System.Windows.Forms;",
2859
2859
  "$img = [System.Windows.Forms.Clipboard]::GetImage();",
@@ -4920,7 +4920,7 @@ function renderMarkdown(src, cols2 = 80) {
4920
4920
  }
4921
4921
  return out;
4922
4922
  }
4923
- var DIR = path3.join(os9.homedir(), ".standardagents");
4923
+ var DIR = path3.join(os10.homedir(), ".standardagents");
4924
4924
  var FILE = path3.join(DIR, "credentials");
4925
4925
  function normalizeEndpoint(endpoint) {
4926
4926
  let e = endpoint.trim();
@@ -5054,13 +5054,13 @@ function relaxTlsForLocalEndpoint(endpoint) {
5054
5054
  process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
5055
5055
  return true;
5056
5056
  }
5057
- var dir = () => path3.join(os9.homedir(), ".standardagents");
5057
+ var dir = () => path3.join(os10.homedir(), ".standardagents");
5058
5058
  var file = () => path3.join(dir(), "machine.json");
5059
5059
  function loadMachineIdentity() {
5060
5060
  try {
5061
5061
  const parsed = JSON.parse(fs4.readFileSync(file(), "utf8"));
5062
5062
  if (typeof parsed.machine_id === "string" && parsed.machine_id.length > 0) {
5063
- return parsed;
5063
+ return { machine_id: parsed.machine_id, created_at: parsed.created_at ?? Date.now() };
5064
5064
  }
5065
5065
  } catch {
5066
5066
  }
@@ -5075,15 +5075,6 @@ function saveMachineIdentity(identity) {
5075
5075
  fs4.mkdirSync(dir(), { recursive: true });
5076
5076
  fs4.writeFileSync(file(), JSON.stringify(identity, null, 2), { mode: 384 });
5077
5077
  }
5078
- function setMachineName(name) {
5079
- const identity = loadMachineIdentity();
5080
- identity.name = name.trim() || void 0;
5081
- saveMachineIdentity(identity);
5082
- return identity;
5083
- }
5084
- function machineDisplayName(identity) {
5085
- return identity.name?.trim() || os9.hostname();
5086
- }
5087
5078
  function daemonClientId(identity) {
5088
5079
  return `daemon:${identity.machine_id}`;
5089
5080
  }
@@ -5096,6 +5087,9 @@ function machineIdFromDaemonClientId(clientId) {
5096
5087
  }
5097
5088
  var KEY_PREFIX = "standardcode.machine.";
5098
5089
  var CMD_SUFFIX = ".cmd";
5090
+ var NAME_SUFFIX = ".name";
5091
+ var FSREQ_SUFFIX = ".fsreq";
5092
+ var FSRES_SUFFIX = ".fsres";
5099
5093
  var DAEMON_ONLINE_WINDOW_MS = 90 * 1e3;
5100
5094
  function machineKey(machineId) {
5101
5095
  return `${KEY_PREFIX}${machineId}`;
@@ -5103,6 +5097,44 @@ function machineKey(machineId) {
5103
5097
  function commandKey(machineId) {
5104
5098
  return `${KEY_PREFIX}${machineId}${CMD_SUFFIX}`;
5105
5099
  }
5100
+ function nameKey(machineId) {
5101
+ return `${KEY_PREFIX}${machineId}${NAME_SUFFIX}`;
5102
+ }
5103
+ function fsRequestKey(machineId) {
5104
+ return `${KEY_PREFIX}${machineId}${FSREQ_SUFFIX}`;
5105
+ }
5106
+ function fsResponseKey(machineId) {
5107
+ return `${KEY_PREFIX}${machineId}${FSRES_SUFFIX}`;
5108
+ }
5109
+ function parseFsRequest(value) {
5110
+ if (!value || typeof value !== "object") return null;
5111
+ const r = value;
5112
+ if (typeof r.nonce !== "string" || r.op !== "list" || typeof r.path !== "string") return null;
5113
+ return {
5114
+ nonce: r.nonce,
5115
+ op: "list",
5116
+ path: r.path,
5117
+ show_hidden: r.show_hidden === true,
5118
+ requested_at: typeof r.requested_at === "number" ? r.requested_at : Date.now()
5119
+ };
5120
+ }
5121
+ async function readFsRequest(api, machineId) {
5122
+ return parseFsRequest(await api.userKvGet(fsRequestKey(machineId)));
5123
+ }
5124
+ async function writeFsResponse(api, machineId, res) {
5125
+ await api.userKvSet(fsResponseKey(machineId), { ...res, responded_at: Date.now() });
5126
+ }
5127
+ function machineDisplayName(record) {
5128
+ return record.name?.trim() || "" || (record.hostname || "") || (record.id || "") || "machine";
5129
+ }
5130
+ async function getMachineName(api, machineId) {
5131
+ const v = await api.userKvGet(nameKey(machineId));
5132
+ return typeof v === "string" && v.trim() ? v.trim() : null;
5133
+ }
5134
+ async function setMachineName(api, machineId, name) {
5135
+ const trimmed = name.trim();
5136
+ await api.userKvSet(nameKey(machineId), trimmed || null);
5137
+ }
5106
5138
  function parseMachineRecord(value) {
5107
5139
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
5108
5140
  const r = value;
@@ -5120,12 +5152,36 @@ function parseMachineRecord(value) {
5120
5152
  updated_at: typeof r.updated_at === "number" ? r.updated_at : 0
5121
5153
  };
5122
5154
  }
5155
+ async function loadRawMachine(api, machineId) {
5156
+ return parseMachineRecord(await api.userKvGet(machineKey(machineId)));
5157
+ }
5123
5158
  async function loadMachines(api) {
5124
5159
  const entries = await api.userKvList(KEY_PREFIX);
5125
- return entries.map((e) => parseMachineRecord(e.value)).filter((m) => m !== null);
5160
+ const records = [];
5161
+ const names = /* @__PURE__ */ new Map();
5162
+ for (const e of entries) {
5163
+ const rest = e.key.slice(KEY_PREFIX.length);
5164
+ if (rest.endsWith(CMD_SUFFIX)) continue;
5165
+ if (rest.endsWith(NAME_SUFFIX)) {
5166
+ const id = rest.slice(0, -NAME_SUFFIX.length);
5167
+ if (typeof e.value === "string" && e.value.trim()) names.set(id, e.value.trim());
5168
+ continue;
5169
+ }
5170
+ const rec = parseMachineRecord(e.value);
5171
+ if (rec) records.push(rec);
5172
+ }
5173
+ for (const rec of records) {
5174
+ const override = names.get(rec.id);
5175
+ if (override) rec.name = override;
5176
+ }
5177
+ return records;
5126
5178
  }
5127
5179
  async function loadMachine(api, machineId) {
5128
- return parseMachineRecord(await api.userKvGet(machineKey(machineId)));
5180
+ const rec = await loadRawMachine(api, machineId);
5181
+ if (!rec) return null;
5182
+ const override = await getMachineName(api, machineId);
5183
+ if (override) rec.name = override;
5184
+ return rec;
5129
5185
  }
5130
5186
  function daemonOnline(record, now = Date.now()) {
5131
5187
  return !!record.daemon && now - record.daemon.last_seen_at < DAEMON_ONLINE_WINDOW_MS;
@@ -5134,8 +5190,8 @@ function newRecord(identity) {
5134
5190
  const now = Date.now();
5135
5191
  return {
5136
5192
  id: identity.machine_id,
5137
- name: machineDisplayName(identity),
5138
- hostname: os9.hostname(),
5193
+ name: os10.hostname(),
5194
+ hostname: os10.hostname(),
5139
5195
  platform: process.platform,
5140
5196
  arch: process.arch,
5141
5197
  version: readVersion() || void 0,
@@ -5146,10 +5202,9 @@ function newRecord(identity) {
5146
5202
  };
5147
5203
  }
5148
5204
  async function updateOwnMachineRecord(api, identity, mutate) {
5149
- const existing = await loadMachine(api, identity.machine_id);
5205
+ const existing = await loadRawMachine(api, identity.machine_id);
5150
5206
  const record = existing ?? newRecord(identity);
5151
- record.name = machineDisplayName(loadMachineIdentity());
5152
- record.hostname = os9.hostname();
5207
+ record.hostname = os10.hostname();
5153
5208
  record.platform = process.platform;
5154
5209
  record.arch = process.arch;
5155
5210
  record.version = readVersion() || record.version;
@@ -5233,24 +5288,17 @@ async function clearMachineCommands(api, machineId, appliedIds) {
5233
5288
  }
5234
5289
  async function applyMachineCommand(api, identity, cmd) {
5235
5290
  switch (cmd.kind) {
5236
- case "rename": {
5237
- const name = typeof cmd.args?.name === "string" ? cmd.args.name.trim() : "";
5238
- if (!name) return "rename: ignored (empty name)";
5239
- setMachineName(name);
5240
- await updateOwnMachineRecord(api, identity);
5241
- return `renamed to "${name}"`;
5242
- }
5243
5291
  case "add_project": {
5244
- const path12 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
5245
- if (!path12) return "add_project: ignored (no path)";
5246
- await registerProject(api, identity, path12);
5247
- return `added project ${path12}`;
5292
+ const path13 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
5293
+ if (!path13) return "add_project: ignored (no path)";
5294
+ await registerProject(api, identity, path13);
5295
+ return `added project ${path13}`;
5248
5296
  }
5249
5297
  case "remove_project": {
5250
- const path12 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
5251
- if (!path12) return "remove_project: ignored (no path)";
5252
- await unregisterProject(api, identity, path12);
5253
- return `removed project ${path12}`;
5298
+ const path13 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
5299
+ if (!path13) return "remove_project: ignored (no path)";
5300
+ await unregisterProject(api, identity, path13);
5301
+ return `removed project ${path13}`;
5254
5302
  }
5255
5303
  case "update":
5256
5304
  return "update requested";
@@ -5312,6 +5360,94 @@ async function awaitApprovalViaRelay(api, threadId, request, options = {}) {
5312
5360
  });
5313
5361
  }
5314
5362
  }
5363
+ var PROJECT_MARKERS = [
5364
+ ".git",
5365
+ "package.json",
5366
+ "pyproject.toml",
5367
+ "Cargo.toml",
5368
+ "go.mod",
5369
+ "pom.xml",
5370
+ "build.gradle",
5371
+ "Gemfile",
5372
+ "composer.json",
5373
+ "requirements.txt",
5374
+ ".standardagents"
5375
+ ];
5376
+ var MAX_ENTRIES2 = 500;
5377
+ function resolveBrowsePath(input3) {
5378
+ const home = os10.homedir();
5379
+ let p = (input3 ?? "").trim();
5380
+ if (!p) return home;
5381
+ if (p === "~") return home;
5382
+ if (p.startsWith("~/")) p = path3.join(home, p.slice(2));
5383
+ return path3.resolve(p);
5384
+ }
5385
+ function markers(dirPath) {
5386
+ let repo = false;
5387
+ let project = false;
5388
+ for (const marker of PROJECT_MARKERS) {
5389
+ let hit = false;
5390
+ try {
5391
+ hit = fs4.existsSync(path3.join(dirPath, marker));
5392
+ } catch {
5393
+ hit = false;
5394
+ }
5395
+ if (!hit) continue;
5396
+ project = true;
5397
+ if (marker === ".git") repo = true;
5398
+ if (repo) break;
5399
+ }
5400
+ return { project, repo };
5401
+ }
5402
+ function browseDirectory(input3, opts = {}) {
5403
+ const home = os10.homedir();
5404
+ const abs = resolveBrowsePath(input3);
5405
+ const parent = path3.dirname(abs);
5406
+ const base = {
5407
+ path: abs,
5408
+ parent: parent === abs ? null : parent,
5409
+ home
5410
+ };
5411
+ let dirents;
5412
+ try {
5413
+ const stat = fs4.statSync(abs);
5414
+ if (!stat.isDirectory()) {
5415
+ return { ...base, entries: [], truncated: false, error: "Not a directory" };
5416
+ }
5417
+ dirents = fs4.readdirSync(abs, { withFileTypes: true });
5418
+ } catch (err) {
5419
+ const code = err?.code;
5420
+ const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
5421
+ return { ...base, entries: [], truncated: false, error: message };
5422
+ }
5423
+ const showHidden = !!opts.showHidden;
5424
+ const rawDirs = [];
5425
+ const rawFiles = [];
5426
+ for (const d of dirents) {
5427
+ const name = d.name;
5428
+ if (!showHidden && name.startsWith(".")) continue;
5429
+ let isDir = d.isDirectory();
5430
+ if (d.isSymbolicLink()) {
5431
+ try {
5432
+ isDir = fs4.statSync(path3.join(abs, name)).isDirectory();
5433
+ } catch {
5434
+ isDir = false;
5435
+ }
5436
+ }
5437
+ if (isDir) {
5438
+ const { project, repo } = markers(path3.join(abs, name));
5439
+ rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
5440
+ } else {
5441
+ rawFiles.push({ name, dir: false });
5442
+ }
5443
+ }
5444
+ const cmp = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());
5445
+ rawDirs.sort(cmp);
5446
+ rawFiles.sort(cmp);
5447
+ const all = [...rawDirs, ...rawFiles];
5448
+ const truncated = all.length > MAX_ENTRIES2;
5449
+ return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES2) : all, truncated };
5450
+ }
5315
5451
  var PKG_NAME = "@standardagents/code";
5316
5452
  var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
5317
5453
  var CACHE_REL_DIR = ".config/standardagents-cli";
@@ -5492,12 +5628,13 @@ function runUpdate(pm) {
5492
5628
  // src/daemon.ts
5493
5629
  var HEARTBEAT_MS = 3e4;
5494
5630
  var COMMAND_POLL_MS = 8e3;
5631
+ var FS_POLL_MS = 1e3;
5495
5632
  var RECLAIM_PROBE_MS = 2 * 6e4;
5496
5633
  var UPDATE_CHECK_MS = 6 * 60 * 6e4;
5497
5634
  var SWEEP_MS = 10 * 6e4;
5498
5635
  var MAX_WORKERS = 30;
5499
5636
  var LOG_MAX_BYTES = 1e6;
5500
- var LOG_FILE = path3.join(os9.homedir(), ".standardagents", "daemon.log");
5637
+ var LOG_FILE = path3.join(os10.homedir(), ".standardagents", "daemon.log");
5501
5638
  function daemonLog(line) {
5502
5639
  try {
5503
5640
  fs4.mkdirSync(path3.dirname(LOG_FILE), { recursive: true });
@@ -5516,14 +5653,13 @@ function pathFromTags(tags) {
5516
5653
  const tag = tags.find((t) => t.startsWith("path:"));
5517
5654
  if (!tag) return null;
5518
5655
  const raw = tag.slice("path:".length);
5519
- return raw.replace(/^~(?=\/|$)/, os9.homedir());
5656
+ return raw.replace(/^~(?=\/|$)/, os10.homedir());
5520
5657
  }
5521
5658
  var ThreadWorker = class {
5522
- constructor(api, identity, threadId, projectDir, createdAt) {
5659
+ constructor(api, identity, machineName, threadId, projectDir, createdAt) {
5523
5660
  this.threadId = threadId;
5524
5661
  this.projectDir = projectDir;
5525
5662
  this.createdAt = createdAt;
5526
- const machineName = machineDisplayName(identity);
5527
5663
  this.perm = { level: 1, alwaysAllow: /* @__PURE__ */ new Set(), allowRisk: /* @__PURE__ */ new Set() };
5528
5664
  this.session = new ExecutionSession({
5529
5665
  api,
@@ -5648,9 +5784,13 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5648
5784
  `);
5649
5785
  process.exit(1);
5650
5786
  }
5787
+ let displayName = machineDisplayName(await loadMachine(api, identity.machine_id).catch(() => null) ?? {
5788
+ hostname: os10.hostname(),
5789
+ id: identity.machine_id
5790
+ });
5651
5791
  const applied = consumeAppliedUpdate(version);
5652
5792
  daemonLog(
5653
- `daemon starting \u2014 v${version}${applied ? " (freshly auto-updated)" : ""}, machine ${identity.machine_id} (${machineDisplayName(identity)}), endpoint ${endpoint}`
5793
+ `daemon starting \u2014 v${version}${applied ? " (freshly auto-updated)" : ""}, machine ${identity.machine_id} (${displayName}), endpoint ${endpoint}`
5654
5794
  );
5655
5795
  process.on("uncaughtException", (err) => {
5656
5796
  daemonLog(`FATAL uncaughtException: ${err?.stack || err}`);
@@ -5677,7 +5817,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5677
5817
  daemonLog(`cannot prepare project dir ${projectDir}: ${e instanceof Error ? e.message : String(e)}`);
5678
5818
  return;
5679
5819
  }
5680
- const worker = new ThreadWorker(api, identity, threadId, projectDir, createdAt || Date.now());
5820
+ const worker = new ThreadWorker(api, identity, displayName, threadId, projectDir, createdAt || Date.now());
5681
5821
  worker.onEvicted = (id) => detach(id);
5682
5822
  workers.set(threadId, worker);
5683
5823
  daemonLog(`attached ${threadId.slice(0, 8)} \u2192 ${projectDir}`);
@@ -5722,6 +5862,10 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5722
5862
  const heartbeat = setInterval(() => {
5723
5863
  void touchDaemon(api, identity, version).catch(() => {
5724
5864
  });
5865
+ void getMachineName(api, identity.machine_id).then((n) => {
5866
+ if (n) displayName = n;
5867
+ }).catch(() => {
5868
+ });
5725
5869
  }, HEARTBEAT_MS);
5726
5870
  const reclaim = setInterval(() => {
5727
5871
  for (const worker of workers.values()) {
@@ -5803,6 +5947,31 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5803
5947
  };
5804
5948
  void drainCommands();
5805
5949
  const commandTimer = setInterval(() => void drainCommands(), COMMAND_POLL_MS);
5950
+ let lastFsNonce = null;
5951
+ let fsBusy = false;
5952
+ const drainFsRequests = async () => {
5953
+ if (fsBusy) return;
5954
+ fsBusy = true;
5955
+ try {
5956
+ const req = await readFsRequest(api, identity.machine_id).catch(() => null);
5957
+ if (!req || req.nonce === lastFsNonce) return;
5958
+ lastFsNonce = req.nonce;
5959
+ let ok = true;
5960
+ let result;
5961
+ let error;
5962
+ try {
5963
+ result = browseDirectory(req.path, { showHidden: req.show_hidden });
5964
+ } catch (err) {
5965
+ ok = false;
5966
+ error = err instanceof Error ? err.message : "browse failed";
5967
+ }
5968
+ await writeFsResponse(api, identity.machine_id, { nonce: req.nonce, ok, result, error }).catch(() => {
5969
+ });
5970
+ } finally {
5971
+ fsBusy = false;
5972
+ }
5973
+ };
5974
+ const fsTimer = setInterval(() => void drainFsRequests(), FS_POLL_MS);
5806
5975
  const sweeper = setInterval(() => {
5807
5976
  if (updateReady && ![...workers.values()].some((w) => w.busy)) {
5808
5977
  daemonLog("restarting to apply the installed update");
@@ -5816,6 +5985,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5816
5985
  clearInterval(reclaim);
5817
5986
  clearInterval(updateTimer);
5818
5987
  clearInterval(commandTimer);
5988
+ clearInterval(fsTimer);
5819
5989
  clearInterval(sweeper);
5820
5990
  for (const worker of workers.values()) worker.stop();
5821
5991
  events.close();
@@ -5873,8 +6043,8 @@ function run2(cmd, args) {
5873
6043
  const output4 = `${res.stdout ?? ""}${res.stderr ?? ""}`.trim();
5874
6044
  return { ok: res.status === 0, output: output4 };
5875
6045
  }
5876
- var plistPath = () => path3.join(os9.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
5877
- var unitPath = () => path3.join(os9.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
6046
+ var plistPath = () => path3.join(os10.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
6047
+ var unitPath = () => path3.join(os10.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
5878
6048
  var xmlEscape = (s) => s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
5879
6049
  function installService(command, endpoint) {
5880
6050
  if (process.platform === "darwin") return installLaunchd(command, endpoint);
@@ -5886,7 +6056,7 @@ function installService(command, endpoint) {
5886
6056
  };
5887
6057
  }
5888
6058
  function installLaunchd(command, endpoint) {
5889
- const logDir = path3.join(os9.homedir(), ".standardagents");
6059
+ const logDir = path3.join(os10.homedir(), ".standardagents");
5890
6060
  fs4.mkdirSync(logDir, { recursive: true });
5891
6061
  fs4.mkdirSync(path3.dirname(plistPath()), { recursive: true });
5892
6062
  const envEntries = [
@@ -5959,10 +6129,10 @@ WantedBy=default.target
5959
6129
  if (!enable.ok) {
5960
6130
  return { ok: false, detail: `systemctl enable failed: ${enable.output}` };
5961
6131
  }
5962
- const linger = run2("loginctl", ["enable-linger", os9.userInfo().username]);
6132
+ const linger = run2("loginctl", ["enable-linger", os10.userInfo().username]);
5963
6133
  return {
5964
6134
  ok: true,
5965
- detail: `systemd user unit installed (${unitPath()})` + (linger.ok ? ", lingering enabled" : ` \u2014 enable lingering manually: sudo loginctl enable-linger ${os9.userInfo().username}`)
6135
+ detail: `systemd user unit installed (${unitPath()})` + (linger.ok ? ", lingering enabled" : ` \u2014 enable lingering manually: sudo loginctl enable-linger ${os10.userInfo().username}`)
5966
6136
  };
5967
6137
  }
5968
6138
  function uninstallService() {
@@ -6089,15 +6259,16 @@ async function installCommand(endpointFlag) {
6089
6259
  const endpoint = resolveEndpoint(endpointFlag);
6090
6260
  const api = await ensureSignedIn(endpoint);
6091
6261
  const identity = loadMachineIdentity();
6262
+ const existing = await loadMachine(api, identity.machine_id).catch(() => null);
6263
+ const suggested = machineDisplayName(existing ?? { hostname: os10.hostname(), id: identity.machine_id });
6092
6264
  const rl = readline2.createInterface({ input: stdin, output: stdout });
6093
- const suggested = machineDisplayName(identity);
6094
6265
  const answer = (await rl.question(
6095
6266
  `${c2.bold}Machine name${c2.reset} ${c2.dim}(shown in the session picker)${c2.reset} [${suggested}]: `
6096
6267
  )).trim();
6097
6268
  rl.close();
6098
- if (answer) setMachineName(answer);
6099
- const named = loadMachineIdentity();
6100
- await updateOwnMachineRecord(api, named);
6269
+ if (answer) await setMachineName(api, identity.machine_id, answer);
6270
+ await updateOwnMachineRecord(api, identity);
6271
+ const displayName = answer || suggested;
6101
6272
  stdout.write(`${c2.dim}Installing the always-on service\u2026${c2.reset}
6102
6273
  `);
6103
6274
  const extra = endpointFlag ? ["--endpoint", endpoint] : [];
@@ -6117,7 +6288,7 @@ async function installCommand(endpointFlag) {
6117
6288
  let alive = false;
6118
6289
  while (Date.now() < deadline) {
6119
6290
  await new Promise((r) => setTimeout(r, 2e3));
6120
- const record = await loadMachine(api, named.machine_id);
6291
+ const record = await loadMachine(api, identity.machine_id);
6121
6292
  if (record && daemonOnline(record)) {
6122
6293
  alive = true;
6123
6294
  break;
@@ -6125,7 +6296,7 @@ async function installCommand(endpointFlag) {
6125
6296
  }
6126
6297
  if (alive) {
6127
6298
  stdout.write(
6128
- `${c2.green}\u2713${c2.reset} ${c2.bold}${machineDisplayName(named)}${c2.reset} is online.
6299
+ `${c2.green}\u2713${c2.reset} ${c2.bold}${displayName}${c2.reset} is online.
6129
6300
 
6130
6301
  Sessions started elsewhere can now run on this machine.
6131
6302
  ${c2.dim}Projects register automatically when you run standardcode in a directory here,
@@ -6144,12 +6315,14 @@ async function statusCommand() {
6144
6315
  const status = serviceStatus();
6145
6316
  const identity = loadMachineIdentity();
6146
6317
  stdout.write(`${c2.bold}Service:${c2.reset} ${status.detail}
6147
- `);
6148
- stdout.write(`${c2.bold}Machine:${c2.reset} ${machineDisplayName(identity)} ${c2.dim}(${identity.machine_id})${c2.reset}
6149
6318
  `);
6150
6319
  const endpoint = resolveEndpoint();
6151
6320
  const cred = getCredential(endpoint);
6152
6321
  if (!cred) {
6322
+ stdout.write(
6323
+ `${c2.bold}Machine:${c2.reset} ${os10.hostname()} ${c2.dim}(${identity.machine_id})${c2.reset}
6324
+ `
6325
+ );
6153
6326
  stdout.write(`${c2.bold}Account:${c2.reset} ${c2.yellow}not signed in to ${endpoint}${c2.reset}
6154
6327
  `);
6155
6328
  return;
@@ -6157,6 +6330,10 @@ async function statusCommand() {
6157
6330
  relaxTlsForLocalEndpoint(endpoint);
6158
6331
  const api = new ApiClient(endpoint, cred.access_token);
6159
6332
  const record = await loadMachine(api, identity.machine_id).catch(() => null);
6333
+ stdout.write(
6334
+ `${c2.bold}Machine:${c2.reset} ${machineDisplayName(record ?? { hostname: os10.hostname(), id: identity.machine_id })} ${c2.dim}(${identity.machine_id})${c2.reset}
6335
+ `
6336
+ );
6160
6337
  if (!record) {
6161
6338
  stdout.write(`${c2.bold}Registry:${c2.reset} not registered yet
6162
6339
  `);
@@ -6187,9 +6364,11 @@ async function projectCommand(action, target) {
6187
6364
  const endpoint = resolveEndpoint();
6188
6365
  const api = await ensureSignedIn(endpoint);
6189
6366
  const identity = loadMachineIdentity();
6367
+ const record = await loadMachine(api, identity.machine_id).catch(() => null);
6368
+ const displayName = machineDisplayName(record ?? { hostname: os10.hostname(), id: identity.machine_id });
6190
6369
  if (action === "add") {
6191
6370
  await registerProject(api, identity, dir2);
6192
- stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${machineDisplayName(identity)}.
6371
+ stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${displayName}.
6193
6372
  `);
6194
6373
  } else {
6195
6374
  await unregisterProject(api, identity, dir2);
@@ -6386,7 +6565,7 @@ ${c3.teal}\u25C7${c3.reset} ${c3.dim}Standard Code \u2014 see you soon.${c3.rese
6386
6565
  `);
6387
6566
  }
6388
6567
  function printWelcome(endpoint, projectDir) {
6389
- const home = os9.homedir();
6568
+ const home = os10.homedir();
6390
6569
  const dir2 = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
6391
6570
  const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
6392
6571
  const version = readVersion();
@@ -6454,7 +6633,7 @@ async function main() {
6454
6633
  const endpointOverride = cliArgs.promptEndpoint || typeof endpointArg === "string" && endpointArg.trim() !== "";
6455
6634
  const dirArg = cliArgs.dir;
6456
6635
  const projectDir = path3.resolve(dirArg || process.cwd());
6457
- const machine = os9.hostname();
6636
+ const machine = os10.hostname();
6458
6637
  const reader = { rl: null };
6459
6638
  let handoffClosing = false;
6460
6639
  let preflightArmed = false;
@@ -6639,7 +6818,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
6639
6818
  void registerProject(api, identity, projectDir).catch(() => {
6640
6819
  });
6641
6820
  const tui = new Tui(1);
6642
- const home = os9.homedir();
6821
+ const home = os10.homedir();
6643
6822
  const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
6644
6823
  const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
6645
6824
  const session = { mode: "local", identity };
@@ -6907,6 +7086,12 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
6907
7086
  if (ownerDaemonMachine === session.identity.machine_id) claim = "takeover";
6908
7087
  }
6909
7088
  if (!remote) {
7089
+ const localDisplayName = machineDisplayName(
7090
+ await loadMachine(api, session.identity.machine_id).catch(() => null) ?? {
7091
+ hostname: machine,
7092
+ id: session.identity.machine_id
7093
+ }
7094
+ );
6910
7095
  exec = new ExecutionSession({
6911
7096
  api,
6912
7097
  threadId,
@@ -6915,7 +7100,7 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
6915
7100
  perm,
6916
7101
  identity: {
6917
7102
  clientId: interactiveClientId(session.identity),
6918
- clientName: `${machineDisplayName(session.identity)} (terminal)`,
7103
+ clientName: `${localDisplayName} (terminal)`,
6919
7104
  clientKind: "interactive",
6920
7105
  claim
6921
7106
  },
@@ -7726,24 +7911,27 @@ async function runMachinesMenu(tui, api, self) {
7726
7911
  async function manageMachine(tui, api, self, machine) {
7727
7912
  const isSelf = machine.id === self.machine_id;
7728
7913
  const online = daemonOnline(machine);
7729
- const remoteReachable = !isSelf && !!machine.daemon;
7914
+ const canRunCommands = isSelf || !!machine.daemon;
7915
+ const options = [
7916
+ { label: "Rename", value: "rename" }
7917
+ ];
7918
+ if (canRunCommands) {
7919
+ options.push(
7920
+ { label: "Update standardcode", hint: `v${machine.version ?? "?"} \u2192 latest`, value: "update" },
7921
+ { label: "Manage projects", hint: `${Object.keys(machine.projects).length}`, value: "projects" }
7922
+ );
7923
+ }
7924
+ options.push({ label: "Back", value: "back" });
7730
7925
  if (!isSelf && !machine.daemon) {
7731
7926
  tui.print(
7732
- `${c3.yellow}${machine.name} has no daemon${c3.reset} ${c3.gray}\u2014 it only runs the interactive CLI, so it can't be managed remotely. Manage it from that machine, or install its daemon.${c3.reset}`
7927
+ `${c3.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c3.reset}`
7733
7928
  );
7734
- return;
7735
- }
7736
- if (remoteReachable && !online) {
7929
+ } else if (!isSelf && machine.daemon && !online) {
7737
7930
  tui.print(
7738
7931
  `${c3.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c3.reset}`
7739
7932
  );
7740
7933
  }
7741
- const action = await tui.select(`${c3.bold}${machine.name}${c3.reset}`, [
7742
- { label: "Rename", value: "rename" },
7743
- { label: "Update standardcode", hint: `v${machine.version ?? "?"} \u2192 latest`, value: "update" },
7744
- { label: "Manage projects", hint: `${Object.keys(machine.projects).length}`, value: "projects" },
7745
- { label: "Back", value: "back" }
7746
- ]);
7934
+ const action = await tui.select(`${c3.bold}${machine.name}${c3.reset}`, options);
7747
7935
  if (!action || action === "back") return;
7748
7936
  const dispatch = async (kind, args) => {
7749
7937
  if (isSelf) {
@@ -7755,9 +7943,9 @@ async function manageMachine(tui, api, self, machine) {
7755
7943
  const applyNote = isSelf ? "applied" : `queued \u2014 ${machine.name}'s daemon will apply it within ~10s`;
7756
7944
  if (action === "rename") {
7757
7945
  const name = await tui.prompt(`New name for ${machine.name}`, machine.name);
7758
- if (!name || !name.trim()) return;
7759
- await dispatch("rename", { name: name.trim() });
7760
- tui.print(`${c3.green}\u2713${c3.reset} Rename to "${name.trim()}" ${c3.gray}${applyNote}.${c3.reset}`);
7946
+ if (name === null || !name.trim()) return;
7947
+ await setMachineName(api, machine.id, name.trim());
7948
+ tui.print(`${c3.green}\u2713${c3.reset} Renamed ${c3.bold}${machine.name}${c3.reset} \u2192 ${c3.bold}${name.trim()}${c3.reset}.`);
7761
7949
  } else if (action === "update") {
7762
7950
  if (isSelf) {
7763
7951
  await runUpdateCommand(tui);
@@ -7786,12 +7974,12 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
7786
7974
  );
7787
7975
  if (!picked) return;
7788
7976
  if (picked === ADD) {
7789
- const path12 = await tui.prompt(
7977
+ const path13 = await tui.prompt(
7790
7978
  `Absolute project path on ${machine.name}`,
7791
7979
  machine.id === self.machine_id ? process.cwd() : "/home/you/project"
7792
7980
  );
7793
- if (!path12 || !path12.trim()) return;
7794
- const trimmed = path12.trim();
7981
+ if (!path13 || !path13.trim()) return;
7982
+ const trimmed = path13.trim();
7795
7983
  if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
7796
7984
  tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
7797
7985
  return;