@standardagents/code 0.9.2 → 0.9.3

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
@@ -5060,7 +5060,7 @@ 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,7 @@ function machineIdFromDaemonClientId(clientId) {
5096
5087
  }
5097
5088
  var KEY_PREFIX = "standardcode.machine.";
5098
5089
  var CMD_SUFFIX = ".cmd";
5090
+ var NAME_SUFFIX = ".name";
5099
5091
  var DAEMON_ONLINE_WINDOW_MS = 90 * 1e3;
5100
5092
  function machineKey(machineId) {
5101
5093
  return `${KEY_PREFIX}${machineId}`;
@@ -5103,6 +5095,20 @@ function machineKey(machineId) {
5103
5095
  function commandKey(machineId) {
5104
5096
  return `${KEY_PREFIX}${machineId}${CMD_SUFFIX}`;
5105
5097
  }
5098
+ function nameKey(machineId) {
5099
+ return `${KEY_PREFIX}${machineId}${NAME_SUFFIX}`;
5100
+ }
5101
+ function machineDisplayName(record) {
5102
+ return record.name?.trim() || "" || (record.hostname || "") || (record.id || "") || "machine";
5103
+ }
5104
+ async function getMachineName(api, machineId) {
5105
+ const v = await api.userKvGet(nameKey(machineId));
5106
+ return typeof v === "string" && v.trim() ? v.trim() : null;
5107
+ }
5108
+ async function setMachineName(api, machineId, name) {
5109
+ const trimmed = name.trim();
5110
+ await api.userKvSet(nameKey(machineId), trimmed || null);
5111
+ }
5106
5112
  function parseMachineRecord(value) {
5107
5113
  if (!value || typeof value !== "object" || Array.isArray(value)) return null;
5108
5114
  const r = value;
@@ -5120,12 +5126,36 @@ function parseMachineRecord(value) {
5120
5126
  updated_at: typeof r.updated_at === "number" ? r.updated_at : 0
5121
5127
  };
5122
5128
  }
5129
+ async function loadRawMachine(api, machineId) {
5130
+ return parseMachineRecord(await api.userKvGet(machineKey(machineId)));
5131
+ }
5123
5132
  async function loadMachines(api) {
5124
5133
  const entries = await api.userKvList(KEY_PREFIX);
5125
- return entries.map((e) => parseMachineRecord(e.value)).filter((m) => m !== null);
5134
+ const records = [];
5135
+ const names = /* @__PURE__ */ new Map();
5136
+ for (const e of entries) {
5137
+ const rest = e.key.slice(KEY_PREFIX.length);
5138
+ if (rest.endsWith(CMD_SUFFIX)) continue;
5139
+ if (rest.endsWith(NAME_SUFFIX)) {
5140
+ const id = rest.slice(0, -NAME_SUFFIX.length);
5141
+ if (typeof e.value === "string" && e.value.trim()) names.set(id, e.value.trim());
5142
+ continue;
5143
+ }
5144
+ const rec = parseMachineRecord(e.value);
5145
+ if (rec) records.push(rec);
5146
+ }
5147
+ for (const rec of records) {
5148
+ const override = names.get(rec.id);
5149
+ if (override) rec.name = override;
5150
+ }
5151
+ return records;
5126
5152
  }
5127
5153
  async function loadMachine(api, machineId) {
5128
- return parseMachineRecord(await api.userKvGet(machineKey(machineId)));
5154
+ const rec = await loadRawMachine(api, machineId);
5155
+ if (!rec) return null;
5156
+ const override = await getMachineName(api, machineId);
5157
+ if (override) rec.name = override;
5158
+ return rec;
5129
5159
  }
5130
5160
  function daemonOnline(record, now = Date.now()) {
5131
5161
  return !!record.daemon && now - record.daemon.last_seen_at < DAEMON_ONLINE_WINDOW_MS;
@@ -5134,7 +5164,7 @@ function newRecord(identity) {
5134
5164
  const now = Date.now();
5135
5165
  return {
5136
5166
  id: identity.machine_id,
5137
- name: machineDisplayName(identity),
5167
+ name: os9.hostname(),
5138
5168
  hostname: os9.hostname(),
5139
5169
  platform: process.platform,
5140
5170
  arch: process.arch,
@@ -5146,9 +5176,8 @@ function newRecord(identity) {
5146
5176
  };
5147
5177
  }
5148
5178
  async function updateOwnMachineRecord(api, identity, mutate) {
5149
- const existing = await loadMachine(api, identity.machine_id);
5179
+ const existing = await loadRawMachine(api, identity.machine_id);
5150
5180
  const record = existing ?? newRecord(identity);
5151
- record.name = machineDisplayName(loadMachineIdentity());
5152
5181
  record.hostname = os9.hostname();
5153
5182
  record.platform = process.platform;
5154
5183
  record.arch = process.arch;
@@ -5233,13 +5262,6 @@ async function clearMachineCommands(api, machineId, appliedIds) {
5233
5262
  }
5234
5263
  async function applyMachineCommand(api, identity, cmd) {
5235
5264
  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
5265
  case "add_project": {
5244
5266
  const path12 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
5245
5267
  if (!path12) return "add_project: ignored (no path)";
@@ -5519,11 +5541,10 @@ function pathFromTags(tags) {
5519
5541
  return raw.replace(/^~(?=\/|$)/, os9.homedir());
5520
5542
  }
5521
5543
  var ThreadWorker = class {
5522
- constructor(api, identity, threadId, projectDir, createdAt) {
5544
+ constructor(api, identity, machineName, threadId, projectDir, createdAt) {
5523
5545
  this.threadId = threadId;
5524
5546
  this.projectDir = projectDir;
5525
5547
  this.createdAt = createdAt;
5526
- const machineName = machineDisplayName(identity);
5527
5548
  this.perm = { level: 1, alwaysAllow: /* @__PURE__ */ new Set(), allowRisk: /* @__PURE__ */ new Set() };
5528
5549
  this.session = new ExecutionSession({
5529
5550
  api,
@@ -5648,9 +5669,13 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5648
5669
  `);
5649
5670
  process.exit(1);
5650
5671
  }
5672
+ let displayName = machineDisplayName(await loadMachine(api, identity.machine_id).catch(() => null) ?? {
5673
+ hostname: os9.hostname(),
5674
+ id: identity.machine_id
5675
+ });
5651
5676
  const applied = consumeAppliedUpdate(version);
5652
5677
  daemonLog(
5653
- `daemon starting \u2014 v${version}${applied ? " (freshly auto-updated)" : ""}, machine ${identity.machine_id} (${machineDisplayName(identity)}), endpoint ${endpoint}`
5678
+ `daemon starting \u2014 v${version}${applied ? " (freshly auto-updated)" : ""}, machine ${identity.machine_id} (${displayName}), endpoint ${endpoint}`
5654
5679
  );
5655
5680
  process.on("uncaughtException", (err) => {
5656
5681
  daemonLog(`FATAL uncaughtException: ${err?.stack || err}`);
@@ -5677,7 +5702,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5677
5702
  daemonLog(`cannot prepare project dir ${projectDir}: ${e instanceof Error ? e.message : String(e)}`);
5678
5703
  return;
5679
5704
  }
5680
- const worker = new ThreadWorker(api, identity, threadId, projectDir, createdAt || Date.now());
5705
+ const worker = new ThreadWorker(api, identity, displayName, threadId, projectDir, createdAt || Date.now());
5681
5706
  worker.onEvicted = (id) => detach(id);
5682
5707
  workers.set(threadId, worker);
5683
5708
  daemonLog(`attached ${threadId.slice(0, 8)} \u2192 ${projectDir}`);
@@ -5722,6 +5747,10 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
5722
5747
  const heartbeat = setInterval(() => {
5723
5748
  void touchDaemon(api, identity, version).catch(() => {
5724
5749
  });
5750
+ void getMachineName(api, identity.machine_id).then((n) => {
5751
+ if (n) displayName = n;
5752
+ }).catch(() => {
5753
+ });
5725
5754
  }, HEARTBEAT_MS);
5726
5755
  const reclaim = setInterval(() => {
5727
5756
  for (const worker of workers.values()) {
@@ -6089,15 +6118,16 @@ async function installCommand(endpointFlag) {
6089
6118
  const endpoint = resolveEndpoint(endpointFlag);
6090
6119
  const api = await ensureSignedIn(endpoint);
6091
6120
  const identity = loadMachineIdentity();
6121
+ const existing = await loadMachine(api, identity.machine_id).catch(() => null);
6122
+ const suggested = machineDisplayName(existing ?? { hostname: os9.hostname(), id: identity.machine_id });
6092
6123
  const rl = readline2.createInterface({ input: stdin, output: stdout });
6093
- const suggested = machineDisplayName(identity);
6094
6124
  const answer = (await rl.question(
6095
6125
  `${c2.bold}Machine name${c2.reset} ${c2.dim}(shown in the session picker)${c2.reset} [${suggested}]: `
6096
6126
  )).trim();
6097
6127
  rl.close();
6098
- if (answer) setMachineName(answer);
6099
- const named = loadMachineIdentity();
6100
- await updateOwnMachineRecord(api, named);
6128
+ if (answer) await setMachineName(api, identity.machine_id, answer);
6129
+ await updateOwnMachineRecord(api, identity);
6130
+ const displayName = answer || suggested;
6101
6131
  stdout.write(`${c2.dim}Installing the always-on service\u2026${c2.reset}
6102
6132
  `);
6103
6133
  const extra = endpointFlag ? ["--endpoint", endpoint] : [];
@@ -6117,7 +6147,7 @@ async function installCommand(endpointFlag) {
6117
6147
  let alive = false;
6118
6148
  while (Date.now() < deadline) {
6119
6149
  await new Promise((r) => setTimeout(r, 2e3));
6120
- const record = await loadMachine(api, named.machine_id);
6150
+ const record = await loadMachine(api, identity.machine_id);
6121
6151
  if (record && daemonOnline(record)) {
6122
6152
  alive = true;
6123
6153
  break;
@@ -6125,7 +6155,7 @@ async function installCommand(endpointFlag) {
6125
6155
  }
6126
6156
  if (alive) {
6127
6157
  stdout.write(
6128
- `${c2.green}\u2713${c2.reset} ${c2.bold}${machineDisplayName(named)}${c2.reset} is online.
6158
+ `${c2.green}\u2713${c2.reset} ${c2.bold}${displayName}${c2.reset} is online.
6129
6159
 
6130
6160
  Sessions started elsewhere can now run on this machine.
6131
6161
  ${c2.dim}Projects register automatically when you run standardcode in a directory here,
@@ -6144,12 +6174,14 @@ async function statusCommand() {
6144
6174
  const status = serviceStatus();
6145
6175
  const identity = loadMachineIdentity();
6146
6176
  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
6177
  `);
6150
6178
  const endpoint = resolveEndpoint();
6151
6179
  const cred = getCredential(endpoint);
6152
6180
  if (!cred) {
6181
+ stdout.write(
6182
+ `${c2.bold}Machine:${c2.reset} ${os9.hostname()} ${c2.dim}(${identity.machine_id})${c2.reset}
6183
+ `
6184
+ );
6153
6185
  stdout.write(`${c2.bold}Account:${c2.reset} ${c2.yellow}not signed in to ${endpoint}${c2.reset}
6154
6186
  `);
6155
6187
  return;
@@ -6157,6 +6189,10 @@ async function statusCommand() {
6157
6189
  relaxTlsForLocalEndpoint(endpoint);
6158
6190
  const api = new ApiClient(endpoint, cred.access_token);
6159
6191
  const record = await loadMachine(api, identity.machine_id).catch(() => null);
6192
+ stdout.write(
6193
+ `${c2.bold}Machine:${c2.reset} ${machineDisplayName(record ?? { hostname: os9.hostname(), id: identity.machine_id })} ${c2.dim}(${identity.machine_id})${c2.reset}
6194
+ `
6195
+ );
6160
6196
  if (!record) {
6161
6197
  stdout.write(`${c2.bold}Registry:${c2.reset} not registered yet
6162
6198
  `);
@@ -6187,9 +6223,11 @@ async function projectCommand(action, target) {
6187
6223
  const endpoint = resolveEndpoint();
6188
6224
  const api = await ensureSignedIn(endpoint);
6189
6225
  const identity = loadMachineIdentity();
6226
+ const record = await loadMachine(api, identity.machine_id).catch(() => null);
6227
+ const displayName = machineDisplayName(record ?? { hostname: os9.hostname(), id: identity.machine_id });
6190
6228
  if (action === "add") {
6191
6229
  await registerProject(api, identity, dir2);
6192
- stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${machineDisplayName(identity)}.
6230
+ stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${displayName}.
6193
6231
  `);
6194
6232
  } else {
6195
6233
  await unregisterProject(api, identity, dir2);
@@ -6907,6 +6945,12 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
6907
6945
  if (ownerDaemonMachine === session.identity.machine_id) claim = "takeover";
6908
6946
  }
6909
6947
  if (!remote) {
6948
+ const localDisplayName = machineDisplayName(
6949
+ await loadMachine(api, session.identity.machine_id).catch(() => null) ?? {
6950
+ hostname: machine,
6951
+ id: session.identity.machine_id
6952
+ }
6953
+ );
6910
6954
  exec = new ExecutionSession({
6911
6955
  api,
6912
6956
  threadId,
@@ -6915,7 +6959,7 @@ async function runInteractive(tui, api, threadId, projectDir, machine, resumed,
6915
6959
  perm,
6916
6960
  identity: {
6917
6961
  clientId: interactiveClientId(session.identity),
6918
- clientName: `${machineDisplayName(session.identity)} (terminal)`,
6962
+ clientName: `${localDisplayName} (terminal)`,
6919
6963
  clientKind: "interactive",
6920
6964
  claim
6921
6965
  },
@@ -7726,24 +7770,27 @@ async function runMachinesMenu(tui, api, self) {
7726
7770
  async function manageMachine(tui, api, self, machine) {
7727
7771
  const isSelf = machine.id === self.machine_id;
7728
7772
  const online = daemonOnline(machine);
7729
- const remoteReachable = !isSelf && !!machine.daemon;
7773
+ const canRunCommands = isSelf || !!machine.daemon;
7774
+ const options = [
7775
+ { label: "Rename", value: "rename" }
7776
+ ];
7777
+ if (canRunCommands) {
7778
+ options.push(
7779
+ { label: "Update standardcode", hint: `v${machine.version ?? "?"} \u2192 latest`, value: "update" },
7780
+ { label: "Manage projects", hint: `${Object.keys(machine.projects).length}`, value: "projects" }
7781
+ );
7782
+ }
7783
+ options.push({ label: "Back", value: "back" });
7730
7784
  if (!isSelf && !machine.daemon) {
7731
7785
  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}`
7786
+ `${c3.dim}${machine.name} has no daemon \u2014 you can rename it here; update and project changes need its daemon installed.${c3.reset}`
7733
7787
  );
7734
- return;
7735
- }
7736
- if (remoteReachable && !online) {
7788
+ } else if (!isSelf && machine.daemon && !online) {
7737
7789
  tui.print(
7738
7790
  `${c3.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c3.reset}`
7739
7791
  );
7740
7792
  }
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
- ]);
7793
+ const action = await tui.select(`${c3.bold}${machine.name}${c3.reset}`, options);
7747
7794
  if (!action || action === "back") return;
7748
7795
  const dispatch = async (kind, args) => {
7749
7796
  if (isSelf) {
@@ -7755,9 +7802,9 @@ async function manageMachine(tui, api, self, machine) {
7755
7802
  const applyNote = isSelf ? "applied" : `queued \u2014 ${machine.name}'s daemon will apply it within ~10s`;
7756
7803
  if (action === "rename") {
7757
7804
  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}`);
7805
+ if (name === null || !name.trim()) return;
7806
+ await setMachineName(api, machine.id, name.trim());
7807
+ tui.print(`${c3.green}\u2713${c3.reset} Renamed ${c3.bold}${machine.name}${c3.reset} \u2192 ${c3.bold}${name.trim()}${c3.reset}.`);
7761
7808
  } else if (action === "update") {
7762
7809
  if (isSelf) {
7763
7810
  await runUpdateCommand(tui);