@standardagents/code 0.9.3 → 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 +298 -47
- 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("..")) {
|
|
@@ -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) {
|
|
@@ -2816,7 +2833,7 @@ function fromFile(filePath) {
|
|
|
2816
2833
|
}
|
|
2817
2834
|
}
|
|
2818
2835
|
async function readDarwin() {
|
|
2819
|
-
const tmp = path3.join(
|
|
2836
|
+
const tmp = path3.join(os10.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
|
|
2820
2837
|
const script = [
|
|
2821
2838
|
`set d to the clipboard as \xABclass PNGf\xBB`,
|
|
2822
2839
|
`set f to open for access POSIX file "${tmp}" with write permission`,
|
|
@@ -2853,7 +2870,7 @@ async function readLinux() {
|
|
|
2853
2870
|
return null;
|
|
2854
2871
|
}
|
|
2855
2872
|
async function readWindows() {
|
|
2856
|
-
const tmp = path3.join(
|
|
2873
|
+
const tmp = path3.join(os10.tmpdir(), `sc-clip-${process.pid}-${Date.now()}.png`);
|
|
2857
2874
|
const ps = [
|
|
2858
2875
|
"Add-Type -AssemblyName System.Windows.Forms;",
|
|
2859
2876
|
"$img = [System.Windows.Forms.Clipboard]::GetImage();",
|
|
@@ -3071,7 +3088,7 @@ var C = {
|
|
|
3071
3088
|
red: "\x1B[31m",
|
|
3072
3089
|
blue: "\x1B[34m",
|
|
3073
3090
|
magenta: "\x1B[35m",
|
|
3074
|
-
gray:
|
|
3091
|
+
gray: themeGray,
|
|
3075
3092
|
teal: "\x1B[38;5;37m"
|
|
3076
3093
|
};
|
|
3077
3094
|
var FRAMES = ["\u28F7", "\u28EF", "\u28DF", "\u287F", "\u28BF", "\u28FB", "\u28FD", "\u28FE"];
|
|
@@ -3225,6 +3242,11 @@ var Tui = class _Tui {
|
|
|
3225
3242
|
/** Enter while the `[⚙ n bg]` badge is selected — opens the bg process panel. */
|
|
3226
3243
|
onBgBadge = () => {
|
|
3227
3244
|
};
|
|
3245
|
+
/** Fired (deduped) when the input draft text changes — index.ts debounce-writes
|
|
3246
|
+
* it into the shared cross-surface presence buffer. */
|
|
3247
|
+
onDraftChange = () => {
|
|
3248
|
+
};
|
|
3249
|
+
lastDraftSeen = "";
|
|
3228
3250
|
onQuit = () => process.exit(0);
|
|
3229
3251
|
levelListeners = [];
|
|
3230
3252
|
/**
|
|
@@ -3935,6 +3957,13 @@ var Tui = class _Tui {
|
|
|
3935
3957
|
*/
|
|
3936
3958
|
renderBottom() {
|
|
3937
3959
|
if (!this.started || this.takeoverHandler) return;
|
|
3960
|
+
if (this.inputBuffer !== this.lastDraftSeen) {
|
|
3961
|
+
this.lastDraftSeen = this.inputBuffer;
|
|
3962
|
+
try {
|
|
3963
|
+
this.onDraftChange(this.inputBuffer);
|
|
3964
|
+
} catch {
|
|
3965
|
+
}
|
|
3966
|
+
}
|
|
3938
3967
|
if (this.resizePending) return;
|
|
3939
3968
|
const cols2 = process.stdout.columns || 80;
|
|
3940
3969
|
this.moveToRegionTop();
|
|
@@ -4920,7 +4949,7 @@ function renderMarkdown(src, cols2 = 80) {
|
|
|
4920
4949
|
}
|
|
4921
4950
|
return out;
|
|
4922
4951
|
}
|
|
4923
|
-
var DIR = path3.join(
|
|
4952
|
+
var DIR = path3.join(os10.homedir(), ".standardagents");
|
|
4924
4953
|
var FILE = path3.join(DIR, "credentials");
|
|
4925
4954
|
function normalizeEndpoint(endpoint) {
|
|
4926
4955
|
let e = endpoint.trim();
|
|
@@ -5054,7 +5083,7 @@ function relaxTlsForLocalEndpoint(endpoint) {
|
|
|
5054
5083
|
process.env.NODE_TLS_REJECT_UNAUTHORIZED = "0";
|
|
5055
5084
|
return true;
|
|
5056
5085
|
}
|
|
5057
|
-
var dir = () => path3.join(
|
|
5086
|
+
var dir = () => path3.join(os10.homedir(), ".standardagents");
|
|
5058
5087
|
var file = () => path3.join(dir(), "machine.json");
|
|
5059
5088
|
function loadMachineIdentity() {
|
|
5060
5089
|
try {
|
|
@@ -5088,6 +5117,9 @@ 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";
|
|
5121
|
+
var FSREQ_SUFFIX = ".fsreq";
|
|
5122
|
+
var FSRES_SUFFIX = ".fsres";
|
|
5091
5123
|
var DAEMON_ONLINE_WINDOW_MS = 90 * 1e3;
|
|
5092
5124
|
function machineKey(machineId) {
|
|
5093
5125
|
return `${KEY_PREFIX}${machineId}`;
|
|
@@ -5098,6 +5130,41 @@ function commandKey(machineId) {
|
|
|
5098
5130
|
function nameKey(machineId) {
|
|
5099
5131
|
return `${KEY_PREFIX}${machineId}${NAME_SUFFIX}`;
|
|
5100
5132
|
}
|
|
5133
|
+
function iconKey(machineId) {
|
|
5134
|
+
return `${KEY_PREFIX}${machineId}${ICON_SUFFIX}`;
|
|
5135
|
+
}
|
|
5136
|
+
function fsRequestKey(machineId) {
|
|
5137
|
+
return `${KEY_PREFIX}${machineId}${FSREQ_SUFFIX}`;
|
|
5138
|
+
}
|
|
5139
|
+
function fsResponseKey(machineId) {
|
|
5140
|
+
return `${KEY_PREFIX}${machineId}${FSRES_SUFFIX}`;
|
|
5141
|
+
}
|
|
5142
|
+
function parseFsRequest(value) {
|
|
5143
|
+
if (!value || typeof value !== "object") return null;
|
|
5144
|
+
const r = value;
|
|
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;
|
|
5147
|
+
return {
|
|
5148
|
+
nonce: r.nonce,
|
|
5149
|
+
op,
|
|
5150
|
+
path: r.path,
|
|
5151
|
+
name: typeof r.name === "string" ? r.name : void 0,
|
|
5152
|
+
show_hidden: r.show_hidden === true,
|
|
5153
|
+
requested_at: typeof r.requested_at === "number" ? r.requested_at : Date.now()
|
|
5154
|
+
};
|
|
5155
|
+
}
|
|
5156
|
+
async function readFsRequest(api, machineId) {
|
|
5157
|
+
return parseFsRequest(await api.userKvGet(fsRequestKey(machineId)));
|
|
5158
|
+
}
|
|
5159
|
+
async function writeFsResponse(api, machineId, res) {
|
|
5160
|
+
await api.userKvSet(fsResponseKey(machineId), { ...res, responded_at: Date.now() });
|
|
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
|
+
}
|
|
5101
5168
|
function machineDisplayName(record) {
|
|
5102
5169
|
return record.name?.trim() || "" || (record.hostname || "") || (record.id || "") || "machine";
|
|
5103
5170
|
}
|
|
@@ -5133,28 +5200,45 @@ async function loadMachines(api) {
|
|
|
5133
5200
|
const entries = await api.userKvList(KEY_PREFIX);
|
|
5134
5201
|
const records = [];
|
|
5135
5202
|
const names = /* @__PURE__ */ new Map();
|
|
5203
|
+
const icons = /* @__PURE__ */ new Map();
|
|
5136
5204
|
for (const e of entries) {
|
|
5137
5205
|
const rest = e.key.slice(KEY_PREFIX.length);
|
|
5138
|
-
if (rest.endsWith(CMD_SUFFIX)) continue;
|
|
5206
|
+
if (rest.endsWith(CMD_SUFFIX) || rest.endsWith(FSREQ_SUFFIX) || rest.endsWith(FSRES_SUFFIX)) continue;
|
|
5139
5207
|
if (rest.endsWith(NAME_SUFFIX)) {
|
|
5140
5208
|
const id = rest.slice(0, -NAME_SUFFIX.length);
|
|
5141
5209
|
if (typeof e.value === "string" && e.value.trim()) names.set(id, e.value.trim());
|
|
5142
5210
|
continue;
|
|
5143
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
|
+
}
|
|
5144
5217
|
const rec = parseMachineRecord(e.value);
|
|
5145
5218
|
if (rec) records.push(rec);
|
|
5146
5219
|
}
|
|
5147
5220
|
for (const rec of records) {
|
|
5148
5221
|
const override = names.get(rec.id);
|
|
5149
5222
|
if (override) rec.name = override;
|
|
5223
|
+
const icon = icons.get(rec.id);
|
|
5224
|
+
if (icon) rec.icon = icon;
|
|
5150
5225
|
}
|
|
5151
5226
|
return records;
|
|
5152
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
|
+
}
|
|
5153
5236
|
async function loadMachine(api, machineId) {
|
|
5154
5237
|
const rec = await loadRawMachine(api, machineId);
|
|
5155
5238
|
if (!rec) return null;
|
|
5156
|
-
const override = await getMachineName(api, machineId);
|
|
5239
|
+
const [override, icon] = await Promise.all([getMachineName(api, machineId), getMachineIcon(api, machineId)]);
|
|
5157
5240
|
if (override) rec.name = override;
|
|
5241
|
+
if (icon) rec.icon = icon;
|
|
5158
5242
|
return rec;
|
|
5159
5243
|
}
|
|
5160
5244
|
function daemonOnline(record, now = Date.now()) {
|
|
@@ -5164,8 +5248,8 @@ function newRecord(identity) {
|
|
|
5164
5248
|
const now = Date.now();
|
|
5165
5249
|
return {
|
|
5166
5250
|
id: identity.machine_id,
|
|
5167
|
-
name:
|
|
5168
|
-
hostname:
|
|
5251
|
+
name: os10.hostname(),
|
|
5252
|
+
hostname: os10.hostname(),
|
|
5169
5253
|
platform: process.platform,
|
|
5170
5254
|
arch: process.arch,
|
|
5171
5255
|
version: readVersion() || void 0,
|
|
@@ -5178,7 +5262,7 @@ function newRecord(identity) {
|
|
|
5178
5262
|
async function updateOwnMachineRecord(api, identity, mutate) {
|
|
5179
5263
|
const existing = await loadRawMachine(api, identity.machine_id);
|
|
5180
5264
|
const record = existing ?? newRecord(identity);
|
|
5181
|
-
record.hostname =
|
|
5265
|
+
record.hostname = os10.hostname();
|
|
5182
5266
|
record.platform = process.platform;
|
|
5183
5267
|
record.arch = process.arch;
|
|
5184
5268
|
record.version = readVersion() || record.version;
|
|
@@ -5263,16 +5347,16 @@ async function clearMachineCommands(api, machineId, appliedIds) {
|
|
|
5263
5347
|
async function applyMachineCommand(api, identity, cmd) {
|
|
5264
5348
|
switch (cmd.kind) {
|
|
5265
5349
|
case "add_project": {
|
|
5266
|
-
const
|
|
5267
|
-
if (!
|
|
5268
|
-
await registerProject(api, identity,
|
|
5269
|
-
return `added project ${
|
|
5350
|
+
const path13 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
|
|
5351
|
+
if (!path13) return "add_project: ignored (no path)";
|
|
5352
|
+
await registerProject(api, identity, path13);
|
|
5353
|
+
return `added project ${path13}`;
|
|
5270
5354
|
}
|
|
5271
5355
|
case "remove_project": {
|
|
5272
|
-
const
|
|
5273
|
-
if (!
|
|
5274
|
-
await unregisterProject(api, identity,
|
|
5275
|
-
return `removed project ${
|
|
5356
|
+
const path13 = typeof cmd.args?.path === "string" ? cmd.args.path : "";
|
|
5357
|
+
if (!path13) return "remove_project: ignored (no path)";
|
|
5358
|
+
await unregisterProject(api, identity, path13);
|
|
5359
|
+
return `removed project ${path13}`;
|
|
5276
5360
|
}
|
|
5277
5361
|
case "update":
|
|
5278
5362
|
return "update requested";
|
|
@@ -5334,6 +5418,114 @@ async function awaitApprovalViaRelay(api, threadId, request, options = {}) {
|
|
|
5334
5418
|
});
|
|
5335
5419
|
}
|
|
5336
5420
|
}
|
|
5421
|
+
var PROJECT_MARKERS = [
|
|
5422
|
+
".git",
|
|
5423
|
+
"package.json",
|
|
5424
|
+
"pyproject.toml",
|
|
5425
|
+
"Cargo.toml",
|
|
5426
|
+
"go.mod",
|
|
5427
|
+
"pom.xml",
|
|
5428
|
+
"build.gradle",
|
|
5429
|
+
"Gemfile",
|
|
5430
|
+
"composer.json",
|
|
5431
|
+
"requirements.txt",
|
|
5432
|
+
".standardagents"
|
|
5433
|
+
];
|
|
5434
|
+
var MAX_ENTRIES2 = 500;
|
|
5435
|
+
function resolveBrowsePath(input3) {
|
|
5436
|
+
const home = os10.homedir();
|
|
5437
|
+
let p = (input3 ?? "").trim();
|
|
5438
|
+
if (!p) return home;
|
|
5439
|
+
if (p === "~") return home;
|
|
5440
|
+
if (p.startsWith("~/")) p = path3.join(home, p.slice(2));
|
|
5441
|
+
return path3.resolve(p);
|
|
5442
|
+
}
|
|
5443
|
+
function markers(dirPath) {
|
|
5444
|
+
let repo = false;
|
|
5445
|
+
let project = false;
|
|
5446
|
+
for (const marker of PROJECT_MARKERS) {
|
|
5447
|
+
let hit = false;
|
|
5448
|
+
try {
|
|
5449
|
+
hit = fs4.existsSync(path3.join(dirPath, marker));
|
|
5450
|
+
} catch {
|
|
5451
|
+
hit = false;
|
|
5452
|
+
}
|
|
5453
|
+
if (!hit) continue;
|
|
5454
|
+
project = true;
|
|
5455
|
+
if (marker === ".git") repo = true;
|
|
5456
|
+
if (repo) break;
|
|
5457
|
+
}
|
|
5458
|
+
return { project, repo };
|
|
5459
|
+
}
|
|
5460
|
+
function browseDirectory(input3, opts = {}) {
|
|
5461
|
+
const home = os10.homedir();
|
|
5462
|
+
const abs = resolveBrowsePath(input3);
|
|
5463
|
+
const parent = path3.dirname(abs);
|
|
5464
|
+
const base = {
|
|
5465
|
+
path: abs,
|
|
5466
|
+
parent: parent === abs ? null : parent,
|
|
5467
|
+
home
|
|
5468
|
+
};
|
|
5469
|
+
let dirents;
|
|
5470
|
+
try {
|
|
5471
|
+
const stat = fs4.statSync(abs);
|
|
5472
|
+
if (!stat.isDirectory()) {
|
|
5473
|
+
return { ...base, entries: [], truncated: false, error: "Not a directory" };
|
|
5474
|
+
}
|
|
5475
|
+
dirents = fs4.readdirSync(abs, { withFileTypes: true });
|
|
5476
|
+
} catch (err) {
|
|
5477
|
+
const code = err?.code;
|
|
5478
|
+
const message = code === "EACCES" || code === "EPERM" ? "Permission denied" : code === "ENOENT" ? "Folder not found" : "Could not read this folder";
|
|
5479
|
+
return { ...base, entries: [], truncated: false, error: message };
|
|
5480
|
+
}
|
|
5481
|
+
const showHidden = !!opts.showHidden;
|
|
5482
|
+
const rawDirs = [];
|
|
5483
|
+
const rawFiles = [];
|
|
5484
|
+
for (const d of dirents) {
|
|
5485
|
+
const name = d.name;
|
|
5486
|
+
if (!showHidden && name.startsWith(".")) continue;
|
|
5487
|
+
let isDir = d.isDirectory();
|
|
5488
|
+
if (d.isSymbolicLink()) {
|
|
5489
|
+
try {
|
|
5490
|
+
isDir = fs4.statSync(path3.join(abs, name)).isDirectory();
|
|
5491
|
+
} catch {
|
|
5492
|
+
isDir = false;
|
|
5493
|
+
}
|
|
5494
|
+
}
|
|
5495
|
+
if (isDir) {
|
|
5496
|
+
const { project, repo } = markers(path3.join(abs, name));
|
|
5497
|
+
rawDirs.push({ name, dir: true, project: project || void 0, repo: repo || void 0 });
|
|
5498
|
+
} else {
|
|
5499
|
+
rawFiles.push({ name, dir: false });
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
const cmp = (a, b) => a.name.toLowerCase().localeCompare(b.name.toLowerCase());
|
|
5503
|
+
rawDirs.sort(cmp);
|
|
5504
|
+
rawFiles.sort(cmp);
|
|
5505
|
+
const all = [...rawDirs, ...rawFiles];
|
|
5506
|
+
const truncated = all.length > MAX_ENTRIES2;
|
|
5507
|
+
return { ...base, entries: truncated ? all.slice(0, MAX_ENTRIES2) : all, truncated };
|
|
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
|
+
}
|
|
5337
5529
|
var PKG_NAME = "@standardagents/code";
|
|
5338
5530
|
var REGISTRY_URL = `https://registry.npmjs.org/${encodeURIComponent(PKG_NAME)}`;
|
|
5339
5531
|
var CACHE_REL_DIR = ".config/standardagents-cli";
|
|
@@ -5514,12 +5706,13 @@ function runUpdate(pm) {
|
|
|
5514
5706
|
// src/daemon.ts
|
|
5515
5707
|
var HEARTBEAT_MS = 3e4;
|
|
5516
5708
|
var COMMAND_POLL_MS = 8e3;
|
|
5709
|
+
var FS_POLL_MS = 1e3;
|
|
5517
5710
|
var RECLAIM_PROBE_MS = 2 * 6e4;
|
|
5518
5711
|
var UPDATE_CHECK_MS = 6 * 60 * 6e4;
|
|
5519
5712
|
var SWEEP_MS = 10 * 6e4;
|
|
5520
5713
|
var MAX_WORKERS = 30;
|
|
5521
5714
|
var LOG_MAX_BYTES = 1e6;
|
|
5522
|
-
var LOG_FILE = path3.join(
|
|
5715
|
+
var LOG_FILE = path3.join(os10.homedir(), ".standardagents", "daemon.log");
|
|
5523
5716
|
function daemonLog(line) {
|
|
5524
5717
|
try {
|
|
5525
5718
|
fs4.mkdirSync(path3.dirname(LOG_FILE), { recursive: true });
|
|
@@ -5538,7 +5731,7 @@ function pathFromTags(tags) {
|
|
|
5538
5731
|
const tag = tags.find((t) => t.startsWith("path:"));
|
|
5539
5732
|
if (!tag) return null;
|
|
5540
5733
|
const raw = tag.slice("path:".length);
|
|
5541
|
-
return raw.replace(/^~(?=\/|$)/,
|
|
5734
|
+
return raw.replace(/^~(?=\/|$)/, os10.homedir());
|
|
5542
5735
|
}
|
|
5543
5736
|
var ThreadWorker = class {
|
|
5544
5737
|
constructor(api, identity, machineName, threadId, projectDir, createdAt) {
|
|
@@ -5670,7 +5863,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
|
|
|
5670
5863
|
process.exit(1);
|
|
5671
5864
|
}
|
|
5672
5865
|
let displayName = machineDisplayName(await loadMachine(api, identity.machine_id).catch(() => null) ?? {
|
|
5673
|
-
hostname:
|
|
5866
|
+
hostname: os10.hostname(),
|
|
5674
5867
|
id: identity.machine_id
|
|
5675
5868
|
});
|
|
5676
5869
|
const applied = consumeAppliedUpdate(version);
|
|
@@ -5832,6 +6025,31 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
|
|
|
5832
6025
|
};
|
|
5833
6026
|
void drainCommands();
|
|
5834
6027
|
const commandTimer = setInterval(() => void drainCommands(), COMMAND_POLL_MS);
|
|
6028
|
+
let lastFsNonce = null;
|
|
6029
|
+
let fsBusy = false;
|
|
6030
|
+
const drainFsRequests = async () => {
|
|
6031
|
+
if (fsBusy) return;
|
|
6032
|
+
fsBusy = true;
|
|
6033
|
+
try {
|
|
6034
|
+
const req = await readFsRequest(api, identity.machine_id).catch(() => null);
|
|
6035
|
+
if (!req || req.nonce === lastFsNonce) return;
|
|
6036
|
+
lastFsNonce = req.nonce;
|
|
6037
|
+
let ok = true;
|
|
6038
|
+
let result;
|
|
6039
|
+
let error;
|
|
6040
|
+
try {
|
|
6041
|
+
result = req.op === "mkdir" ? mkdirDirectory(req.path, req.name ?? "", { showHidden: req.show_hidden }) : browseDirectory(req.path, { showHidden: req.show_hidden });
|
|
6042
|
+
} catch (err) {
|
|
6043
|
+
ok = false;
|
|
6044
|
+
error = err instanceof Error ? err.message : "browse failed";
|
|
6045
|
+
}
|
|
6046
|
+
await writeFsResponse(api, identity.machine_id, { nonce: req.nonce, ok, result, error }).catch(() => {
|
|
6047
|
+
});
|
|
6048
|
+
} finally {
|
|
6049
|
+
fsBusy = false;
|
|
6050
|
+
}
|
|
6051
|
+
};
|
|
6052
|
+
const fsTimer = setInterval(() => void drainFsRequests(), FS_POLL_MS);
|
|
5835
6053
|
const sweeper = setInterval(() => {
|
|
5836
6054
|
if (updateReady && ![...workers.values()].some((w) => w.busy)) {
|
|
5837
6055
|
daemonLog("restarting to apply the installed update");
|
|
@@ -5845,6 +6063,7 @@ Run \`standardcode daemon install\` (or plain \`standardcode\`) once to sign in.
|
|
|
5845
6063
|
clearInterval(reclaim);
|
|
5846
6064
|
clearInterval(updateTimer);
|
|
5847
6065
|
clearInterval(commandTimer);
|
|
6066
|
+
clearInterval(fsTimer);
|
|
5848
6067
|
clearInterval(sweeper);
|
|
5849
6068
|
for (const worker of workers.values()) worker.stop();
|
|
5850
6069
|
events.close();
|
|
@@ -5902,8 +6121,8 @@ function run2(cmd, args) {
|
|
|
5902
6121
|
const output4 = `${res.stdout ?? ""}${res.stderr ?? ""}`.trim();
|
|
5903
6122
|
return { ok: res.status === 0, output: output4 };
|
|
5904
6123
|
}
|
|
5905
|
-
var plistPath = () => path3.join(
|
|
5906
|
-
var unitPath = () => path3.join(
|
|
6124
|
+
var plistPath = () => path3.join(os10.homedir(), "Library", "LaunchAgents", `${SERVICE_LABEL}.plist`);
|
|
6125
|
+
var unitPath = () => path3.join(os10.homedir(), ".config", "systemd", "user", SYSTEMD_UNIT);
|
|
5907
6126
|
var xmlEscape = (s) => s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
5908
6127
|
function installService(command, endpoint) {
|
|
5909
6128
|
if (process.platform === "darwin") return installLaunchd(command, endpoint);
|
|
@@ -5915,7 +6134,7 @@ function installService(command, endpoint) {
|
|
|
5915
6134
|
};
|
|
5916
6135
|
}
|
|
5917
6136
|
function installLaunchd(command, endpoint) {
|
|
5918
|
-
const logDir = path3.join(
|
|
6137
|
+
const logDir = path3.join(os10.homedir(), ".standardagents");
|
|
5919
6138
|
fs4.mkdirSync(logDir, { recursive: true });
|
|
5920
6139
|
fs4.mkdirSync(path3.dirname(plistPath()), { recursive: true });
|
|
5921
6140
|
const envEntries = [
|
|
@@ -5988,10 +6207,10 @@ WantedBy=default.target
|
|
|
5988
6207
|
if (!enable.ok) {
|
|
5989
6208
|
return { ok: false, detail: `systemctl enable failed: ${enable.output}` };
|
|
5990
6209
|
}
|
|
5991
|
-
const linger = run2("loginctl", ["enable-linger",
|
|
6210
|
+
const linger = run2("loginctl", ["enable-linger", os10.userInfo().username]);
|
|
5992
6211
|
return {
|
|
5993
6212
|
ok: true,
|
|
5994
|
-
detail: `systemd user unit installed (${unitPath()})` + (linger.ok ? ", lingering enabled" : ` \u2014 enable lingering manually: sudo loginctl enable-linger ${
|
|
6213
|
+
detail: `systemd user unit installed (${unitPath()})` + (linger.ok ? ", lingering enabled" : ` \u2014 enable lingering manually: sudo loginctl enable-linger ${os10.userInfo().username}`)
|
|
5995
6214
|
};
|
|
5996
6215
|
}
|
|
5997
6216
|
function uninstallService() {
|
|
@@ -6119,7 +6338,7 @@ async function installCommand(endpointFlag) {
|
|
|
6119
6338
|
const api = await ensureSignedIn(endpoint);
|
|
6120
6339
|
const identity = loadMachineIdentity();
|
|
6121
6340
|
const existing = await loadMachine(api, identity.machine_id).catch(() => null);
|
|
6122
|
-
const suggested = machineDisplayName(existing ?? { hostname:
|
|
6341
|
+
const suggested = machineDisplayName(existing ?? { hostname: os10.hostname(), id: identity.machine_id });
|
|
6123
6342
|
const rl = readline2.createInterface({ input: stdin, output: stdout });
|
|
6124
6343
|
const answer = (await rl.question(
|
|
6125
6344
|
`${c2.bold}Machine name${c2.reset} ${c2.dim}(shown in the session picker)${c2.reset} [${suggested}]: `
|
|
@@ -6179,7 +6398,7 @@ async function statusCommand() {
|
|
|
6179
6398
|
const cred = getCredential(endpoint);
|
|
6180
6399
|
if (!cred) {
|
|
6181
6400
|
stdout.write(
|
|
6182
|
-
`${c2.bold}Machine:${c2.reset} ${
|
|
6401
|
+
`${c2.bold}Machine:${c2.reset} ${os10.hostname()} ${c2.dim}(${identity.machine_id})${c2.reset}
|
|
6183
6402
|
`
|
|
6184
6403
|
);
|
|
6185
6404
|
stdout.write(`${c2.bold}Account:${c2.reset} ${c2.yellow}not signed in to ${endpoint}${c2.reset}
|
|
@@ -6190,7 +6409,7 @@ async function statusCommand() {
|
|
|
6190
6409
|
const api = new ApiClient(endpoint, cred.access_token);
|
|
6191
6410
|
const record = await loadMachine(api, identity.machine_id).catch(() => null);
|
|
6192
6411
|
stdout.write(
|
|
6193
|
-
`${c2.bold}Machine:${c2.reset} ${machineDisplayName(record ?? { hostname:
|
|
6412
|
+
`${c2.bold}Machine:${c2.reset} ${machineDisplayName(record ?? { hostname: os10.hostname(), id: identity.machine_id })} ${c2.dim}(${identity.machine_id})${c2.reset}
|
|
6194
6413
|
`
|
|
6195
6414
|
);
|
|
6196
6415
|
if (!record) {
|
|
@@ -6224,7 +6443,7 @@ async function projectCommand(action, target) {
|
|
|
6224
6443
|
const api = await ensureSignedIn(endpoint);
|
|
6225
6444
|
const identity = loadMachineIdentity();
|
|
6226
6445
|
const record = await loadMachine(api, identity.machine_id).catch(() => null);
|
|
6227
|
-
const displayName = machineDisplayName(record ?? { hostname:
|
|
6446
|
+
const displayName = machineDisplayName(record ?? { hostname: os10.hostname(), id: identity.machine_id });
|
|
6228
6447
|
if (action === "add") {
|
|
6229
6448
|
await registerProject(api, identity, dir2);
|
|
6230
6449
|
stdout.write(`${c2.green}\u2713${c2.reset} Registered ${dir2} for remote sessions on ${displayName}.
|
|
@@ -6284,10 +6503,10 @@ var c3 = {
|
|
|
6284
6503
|
reset: "\x1B[0m",
|
|
6285
6504
|
dim: "\x1B[2m",
|
|
6286
6505
|
bold: "\x1B[1m",
|
|
6287
|
-
white:
|
|
6506
|
+
white: themeWhite,
|
|
6288
6507
|
cyan: "\x1B[36m",
|
|
6289
6508
|
green: "\x1B[32m",
|
|
6290
|
-
gray:
|
|
6509
|
+
gray: themeGray,
|
|
6291
6510
|
magenta: "\x1B[35m",
|
|
6292
6511
|
yellow: "\x1B[33m",
|
|
6293
6512
|
red: "\x1B[31m",
|
|
@@ -6424,7 +6643,7 @@ ${c3.teal}\u25C7${c3.reset} ${c3.dim}Standard Code \u2014 see you soon.${c3.rese
|
|
|
6424
6643
|
`);
|
|
6425
6644
|
}
|
|
6426
6645
|
function printWelcome(endpoint, projectDir) {
|
|
6427
|
-
const home =
|
|
6646
|
+
const home = os10.homedir();
|
|
6428
6647
|
const dir2 = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
|
|
6429
6648
|
const host = endpoint.replace(/^https?:\/\//, "").replace(/\/$/, "");
|
|
6430
6649
|
const version = readVersion();
|
|
@@ -6492,7 +6711,7 @@ async function main() {
|
|
|
6492
6711
|
const endpointOverride = cliArgs.promptEndpoint || typeof endpointArg === "string" && endpointArg.trim() !== "";
|
|
6493
6712
|
const dirArg = cliArgs.dir;
|
|
6494
6713
|
const projectDir = path3.resolve(dirArg || process.cwd());
|
|
6495
|
-
const machine =
|
|
6714
|
+
const machine = os10.hostname();
|
|
6496
6715
|
const reader = { rl: null };
|
|
6497
6716
|
let handoffClosing = false;
|
|
6498
6717
|
let preflightArmed = false;
|
|
@@ -6677,7 +6896,7 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
|
|
|
6677
6896
|
void registerProject(api, identity, projectDir).catch(() => {
|
|
6678
6897
|
});
|
|
6679
6898
|
const tui = new Tui(1);
|
|
6680
|
-
const home =
|
|
6899
|
+
const home = os10.homedir();
|
|
6681
6900
|
const tildeDir = projectDir.startsWith(home) ? "~" + projectDir.slice(home.length) : projectDir;
|
|
6682
6901
|
const shortDir = tildeDir.length > 38 ? "\u2026" + tildeDir.slice(-37) : tildeDir;
|
|
6683
6902
|
const session = { mode: "local", identity };
|
|
@@ -6691,9 +6910,9 @@ ${c3.dim}Press Control-C again to exit${c3.reset}
|
|
|
6691
6910
|
const where = await tui.select(
|
|
6692
6911
|
`${c3.bold}${gradientText("Where should this session run?")}${c3.reset} ${c3.dim}\u2191\u2193 \xB7 enter \xB7 esc${c3.reset}`,
|
|
6693
6912
|
[
|
|
6694
|
-
{ label:
|
|
6913
|
+
{ label: `\u{1F5A5}\uFE0F This machine \u2014 ${shortDir}`, hint: "tools run locally", value: null },
|
|
6695
6914
|
...remoteTargets.map((m) => ({
|
|
6696
|
-
label: m.name
|
|
6915
|
+
label: `${machineIcon(m)} ${m.name}`,
|
|
6697
6916
|
hint: `${m.hostname} \xB7 daemon online${m.daemon ? ` \xB7 v${m.daemon.version}` : ""}`,
|
|
6698
6917
|
value: m
|
|
6699
6918
|
}))
|
|
@@ -7429,7 +7648,24 @@ ${c3.gray}Close another session (its slot frees within ~90s), then resend your m
|
|
|
7429
7648
|
]);
|
|
7430
7649
|
const history = await loadHistory(api, threadId, historySeedThreadId);
|
|
7431
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
|
+
};
|
|
7432
7667
|
tui.onSubmit = (text, images) => {
|
|
7668
|
+
clearPresence();
|
|
7433
7669
|
const trimmed = text.trimStart();
|
|
7434
7670
|
if (trimmed.startsWith("!") && !trimmed.startsWith("!!")) {
|
|
7435
7671
|
const command = trimmed.slice(1).trim();
|
|
@@ -7757,7 +7993,7 @@ async function runMachinesMenu(tui, api, self) {
|
|
|
7757
7993
|
const daemonBit = m.daemon ? online ? "daemon online" : "daemon offline" : "no daemon";
|
|
7758
7994
|
const nproj = Object.keys(m.projects).length;
|
|
7759
7995
|
return {
|
|
7760
|
-
label: `${m.name}${isSelf ? " (this machine)" : ""}`,
|
|
7996
|
+
label: `${machineIcon(m)} ${m.name}${isSelf ? " (this machine)" : ""}`,
|
|
7761
7997
|
hint: `${m.hostname} \xB7 ${m.platform}/${m.arch} \xB7 v${m.version ?? "?"} \xB7 ${daemonBit} \xB7 ${nproj} project${nproj === 1 ? "" : "s"}`,
|
|
7762
7998
|
value: m.id
|
|
7763
7999
|
};
|
|
@@ -7772,7 +8008,8 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
7772
8008
|
const online = daemonOnline(machine);
|
|
7773
8009
|
const canRunCommands = isSelf || !!machine.daemon;
|
|
7774
8010
|
const options = [
|
|
7775
|
-
{ label: "Rename", value: "rename" }
|
|
8011
|
+
{ label: "Rename", value: "rename" },
|
|
8012
|
+
{ label: "Set icon", hint: machine.icon || "default", value: "icon" }
|
|
7776
8013
|
];
|
|
7777
8014
|
if (canRunCommands) {
|
|
7778
8015
|
options.push(
|
|
@@ -7790,8 +8027,22 @@ async function manageMachine(tui, api, self, machine) {
|
|
|
7790
8027
|
`${c3.dim}${machine.name}'s daemon is offline \u2014 queued changes apply when it next comes online.${c3.reset}`
|
|
7791
8028
|
);
|
|
7792
8029
|
}
|
|
7793
|
-
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);
|
|
7794
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
|
+
}
|
|
7795
8046
|
const dispatch = async (kind, args) => {
|
|
7796
8047
|
if (isSelf) {
|
|
7797
8048
|
await applyMachineCommand(api, self, { kind, args});
|
|
@@ -7833,12 +8084,12 @@ async function manageMachineProjects(tui, api, self, machine, dispatch, applyNot
|
|
|
7833
8084
|
);
|
|
7834
8085
|
if (!picked) return;
|
|
7835
8086
|
if (picked === ADD) {
|
|
7836
|
-
const
|
|
8087
|
+
const path13 = await tui.prompt(
|
|
7837
8088
|
`Absolute project path on ${machine.name}`,
|
|
7838
8089
|
machine.id === self.machine_id ? process.cwd() : "/home/you/project"
|
|
7839
8090
|
);
|
|
7840
|
-
if (!
|
|
7841
|
-
const trimmed =
|
|
8091
|
+
if (!path13 || !path13.trim()) return;
|
|
8092
|
+
const trimmed = path13.trim();
|
|
7842
8093
|
if (!trimmed.startsWith("/") && !trimmed.startsWith("~")) {
|
|
7843
8094
|
tui.print(`${c3.yellow}Use an absolute path (starting with / or ~).${c3.reset}`);
|
|
7844
8095
|
return;
|