claude-yes 1.148.0 → 1.149.0
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/SUPPORTED_CLIS-CFaRUz70.js +8 -0
- package/dist/{SUPPORTED_CLIS-CmsPbgMe.js → SUPPORTED_CLIS-DmEBbACr.js} +2 -2
- package/dist/cli.js +3 -3
- package/dist/index.js +2 -2
- package/dist/{schedule-DkTLO7No.js → schedule-CdJjOPt7.js} +5 -5
- package/dist/{serve-DQK6u2KS.js → serve-j3Jwmk5O.js} +64 -8
- package/dist/{setup-6R_yJsKX.js → setup-BsVxgyr7.js} +3 -3
- package/dist/{subcommands-BQ2tu-y9.js → subcommands-DCsphJXW.js} +1 -1
- package/dist/{subcommands-Df_SjmLN.js → subcommands-PCUd7vJf.js} +4 -4
- package/dist/{ts-CaCRPaos.js → ts-9In7C_H_.js} +2 -2
- package/dist/{versionChecker-DfO37H1R.js → versionChecker-xwsWu7p6.js} +2 -2
- package/dist/workspaceConfig-B9VX8tVO.js +91 -0
- package/lab/ui/index.html +5 -2
- package/package.json +1 -1
- package/ts/serve.ts +102 -5
- package/ts/workspaceConfig.spec.ts +72 -1
- package/ts/workspaceConfig.ts +43 -0
- package/dist/SUPPORTED_CLIS-DpHVL8Mi.js +0 -8
- package/dist/workspaceConfig-B3ylOZAO.js +0 -56
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import "./ts-9In7C_H_.js";
|
|
2
|
+
import "./logger-CDIsZ-Pp.js";
|
|
3
|
+
import "./versionChecker-xwsWu7p6.js";
|
|
4
|
+
import "./pidStore-fqXqTKkh.js";
|
|
5
|
+
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DmEBbACr.js";
|
|
7
|
+
|
|
8
|
+
export { SUPPORTED_CLIS };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { t as CLIS_CONFIG } from "./ts-9In7C_H_.js";
|
|
2
2
|
|
|
3
3
|
//#region ts/SUPPORTED_CLIS.ts
|
|
4
4
|
const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
|
|
5
5
|
|
|
6
6
|
//#endregion
|
|
7
7
|
export { SUPPORTED_CLIS as t };
|
|
8
|
-
//# sourceMappingURL=SUPPORTED_CLIS-
|
|
8
|
+
//# sourceMappingURL=SUPPORTED_CLIS-DmEBbACr.js.map
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { t as invokedCliName } from "./invokedCli-uqM2YYA7.js";
|
|
3
3
|
import { n as logger } from "./logger-CDIsZ-Pp.js";
|
|
4
|
-
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-
|
|
4
|
+
import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-xwsWu7p6.js";
|
|
5
5
|
import { argv } from "process";
|
|
6
6
|
import { execFileSync, spawn } from "child_process";
|
|
7
7
|
import ms from "ms";
|
|
@@ -480,7 +480,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
|
|
|
480
480
|
const rawArg = process.argv[2];
|
|
481
481
|
const managerCommands = !invokedCliName(process.argv);
|
|
482
482
|
const isHelpFlag = rawArg === "-h" || rawArg === "--help";
|
|
483
|
-
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-
|
|
483
|
+
const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DCsphJXW.js");
|
|
484
484
|
if (isHelpFlag && process.argv.length === 3) {
|
|
485
485
|
cmdHelp(managerCommands);
|
|
486
486
|
process.exit(0);
|
|
@@ -513,7 +513,7 @@ if (config.useRust) {
|
|
|
513
513
|
}
|
|
514
514
|
}
|
|
515
515
|
if (rustBinary) {
|
|
516
|
-
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-
|
|
516
|
+
const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CFaRUz70.js");
|
|
517
517
|
const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
|
|
518
518
|
if (config.verbose) {
|
|
519
519
|
console.log(`[rust] Using binary: ${rustBinary}`);
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-
|
|
1
|
+
import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-9In7C_H_.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-xwsWu7p6.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.js";
|
|
5
5
|
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
6
|
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-9In7C_H_.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-
|
|
3
|
+
import "./versionChecker-xwsWu7p6.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.js";
|
|
5
5
|
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
7
|
-
import {
|
|
6
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DmEBbACr.js";
|
|
7
|
+
import { i as resolveSpawnCwd } from "./workspaceConfig-B9VX8tVO.js";
|
|
8
8
|
import { createHash } from "node:crypto";
|
|
9
9
|
|
|
10
10
|
//#region ts/oxmgrService.ts
|
|
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
|
|
|
141
141
|
|
|
142
142
|
//#endregion
|
|
143
143
|
export { cmdSchedule };
|
|
144
|
-
//# sourceMappingURL=schedule-
|
|
144
|
+
//# sourceMappingURL=schedule-CdJjOPt7.js.map
|
|
@@ -1,15 +1,16 @@
|
|
|
1
|
-
import "./ts-
|
|
1
|
+
import "./ts-9In7C_H_.js";
|
|
2
2
|
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
3
|
+
import { r as getInstalledPackage } from "./versionChecker-xwsWu7p6.js";
|
|
4
4
|
import "./pidStore-fqXqTKkh.js";
|
|
5
5
|
import { a as updateGlobalPidStatus } from "./globalPidIndex-DlmmJlO8.js";
|
|
6
6
|
import { t as pgidForWrapper } from "./reaper-C-eWAxIj.js";
|
|
7
7
|
import "./configShared-C1C04bbq.js";
|
|
8
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-
|
|
8
|
+
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DmEBbACr.js";
|
|
9
9
|
import "./e2e-ClOI_aqV.js";
|
|
10
10
|
import "./webrtcLink-BWhuA74k.js";
|
|
11
11
|
import "./remotes-qK6uozO4.js";
|
|
12
|
-
import {
|
|
12
|
+
import { i as resolveSpawnCwd, r as isProvisionAllowed, t as getProvisionRoot } from "./workspaceConfig-B9VX8tVO.js";
|
|
13
|
+
import { S as snapshotStatus, _ as renderRawLog, c as extractTaskCounts, g as readNotes, i as controlCodeFromName, m as listRecords, o as deriveLiveStatus, w as writeToIpc, y as resolveOne } from "./subcommands-PCUd7vJf.js";
|
|
13
14
|
import yargs from "yargs";
|
|
14
15
|
import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
|
|
15
16
|
import { homedir, hostname, userInfo } from "os";
|
|
@@ -20,6 +21,22 @@ import { randomBytes, timingSafeEqual } from "crypto";
|
|
|
20
21
|
|
|
21
22
|
//#region ts/serve.ts
|
|
22
23
|
const DEFAULT_PORT = 7432;
|
|
24
|
+
/**
|
|
25
|
+
* Normalize a user-supplied GitHub-ish source into the standard
|
|
26
|
+
* `<owner>/<repo>/tree/<branch>` path that codehost/provision's `parseSpec`
|
|
27
|
+
* understands. Interim fallback used only when the linked codehost build
|
|
28
|
+
* predates `parseSource` (the canonical normalizer in the standard):
|
|
29
|
+
* https://github.com/o/r/tree/b · github.com/o/r/tree/b · o/r/tree/b
|
|
30
|
+
* o/r@branch · o/r (→ default branch main)
|
|
31
|
+
*/
|
|
32
|
+
function normalizeGithubSource(s) {
|
|
33
|
+
let v = s.trim().replace(/^https?:\/\//, "").replace(/^github\.com\//, "");
|
|
34
|
+
v = v.replace(/[?#].*$/, "").replace(/\.git$/, "").replace(/^\/+|\/+$/g, "");
|
|
35
|
+
const at = v.match(/^([^/]+)\/([^/@]+)@(.+)$/);
|
|
36
|
+
if (at) return `${at[1]}/${at[2]}/tree/${at[3]}`;
|
|
37
|
+
if (/^[^/]+\/[^/]+$/.test(v)) return `${v}/tree/main`;
|
|
38
|
+
return v;
|
|
39
|
+
}
|
|
23
40
|
function agentYesHome() {
|
|
24
41
|
return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
25
42
|
}
|
|
@@ -1031,9 +1048,47 @@ Options:
|
|
|
1031
1048
|
}
|
|
1032
1049
|
const cli = String(body.cli ?? "claude");
|
|
1033
1050
|
if (!SUPPORTED_CLIS.includes(cli)) return new Response(`unsupported cli: ${cli}`, { status: 400 });
|
|
1034
|
-
const cwd = typeof body.cwd === "string" && body.cwd ? body.cwd : process.cwd();
|
|
1035
1051
|
const prompt = String(body.prompt ?? "");
|
|
1036
|
-
|
|
1052
|
+
let cwd;
|
|
1053
|
+
let provisioned = null;
|
|
1054
|
+
const from = typeof body.from === "string" ? body.from.trim() : "";
|
|
1055
|
+
if (from) {
|
|
1056
|
+
let prov;
|
|
1057
|
+
try {
|
|
1058
|
+
prov = await import("codehost/provision");
|
|
1059
|
+
} catch (e) {
|
|
1060
|
+
return new Response(`spawn-from needs the 'codehost' package (codehost/provision) — install it (npm i -g codehost) or 'bun link' it for local dev: ${e.message}`, { status: 501 });
|
|
1061
|
+
}
|
|
1062
|
+
let spec;
|
|
1063
|
+
try {
|
|
1064
|
+
spec = typeof prov.parseSource === "function" ? prov.parseSource(from) : prov.parseSpec(normalizeGithubSource(from));
|
|
1065
|
+
} catch {
|
|
1066
|
+
spec = null;
|
|
1067
|
+
}
|
|
1068
|
+
if (!spec) return new Response(`unrecognized spawn source: ${from}`, { status: 400 });
|
|
1069
|
+
if (!isProvisionAllowed(spec.owner, spec.repo)) return new Response(`provisioning '${spec.owner}/${spec.repo}' is not allowed — add the owner to provisionAllowlist in ~/.agent-yes/config.json (or "*" to allow all)`, { status: 403 });
|
|
1070
|
+
let result;
|
|
1071
|
+
try {
|
|
1072
|
+
const wsRoot = getProvisionRoot();
|
|
1073
|
+
result = await prov.provision(spec, wsRoot ? { wsRoot } : void 0);
|
|
1074
|
+
} catch (e) {
|
|
1075
|
+
return new Response(`provision failed: ${e.message}`, { status: 502 });
|
|
1076
|
+
}
|
|
1077
|
+
if (!result?.ok) return new Response(result?.error ?? "provision failed", { status: 502 });
|
|
1078
|
+
cwd = result.folder;
|
|
1079
|
+
provisioned = {
|
|
1080
|
+
action: result.action,
|
|
1081
|
+
folder: result.folder
|
|
1082
|
+
};
|
|
1083
|
+
} else {
|
|
1084
|
+
cwd = resolveSpawnCwd(body.cwd);
|
|
1085
|
+
try {
|
|
1086
|
+
await mkdir(cwd, { recursive: true });
|
|
1087
|
+
} catch (e) {
|
|
1088
|
+
return new Response(`cannot create cwd ${cwd}: ${e.message}`, { status: 500 });
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
process.stderr.write(`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd}${provisioned ? `, ${provisioned.action} from ${from}` : ""})\n`);
|
|
1037
1092
|
const ayBin = Bun.which("ay") ?? process.argv[1];
|
|
1038
1093
|
const ayCmd = process.platform === "win32" && ayBin.toLowerCase().endsWith(".exe") ? [ayBin] : [process.execPath, ayBin];
|
|
1039
1094
|
try {
|
|
@@ -1053,7 +1108,8 @@ Options:
|
|
|
1053
1108
|
ok: true,
|
|
1054
1109
|
pid: child.pid,
|
|
1055
1110
|
cli,
|
|
1056
|
-
cwd
|
|
1111
|
+
cwd,
|
|
1112
|
+
...provisioned ? { provisioned } : {}
|
|
1057
1113
|
});
|
|
1058
1114
|
} catch (e) {
|
|
1059
1115
|
return new Response(e.message, { status: 500 });
|
|
@@ -1179,4 +1235,4 @@ Options:
|
|
|
1179
1235
|
|
|
1180
1236
|
//#endregion
|
|
1181
1237
|
export { cmdServe };
|
|
1182
|
-
//# sourceMappingURL=serve-
|
|
1238
|
+
//# sourceMappingURL=serve-j3Jwmk5O.js.map
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as setWorkspaceRoot, n as getWorkspaceRoot } from "./workspaceConfig-B9VX8tVO.js";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import { stdin, stdout } from "node:process";
|
|
4
4
|
import { createInterface } from "node:readline/promises";
|
|
@@ -32,7 +32,7 @@ async function cmdSetup(rest) {
|
|
|
32
32
|
if (!existsSync(abs)) process.stderr.write(` note: that directory doesn't exist yet — create it, or agents spawned there will fail\n`);
|
|
33
33
|
if (noShare) return 0;
|
|
34
34
|
process.stdout.write(`\nsharing this machine to agent-yes.com…\n`);
|
|
35
|
-
const { cmdServe } = await import("./serve-
|
|
35
|
+
const { cmdServe } = await import("./serve-j3Jwmk5O.js");
|
|
36
36
|
return cmdServe([
|
|
37
37
|
"install",
|
|
38
38
|
"--share",
|
|
@@ -42,4 +42,4 @@ async function cmdSetup(rest) {
|
|
|
42
42
|
|
|
43
43
|
//#endregion
|
|
44
44
|
export { cmdSetup };
|
|
45
|
-
//# sourceMappingURL=setup-
|
|
45
|
+
//# sourceMappingURL=setup-BsVxgyr7.js.map
|
|
@@ -4,6 +4,6 @@ import "./configShared-C1C04bbq.js";
|
|
|
4
4
|
import "./e2e-ClOI_aqV.js";
|
|
5
5
|
import "./webrtcLink-BWhuA74k.js";
|
|
6
6
|
import "./remotes-qK6uozO4.js";
|
|
7
|
-
import { C as stopTipForCli, S as snapshotStatus, _ as renderRawLog, a as cursorAbs, b as resolveReadWindow, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLogLines, w as writeToIpc, x as runSubcommand, y as resolveOne } from "./subcommands-
|
|
7
|
+
import { C as stopTipForCli, S as snapshotStatus, _ as renderRawLog, a as cursorAbs, b as resolveReadWindow, c as extractTaskCounts, d as isExitRequest, f as isPidAlive, g as readNotes, h as matchKeyword, i as controlCodeFromName, l as finalizedLines, m as listRecords, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as isSubcommand, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as renderRawLogLines, w as writeToIpc, x as runSubcommand, y as resolveOne } from "./subcommands-PCUd7vJf.js";
|
|
8
8
|
|
|
9
9
|
export { cmdHelp, isSubcommand, runSubcommand };
|
|
@@ -554,15 +554,15 @@ async function runSubcommand(argv) {
|
|
|
554
554
|
case "restart": return await cmdRestart(rest);
|
|
555
555
|
case "note": return await cmdNote(rest);
|
|
556
556
|
case "serve": {
|
|
557
|
-
const { cmdServe } = await import("./serve-
|
|
557
|
+
const { cmdServe } = await import("./serve-j3Jwmk5O.js");
|
|
558
558
|
return cmdServe(rest);
|
|
559
559
|
}
|
|
560
560
|
case "setup": {
|
|
561
|
-
const { cmdSetup } = await import("./setup-
|
|
561
|
+
const { cmdSetup } = await import("./setup-BsVxgyr7.js");
|
|
562
562
|
return cmdSetup(rest);
|
|
563
563
|
}
|
|
564
564
|
case "schedule": {
|
|
565
|
-
const { cmdSchedule } = await import("./schedule-
|
|
565
|
+
const { cmdSchedule } = await import("./schedule-CdJjOPt7.js");
|
|
566
566
|
return cmdSchedule(rest);
|
|
567
567
|
}
|
|
568
568
|
case "remote": {
|
|
@@ -2593,4 +2593,4 @@ async function cmdResultSet(rest) {
|
|
|
2593
2593
|
|
|
2594
2594
|
//#endregion
|
|
2595
2595
|
export { stopTipForCli as C, snapshotStatus as S, renderRawLog as _, cursorAbs as a, resolveReadWindow as b, extractTaskCounts as c, isExitRequest as d, isPidAlive as f, readNotes as g, matchKeyword as h, controlCodeFromName as i, finalizedLines as l, listRecords as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, isSubcommand as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, renderRawLogLines as v, writeToIpc as w, runSubcommand as x, resolveOne as y };
|
|
2596
|
-
//# sourceMappingURL=subcommands-
|
|
2596
|
+
//# sourceMappingURL=subcommands-PCUd7vJf.js.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { n as logger, t as addTransport } from "./logger-CDIsZ-Pp.js";
|
|
2
|
-
import { r as getInstalledPackage } from "./versionChecker-
|
|
2
|
+
import { r as getInstalledPackage } from "./versionChecker-xwsWu7p6.js";
|
|
3
3
|
import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
|
|
4
4
|
import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-V4qvXgAw.js";
|
|
5
5
|
import { t as PidStore } from "./pidStore-fqXqTKkh.js";
|
|
@@ -1800,4 +1800,4 @@ function sleep(ms) {
|
|
|
1800
1800
|
|
|
1801
1801
|
//#endregion
|
|
1802
1802
|
export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
|
|
1803
|
-
//# sourceMappingURL=ts-
|
|
1803
|
+
//# sourceMappingURL=ts-9In7C_H_.js.map
|
|
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
|
|
|
7
7
|
|
|
8
8
|
//#region package.json
|
|
9
9
|
var name = "claude-yes";
|
|
10
|
-
var version = "1.
|
|
10
|
+
var version = "1.149.0";
|
|
11
11
|
|
|
12
12
|
//#endregion
|
|
13
13
|
//#region ts/versionChecker.ts
|
|
@@ -215,4 +215,4 @@ async function displayVersion() {
|
|
|
215
215
|
|
|
216
216
|
//#endregion
|
|
217
217
|
export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
|
|
218
|
-
//# sourceMappingURL=versionChecker-
|
|
218
|
+
//# sourceMappingURL=versionChecker-xwsWu7p6.js.map
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
|
|
2
|
+
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
+
import { homedir } from "os";
|
|
4
|
+
import path from "path";
|
|
5
|
+
|
|
6
|
+
//#region ts/workspaceConfig.ts
|
|
7
|
+
function configPath() {
|
|
8
|
+
return path.join(agentYesHome(), "config.json");
|
|
9
|
+
}
|
|
10
|
+
function readConfig() {
|
|
11
|
+
try {
|
|
12
|
+
return JSON.parse(readFileSync(configPath(), "utf-8"));
|
|
13
|
+
} catch {
|
|
14
|
+
return {};
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
/** Expand a leading `~` (`~` or `~/x`) to an absolute home-based path. */
|
|
18
|
+
function expandTilde(p) {
|
|
19
|
+
const s = p.trim();
|
|
20
|
+
if (s === "~") return homedir();
|
|
21
|
+
if (s.startsWith("~/") || s.startsWith("~\\")) return path.join(homedir(), s.slice(2));
|
|
22
|
+
return s;
|
|
23
|
+
}
|
|
24
|
+
/** The configured workspace root (absolute), or the home dir if unset. */
|
|
25
|
+
function getWorkspaceRoot() {
|
|
26
|
+
const w = readConfig().workspace;
|
|
27
|
+
return w && w.trim() ? w : homedir();
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Root for `from`-provisioned worktrees, handed to codehost/provision so they
|
|
31
|
+
* land in `<root>/<owner>/<repo>/tree/<branch>`. Resolution order: env
|
|
32
|
+
* `CODEHOST_WS_ROOT` wins (ops override), then the configured `provisionRoot`,
|
|
33
|
+
* else undefined — letting codehost/provision fall back to its own `~/ws`
|
|
34
|
+
* default. Kept separate from `workspace` (the plain-cwd default), which may be a
|
|
35
|
+
* specific project dir rather than a root.
|
|
36
|
+
*/
|
|
37
|
+
function getProvisionRoot() {
|
|
38
|
+
const env = process.env.CODEHOST_WS_ROOT?.trim();
|
|
39
|
+
if (env) return path.resolve(expandTilde(env));
|
|
40
|
+
const r = readConfig().provisionRoot;
|
|
41
|
+
return r && r.trim() ? path.resolve(expandTilde(r)) : void 0;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Owner/repo allowlist for `from`-provisioning. Provisioning clones a repo and
|
|
45
|
+
* runs its setup script (dependency installs + package lifecycle hooks = code
|
|
46
|
+
* execution on the host), so an **empty allowlist means DENY ALL** — a secure
|
|
47
|
+
* default the host opts out of by listing owners it trusts. Entries match
|
|
48
|
+
* `<owner>` (any repo of that owner), `<owner>/<repo>` (exact), or `*` (allow
|
|
49
|
+
* all — an explicit opt-in to the wide-open behavior). Env
|
|
50
|
+
* `CODEHOST_PROVISION_ALLOWLIST` (comma-separated) overrides the config.
|
|
51
|
+
*/
|
|
52
|
+
function getProvisionAllowlist() {
|
|
53
|
+
const env = process.env.CODEHOST_PROVISION_ALLOWLIST?.trim();
|
|
54
|
+
return (env ? env.split(",") : readConfig().provisionAllowlist ?? []).map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
55
|
+
}
|
|
56
|
+
/** Whether `<owner>/<repo>` may be `from`-provisioned, per {@link getProvisionAllowlist}. */
|
|
57
|
+
function isProvisionAllowed(owner, repo) {
|
|
58
|
+
const list = getProvisionAllowlist();
|
|
59
|
+
if (list.includes("*")) return true;
|
|
60
|
+
const o = owner.toLowerCase();
|
|
61
|
+
const full = `${owner}/${repo}`.toLowerCase();
|
|
62
|
+
return list.some((e) => e.replace(/\/\*$/, "") === o || e === full);
|
|
63
|
+
}
|
|
64
|
+
/** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
|
|
65
|
+
function setWorkspaceRoot(dir) {
|
|
66
|
+
const abs = path.resolve(expandTilde(dir));
|
|
67
|
+
const cfg = readConfig();
|
|
68
|
+
cfg.workspace = abs;
|
|
69
|
+
mkdirSync(agentYesHome(), { recursive: true });
|
|
70
|
+
writeFileSync(configPath(), JSON.stringify(cfg, null, 2));
|
|
71
|
+
return abs;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Resolve a user-supplied spawn location to an absolute cwd:
|
|
75
|
+
* - empty → the workspace root
|
|
76
|
+
* - a bare name → `<workspace>/<name>` (so "myproject" lands under the root)
|
|
77
|
+
* - `~`-prefixed → home-based absolute
|
|
78
|
+
* - anything with a path separator → resolved as-is
|
|
79
|
+
*/
|
|
80
|
+
function resolveSpawnCwd(input) {
|
|
81
|
+
const root = getWorkspaceRoot();
|
|
82
|
+
const v = (input ?? "").trim();
|
|
83
|
+
if (!v) return root;
|
|
84
|
+
if (v.startsWith("~")) return path.resolve(expandTilde(v));
|
|
85
|
+
if (v.includes("/") || v.includes("\\") || path.isAbsolute(v)) return path.resolve(v);
|
|
86
|
+
return path.join(root, v);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
//#endregion
|
|
90
|
+
export { setWorkspaceRoot as a, resolveSpawnCwd as i, getWorkspaceRoot as n, isProvisionAllowed as r, getProvisionRoot as t };
|
|
91
|
+
//# sourceMappingURL=workspaceConfig-B9VX8tVO.js.map
|
package/lab/ui/index.html
CHANGED
|
@@ -4334,6 +4334,7 @@
|
|
|
4334
4334
|
const before = new Set(entries.filter((e) => e._room === target.id).map((e) => e.pid));
|
|
4335
4335
|
const res = await target.tx.post("/api/spawn", {
|
|
4336
4336
|
cli: spec.cli || "claude",
|
|
4337
|
+
from: spec.from || undefined,
|
|
4337
4338
|
cwd: spec.cwd || undefined,
|
|
4338
4339
|
prompt: spec.prompt || undefined,
|
|
4339
4340
|
});
|
|
@@ -4385,7 +4386,8 @@
|
|
|
4385
4386
|
$("newform").innerHTML = `<div class="lcard">
|
|
4386
4387
|
<div class="ltitle">New agent · ${esc(where)}</div>
|
|
4387
4388
|
<div class="nfield"><label>CLI</label><input id="nf-cli" value="claude" spellcheck="false" autocapitalize="off" /></div>
|
|
4388
|
-
<div class="nfield"><label>
|
|
4389
|
+
<div class="nfield"><label>Spawn from — optional</label><input id="nf-from" placeholder="github URL / owner/repo@branch — provisions a worktree" spellcheck="false" autocapitalize="off" /></div>
|
|
4390
|
+
<div class="nfield"><label>Working dir</label><input id="nf-cwd" value="${esc(cwd)}" placeholder="(host default — ignored when Spawn from is set)" spellcheck="false" autocapitalize="off" /></div>
|
|
4389
4391
|
<div class="nfield"><label>Prompt — optional</label><textarea id="nf-prompt" rows="3" placeholder="initial message to send the agent…"></textarea></div>
|
|
4390
4392
|
<div class="lrow">
|
|
4391
4393
|
<button class="lfleet" id="nf-go">▷ Launch</button>
|
|
@@ -4401,6 +4403,7 @@
|
|
|
4401
4403
|
if (!go || go.disabled) return;
|
|
4402
4404
|
const spec = {
|
|
4403
4405
|
cli: ($("nf-cli").value || "claude").trim(),
|
|
4406
|
+
from: $("nf-from")?.value.trim() || "",
|
|
4404
4407
|
cwd: $("nf-cwd").value.trim(),
|
|
4405
4408
|
prompt: $("nf-prompt").value,
|
|
4406
4409
|
};
|
|
@@ -4409,7 +4412,7 @@
|
|
|
4409
4412
|
// target fleet. A 2nd `claude -c` in the same repo fights over the same
|
|
4410
4413
|
// session/files and usually exits on startup — which looks like "the new
|
|
4411
4414
|
// agent never appears". Let the user pick a different dir or proceed.
|
|
4412
|
-
if (spec.cwd) {
|
|
4415
|
+
if (spec.cwd && !spec.from) {
|
|
4413
4416
|
await loadList();
|
|
4414
4417
|
const norm = (p) => (p || "").replace(/\/+$/, "");
|
|
4415
4418
|
const busy = entries.some(
|
package/package.json
CHANGED
package/ts/serve.ts
CHANGED
|
@@ -21,9 +21,33 @@ import { updateGlobalPidStatus } from "./globalPidIndex.ts";
|
|
|
21
21
|
import { pgidForWrapper } from "./reaper.ts";
|
|
22
22
|
import { SUPPORTED_CLIS } from "./SUPPORTED_CLIS.ts";
|
|
23
23
|
import { getInstalledPackage } from "./versionChecker.ts";
|
|
24
|
+
import { getProvisionRoot, isProvisionAllowed, resolveSpawnCwd } from "./workspaceConfig.ts";
|
|
24
25
|
|
|
25
26
|
const DEFAULT_PORT = 7432;
|
|
26
27
|
|
|
28
|
+
/**
|
|
29
|
+
* Normalize a user-supplied GitHub-ish source into the standard
|
|
30
|
+
* `<owner>/<repo>/tree/<branch>` path that codehost/provision's `parseSpec`
|
|
31
|
+
* understands. Interim fallback used only when the linked codehost build
|
|
32
|
+
* predates `parseSource` (the canonical normalizer in the standard):
|
|
33
|
+
* https://github.com/o/r/tree/b · github.com/o/r/tree/b · o/r/tree/b
|
|
34
|
+
* o/r@branch · o/r (→ default branch main)
|
|
35
|
+
*/
|
|
36
|
+
function normalizeGithubSource(s: string): string {
|
|
37
|
+
let v = s
|
|
38
|
+
.trim()
|
|
39
|
+
.replace(/^https?:\/\//, "")
|
|
40
|
+
.replace(/^github\.com\//, "");
|
|
41
|
+
v = v
|
|
42
|
+
.replace(/[?#].*$/, "")
|
|
43
|
+
.replace(/\.git$/, "")
|
|
44
|
+
.replace(/^\/+|\/+$/g, "");
|
|
45
|
+
const at = v.match(/^([^/]+)\/([^/@]+)@(.+)$/);
|
|
46
|
+
if (at) return `${at[1]}/${at[2]}/tree/${at[3]}`;
|
|
47
|
+
if (/^[^/]+\/[^/]+$/.test(v)) return `${v}/tree/main`;
|
|
48
|
+
return v;
|
|
49
|
+
}
|
|
50
|
+
|
|
27
51
|
function agentYesHome(): string {
|
|
28
52
|
return process.env.AGENT_YES_HOME ?? path.join(homedir(), ".agent-yes");
|
|
29
53
|
}
|
|
@@ -1448,9 +1472,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1448
1472
|
return Response.json(live);
|
|
1449
1473
|
}
|
|
1450
1474
|
|
|
1451
|
-
// POST /api/spawn body {cli, cwd
|
|
1475
|
+
// POST /api/spawn body {cli, cwd?, from?, prompt?} — launch a new agent.
|
|
1476
|
+
// `from` (a GitHub URL / owner/repo@branch / owner/repo/tree/branch) is
|
|
1477
|
+
// resolved to a ready worktree via codehost/provision (the shared workspace
|
|
1478
|
+
// standard); otherwise `cwd` is resolved against the workspace root and
|
|
1479
|
+
// created if missing (Layer-0 plain-dir provisioning — no more ENOENT 500).
|
|
1452
1480
|
if (req.method === "POST" && p === "/api/spawn") {
|
|
1453
|
-
let body: { cli?: string; cwd?: string; prompt?: string };
|
|
1481
|
+
let body: { cli?: string; cwd?: string; from?: string; prompt?: string };
|
|
1454
1482
|
try {
|
|
1455
1483
|
body = (await req.json()) as typeof body;
|
|
1456
1484
|
} catch {
|
|
@@ -1459,10 +1487,73 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1459
1487
|
const cli = String(body.cli ?? "claude");
|
|
1460
1488
|
if (!SUPPORTED_CLIS.includes(cli as never))
|
|
1461
1489
|
return new Response(`unsupported cli: ${cli}`, { status: 400 });
|
|
1462
|
-
const cwd = typeof body.cwd === "string" && body.cwd ? body.cwd : process.cwd();
|
|
1463
1490
|
const prompt = String(body.prompt ?? "");
|
|
1491
|
+
|
|
1492
|
+
// Resolve the working directory. A `from` source is provisioned (clone /
|
|
1493
|
+
// worktree) through codehost/provision; a plain `cwd` is resolved to the
|
|
1494
|
+
// workspace root and mkdir-p'd so a missing dir no longer ENOENTs.
|
|
1495
|
+
let cwd: string;
|
|
1496
|
+
let provisioned: { action: string; folder: string } | null = null;
|
|
1497
|
+
const from = typeof body.from === "string" ? body.from.trim() : "";
|
|
1498
|
+
if (from) {
|
|
1499
|
+
type Spec = { owner: string; repo: string; branch: string };
|
|
1500
|
+
let prov: {
|
|
1501
|
+
parseSource?: (s: string) => Spec | null;
|
|
1502
|
+
parseSpec: (s: string) => Spec | null;
|
|
1503
|
+
provision: (
|
|
1504
|
+
spec: Spec,
|
|
1505
|
+
opts?: { wsRoot?: string },
|
|
1506
|
+
) => Promise<{ ok: boolean; folder: string; action: string; error?: string }>;
|
|
1507
|
+
};
|
|
1508
|
+
try {
|
|
1509
|
+
prov = (await import("codehost/provision")) as typeof prov;
|
|
1510
|
+
} catch (e) {
|
|
1511
|
+
return new Response(
|
|
1512
|
+
`spawn-from needs the 'codehost' package (codehost/provision) — install it ` +
|
|
1513
|
+
`(npm i -g codehost) or 'bun link' it for local dev: ${(e as Error).message}`,
|
|
1514
|
+
{ status: 501 },
|
|
1515
|
+
);
|
|
1516
|
+
}
|
|
1517
|
+
// Malformed input (e.g. bad %-encoding) must surface as 400, not a 500.
|
|
1518
|
+
let spec: Spec | null;
|
|
1519
|
+
try {
|
|
1520
|
+
spec =
|
|
1521
|
+
typeof prov.parseSource === "function"
|
|
1522
|
+
? prov.parseSource(from)
|
|
1523
|
+
: prov.parseSpec(normalizeGithubSource(from));
|
|
1524
|
+
} catch {
|
|
1525
|
+
spec = null;
|
|
1526
|
+
}
|
|
1527
|
+
if (!spec) return new Response(`unrecognized spawn source: ${from}`, { status: 400 });
|
|
1528
|
+
// Provisioning clones the repo and runs its setup script (dependency
|
|
1529
|
+
// installs + package lifecycle hooks = code execution on the host), so
|
|
1530
|
+
// gate it behind an owner/repo allowlist — empty allowlist = deny all.
|
|
1531
|
+
if (!isProvisionAllowed(spec.owner, spec.repo))
|
|
1532
|
+
return new Response(
|
|
1533
|
+
`provisioning '${spec.owner}/${spec.repo}' is not allowed — add the owner to ` +
|
|
1534
|
+
`provisionAllowlist in ~/.agent-yes/config.json (or "*" to allow all)`,
|
|
1535
|
+
{ status: 403 },
|
|
1536
|
+
);
|
|
1537
|
+
let result: { ok: boolean; folder: string; action: string; error?: string };
|
|
1538
|
+
try {
|
|
1539
|
+
const wsRoot = getProvisionRoot();
|
|
1540
|
+
result = await prov.provision(spec, wsRoot ? { wsRoot } : undefined);
|
|
1541
|
+
} catch (e) {
|
|
1542
|
+
return new Response(`provision failed: ${(e as Error).message}`, { status: 502 });
|
|
1543
|
+
}
|
|
1544
|
+
if (!result?.ok) return new Response(result?.error ?? "provision failed", { status: 502 });
|
|
1545
|
+
cwd = result.folder;
|
|
1546
|
+
provisioned = { action: result.action, folder: result.folder };
|
|
1547
|
+
} else {
|
|
1548
|
+
cwd = resolveSpawnCwd(body.cwd);
|
|
1549
|
+
try {
|
|
1550
|
+
await mkdir(cwd, { recursive: true });
|
|
1551
|
+
} catch (e) {
|
|
1552
|
+
return new Response(`cannot create cwd ${cwd}: ${(e as Error).message}`, { status: 500 });
|
|
1553
|
+
}
|
|
1554
|
+
}
|
|
1464
1555
|
process.stderr.write(
|
|
1465
|
-
`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd})\n`,
|
|
1556
|
+
`→ console spawned: ay ${cli}${prompt ? ` -- "${prompt.slice(0, 60)}"` : ""} (cwd: ${cwd}${provisioned ? `, ${provisioned.action} from ${from}` : ""})\n`,
|
|
1466
1557
|
);
|
|
1467
1558
|
// Resolve `ay` to an absolute command. The detached daemon (oxmgr/launchd/
|
|
1468
1559
|
// pm2) usually has a PATH WITHOUT ~/.bun/bin, so a bare "ay" fails with
|
|
@@ -1483,7 +1574,13 @@ export async function cmdServe(rest: string[]): Promise<number> {
|
|
|
1483
1574
|
stderr: "ignore",
|
|
1484
1575
|
});
|
|
1485
1576
|
child.unref();
|
|
1486
|
-
return Response.json({
|
|
1577
|
+
return Response.json({
|
|
1578
|
+
ok: true,
|
|
1579
|
+
pid: child.pid,
|
|
1580
|
+
cli,
|
|
1581
|
+
cwd,
|
|
1582
|
+
...(provisioned ? { provisioned } : {}),
|
|
1583
|
+
});
|
|
1487
1584
|
} catch (e) {
|
|
1488
1585
|
return new Response((e as Error).message, { status: 500 });
|
|
1489
1586
|
}
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
-
import { mkdtempSync, rmSync } from "fs";
|
|
2
|
+
import { mkdtempSync, rmSync, writeFileSync } from "fs";
|
|
3
3
|
import { homedir, tmpdir } from "os";
|
|
4
4
|
import path from "path";
|
|
5
5
|
import {
|
|
6
6
|
expandTilde,
|
|
7
|
+
getProvisionAllowlist,
|
|
8
|
+
getProvisionRoot,
|
|
7
9
|
getWorkspaceRoot,
|
|
10
|
+
isProvisionAllowed,
|
|
8
11
|
resolveSpawnCwd,
|
|
9
12
|
setWorkspaceRoot,
|
|
10
13
|
} from "./workspaceConfig.ts";
|
|
@@ -20,6 +23,8 @@ describe("workspaceConfig", () => {
|
|
|
20
23
|
afterEach(() => {
|
|
21
24
|
if (original === undefined) delete process.env.AGENT_YES_HOME;
|
|
22
25
|
else process.env.AGENT_YES_HOME = original;
|
|
26
|
+
delete process.env.CODEHOST_WS_ROOT;
|
|
27
|
+
delete process.env.CODEHOST_PROVISION_ALLOWLIST;
|
|
23
28
|
rmSync(tmp, { recursive: true, force: true });
|
|
24
29
|
});
|
|
25
30
|
|
|
@@ -71,4 +76,70 @@ describe("workspaceConfig", () => {
|
|
|
71
76
|
expect(resolveSpawnCwd("a/b")).toBe(path.resolve("a/b"));
|
|
72
77
|
});
|
|
73
78
|
});
|
|
79
|
+
|
|
80
|
+
const writeConfig = (c: Record<string, unknown>) =>
|
|
81
|
+
writeFileSync(path.join(tmp, "config.json"), JSON.stringify(c));
|
|
82
|
+
|
|
83
|
+
describe("getProvisionRoot", () => {
|
|
84
|
+
it("is undefined when neither env nor config is set", () => {
|
|
85
|
+
expect(getProvisionRoot()).toBeUndefined();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it("returns the configured provisionRoot, resolved", () => {
|
|
89
|
+
writeConfig({ provisionRoot: "/code" });
|
|
90
|
+
expect(getProvisionRoot()).toBe(path.resolve("/code"));
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("env CODEHOST_WS_ROOT overrides the config and expands ~", () => {
|
|
94
|
+
writeConfig({ provisionRoot: "/code" });
|
|
95
|
+
process.env.CODEHOST_WS_ROOT = "~/ws";
|
|
96
|
+
expect(getProvisionRoot()).toBe(path.join(homedir(), "ws"));
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("ignores a blank configured value", () => {
|
|
100
|
+
writeConfig({ provisionRoot: " " });
|
|
101
|
+
expect(getProvisionRoot()).toBeUndefined();
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
describe("getProvisionAllowlist", () => {
|
|
106
|
+
it("is empty when unset", () => {
|
|
107
|
+
expect(getProvisionAllowlist()).toEqual([]);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
it("reads and normalizes the configured list (trim/lowercase/drop empties)", () => {
|
|
111
|
+
writeConfig({ provisionAllowlist: [" Snomiao ", "Acme/Repo", ""] });
|
|
112
|
+
expect(getProvisionAllowlist()).toEqual(["snomiao", "acme/repo"]);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("env CODEHOST_PROVISION_ALLOWLIST (comma-separated) overrides config", () => {
|
|
116
|
+
writeConfig({ provisionAllowlist: ["snomiao"] });
|
|
117
|
+
process.env.CODEHOST_PROVISION_ALLOWLIST = "Foo, bar/baz ,";
|
|
118
|
+
expect(getProvisionAllowlist()).toEqual(["foo", "bar/baz"]);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe("isProvisionAllowed", () => {
|
|
123
|
+
it("denies everything when the allowlist is empty (secure default)", () => {
|
|
124
|
+
expect(isProvisionAllowed("snomiao", "agent-yes")).toBe(false);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("'*' allows any owner/repo", () => {
|
|
128
|
+
writeConfig({ provisionAllowlist: ["*"] });
|
|
129
|
+
expect(isProvisionAllowed("anyone", "anything")).toBe(true);
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
it("matches by owner case-insensitively and rejects others", () => {
|
|
133
|
+
writeConfig({ provisionAllowlist: ["snomiao"] });
|
|
134
|
+
expect(isProvisionAllowed("SNOMIAO", "x")).toBe(true);
|
|
135
|
+
expect(isProvisionAllowed("evil", "x")).toBe(false);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("matches an exact owner/repo and an owner/* glob", () => {
|
|
139
|
+
writeConfig({ provisionAllowlist: ["acme/widget", "org/*"] });
|
|
140
|
+
expect(isProvisionAllowed("acme", "widget")).toBe(true);
|
|
141
|
+
expect(isProvisionAllowed("acme", "other")).toBe(false);
|
|
142
|
+
expect(isProvisionAllowed("org", "anything")).toBe(true);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
74
145
|
});
|
package/ts/workspaceConfig.ts
CHANGED
|
@@ -15,6 +15,10 @@ import { agentYesHome } from "./agentYesHome.ts";
|
|
|
15
15
|
|
|
16
16
|
interface Config {
|
|
17
17
|
workspace?: string;
|
|
18
|
+
/** Root for `from`-provisioned worktrees (`<root>/<owner>/<repo>/tree/<branch>`). */
|
|
19
|
+
provisionRoot?: string;
|
|
20
|
+
/** Owners/repos permitted for `from`-provisioning; empty = deny all. */
|
|
21
|
+
provisionAllowlist?: string[];
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
function configPath(): string {
|
|
@@ -43,6 +47,45 @@ export function getWorkspaceRoot(): string {
|
|
|
43
47
|
return w && w.trim() ? w : homedir();
|
|
44
48
|
}
|
|
45
49
|
|
|
50
|
+
/**
|
|
51
|
+
* Root for `from`-provisioned worktrees, handed to codehost/provision so they
|
|
52
|
+
* land in `<root>/<owner>/<repo>/tree/<branch>`. Resolution order: env
|
|
53
|
+
* `CODEHOST_WS_ROOT` wins (ops override), then the configured `provisionRoot`,
|
|
54
|
+
* else undefined — letting codehost/provision fall back to its own `~/ws`
|
|
55
|
+
* default. Kept separate from `workspace` (the plain-cwd default), which may be a
|
|
56
|
+
* specific project dir rather than a root.
|
|
57
|
+
*/
|
|
58
|
+
export function getProvisionRoot(): string | undefined {
|
|
59
|
+
const env = process.env.CODEHOST_WS_ROOT?.trim();
|
|
60
|
+
if (env) return path.resolve(expandTilde(env));
|
|
61
|
+
const r = readConfig().provisionRoot;
|
|
62
|
+
return r && r.trim() ? path.resolve(expandTilde(r)) : undefined;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Owner/repo allowlist for `from`-provisioning. Provisioning clones a repo and
|
|
67
|
+
* runs its setup script (dependency installs + package lifecycle hooks = code
|
|
68
|
+
* execution on the host), so an **empty allowlist means DENY ALL** — a secure
|
|
69
|
+
* default the host opts out of by listing owners it trusts. Entries match
|
|
70
|
+
* `<owner>` (any repo of that owner), `<owner>/<repo>` (exact), or `*` (allow
|
|
71
|
+
* all — an explicit opt-in to the wide-open behavior). Env
|
|
72
|
+
* `CODEHOST_PROVISION_ALLOWLIST` (comma-separated) overrides the config.
|
|
73
|
+
*/
|
|
74
|
+
export function getProvisionAllowlist(): string[] {
|
|
75
|
+
const env = process.env.CODEHOST_PROVISION_ALLOWLIST?.trim();
|
|
76
|
+
const raw = env ? env.split(",") : (readConfig().provisionAllowlist ?? []);
|
|
77
|
+
return raw.map((s) => s.trim().toLowerCase()).filter(Boolean);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Whether `<owner>/<repo>` may be `from`-provisioned, per {@link getProvisionAllowlist}. */
|
|
81
|
+
export function isProvisionAllowed(owner: string, repo: string): boolean {
|
|
82
|
+
const list = getProvisionAllowlist();
|
|
83
|
+
if (list.includes("*")) return true;
|
|
84
|
+
const o = owner.toLowerCase();
|
|
85
|
+
const full = `${owner}/${repo}`.toLowerCase();
|
|
86
|
+
return list.some((e) => e.replace(/\/\*$/, "") === o || e === full);
|
|
87
|
+
}
|
|
88
|
+
|
|
46
89
|
/** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
|
|
47
90
|
export function setWorkspaceRoot(dir: string): string {
|
|
48
91
|
const abs = path.resolve(expandTilde(dir));
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import "./ts-CaCRPaos.js";
|
|
2
|
-
import "./logger-CDIsZ-Pp.js";
|
|
3
|
-
import "./versionChecker-DfO37H1R.js";
|
|
4
|
-
import "./pidStore-fqXqTKkh.js";
|
|
5
|
-
import "./globalPidIndex-DlmmJlO8.js";
|
|
6
|
-
import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CmsPbgMe.js";
|
|
7
|
-
|
|
8
|
-
export { SUPPORTED_CLIS };
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import { t as agentYesHome } from "./agentYesHome-_eJa3DaX.js";
|
|
2
|
-
import { mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
3
|
-
import { homedir } from "os";
|
|
4
|
-
import path from "path";
|
|
5
|
-
|
|
6
|
-
//#region ts/workspaceConfig.ts
|
|
7
|
-
function configPath() {
|
|
8
|
-
return path.join(agentYesHome(), "config.json");
|
|
9
|
-
}
|
|
10
|
-
function readConfig() {
|
|
11
|
-
try {
|
|
12
|
-
return JSON.parse(readFileSync(configPath(), "utf-8"));
|
|
13
|
-
} catch {
|
|
14
|
-
return {};
|
|
15
|
-
}
|
|
16
|
-
}
|
|
17
|
-
/** Expand a leading `~` (`~` or `~/x`) to an absolute home-based path. */
|
|
18
|
-
function expandTilde(p) {
|
|
19
|
-
const s = p.trim();
|
|
20
|
-
if (s === "~") return homedir();
|
|
21
|
-
if (s.startsWith("~/") || s.startsWith("~\\")) return path.join(homedir(), s.slice(2));
|
|
22
|
-
return s;
|
|
23
|
-
}
|
|
24
|
-
/** The configured workspace root (absolute), or the home dir if unset. */
|
|
25
|
-
function getWorkspaceRoot() {
|
|
26
|
-
const w = readConfig().workspace;
|
|
27
|
-
return w && w.trim() ? w : homedir();
|
|
28
|
-
}
|
|
29
|
-
/** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
|
|
30
|
-
function setWorkspaceRoot(dir) {
|
|
31
|
-
const abs = path.resolve(expandTilde(dir));
|
|
32
|
-
const cfg = readConfig();
|
|
33
|
-
cfg.workspace = abs;
|
|
34
|
-
mkdirSync(agentYesHome(), { recursive: true });
|
|
35
|
-
writeFileSync(configPath(), JSON.stringify(cfg, null, 2));
|
|
36
|
-
return abs;
|
|
37
|
-
}
|
|
38
|
-
/**
|
|
39
|
-
* Resolve a user-supplied spawn location to an absolute cwd:
|
|
40
|
-
* - empty → the workspace root
|
|
41
|
-
* - a bare name → `<workspace>/<name>` (so "myproject" lands under the root)
|
|
42
|
-
* - `~`-prefixed → home-based absolute
|
|
43
|
-
* - anything with a path separator → resolved as-is
|
|
44
|
-
*/
|
|
45
|
-
function resolveSpawnCwd(input) {
|
|
46
|
-
const root = getWorkspaceRoot();
|
|
47
|
-
const v = (input ?? "").trim();
|
|
48
|
-
if (!v) return root;
|
|
49
|
-
if (v.startsWith("~")) return path.resolve(expandTilde(v));
|
|
50
|
-
if (v.includes("/") || v.includes("\\") || path.isAbsolute(v)) return path.resolve(v);
|
|
51
|
-
return path.join(root, v);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
//#endregion
|
|
55
|
-
export { resolveSpawnCwd as n, setWorkspaceRoot as r, getWorkspaceRoot as t };
|
|
56
|
-
//# sourceMappingURL=workspaceConfig-B3ylOZAO.js.map
|