agent-yes 1.100.0 → 1.102.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.
@@ -0,0 +1,8 @@
1
+ import "./ts-cYD43w4Z.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-DdMHudEv.js";
4
+ import "./pidStore-DBjlqzo8.js";
5
+ import "./globalPidIndex-yVd3mbsV.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DZBdcl3r.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-CUn393DD.js";
1
+ import { t as CLIS_CONFIG } from "./ts-cYD43w4Z.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-DIHMEdRx.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DZBdcl3r.js.map
package/dist/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env bun
2
2
  import { n as logger } from "./logger-B9h0djqx.js";
3
- import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-BWdncsn6.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DdMHudEv.js";
4
4
  import { argv } from "process";
5
5
  import { execFileSync, spawn } from "child_process";
6
6
  import ms from "ms";
@@ -482,7 +482,7 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
482
482
  {
483
483
  const rawArg = process.argv[2];
484
484
  const isHelpFlag = rawArg === "-h" || rawArg === "--help";
485
- const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-dnjUZ9nY.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-Clfntx3R.js");
486
486
  if (isHelpFlag && process.argv.length === 3) {
487
487
  cmdHelp();
488
488
  process.exit(0);
@@ -515,7 +515,7 @@ if (config.useRust) {
515
515
  }
516
516
  }
517
517
  if (rustBinary) {
518
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-C-cenkTG.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-C6FspQLb.js");
519
519
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
520
520
  if (config.verbose) {
521
521
  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-CUn393DD.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-cYD43w4Z.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BWdncsn6.js";
3
+ import "./versionChecker-DdMHudEv.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
6
 
@@ -1,11 +1,11 @@
1
- import "./ts-CUn393DD.js";
1
+ import "./ts-cYD43w4Z.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BWdncsn6.js";
3
+ import "./versionChecker-DdMHudEv.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DIHMEdRx.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DZBdcl3r.js";
7
7
  import "./remotes-C3xPRtfg.js";
8
- import { c as readNotes, f as snapshotStatus, l as renderRawLog, m as writeToIpc, o as listRecords, r as controlCodeFromName, u as resolveOne } from "./subcommands-BKY3nQV4.js";
8
+ import { c as readNotes, f as snapshotStatus, l as renderRawLog, m as writeToIpc, o as listRecords, r as controlCodeFromName, u as resolveOne } from "./subcommands-2xkgzhhI.js";
9
9
  import yargs from "yargs";
10
10
  import { mkdir, open, readFile, writeFile } from "fs/promises";
11
11
  import { homedir } from "os";
@@ -482,6 +482,7 @@ Options:
482
482
  const p = new URL(req.url).pathname;
483
483
  if (req.method === "GET" && (p === "/" || p === "/index.html")) return serveUiFile("index.html", "text/html; charset=utf-8");
484
484
  if (req.method === "GET" && p === "/room-client.js") return serveUiFile("room-client.js", "text/javascript; charset=utf-8");
485
+ if (req.method === "GET" && p === "/console-logic.js") return serveUiFile("console-logic.js", "text/javascript; charset=utf-8");
485
486
  if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
486
487
  return apiFetch(req);
487
488
  };
@@ -551,4 +552,4 @@ Options:
551
552
 
552
553
  //#endregion
553
554
  export { cmdServe };
554
- //# sourceMappingURL=serve-C4fZSjh9.js.map
555
+ //# sourceMappingURL=serve-D_NsFccM.js.map
@@ -163,7 +163,7 @@ async function runSubcommand(argv) {
163
163
  case "restart": return await cmdRestart(rest);
164
164
  case "note": return await cmdNote(rest);
165
165
  case "serve": {
166
- const { cmdServe } = await import("./serve-C4fZSjh9.js");
166
+ const { cmdServe } = await import("./serve-D_NsFccM.js");
167
167
  return cmdServe(rest);
168
168
  }
169
169
  case "setup": {
@@ -1452,4 +1452,4 @@ async function cmdStatus(rest) {
1452
1452
 
1453
1453
  //#endregion
1454
1454
  export { isSubcommand as a, readNotes as c, runSubcommand as d, snapshotStatus as f, isPidAlive as i, renderRawLog as l, writeToIpc as m, cmdHelp as n, listRecords as o, stopTipForCli as p, controlCodeFromName as r, matchKeyword as s, GRACEFUL_EXIT_COMMANDS as t, resolveOne as u };
1455
- //# sourceMappingURL=subcommands-BKY3nQV4.js.map
1455
+ //# sourceMappingURL=subcommands-2xkgzhhI.js.map
@@ -1,6 +1,6 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-yVd3mbsV.js";
3
3
  import "./remotes-C3xPRtfg.js";
4
- import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-BKY3nQV4.js";
4
+ import { a as isSubcommand, c as readNotes, d as runSubcommand, f as snapshotStatus, i as isPidAlive, l as renderRawLog, m as writeToIpc, n as cmdHelp, o as listRecords, p as stopTipForCli, r as controlCodeFromName, s as matchKeyword, t as GRACEFUL_EXIT_COMMANDS, u as resolveOne } from "./subcommands-2xkgzhhI.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-BWdncsn6.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DdMHudEv.js";
3
3
  import { n as agentYesHome, t as PidStore } from "./pidStore-DBjlqzo8.js";
4
4
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-CJxsoGdb.js";
5
5
  import { i as readGlobalPids } from "./globalPidIndex-yVd3mbsV.js";
@@ -1714,4 +1714,4 @@ function sleep(ms) {
1714
1714
 
1715
1715
  //#endregion
1716
1716
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1717
- //# sourceMappingURL=ts-CUn393DD.js.map
1717
+ //# sourceMappingURL=ts-cYD43w4Z.js.map
@@ -7,7 +7,7 @@ import { fileURLToPath } from "url";
7
7
 
8
8
  //#region package.json
9
9
  var name = "agent-yes";
10
- var version = "1.100.0";
10
+ var version = "1.102.0";
11
11
 
12
12
  //#endregion
13
13
  //#region ts/versionChecker.ts
@@ -221,4 +221,4 @@ async function displayVersion() {
221
221
 
222
222
  //#endregion
223
223
  export { versionString as i, displayVersion as n, getInstalledPackage as r, checkAndAutoUpdate as t };
224
- //# sourceMappingURL=versionChecker-BWdncsn6.js.map
224
+ //# sourceMappingURL=versionChecker-DdMHudEv.js.map
@@ -0,0 +1,79 @@
1
+ // Pure, DOM-free logic for the agent-yes console (lab/ui/index.html).
2
+ //
3
+ // Extracted into its own ES module so it can be unit-tested in vitest
4
+ // (tests/ui-logic/console-logic.spec.ts) while the browser imports it directly
5
+ // — no build step. Everything here is a pure function of its arguments: no
6
+ // document/window/localStorage access, no Date.now() except via an injected
7
+ // `now` so age() is deterministic under test.
8
+
9
+ // An agent entry as surfaced by /api/ls (the fields this module reads):
10
+ // { cli, cwd, title, prompt, status, started_at, pid, _host }
11
+
12
+ // claude is the default CLI — show the cli name only when it differs, so the
13
+ // common case stays uncluttered and the identity (repo/branch) leads instead.
14
+ export const cliLabel = (e) => (e.cli && e.cli !== "claude" ? e.cli : "");
15
+
16
+ // Parse owner/repo/branch from a cwd like .../ws/<owner>/<repo>/tree/<branch>.
17
+ export function repoBranch(e) {
18
+ const m = /\/([^/]+)\/([^/]+)\/tree\/([^/]+)/.exec(e.cwd || "");
19
+ return m ? { owner: m[1], repo: m[2], branch: m[3] } : null;
20
+ }
21
+
22
+ // Identity string for the left panel. cap=true → repo/branch each clipped to
23
+ // 3 chars for the compact one-line view (e.g. "age/mai").
24
+ export function ident(e, cap) {
25
+ const rb = repoBranch(e);
26
+ if (!rb) return "";
27
+ const c = (s) => (cap && s.length > 3 ? s.slice(0, 3) : s);
28
+ return `${c(rb.repo)}/${c(rb.branch)}`;
29
+ }
30
+
31
+ // Derive codehost-style mnemonic tags from a cwd like .../ws/<owner>/<repo>/tree/<wt>.
32
+ export function tagsFor(e) {
33
+ const t = [];
34
+ const rb = repoBranch(e);
35
+ if (rb) {
36
+ t.push(["repo", `${rb.owner}/${rb.repo}`], ["wt", rb.branch]);
37
+ }
38
+ const cli = cliLabel(e);
39
+ if (cli) t.push(["cli", cli]);
40
+ if (e._host) t.push(["host", e._host]); // codehost rooms: which machine
41
+ return t;
42
+ }
43
+
44
+ // Human age of an agent ("12s" / "5m" / "3h"). `now` is injectable so tests
45
+ // don't depend on the wall clock; the browser calls age(e) and gets Date.now().
46
+ export function age(e, now = Date.now()) {
47
+ if (!e.started_at) return "";
48
+ const s = Math.max(0, (now - e.started_at) / 1000);
49
+ if (s < 60) return Math.floor(s) + "s";
50
+ if (s < 3600) return Math.floor(s / 60) + "m";
51
+ return Math.floor(s / 3600) + "h";
52
+ }
53
+
54
+ // Filter predicate: every space-separated token must match. A `key:value` token
55
+ // matches against the mnemonic tags (repo/wt/cli/host); a bare token is a
56
+ // case-insensitive substring search over title/prompt/cli/cwd/status.
57
+ export function matches(e, toks) {
58
+ const hay =
59
+ (e.title || "") + " " + (e.prompt || "") + " " + e.cli + " " + (e.cwd || "") + " " + e.status;
60
+ return toks.every((tok) => {
61
+ tok = tok.toLowerCase();
62
+ const ci = tok.indexOf(":");
63
+ if (ci > 0) {
64
+ const k = tok.slice(0, ci),
65
+ v = tok.slice(ci + 1);
66
+ return tagsFor(e).some(([tk, tv]) => tk === k && tv.toLowerCase().includes(v));
67
+ }
68
+ return hay.toLowerCase().includes(tok);
69
+ });
70
+ }
71
+
72
+ // Next selection index when stepping the list by `dir` (+1 down / -1 up).
73
+ // No current selection (i<0) lands on the first (down) or last (up) row;
74
+ // otherwise clamps at the ends. Returns -1 for an empty list.
75
+ export function nextIndex(len, i, dir) {
76
+ if (len <= 0) return -1;
77
+ if (i < 0) return dir > 0 ? 0 : len - 1;
78
+ return Math.max(0, Math.min(len - 1, i + dir));
79
+ }
package/lab/ui/index.html CHANGED
@@ -650,7 +650,21 @@
650
650
  <div class="launchoverlay" id="launch" style="display: none"></div>
651
651
  <div class="launchoverlay" id="newform" style="display: none"></div>
652
652
 
653
- <script>
653
+ <script type="module">
654
+ // Pure list/identity/filter helpers live in a sibling module so they can be
655
+ // unit-tested (tests/ui-logic/console-logic.spec.ts) without a DOM. This
656
+ // script is a module (deferred, runs after the codehost shim above sets
657
+ // window.__codehost), so a static import is safe.
658
+ import {
659
+ cliLabel,
660
+ repoBranch,
661
+ ident,
662
+ tagsFor,
663
+ age,
664
+ matches,
665
+ nextIndex,
666
+ } from "./console-logic.js";
667
+
654
668
  let entries = [];
655
669
  let sel = null; // selected keyword (pid as string)
656
670
  let es = null; // live-tail subscription closer
@@ -984,66 +998,6 @@
984
998
  },
985
999
  };
986
1000
 
987
- // claude is the default CLI — show the cli name only when it differs, so the
988
- // common case stays uncluttered and the identity (repo/branch) leads instead.
989
- const cliLabel = (e) => (e.cli && e.cli !== "claude" ? e.cli : "");
990
- // Parse owner/repo/branch from a cwd like .../ws/<owner>/<repo>/tree/<branch>.
991
- function repoBranch(e) {
992
- const m = /\/([^/]+)\/([^/]+)\/tree\/([^/]+)/.exec(e.cwd || "");
993
- return m ? { owner: m[1], repo: m[2], branch: m[3] } : null;
994
- }
995
- // Identity string for the left panel. cap=true → repo/branch each clipped to
996
- // 3 chars for the compact one-line view (e.g. "age/mai").
997
- function ident(e, cap) {
998
- const rb = repoBranch(e);
999
- if (!rb) return "";
1000
- const c = (s) => (cap && s.length > 3 ? s.slice(0, 3) : s);
1001
- return `${c(rb.repo)}/${c(rb.branch)}`;
1002
- }
1003
-
1004
- // Derive codehost-style mnemonic tags from a cwd like .../ws/<owner>/<repo>/tree/<wt>
1005
- function tagsFor(e) {
1006
- const t = [];
1007
- const rb = repoBranch(e);
1008
- if (rb) {
1009
- t.push(["repo", `${rb.owner}/${rb.repo}`], ["wt", rb.branch]);
1010
- }
1011
- const cli = cliLabel(e);
1012
- if (cli) t.push(["cli", cli]);
1013
- if (e._host) t.push(["host", e._host]); // codehost rooms: which machine
1014
- return t;
1015
- }
1016
- function age(e) {
1017
- if (!e.started_at) return "";
1018
- const s = Math.max(0, (Date.now() - e.started_at) / 1000);
1019
- if (s < 60) return Math.floor(s) + "s";
1020
- if (s < 3600) return Math.floor(s / 60) + "m";
1021
- return Math.floor(s / 3600) + "h";
1022
- }
1023
-
1024
- function matches(e, toks) {
1025
- const hay =
1026
- (e.title || "") +
1027
- " " +
1028
- (e.prompt || "") +
1029
- " " +
1030
- e.cli +
1031
- " " +
1032
- (e.cwd || "") +
1033
- " " +
1034
- e.status;
1035
- return toks.every((tok) => {
1036
- tok = tok.toLowerCase();
1037
- const ci = tok.indexOf(":");
1038
- if (ci > 0) {
1039
- const k = tok.slice(0, ci),
1040
- v = tok.slice(ci + 1);
1041
- return tagsFor(e).some(([tk, tv]) => tk === k && tv.toLowerCase().includes(v));
1042
- }
1043
- return hay.toLowerCase().includes(tok);
1044
- });
1045
- }
1046
-
1047
1001
  // Compact list: one line per agent (dot + cli + title), persisted per device.
1048
1002
  let compactList = localStorage.getItem("ay.compactList") === "1";
1049
1003
 
@@ -1238,6 +1192,32 @@
1238
1192
  } catch {}
1239
1193
  });
1240
1194
 
1195
+ // Step the selection up/down the (filtered) list — same order the left panel
1196
+ // renders. Clamps at the ends, scrolls the row into view.
1197
+ function stepSelection(dir) {
1198
+ const toks = $("q").value.trim().split(/\s+/).filter(Boolean);
1199
+ const shown = entries.filter((e) => matches(e, toks));
1200
+ if (!shown.length) return;
1201
+ const cur = shown.findIndex((e) => String(e.pid) === sel);
1202
+ const next = shown[nextIndex(shown.length, cur, dir)];
1203
+ select(String(next.pid));
1204
+ const row = $("list").querySelector('.row[data-pid="' + next.pid + '"]');
1205
+ if (row) row.scrollIntoView({ block: "nearest" });
1206
+ }
1207
+ // Alt+PageDown / Alt+PageUp cycles agents. Capture phase on window so it
1208
+ // fires BEFORE xterm's textarea handler — stopPropagation then keeps the
1209
+ // combo from being forwarded to the focused agent's PTY as keystrokes.
1210
+ window.addEventListener(
1211
+ "keydown",
1212
+ (e) => {
1213
+ if (!e.altKey || (e.key !== "PageDown" && e.key !== "PageUp")) return;
1214
+ e.preventDefault();
1215
+ e.stopPropagation();
1216
+ stepSelection(e.key === "PageDown" ? 1 : -1);
1217
+ },
1218
+ true,
1219
+ );
1220
+
1241
1221
  // ---- rooms: localStorage cache + a manager you open by clicking the badge ----
1242
1222
  const ROOMS_KEY = "ay.rooms";
1243
1223
  let curRoom = null;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.100.0",
3
+ "version": "1.102.0",
4
4
  "description": "A wrapper tool that automates interactions with various AI CLI tools by automatically handling common prompts and responses.",
5
5
  "keywords": [
6
6
  "ai",
@@ -55,6 +55,7 @@
55
55
  "ts/*.ts",
56
56
  "!dist/**/*.map",
57
57
  "dist/**/*.js",
58
+ "lab/ui/console-logic.js",
58
59
  "lab/ui/index.html",
59
60
  "lab/ui/room-client.js"
60
61
  ],
@@ -86,7 +87,8 @@
86
87
  "release:beta": "standard-version && npm publish --tag beta",
87
88
  "test": "vitest run",
88
89
  "test:coverage": "vitest run --coverage",
89
- "test:ui": "vitest run --config tests/ui-test/vitest.config.ts"
90
+ "test:ui": "vitest run --config tests/ui-test/vitest.config.ts",
91
+ "test:ui-dom": "vitest run --config tests/ui-dom/vitest.config.ts"
90
92
  },
91
93
  "dependencies": {
92
94
  "@snomiao/bun-pty": "^0.3.4",
package/ts/serve.ts CHANGED
@@ -620,6 +620,8 @@ export async function cmdServe(rest: string[]): Promise<number> {
620
620
  return serveUiFile("index.html", "text/html; charset=utf-8");
621
621
  if (req.method === "GET" && p === "/room-client.js")
622
622
  return serveUiFile("room-client.js", "text/javascript; charset=utf-8");
623
+ if (req.method === "GET" && p === "/console-logic.js")
624
+ return serveUiFile("console-logic.js", "text/javascript; charset=utf-8");
623
625
  if (req.method === "GET" && p === "/favicon.ico") return new Response(null, { status: 204 });
624
626
  return apiFetch(req);
625
627
  };
@@ -0,0 +1,71 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
2
+ import { mkdtempSync, rmSync } from "fs";
3
+ import { homedir, tmpdir } from "os";
4
+ import path from "path";
5
+ import {
6
+ expandTilde,
7
+ getWorkspaceRoot,
8
+ resolveSpawnCwd,
9
+ setWorkspaceRoot,
10
+ } from "./workspaceConfig.ts";
11
+
12
+ describe("workspaceConfig", () => {
13
+ let original: string | undefined;
14
+ let tmp: string;
15
+ beforeEach(() => {
16
+ original = process.env.AGENT_YES_HOME;
17
+ tmp = mkdtempSync(path.join(tmpdir(), "ay-cfg-"));
18
+ process.env.AGENT_YES_HOME = tmp;
19
+ });
20
+ afterEach(() => {
21
+ if (original === undefined) delete process.env.AGENT_YES_HOME;
22
+ else process.env.AGENT_YES_HOME = original;
23
+ rmSync(tmp, { recursive: true, force: true });
24
+ });
25
+
26
+ it("defaults the workspace root to the home dir when unset", () => {
27
+ expect(getWorkspaceRoot()).toBe(homedir());
28
+ });
29
+
30
+ it("round-trips set/get and resolves to an absolute path", () => {
31
+ const saved = setWorkspaceRoot(path.join(tmp, "ws"));
32
+ expect(saved).toBe(path.join(tmp, "ws"));
33
+ expect(getWorkspaceRoot()).toBe(path.join(tmp, "ws"));
34
+ });
35
+
36
+ it("expands a leading ~", () => {
37
+ expect(expandTilde("~")).toBe(homedir());
38
+ expect(expandTilde("~/projects")).toBe(path.join(homedir(), "projects"));
39
+ expect(expandTilde("/abs/path")).toBe("/abs/path");
40
+ });
41
+
42
+ it("stores a tilde path as home-based absolute", () => {
43
+ const saved = setWorkspaceRoot("~/myws");
44
+ expect(saved).toBe(path.join(homedir(), "myws"));
45
+ });
46
+
47
+ describe("resolveSpawnCwd", () => {
48
+ beforeEach(() => setWorkspaceRoot(path.join(tmp, "ws")));
49
+
50
+ it("empty input → workspace root", () => {
51
+ expect(resolveSpawnCwd("")).toBe(path.join(tmp, "ws"));
52
+ expect(resolveSpawnCwd(undefined)).toBe(path.join(tmp, "ws"));
53
+ });
54
+
55
+ it("bare name → <workspace>/<name>", () => {
56
+ expect(resolveSpawnCwd("myproject")).toBe(path.join(tmp, "ws", "myproject"));
57
+ });
58
+
59
+ it("absolute path → used as-is", () => {
60
+ expect(resolveSpawnCwd("/tmp/elsewhere")).toBe("/tmp/elsewhere");
61
+ });
62
+
63
+ it("tilde path → home-based", () => {
64
+ expect(resolveSpawnCwd("~/docs")).toBe(path.join(homedir(), "docs"));
65
+ });
66
+
67
+ it("a relative path with a separator is resolved, not joined to the workspace", () => {
68
+ expect(resolveSpawnCwd("a/b")).toBe(path.resolve("a/b"));
69
+ });
70
+ });
71
+ });
@@ -0,0 +1,70 @@
1
+ import { mkdirSync, readFileSync, writeFileSync } from "fs";
2
+ import { homedir } from "os";
3
+ import path from "path";
4
+ import { agentYesHome } from "./agentYesHome.ts";
5
+
6
+ /**
7
+ * Machine-global agent-yes config (workspace root, etc.), stored alongside the
8
+ * pid index and serve token under `agentYesHome()`. Kept tiny and synchronous —
9
+ * it's read on the spawn hot path and written once during `ay setup`.
10
+ *
11
+ * The *workspace root* is the default directory new agents are spawned into when
12
+ * the console doesn't pass an explicit cwd. It defaults to the user's home dir so
13
+ * a non-engineer can just run agents in their files without knowing about paths.
14
+ */
15
+
16
+ interface Config {
17
+ workspace?: string;
18
+ }
19
+
20
+ function configPath(): string {
21
+ return path.join(agentYesHome(), "config.json");
22
+ }
23
+
24
+ function readConfig(): Config {
25
+ try {
26
+ return JSON.parse(readFileSync(configPath(), "utf-8")) as Config;
27
+ } catch {
28
+ return {};
29
+ }
30
+ }
31
+
32
+ /** Expand a leading `~` (`~` or `~/x`) to an absolute home-based path. */
33
+ export function expandTilde(p: string): string {
34
+ const s = p.trim();
35
+ if (s === "~") return homedir();
36
+ if (s.startsWith("~/") || s.startsWith("~\\")) return path.join(homedir(), s.slice(2));
37
+ return s;
38
+ }
39
+
40
+ /** The configured workspace root (absolute), or the home dir if unset. */
41
+ export function getWorkspaceRoot(): string {
42
+ const w = readConfig().workspace;
43
+ return w && w.trim() ? w : homedir();
44
+ }
45
+
46
+ /** Persist the workspace root, tilde-expanded and resolved to an absolute path. */
47
+ export function setWorkspaceRoot(dir: string): string {
48
+ const abs = path.resolve(expandTilde(dir));
49
+ const cfg = readConfig();
50
+ cfg.workspace = abs;
51
+ mkdirSync(agentYesHome(), { recursive: true });
52
+ writeFileSync(configPath(), JSON.stringify(cfg, null, 2));
53
+ return abs;
54
+ }
55
+
56
+ /**
57
+ * Resolve a user-supplied spawn location to an absolute cwd:
58
+ * - empty → the workspace root
59
+ * - a bare name → `<workspace>/<name>` (so "myproject" lands under the root)
60
+ * - `~`-prefixed → home-based absolute
61
+ * - anything with a path separator → resolved as-is
62
+ */
63
+ export function resolveSpawnCwd(input?: string): string {
64
+ const root = getWorkspaceRoot();
65
+ const v = (input ?? "").trim();
66
+ if (!v) return root;
67
+ if (v.startsWith("~")) return path.resolve(expandTilde(v));
68
+ if (v.includes("/") || v.includes("\\") || path.isAbsolute(v)) return path.resolve(v);
69
+ return path.join(root, v);
70
+ }
@@ -1,8 +0,0 @@
1
- import "./ts-CUn393DD.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BWdncsn6.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DIHMEdRx.js";
7
-
8
- export { SUPPORTED_CLIS };