claude-yes 1.87.0 → 1.88.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.
@@ -1,6 +1,6 @@
1
- import { t as CLIS_CONFIG } from "./ts-Oxr2rEMA.js";
1
+ import { t as CLIS_CONFIG } from "./ts-CDrRTQFz.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker--29PmTlR.js";
3
+ import "./versionChecker-BjRotlWY.js";
4
4
  import "./pidStore-C1JXxoPi.js";
5
5
  import "./globalPidIndex-Cr-g75QF.js";
6
6
 
@@ -9,4 +9,4 @@ const SUPPORTED_CLIS = Object.keys(CLIS_CONFIG);
9
9
 
10
10
  //#endregion
11
11
  export { SUPPORTED_CLIS };
12
- //# sourceMappingURL=SUPPORTED_CLIS-DFGyxVkl.js.map
12
+ //# sourceMappingURL=SUPPORTED_CLIS-VbHPdSIt.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--29PmTlR.js";
3
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-BjRotlWY.js";
4
4
  import { argv } from "process";
5
5
  import { execFileSync, spawn } from "child_process";
6
6
  import ms from "ms";
@@ -480,8 +480,14 @@ function buildRustArgs(argv, cliFromScript, supportedClis) {
480
480
  }
481
481
  }
482
482
  {
483
- const { isSubcommand, runSubcommand } = await import("./subcommands-B2lxwpQn.js");
484
- if (isSubcommand(process.argv[2])) {
483
+ const rawArg = process.argv[2];
484
+ const isHelpFlag = rawArg === "-h" || rawArg === "--help";
485
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-Dnh67Qo3.js");
486
+ if (isHelpFlag && process.argv.length === 3) {
487
+ cmdHelp();
488
+ process.exit(0);
489
+ }
490
+ if (isSubcommand(rawArg)) {
485
491
  const code = await runSubcommand(process.argv);
486
492
  process.exit(code ?? 0);
487
493
  }
@@ -509,7 +515,7 @@ if (config.useRust) {
509
515
  }
510
516
  }
511
517
  if (rustBinary) {
512
- const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-DFGyxVkl.js");
518
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-VbHPdSIt.js");
513
519
  const rustArgs = buildRustArgs(process.argv, config.cli, SUPPORTED_CLIS);
514
520
  if (config.verbose) {
515
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-Oxr2rEMA.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-CDrRTQFz.js";
2
2
  import "./logger-B9h0djqx.js";
3
- import "./versionChecker--29PmTlR.js";
3
+ import "./versionChecker-BjRotlWY.js";
4
4
  import "./pidStore-C1JXxoPi.js";
5
5
  import "./globalPidIndex-Cr-g75QF.js";
6
6
 
@@ -79,11 +79,15 @@ async function resolveRemoteSpec(spec) {
79
79
  }
80
80
  async function cmdRemote(rest) {
81
81
  const sub = rest[0];
82
+ if (sub === "-h" || sub === "--help") {
83
+ process.stdout.write("Usage: ay remote <subcommand>\n\nManage saved remote server aliases.\n\nSubcommands:\n ay remote ls list configured remotes\n ay remote add <alias> http://<token>@<host>:<port> add a remote\n ay remote rm <alias> remove a remote\n\nOnce added, use the alias anywhere a keyword is accepted:\n ay ls <alias>\n ay tail <alias>:<keyword>\n ay send <alias>:<keyword> \"message\"\n");
84
+ return 0;
85
+ }
82
86
  if (!sub || sub === "ls" || sub === "list") {
83
87
  const remotes = await readRemotes();
84
88
  if (remotes.size === 0) {
85
89
  process.stdout.write("no remotes configured\n");
86
- process.stderr.write("\n ay remote add <alias> <url> <token> # add a remote\n ay serve # start server (prints token)\n");
90
+ process.stderr.write("\n ay remote add <alias> http://<token>@<host>:<port> # add a remote\n ay serve # start server (prints token)\n");
87
91
  return 0;
88
92
  }
89
93
  for (const [alias, cfg] of remotes) {
@@ -93,10 +97,25 @@ async function cmdRemote(rest) {
93
97
  return 0;
94
98
  }
95
99
  if (sub === "add") {
96
- const [, alias, url, token] = rest;
97
- if (!alias || !url || !token) {
98
- process.stderr.write("usage: ay remote add <alias> <url> <token>\n");
99
- process.stderr.write(" example: ay remote add work-mac http://192.168.1.5:7432 mytoken123\n");
100
+ const [, alias, rawUrl] = rest;
101
+ if (!alias || !rawUrl) {
102
+ process.stderr.write("usage: ay remote add <alias> http://<token>@<host>:<port>\n");
103
+ process.stderr.write(" example: ay remote add work-mac http://mytoken123@192.168.1.5:7432\n");
104
+ return 1;
105
+ }
106
+ let url, token;
107
+ try {
108
+ const parsed = new URL(rawUrl);
109
+ token = parsed.username;
110
+ parsed.username = "";
111
+ parsed.password = "";
112
+ url = parsed.toString().replace(/\/$/, "");
113
+ } catch {
114
+ process.stderr.write(`ay remote add: invalid URL '${rawUrl}'\n`);
115
+ return 1;
116
+ }
117
+ if (!token) {
118
+ process.stderr.write(`ay remote add: no token in URL — expected http://<token>@<host>:<port>\n`);
100
119
  return 1;
101
120
  }
102
121
  await writeRemoteAlias(alias, {
@@ -122,10 +141,10 @@ async function cmdRemote(rest) {
122
141
  return 0;
123
142
  }
124
143
  process.stderr.write(`ay remote: unknown subcommand '${sub}'\n`);
125
- process.stderr.write(" ay remote ls # list configured remotes\n ay remote add <alias> <url> <token> # add a remote\n ay remote rm <alias> # remove a remote\n");
144
+ process.stderr.write(" ay remote ls # list configured remotes\n ay remote add <alias> http://<token>@<host>:<port> # add a remote\n ay remote rm <alias> # remove a remote\n");
126
145
  return 1;
127
146
  }
128
147
 
129
148
  //#endregion
130
149
  export { resolveRemoteSpec as a, readRemotes as i, deleteRemoteAlias as n, writeRemoteAlias as o, parseDirectRemoteSpec as r, cmdRemote as t };
131
- //# sourceMappingURL=remotes-CFrho898.js.map
150
+ //# sourceMappingURL=remotes-Bjp2GYPz.js.map
@@ -1,3 +1,3 @@
1
- import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-CFrho898.js";
1
+ import { a as resolveRemoteSpec, i as readRemotes, n as deleteRemoteAlias, o as writeRemoteAlias, r as parseDirectRemoteSpec, t as cmdRemote } from "./remotes-Bjp2GYPz.js";
2
2
 
3
3
  export { cmdRemote };
@@ -1,7 +1,7 @@
1
1
  import "./logger-B9h0djqx.js";
2
2
  import "./globalPidIndex-Cr-g75QF.js";
3
- import "./remotes-CFrho898.js";
4
- import { c as resolveOne, d as writeToIpc, i as listRecords, o as readNotes, s as renderRawLog, t as controlCodeFromName, u as snapshotStatus } from "./subcommands-BltyYaEs.js";
3
+ import "./remotes-Bjp2GYPz.js";
4
+ import { a as listRecords, c as renderRawLog, d as snapshotStatus, f as writeToIpc, l as resolveOne, n as controlCodeFromName, s as readNotes } from "./subcommands-DIworIYh.js";
5
5
  import yargs from "yargs";
6
6
  import { mkdir, readFile, writeFile } from "fs/promises";
7
7
  import { homedir } from "os";
@@ -72,7 +72,8 @@ async function cmdServeDaemon(sub, args) {
72
72
  if (code === 0) {
73
73
  process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
74
74
  process.stdout.write(`token: ${token}\n\n`);
75
- process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
75
+ process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
76
+ process.stdout.write(` ay remote add <alias> http://${token}@<host>:${DEFAULT_PORT}\n`);
76
77
  process.stdout.write(` ay serve logs # view server logs\n`);
77
78
  process.stdout.write(` ay serve uninstall # remove daemon\n`);
78
79
  }
@@ -100,6 +101,15 @@ async function cmdServeDaemon(sub, args) {
100
101
  return 1;
101
102
  }
102
103
  async function cmdServe(rest) {
104
+ if (rest.includes("-h") || rest.includes("--help")) {
105
+ process.stdout.write(`Usage: ay serve [options]
106
+
107
+ Start an HTTP API server so remote machines can list/tail/send agents.
108
+
109
+ Options:
110
+ --port N Port to listen on (default: ${DEFAULT_PORT})\n --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n --token TOKEN Auth token (auto-generated and saved if omitted)\n --tls-cert FILE TLS certificate PEM\n --tls-key FILE TLS private key PEM\n\nSubcommands:\n ay serve install install as background daemon via oxmgr\n ay serve uninstall remove daemon\n ay serve logs view daemon logs\n\nOnce running, connect from another machine:\n ay ls <token>@<host>:${DEFAULT_PORT}\n ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`);
111
+ return 0;
112
+ }
103
113
  const sub = rest[0];
104
114
  if (sub === "install" || sub === "uninstall" || sub === "logs") return cmdServeDaemon(sub, rest.slice(1));
105
115
  const argv = await yargs(rest).usage("Usage: ay serve [options]").option("port", {
@@ -283,6 +293,8 @@ async function cmdServe(rest) {
283
293
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
284
294
  process.stdout.write(` ay tail ${token}@<host>:${port}:<keyword>\n`);
285
295
  process.stdout.write(` ay send ${token}@<host>:${port}:<keyword> "message"\n\n`);
296
+ process.stdout.write(`save as alias:\n`);
297
+ process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
286
298
  if (!useHttps) process.stdout.write("for HTTPS: ay serve --tls-cert cert.pem --tls-key key.pem\n openssl req -x509 -newkey rsa:2048 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'\n\n");
287
299
  process.stdout.write(`(Ctrl-C to stop)\n`);
288
300
  await new Promise((resolve) => {
@@ -300,4 +312,4 @@ async function cmdServe(rest) {
300
312
 
301
313
  //#endregion
302
314
  export { cmdServe };
303
- //# sourceMappingURL=serve-8dWQHSBu.js.map
315
+ //# sourceMappingURL=serve-CAEEkFLd.js.map
@@ -1,5 +1,5 @@
1
1
  import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
2
- import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-CFrho898.js";
2
+ import { a as resolveRemoteSpec, i as readRemotes } from "./remotes-Bjp2GYPz.js";
3
3
  import ms from "ms";
4
4
  import yargs from "yargs";
5
5
  import { appendFile, mkdir, open, readFile, stat, writeFile } from "fs/promises";
@@ -129,7 +129,8 @@ const SUBCOMMANDS = new Set([
129
129
  "restart",
130
130
  "note",
131
131
  "serve",
132
- "remote"
132
+ "remote",
133
+ "help"
133
134
  ]);
134
135
  const IDLE_THRESHOLD_MS = 60 * 1e3;
135
136
  function isSubcommand(name) {
@@ -157,13 +158,14 @@ async function runSubcommand(argv) {
157
158
  case "restart": return await cmdRestart(rest);
158
159
  case "note": return await cmdNote(rest);
159
160
  case "serve": {
160
- const { cmdServe } = await import("./serve-8dWQHSBu.js");
161
+ const { cmdServe } = await import("./serve-CAEEkFLd.js");
161
162
  return cmdServe(rest);
162
163
  }
163
164
  case "remote": {
164
- const { cmdRemote } = await import("./remotes-kfUzk-JT.js");
165
+ const { cmdRemote } = await import("./remotes-oNI1fR7_.js");
165
166
  return cmdRemote(rest);
166
167
  }
168
+ case "help": return cmdHelp();
167
169
  default: return null;
168
170
  }
169
171
  } catch (err) {
@@ -172,6 +174,10 @@ async function runSubcommand(argv) {
172
174
  return 1;
173
175
  }
174
176
  }
177
+ function cmdHelp() {
178
+ process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay status <keyword> agent status snapshot\n\nRemote:\n ay serve [--port N] start HTTP API server (prints token)\n ay remote add <alias> http://<token>@<host>:<port>\n ay remote ls / rm <alias> manage saved remotes\n ay ls <token>@<host>:<port> connect inline (no alias needed)\n ay send <token>@<host>:<port>:<kw> <msg>\n\nRun an agent:\n ay [claude|codex|gemini|...] [options] -- [prompt]\n ay claude -- \"fix the bug in auth.ts\"\n ay claude --help full agent-runner options\n\nLabs (examples in ./lab/):\n local-role-play/ designer + builder on one machine\n http-remote/ ay serve remote access demo\n p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n");
179
+ return 0;
180
+ }
175
181
  function matchKeyword(record, keyword) {
176
182
  if (!keyword) return true;
177
183
  const kw = keyword.toLowerCase();
@@ -201,12 +207,70 @@ function isPidAlive(pid) {
201
207
  return false;
202
208
  }
203
209
  }
210
+ async function pickInteractive(matches) {
211
+ const list = matches.slice(0, 10);
212
+ let sel = 0;
213
+ const render = () => {
214
+ for (let i = 0; i < list.length; i++) {
215
+ const r = list[i];
216
+ const marker = i === sel ? "\x1B[36m>\x1B[0m" : " ";
217
+ process.stderr.write(`${marker} ${r.pid} ${r.cli} ${r.cwd}\n`);
218
+ }
219
+ };
220
+ process.stderr.write(`Multiple matches — select with ↑↓ Enter (or type 1-${list.length}):\n`);
221
+ render();
222
+ return new Promise((resolve) => {
223
+ process.stdin.setRawMode(true);
224
+ process.stdin.resume();
225
+ process.stdin.setEncoding("utf8");
226
+ const redraw = () => {
227
+ process.stderr.write(`\x1b[${list.length}A\x1b[0J`);
228
+ render();
229
+ };
230
+ const cleanup = () => {
231
+ process.stdin.off("data", onData);
232
+ try {
233
+ process.stdin.setRawMode(false);
234
+ } catch {}
235
+ process.stdin.pause();
236
+ };
237
+ const onData = (key) => {
238
+ if (key === "") {
239
+ cleanup();
240
+ process.stderr.write("\n");
241
+ resolve(null);
242
+ } else if (key === "\r" || key === "\n") {
243
+ cleanup();
244
+ process.stderr.write("\n");
245
+ resolve(list[sel]);
246
+ } else if (key === "\x1B[A") {
247
+ sel = Math.max(0, sel - 1);
248
+ redraw();
249
+ } else if (key === "\x1B[B") {
250
+ sel = Math.min(list.length - 1, sel + 1);
251
+ redraw();
252
+ } else if (key >= "1" && key <= String(list.length)) {
253
+ sel = parseInt(key, 10) - 1;
254
+ redraw();
255
+ cleanup();
256
+ process.stderr.write("\n");
257
+ resolve(list[sel]);
258
+ }
259
+ };
260
+ process.stdin.on("data", onData);
261
+ });
262
+ }
204
263
  async function resolveOne(keyword, opts) {
205
264
  if (!keyword) throw new Error("keyword required (pid, cwd substring, cli name, or prompt substring)");
206
265
  const matches = await listRecords(keyword, opts);
207
266
  if (matches.length === 0) throw new Error(`no agent matched "${keyword}"`);
208
267
  if (matches.length === 1) return matches[0];
209
268
  if (opts.latest) return matches[0];
269
+ if (process.stderr.isTTY && process.stdin.isTTY) {
270
+ const chosen = await pickInteractive(matches);
271
+ if (chosen) return chosen;
272
+ throw new Error("no agent selected");
273
+ }
210
274
  const lines = matches.slice(0, 10).map((r) => ` ${r.pid} ${r.cli} ${r.cwd}`).join("\n");
211
275
  throw new Error(`keyword "${keyword}" matched ${matches.length} agents — disambiguate by pid or pass --latest:\n${lines}`);
212
276
  }
@@ -1108,5 +1172,5 @@ async function cmdStatus(rest) {
1108
1172
  }
1109
1173
 
1110
1174
  //#endregion
1111
- export { matchKeyword as a, resolveOne as c, writeToIpc as d, listRecords as i, runSubcommand as l, isPidAlive as n, readNotes as o, isSubcommand as r, renderRawLog as s, controlCodeFromName as t, snapshotStatus as u };
1112
- //# sourceMappingURL=subcommands-BltyYaEs.js.map
1175
+ export { listRecords as a, renderRawLog as c, snapshotStatus as d, writeToIpc as f, isSubcommand as i, resolveOne as l, controlCodeFromName as n, matchKeyword as o, isPidAlive as r, readNotes as s, cmdHelp as t, runSubcommand as u };
1176
+ //# sourceMappingURL=subcommands-DIworIYh.js.map
@@ -0,0 +1,6 @@
1
+ import "./logger-B9h0djqx.js";
2
+ import "./globalPidIndex-Cr-g75QF.js";
3
+ import "./remotes-Bjp2GYPz.js";
4
+ import { a as listRecords, c as renderRawLog, d as snapshotStatus, f as writeToIpc, i as isSubcommand, l as resolveOne, n as controlCodeFromName, o as matchKeyword, r as isPidAlive, s as readNotes, t as cmdHelp, u as runSubcommand } from "./subcommands-DIworIYh.js";
5
+
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--29PmTlR.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-BjRotlWY.js";
3
3
  import { i as shouldUseLock, r as releaseLock, t as acquireLock } from "./runningLock-C22d9SRJ.js";
4
4
  import { t as PidStore } from "./pidStore-C1JXxoPi.js";
5
5
  import { r as readGlobalPids } from "./globalPidIndex-Cr-g75QF.js";
@@ -1693,4 +1693,4 @@ function sleep(ms) {
1693
1693
 
1694
1694
  //#endregion
1695
1695
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1696
- //# sourceMappingURL=ts-Oxr2rEMA.js.map
1696
+ //# sourceMappingURL=ts-CDrRTQFz.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.87.0";
10
+ var version = "1.88.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--29PmTlR.js.map
224
+ //# sourceMappingURL=versionChecker-BjRotlWY.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-yes",
3
- "version": "1.87.0",
3
+ "version": "1.88.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",
package/ts/cli.ts CHANGED
@@ -20,8 +20,15 @@ import { buildRustArgs } from "./buildRustArgs.ts";
20
20
  // agent-spawn machinery (and the --rust dispatch) and operate on the global
21
21
  // pid index instead. Must run before checkAndAutoUpdate / yargs / Rust spawn.
22
22
  {
23
- const { isSubcommand, runSubcommand } = await import("./subcommands.ts");
24
- if (isSubcommand(process.argv[2])) {
23
+ const rawArg = process.argv[2];
24
+ // Intercept bare -h/--help so we show TS subcommands, not just Rust agent-runner options.
25
+ const isHelpFlag = rawArg === "-h" || rawArg === "--help";
26
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands.ts");
27
+ if (isHelpFlag && process.argv.length === 3) {
28
+ cmdHelp();
29
+ process.exit(0);
30
+ }
31
+ if (isSubcommand(rawArg)) {
25
32
  const code = await runSubcommand(process.argv);
26
33
  process.exit(code ?? 0);
27
34
  }
package/ts/remotes.ts CHANGED
@@ -102,14 +102,30 @@ export async function resolveRemoteSpec(spec: string): Promise<ResolvedRemote |
102
102
  export async function cmdRemote(rest: string[]): Promise<number> {
103
103
  const sub = rest[0];
104
104
 
105
+ if (sub === "-h" || sub === "--help") {
106
+ process.stdout.write(
107
+ `Usage: ay remote <subcommand>\n\n` +
108
+ `Manage saved remote server aliases.\n\n` +
109
+ `Subcommands:\n` +
110
+ ` ay remote ls list configured remotes\n` +
111
+ ` ay remote add <alias> http://<token>@<host>:<port> add a remote\n` +
112
+ ` ay remote rm <alias> remove a remote\n\n` +
113
+ `Once added, use the alias anywhere a keyword is accepted:\n` +
114
+ ` ay ls <alias>\n` +
115
+ ` ay tail <alias>:<keyword>\n` +
116
+ ` ay send <alias>:<keyword> "message"\n`,
117
+ );
118
+ return 0;
119
+ }
120
+
105
121
  if (!sub || sub === "ls" || sub === "list") {
106
122
  const remotes = await readRemotes();
107
123
  if (remotes.size === 0) {
108
124
  process.stdout.write("no remotes configured\n");
109
125
  process.stderr.write(
110
126
  "\n" +
111
- " ay remote add <alias> <url> <token> # add a remote\n" +
112
- " ay serve # start server (prints token)\n",
127
+ " ay remote add <alias> http://<token>@<host>:<port> # add a remote\n" +
128
+ " ay serve # start server (prints token)\n",
113
129
  );
114
130
  return 0;
115
131
  }
@@ -121,11 +137,28 @@ export async function cmdRemote(rest: string[]): Promise<number> {
121
137
  }
122
138
 
123
139
  if (sub === "add") {
124
- const [, alias, url, token] = rest;
125
- if (!alias || !url || !token) {
126
- process.stderr.write("usage: ay remote add <alias> <url> <token>\n");
140
+ const [, alias, rawUrl] = rest;
141
+ if (!alias || !rawUrl) {
142
+ process.stderr.write("usage: ay remote add <alias> http://<token>@<host>:<port>\n");
143
+ process.stderr.write(
144
+ " example: ay remote add work-mac http://mytoken123@192.168.1.5:7432\n",
145
+ );
146
+ return 1;
147
+ }
148
+ let url: string, token: string;
149
+ try {
150
+ const parsed = new URL(rawUrl);
151
+ token = parsed.username;
152
+ parsed.username = "";
153
+ parsed.password = "";
154
+ url = parsed.toString().replace(/\/$/, "");
155
+ } catch {
156
+ process.stderr.write(`ay remote add: invalid URL '${rawUrl}'\n`);
157
+ return 1;
158
+ }
159
+ if (!token) {
127
160
  process.stderr.write(
128
- " example: ay remote add work-mac http://192.168.1.5:7432 mytoken123\n",
161
+ `ay remote add: no token in URL — expected http://<token>@<host>:<port>\n`,
129
162
  );
130
163
  return 1;
131
164
  }
@@ -153,9 +186,9 @@ export async function cmdRemote(rest: string[]): Promise<number> {
153
186
 
154
187
  process.stderr.write(`ay remote: unknown subcommand '${sub}'\n`);
155
188
  process.stderr.write(
156
- " ay remote ls # list configured remotes\n" +
157
- " ay remote add <alias> <url> <token> # add a remote\n" +
158
- " ay remote rm <alias> # remove a remote\n",
189
+ " ay remote ls # list configured remotes\n" +
190
+ " ay remote add <alias> http://<token>@<host>:<port> # add a remote\n" +
191
+ " ay remote rm <alias> # remove a remote\n",
159
192
  );
160
193
  return 1;
161
194
  }
package/ts/serve.ts CHANGED
@@ -85,7 +85,8 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
85
85
  if (code === 0) {
86
86
  process.stdout.write(`\ninstalled '${DAEMON_NAME}' as a daemon via oxmgr\n`);
87
87
  process.stdout.write(`token: ${token}\n\n`);
88
- process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
88
+ process.stdout.write(` ay ls ${token}@<host>:${DEFAULT_PORT}\n`);
89
+ process.stdout.write(` ay remote add <alias> http://${token}@<host>:${DEFAULT_PORT}\n`);
89
90
  process.stdout.write(` ay serve logs # view server logs\n`);
90
91
  process.stdout.write(` ay serve uninstall # remove daemon\n`);
91
92
  }
@@ -114,6 +115,27 @@ async function cmdServeDaemon(sub: string, args: string[]): Promise<number> {
114
115
  // ---------------------------------------------------------------------------
115
116
 
116
117
  export async function cmdServe(rest: string[]): Promise<number> {
118
+ if (rest.includes("-h") || rest.includes("--help")) {
119
+ process.stdout.write(
120
+ `Usage: ay serve [options]\n\n` +
121
+ `Start an HTTP API server so remote machines can list/tail/send agents.\n\n` +
122
+ `Options:\n` +
123
+ ` --port N Port to listen on (default: ${DEFAULT_PORT})\n` +
124
+ ` --host HOST Interface to bind (default: 127.0.0.1; use 0.0.0.0 to expose)\n` +
125
+ ` --token TOKEN Auth token (auto-generated and saved if omitted)\n` +
126
+ ` --tls-cert FILE TLS certificate PEM\n` +
127
+ ` --tls-key FILE TLS private key PEM\n\n` +
128
+ `Subcommands:\n` +
129
+ ` ay serve install install as background daemon via oxmgr\n` +
130
+ ` ay serve uninstall remove daemon\n` +
131
+ ` ay serve logs view daemon logs\n\n` +
132
+ `Once running, connect from another machine:\n` +
133
+ ` ay ls <token>@<host>:${DEFAULT_PORT}\n` +
134
+ ` ay remote add <alias> http://<token>@<host>:${DEFAULT_PORT}\n`,
135
+ );
136
+ return 0;
137
+ }
138
+
117
139
  // Daemon subcommands
118
140
  const sub = rest[0];
119
141
  if (sub === "install" || sub === "uninstall" || sub === "logs") {
@@ -350,6 +372,8 @@ export async function cmdServe(rest: string[]): Promise<number> {
350
372
  process.stdout.write(` ay ls ${token}@<host>:${port}\n`);
351
373
  process.stdout.write(` ay tail ${token}@<host>:${port}:<keyword>\n`);
352
374
  process.stdout.write(` ay send ${token}@<host>:${port}:<keyword> "message"\n\n`);
375
+ process.stdout.write(`save as alias:\n`);
376
+ process.stdout.write(` ay remote add <alias> ${scheme}://${token}@<host>:${port}\n\n`);
353
377
  if (!useHttps) {
354
378
  process.stdout.write(
355
379
  `for HTTPS: ay serve --tls-cert cert.pem --tls-key key.pem\n` +
package/ts/subcommands.ts CHANGED
@@ -140,6 +140,7 @@ const SUBCOMMANDS = new Set([
140
140
  "note",
141
141
  "serve",
142
142
  "remote",
143
+ "help",
143
144
  ]);
144
145
 
145
146
  const IDLE_THRESHOLD_MS = 60 * 1000;
@@ -187,6 +188,8 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
187
188
  const { cmdRemote } = await import("./remotes.ts");
188
189
  return cmdRemote(rest);
189
190
  }
191
+ case "help":
192
+ return cmdHelp();
190
193
  default:
191
194
  return null;
192
195
  }
@@ -197,6 +200,42 @@ export async function runSubcommand(argv: string[]): Promise<number | null> {
197
200
  }
198
201
  }
199
202
 
203
+ // ---------------------------------------------------------------------------
204
+ // ay help
205
+ // ---------------------------------------------------------------------------
206
+
207
+ export function cmdHelp(): number {
208
+ process.stdout.write(
209
+ `ay - agent-yes CLI\n` +
210
+ `\n` +
211
+ `Management:\n` +
212
+ ` ay ls [keyword] list running agents\n` +
213
+ ` ay tail [-f] <keyword> stream output (Ctrl-C to stop)\n` +
214
+ ` ay cat <keyword> full log\n` +
215
+ ` ay head <keyword> first N lines\n` +
216
+ ` ay send <keyword> <msg> send a message\n` +
217
+ ` ay status <keyword> agent status snapshot\n` +
218
+ `\n` +
219
+ `Remote:\n` +
220
+ ` ay serve [--port N] start HTTP API server (prints token)\n` +
221
+ ` ay remote add <alias> http://<token>@<host>:<port>\n` +
222
+ ` ay remote ls / rm <alias> manage saved remotes\n` +
223
+ ` ay ls <token>@<host>:<port> connect inline (no alias needed)\n` +
224
+ ` ay send <token>@<host>:<port>:<kw> <msg>\n` +
225
+ `\n` +
226
+ `Run an agent:\n` +
227
+ ` ay [claude|codex|gemini|...] [options] -- [prompt]\n` +
228
+ ` ay claude -- "fix the bug in auth.ts"\n` +
229
+ ` ay claude --help full agent-runner options\n` +
230
+ `\n` +
231
+ `Labs (examples in ./lab/):\n` +
232
+ ` local-role-play/ designer + builder on one machine\n` +
233
+ ` http-remote/ ay serve remote access demo\n` +
234
+ ` p2p-pairing/ libp2p P2P (needs: cargo build --features swarm)\n`,
235
+ );
236
+ return 0;
237
+ }
238
+
200
239
  // ---------------------------------------------------------------------------
201
240
  // shared helpers
202
241
  // ---------------------------------------------------------------------------
@@ -261,6 +300,69 @@ export function isPidAlive(pid: number): boolean {
261
300
  }
262
301
  }
263
302
 
303
+ async function pickInteractive(matches: GlobalPidRecord[]): Promise<GlobalPidRecord | null> {
304
+ const list = matches.slice(0, 10);
305
+ let sel = 0;
306
+
307
+ const render = () => {
308
+ for (let i = 0; i < list.length; i++) {
309
+ const r = list[i]!;
310
+ const marker = i === sel ? "\x1b[36m>\x1b[0m" : " ";
311
+ process.stderr.write(`${marker} ${r.pid} ${r.cli} ${r.cwd}\n`);
312
+ }
313
+ };
314
+
315
+ process.stderr.write(`Multiple matches — select with ↑↓ Enter (or type 1-${list.length}):\n`);
316
+ render();
317
+
318
+ return new Promise((resolve) => {
319
+ process.stdin.setRawMode(true);
320
+ process.stdin.resume();
321
+ process.stdin.setEncoding("utf8");
322
+
323
+ const redraw = () => {
324
+ process.stderr.write(`\x1b[${list.length}A\x1b[0J`);
325
+ render();
326
+ };
327
+
328
+ const cleanup = () => {
329
+ process.stdin.off("data", onData);
330
+ try {
331
+ process.stdin.setRawMode(false);
332
+ } catch {
333
+ /* not a tty */
334
+ }
335
+ process.stdin.pause();
336
+ };
337
+
338
+ const onData = (key: string) => {
339
+ if (key === "\x03") {
340
+ cleanup();
341
+ process.stderr.write("\n");
342
+ resolve(null);
343
+ } else if (key === "\r" || key === "\n") {
344
+ cleanup();
345
+ process.stderr.write("\n");
346
+ resolve(list[sel]!);
347
+ } else if (key === "\x1b[A") {
348
+ sel = Math.max(0, sel - 1);
349
+ redraw();
350
+ } else if (key === "\x1b[B") {
351
+ sel = Math.min(list.length - 1, sel + 1);
352
+ redraw();
353
+ } else if (key >= "1" && key <= String(list.length)) {
354
+ sel = parseInt(key, 10) - 1;
355
+ redraw();
356
+ cleanup();
357
+ process.stderr.write("\n");
358
+ resolve(list[sel]!);
359
+ }
360
+ };
361
+
362
+ process.stdin.on("data", onData);
363
+ });
364
+ }
365
+
264
366
  export async function resolveOne(
265
367
  keyword: string | undefined,
266
368
  opts: CommonOpts,
@@ -274,6 +376,11 @@ export async function resolveOne(
274
376
  }
275
377
  if (matches.length === 1) return matches[0]!;
276
378
  if (opts.latest) return matches[0]!; // already sorted newest-first
379
+ if (process.stderr.isTTY && process.stdin.isTTY) {
380
+ const chosen = await pickInteractive(matches);
381
+ if (chosen) return chosen;
382
+ throw new Error("no agent selected");
383
+ }
277
384
  const lines = matches
278
385
  .slice(0, 10)
279
386
  .map((r) => ` ${r.pid} ${r.cli} ${r.cwd}`)
@@ -1,6 +0,0 @@
1
- import "./logger-B9h0djqx.js";
2
- import "./globalPidIndex-Cr-g75QF.js";
3
- import "./remotes-CFrho898.js";
4
- import { a as matchKeyword, c as resolveOne, d as writeToIpc, i as listRecords, l as runSubcommand, n as isPidAlive, o as readNotes, r as isSubcommand, s as renderRawLog, t as controlCodeFromName, u as snapshotStatus } from "./subcommands-BltyYaEs.js";
5
-
6
- export { isSubcommand, runSubcommand };