@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 +269 -81
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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:
|
|
5138
|
-
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
|
|
5205
|
+
const existing = await loadRawMachine(api, identity.machine_id);
|
|
5150
5206
|
const record = existing ?? newRecord(identity);
|
|
5151
|
-
record.
|
|
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
|
|
5245
|
-
if (!
|
|
5246
|
-
await registerProject(api, identity,
|
|
5247
|
-
return `added project ${
|
|
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
|
|
5251
|
-
if (!
|
|
5252
|
-
await unregisterProject(api, identity,
|
|
5253
|
-
return `removed project ${
|
|
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(
|
|
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(/^~(?=\/|$)/,
|
|
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} (${
|
|
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(
|
|
5877
|
-
var unitPath = () => path3.join(
|
|
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, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
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(
|
|
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",
|
|
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 ${
|
|
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
|
-
|
|
6100
|
-
|
|
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,
|
|
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}${
|
|
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 ${
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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: `${
|
|
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
|
|
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.
|
|
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
|
-
|
|
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 (
|
|
7759
|
-
await
|
|
7760
|
-
tui.print(`${c3.green}\u2713${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
|
|
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 (!
|
|
7794
|
-
const trimmed =
|
|
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;
|