agent-yes 1.101.0 → 1.103.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/README.md CHANGED
@@ -4,6 +4,28 @@ A wrapper tool that automates interactions with various AI CLI tools by automati
4
4
 
5
5
  ⚠️ **Important Security Warning**: Only run this on trusted repositories. This tool automatically responds to prompts and can execute commands without user confirmation. Be aware of potential prompt injection attacks where malicious code or instructions could be embedded in files or user inputs to manipulate the automated responses.
6
6
 
7
+ ## Install
8
+
9
+ One-liner (installs Bun if needed, then the `ay` / `cy` / `claude-yes` … CLIs):
10
+
11
+ ```bash
12
+ # macOS / Linux
13
+ curl -fsSL https://agent-yes.com/setup.sh | sh
14
+ ```
15
+
16
+ ```powershell
17
+ # Windows (PowerShell)
18
+ irm https://agent-yes.com/setup.ps1 | iex
19
+ ```
20
+
21
+ Or with a package manager you already have:
22
+
23
+ ```bash
24
+ bun add -g agent-yes # or: npm install -g agent-yes
25
+ ```
26
+
27
+ Then: `ay claude` (run an agent with auto-yes) · `ay serve share` (web console + shareable link) · live console at https://agent-yes.com
28
+
7
29
  ## Features
8
30
 
9
31
  - **Multi-CLI Support**: Works with Claude, Gemini, Codex, Copilot, and Cursor CLI tools
@@ -0,0 +1,8 @@
1
+ import "./ts-CFWQFwe3.js";
2
+ import "./logger-B9h0djqx.js";
3
+ import "./versionChecker-IfF6V9MT.js";
4
+ import "./pidStore-DBjlqzo8.js";
5
+ import "./globalPidIndex-yVd3mbsV.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DLa81elJ.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-mo_bB4au.js";
1
+ import { t as CLIS_CONFIG } from "./ts-CFWQFwe3.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-CXsHRSdB.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DLa81elJ.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-BhU9-zSi.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-IfF6V9MT.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-Dt9kMsEQ.js");
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-DDbKmEBG.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-DGQZ0sRr.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-Bfvn4t4T.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-mo_bB4au.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-CFWQFwe3.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BhU9-zSi.js";
3
+ import "./versionChecker-IfF6V9MT.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
6
 
@@ -1,11 +1,11 @@
1
- import "./ts-mo_bB4au.js";
1
+ import "./ts-CFWQFwe3.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BhU9-zSi.js";
3
+ import "./versionChecker-IfF6V9MT.js";
4
4
  import "./pidStore-DBjlqzo8.js";
5
5
  import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CXsHRSdB.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DLa81elJ.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-wyGQaaaK.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-DZRLQ15r.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-Bbl4BGZs.js.map
555
+ //# sourceMappingURL=serve-_SDEh-NR.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-wyGQaaaK.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-DZRLQ15r.js";
5
5
 
6
6
  export { cmdHelp, isSubcommand, runSubcommand };
@@ -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-Bbl4BGZs.js");
166
+ const { cmdServe } = await import("./serve-_SDEh-NR.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-wyGQaaaK.js.map
1455
+ //# sourceMappingURL=subcommands-DZRLQ15r.js.map
@@ -1,5 +1,5 @@
1
1
  import { n as logger, t as addTransport } from "./logger-B9h0djqx.js";
2
- import { r as getInstalledPackage } from "./versionChecker-BhU9-zSi.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-IfF6V9MT.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-mo_bB4au.js.map
1717
+ //# sourceMappingURL=ts-CFWQFwe3.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.101.0";
10
+ var version = "1.103.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-BhU9-zSi.js.map
224
+ //# sourceMappingURL=versionChecker-IfF6V9MT.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
@@ -101,6 +101,23 @@
101
101
  color: var(--muted);
102
102
  font-size: 12.5px;
103
103
  }
104
+ .install {
105
+ margin-top: 8px;
106
+ color: var(--muted);
107
+ font-size: 11.5px;
108
+ }
109
+ .install code {
110
+ cursor: pointer;
111
+ color: var(--green);
112
+ }
113
+ .install code:hover {
114
+ filter: brightness(1.25);
115
+ }
116
+ @media (max-width: 720px) {
117
+ .install .winhint {
118
+ display: none;
119
+ }
120
+ }
104
121
  .ibox {
105
122
  display: flex;
106
123
  align-items: center;
@@ -604,6 +621,14 @@
604
621
  <div class="sub">
605
622
  Live <code>ay ls</code> + per-agent tail &amp; send. Backed by <code>ay serve</code>.
606
623
  </div>
624
+ <div class="install" title="click to copy">
625
+ install:
626
+ <code class="copy">curl -fsSL https://agent-yes.com/setup.sh | sh</code>
627
+ <span class="winhint"
628
+ >·&nbsp;Windows:
629
+ <code class="copy">irm https://agent-yes.com/setup.ps1 | iex</code></span
630
+ >
631
+ </div>
607
632
  <div class="ibox">
608
633
  <span class="mag">⌕</span>
609
634
  <input
@@ -650,7 +675,21 @@
650
675
  <div class="launchoverlay" id="launch" style="display: none"></div>
651
676
  <div class="launchoverlay" id="newform" style="display: none"></div>
652
677
 
653
- <script>
678
+ <script type="module">
679
+ // Pure list/identity/filter helpers live in a sibling module so they can be
680
+ // unit-tested (tests/ui-logic/console-logic.spec.ts) without a DOM. This
681
+ // script is a module (deferred, runs after the codehost shim above sets
682
+ // window.__codehost), so a static import is safe.
683
+ import {
684
+ cliLabel,
685
+ repoBranch,
686
+ ident,
687
+ tagsFor,
688
+ age,
689
+ matches,
690
+ nextIndex,
691
+ } from "./console-logic.js";
692
+
654
693
  let entries = [];
655
694
  let sel = null; // selected keyword (pid as string)
656
695
  let es = null; // live-tail subscription closer
@@ -984,66 +1023,6 @@
984
1023
  },
985
1024
  };
986
1025
 
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
1026
  // Compact list: one line per agent (dot + cli + title), persisted per device.
1048
1027
  let compactList = localStorage.getItem("ay.compactList") === "1";
1049
1028
 
@@ -1244,10 +1223,8 @@
1244
1223
  const toks = $("q").value.trim().split(/\s+/).filter(Boolean);
1245
1224
  const shown = entries.filter((e) => matches(e, toks));
1246
1225
  if (!shown.length) return;
1247
- let i = shown.findIndex((e) => String(e.pid) === sel);
1248
- if (i < 0) i = dir > 0 ? 0 : shown.length - 1;
1249
- else i = Math.max(0, Math.min(shown.length - 1, i + dir));
1250
- const next = shown[i];
1226
+ const cur = shown.findIndex((e) => String(e.pid) === sel);
1227
+ const next = shown[nextIndex(shown.length, cur, dir)];
1251
1228
  select(String(next.pid));
1252
1229
  const row = $("list").querySelector('.row[data-pid="' + next.pid + '"]');
1253
1230
  if (row) row.scrollIntoView({ block: "nearest" });
@@ -1384,8 +1361,12 @@
1384
1361
  : `<div class="empty2">no saved rooms — paste a share link below</div>`;
1385
1362
  $("rooms").innerHTML = `<div class="rtitle">rooms · stored on this device</div>${items}
1386
1363
  <div class="radd"><input id="roomin" placeholder="room:token or https://…/#room:token" /><button id="roomadd">add</button></div>
1364
+ <div class="rconnect">new here? install agent-yes (bun or npm, auto):
1365
+ <code id="cmdinstall" class="copy" title="click to copy">curl -fsSL https://agent-yes.com/setup.sh | sh</code>
1366
+ <span style="opacity:.6">Windows:</span>
1367
+ <code id="cmdinstallwin" class="copy" title="click to copy">irm https://agent-yes.com/setup.ps1 | iex</code></div>
1387
1368
  <div class="rconnect">share your own fleet — run this, then open the printed link:
1388
- <code id="cmd" title="click to copy">bunx agent-yes serve --share</code></div>
1369
+ <code id="cmd" class="copy" title="click to copy">ay serve share</code></div>
1389
1370
  <div class="rconnect">or view a <b>codehost</b> room — paste <code>ch:&lt;room-token&gt;</code> (or a
1390
1371
  codehost.dev share link) above; every machine in the room shows its agents here.</div>`;
1391
1372
  }
@@ -1421,15 +1402,20 @@
1421
1402
  $("rooms").style.display = "none";
1422
1403
  } else alert("expected room:token or a share link");
1423
1404
  }
1424
- if (ev.target.id === "cmd") {
1425
- navigator.clipboard?.writeText(ev.target.textContent).then(() => {
1426
- const o = ev.target.textContent;
1427
- ev.target.textContent = "copied ✓";
1428
- setTimeout(() => {
1429
- ev.target.textContent = o;
1430
- }, 1000);
1431
- });
1432
- }
1405
+ });
1406
+
1407
+ // Click-to-copy for any <code class="copy"> (install one-liners, share cmd…),
1408
+ // wherever it lives — header or the rooms panel.
1409
+ document.addEventListener("click", (ev) => {
1410
+ const c = ev.target.closest && ev.target.closest("code.copy");
1411
+ if (!c) return;
1412
+ navigator.clipboard?.writeText(c.textContent).then(() => {
1413
+ const o = c.textContent;
1414
+ c.textContent = "copied ✓";
1415
+ setTimeout(() => {
1416
+ c.textContent = o;
1417
+ }, 1000);
1418
+ });
1433
1419
  });
1434
1420
 
1435
1421
  // ---- launch: command-only URLs (#launch=<json>, NO token) ----
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.101.0",
3
+ "version": "1.103.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-mo_bB4au.js";
2
- import "./logger-B9h0djqx.js";
3
- import "./versionChecker-BhU9-zSi.js";
4
- import "./pidStore-DBjlqzo8.js";
5
- import "./globalPidIndex-yVd3mbsV.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CXsHRSdB.js";
7
-
8
- export { SUPPORTED_CLIS };