agent-yes 1.141.0 → 1.143.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
@@ -136,6 +136,20 @@ gemini-yes -- debug this code
136
136
  # Use Auggie directly
137
137
  auggie-yes -- analyze code patterns
138
138
 
139
+ # Use GLM (Z.AI) directly — runs Claude Code against Z.AI's
140
+ # Anthropic-compatible endpoint. Set ZAI_API_KEY first
141
+ # (https://z.ai/manage-apikey/apikey-list).
142
+ ZAI_API_KEY=... glm-yes -- help me with this code
143
+
144
+ # Use OpenRouter directly — runs Claude Code against OpenRouter's
145
+ # Anthropic-compatible endpoint. Set OPENROUTER_API_KEY first
146
+ # (https://openrouter.ai/keys).
147
+ OPENROUTER_API_KEY=... openrouter-yes -- help me with this code
148
+
149
+ # Use Pi directly — minimal multi-provider coding agent
150
+ # (https://github.com/earendil-works/pi)
151
+ pi-yes -- refactor this module
152
+
139
153
  claude-yes "help me with this code"
140
154
  claude-yes "optimize performance"
141
155
  ```
@@ -94,6 +94,11 @@
94
94
  "type": "string",
95
95
  "description": "Actual binary name if different from CLI name (e.g., 'cursor-agent' for cursor)"
96
96
  },
97
+ "env": {
98
+ "type": "object",
99
+ "additionalProperties": { "type": "string" },
100
+ "description": "Environment variables injected into the spawned agent process (e.g. GLM points the `claude` binary at Z.AI). Values support ${VAR} expansion against the launching environment; an entry whose variable is unset is skipped so it can't blank out an inherited value."
101
+ },
97
102
  "defaultArgs": {
98
103
  "type": "array",
99
104
  "items": { "type": "string" },
@@ -76,6 +76,191 @@ clis:
76
76
  bunx: true
77
77
  defaultArgs: []
78
78
 
79
+ # GLM (Z.AI) — runs the `claude` binary against Z.AI's Anthropic-compatible
80
+ # endpoint, so it inherits every claude marker (ready/working/enter/autoRetry…)
81
+ # while talking to GLM models. Set ZAI_API_KEY (from
82
+ # https://z.ai/manage-apikey/apikey-list) before launching. Override the model
83
+ # via ~/.claude/settings.json (ANTHROPIC_DEFAULT_*_MODEL) or by exporting the
84
+ # same env vars; unset → Z.AI's default (GLM-4.7). See
85
+ # https://docs.z.ai/devpack/tool/claude
86
+ glm:
87
+ binary: claude
88
+ # ${VAR} entries expand against the launching environment at spawn time; an
89
+ # entry whose variable is unset is skipped (so it can't blank out an
90
+ # inherited value). Static values are passed through verbatim.
91
+ env:
92
+ ANTHROPIC_BASE_URL: https://api.z.ai/api/anthropic
93
+ ANTHROPIC_AUTH_TOKEN: ${ZAI_API_KEY}
94
+ API_TIMEOUT_MS: "3000000"
95
+ promptArg: last-arg
96
+ systemPrompt: --append-system-prompt
97
+ yesArgs:
98
+ - --dangerously-skip-permissions
99
+ install:
100
+ powershell: 'powershell -Command "irm https://claude.ai/install.ps1 | iex"'
101
+ bash: "curl -fsSL https://claude.ai/install.sh | bash"
102
+ npm: "npm i -g @anthropic-ai/claude-code@latest"
103
+ ready:
104
+ - '\? for shortcuts'
105
+ - ' Try "'
106
+ - '^\? for shortcuts'
107
+ - "^>[ ]"
108
+ - "──────────+"
109
+ working:
110
+ - esc to interrupt
111
+ - to run in background
112
+ needsInput:
113
+ - pattern: '❯ ?\d+\.'
114
+ flags: m
115
+ typingRespond:
116
+ "1\n":
117
+ - '│ Do you want to use this API key\?'
118
+ enter:
119
+ - pattern: ' > 1\. Yes, I trust this folder'
120
+ flags: m
121
+ - pattern: '❯ ?1\. ?Dark mode ?✔'
122
+ flags: m
123
+ - pattern: '❯ ?1\. ?Auto \(match terminal\)'
124
+ flags: m
125
+ - pattern: '❯ ?1\. ?Yes'
126
+ flags: m
127
+ - pattern: '^.{0,4} ?1\. ?Dark mode ?✔'
128
+ flags: m
129
+ - pattern: '^.{0,4} ?1\. ?Auto \(match terminal\)'
130
+ flags: m
131
+ - pattern: '^.{0,4} ?1\. ?Yes'
132
+ flags: m
133
+ - pattern: Press Enter to continue
134
+ flags: m
135
+ fatal:
136
+ - "^error: unknown option"
137
+ autoRetry:
138
+ - pattern: "API Error.*Overloaded"
139
+ flags: i
140
+ - Overloaded
141
+ - pattern: 'API Error.{0,4}5\d\d'
142
+ flags: i
143
+ - Claude usage limit reached
144
+ - hit your usage limit
145
+ - pattern: "rate.?limit"
146
+ flags: i
147
+ - pattern: "session limit"
148
+ flags: i
149
+ restoreArgs:
150
+ - --continue
151
+ restartWithoutContinueArg:
152
+ - No conversation found to continue
153
+ exitCommands:
154
+ - /exit
155
+ bunx: true
156
+ defaultArgs: []
157
+
158
+ # OpenRouter — runs the `claude` binary against OpenRouter's Anthropic-compatible
159
+ # endpoint, inheriting every claude marker while routing to OpenRouter models.
160
+ # Set OPENROUTER_API_KEY (from https://openrouter.ai/keys). ANTHROPIC_API_KEY is
161
+ # pinned to "" so Claude Code doesn't fall back to a first-party Anthropic
162
+ # account. The native skin targets Anthropic models by default; pick another via
163
+ # ~/.claude/settings.json (ANTHROPIC_DEFAULT_*_MODEL = an OpenRouter slug like
164
+ # "anthropic/claude-sonnet-4"). See
165
+ # https://openrouter.ai/docs/cookbook/coding-agents/claude-code-integration
166
+ openrouter:
167
+ binary: claude
168
+ env:
169
+ ANTHROPIC_BASE_URL: https://openrouter.ai/api
170
+ ANTHROPIC_AUTH_TOKEN: ${OPENROUTER_API_KEY}
171
+ ANTHROPIC_API_KEY: ""
172
+ promptArg: last-arg
173
+ systemPrompt: --append-system-prompt
174
+ yesArgs:
175
+ - --dangerously-skip-permissions
176
+ install:
177
+ powershell: 'powershell -Command "irm https://claude.ai/install.ps1 | iex"'
178
+ bash: "curl -fsSL https://claude.ai/install.sh | bash"
179
+ npm: "npm i -g @anthropic-ai/claude-code@latest"
180
+ ready:
181
+ - '\? for shortcuts'
182
+ - ' Try "'
183
+ - '^\? for shortcuts'
184
+ - "^>[ ]"
185
+ - "──────────+"
186
+ working:
187
+ - esc to interrupt
188
+ - to run in background
189
+ needsInput:
190
+ - pattern: '❯ ?\d+\.'
191
+ flags: m
192
+ typingRespond:
193
+ "1\n":
194
+ - '│ Do you want to use this API key\?'
195
+ enter:
196
+ - pattern: ' > 1\. Yes, I trust this folder'
197
+ flags: m
198
+ - pattern: '❯ ?1\. ?Dark mode ?✔'
199
+ flags: m
200
+ - pattern: '❯ ?1\. ?Auto \(match terminal\)'
201
+ flags: m
202
+ - pattern: '❯ ?1\. ?Yes'
203
+ flags: m
204
+ - pattern: '^.{0,4} ?1\. ?Dark mode ?✔'
205
+ flags: m
206
+ - pattern: '^.{0,4} ?1\. ?Auto \(match terminal\)'
207
+ flags: m
208
+ - pattern: '^.{0,4} ?1\. ?Yes'
209
+ flags: m
210
+ - pattern: Press Enter to continue
211
+ flags: m
212
+ fatal:
213
+ - "^error: unknown option"
214
+ autoRetry:
215
+ - pattern: "API Error.*Overloaded"
216
+ flags: i
217
+ - Overloaded
218
+ - pattern: 'API Error.{0,4}5\d\d'
219
+ flags: i
220
+ - Claude usage limit reached
221
+ - hit your usage limit
222
+ - pattern: "rate.?limit"
223
+ flags: i
224
+ - pattern: "session limit"
225
+ flags: i
226
+ restoreArgs:
227
+ - --continue
228
+ restartWithoutContinueArg:
229
+ - No conversation found to continue
230
+ exitCommands:
231
+ - /exit
232
+ bunx: true
233
+ defaultArgs: []
234
+
235
+ # Pi — minimal multi-provider coding agent (https://github.com/earendil-works/pi).
236
+ # Interactive TUI: a bordered input box with a footer status line showing the
237
+ # context-usage gauge (e.g. "0.0%/272k") + model. Pi has NO tool-permission
238
+ # popups by design (run it in a container), so there's no "yolo" flag to map
239
+ # `-y` to — yesArgs is intentionally omitted. Pass the provider/model via
240
+ # PI_* env vars or --provider/--model; default provider is google.
241
+ pi:
242
+ binary: pi
243
+ promptArg: last-arg
244
+ systemPrompt: --append-system-prompt
245
+ help: https://github.com/earendil-works/pi
246
+ install:
247
+ npm: "npm install -g --ignore-scripts @earendil-works/pi-coding-agent"
248
+ # The footer context-usage gauge is re-rendered every time pi returns to the
249
+ # prompt, so it doubles as the "ready for input" marker. Idle detection also
250
+ # requires output quiescence, so a streaming response (gauge still updating)
251
+ # is not mis-read as idle.
252
+ ready:
253
+ - '%/\d+k'
254
+ - clear/exit
255
+ - "──────────+"
256
+ fatal:
257
+ - "^error: unknown option"
258
+ restoreArgs:
259
+ - --continue
260
+ exitCommands:
261
+ - /quit
262
+ defaultArgs: []
263
+
79
264
  gemini:
80
265
  install:
81
266
  npm: "npm install -g @google/gemini-cli@latest"
@@ -0,0 +1,8 @@
1
+ import "./ts-CV4m7CUC.js";
2
+ import "./logger-CDIsZ-Pp.js";
3
+ import "./versionChecker-DatX-QMn.js";
4
+ import "./pidStore-fqXqTKkh.js";
5
+ import "./globalPidIndex-DlmmJlO8.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DdzFdshj.js";
7
+
8
+ export { SUPPORTED_CLIS };
@@ -1,8 +1,8 @@
1
- import { t as CLIS_CONFIG } from "./ts-CMMwNq--.js";
1
+ import { t as CLIS_CONFIG } from "./ts-CV4m7CUC.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-CnBoRIDN.js.map
8
+ //# sourceMappingURL=SUPPORTED_CLIS-DdzFdshj.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-zdFbz1ST.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-DMpCuaZe.js";
4
+ import { i as versionString, n as displayVersion, r as getInstalledPackage, t as checkAndAutoUpdate } from "./versionChecker-DatX-QMn.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-3BaA-AmB.js");
483
+ const { isSubcommand, runSubcommand, cmdHelp } = await import("./subcommands-BtCnrZmr.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-bW-3DoHX.js");
516
+ const { SUPPORTED_CLIS } = await import("./SUPPORTED_CLIS-CPLtBfWE.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}`);
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
6
+ if (typeof Bun !== "undefined" && existsSync(join(root, ".git"))) {
7
+ await import("../ts/cli.ts");
8
+ } else {
9
+ await import("./cli.js");
10
+ }
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-CMMwNq--.js";
1
+ import { a as removeControlCharacters, i as AgentContext, n as agentYes, r as config, t as CLIS_CONFIG } from "./ts-CV4m7CUC.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-DMpCuaZe.js";
3
+ import "./versionChecker-DatX-QMn.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
6
 
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
6
+ if (typeof Bun !== "undefined" && existsSync(join(root, ".git"))) {
7
+ await import("../ts/cli.ts");
8
+ } else {
9
+ await import("./cli.js");
10
+ }
package/dist/pi-yes.js ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env bun
2
+ import { existsSync } from "node:fs";
3
+ import { join, dirname } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ const root = join(dirname(fileURLToPath(import.meta.url)), "..");
6
+ if (typeof Bun !== "undefined" && existsSync(join(root, ".git"))) {
7
+ await import("../ts/cli.ts");
8
+ } else {
9
+ await import("./cli.js");
10
+ }
@@ -1,9 +1,9 @@
1
- import "./ts-CMMwNq--.js";
1
+ import "./ts-CV4m7CUC.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-DMpCuaZe.js";
3
+ import "./versionChecker-DatX-QMn.js";
4
4
  import "./pidStore-fqXqTKkh.js";
5
5
  import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CnBoRIDN.js";
6
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DdzFdshj.js";
7
7
  import { n as resolveSpawnCwd } from "./workspaceConfig-BCOqRBEW.js";
8
8
  import { createHash } from "node:crypto";
9
9
 
@@ -141,4 +141,4 @@ async function cmdSchedule(rest) {
141
141
 
142
142
  //#endregion
143
143
  export { cmdSchedule };
144
- //# sourceMappingURL=schedule-PVnlPldE.js.map
144
+ //# sourceMappingURL=schedule-C8QKzvq-.js.map
@@ -1,13 +1,13 @@
1
- import "./ts-CMMwNq--.js";
1
+ import "./ts-CV4m7CUC.js";
2
2
  import "./logger-CDIsZ-Pp.js";
3
- import { r as getInstalledPackage } from "./versionChecker-DMpCuaZe.js";
3
+ import { r as getInstalledPackage } from "./versionChecker-DatX-QMn.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-CnBoRIDN.js";
8
+ import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-DdzFdshj.js";
9
9
  import "./remotes-PKKjfTI1.js";
10
- import { C as writeToIpc, c as extractTaskCounts, g as renderRawLog, h as readNotes, i as controlCodeFromName, o as deriveLiveStatus, p as listRecords, v as resolveOne, x as snapshotStatus } from "./subcommands-dbx7EoK-.js";
10
+ 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-BthznoYM.js";
11
11
  import yargs from "yargs";
12
12
  import { mkdir, open, readFile, stat, writeFile } from "fs/promises";
13
13
  import { homedir, hostname, userInfo } from "os";
@@ -77,7 +77,8 @@ const SESSION_PIN_ENV = new Set([
77
77
  "CLAUDE_CODE_SSE_PORT",
78
78
  "CLAUDE_CODE_SESSION_ID",
79
79
  "CLAUDE_CODE_CHILD_SESSION",
80
- "CLAUDE_CODE_ENTRYPOINT"
80
+ "CLAUDE_CODE_ENTRYPOINT",
81
+ "AGENT_YES_PID"
81
82
  ]);
82
83
  function freshAgentEnv() {
83
84
  const env = {};
@@ -499,6 +500,7 @@ Options:
499
500
  const hint = stray.includes("share") ? " (did you mean --share?)" : "";
500
501
  process.stderr.write(`ay serve: ignoring unknown argument${stray.length > 1 ? "s" : ""}: ${stray.join(" ")}${hint}\n`);
501
502
  }
503
+ delete process.env.AGENT_YES_PID;
502
504
  const port = argv.port ?? DEFAULT_PORT;
503
505
  const host = argv.host ?? "127.0.0.1";
504
506
  const tokenFlag = typeof argv.token === "string" ? argv.token : void 0;
@@ -1175,4 +1177,4 @@ Options:
1175
1177
 
1176
1178
  //#endregion
1177
1179
  export { cmdServe };
1178
- //# sourceMappingURL=serve-D8UEMOas.js.map
1180
+ //# sourceMappingURL=serve-Bathp8Fu.js.map
@@ -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-D8UEMOas.js");
35
+ const { cmdServe } = await import("./serve-Bathp8Fu.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-BPAdHGDG.js.map
45
+ //# sourceMappingURL=setup-ygcLSFBT.js.map
@@ -0,0 +1,7 @@
1
+ import "./logger-CDIsZ-Pp.js";
2
+ import "./globalPidIndex-DlmmJlO8.js";
3
+ import "./configShared-C1C04bbq.js";
4
+ import "./remotes-PKKjfTI1.js";
5
+ 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-BthznoYM.js";
6
+
7
+ export { cmdHelp, isSubcommand, runSubcommand };
@@ -501,6 +501,7 @@ const SUBCOMMANDS = new Set([
501
501
  "send",
502
502
  "attach",
503
503
  "stop",
504
+ "exit",
504
505
  "restart",
505
506
  "note",
506
507
  "serve",
@@ -548,18 +549,19 @@ async function runSubcommand(argv) {
548
549
  case "send": return await cmdSend(rest);
549
550
  case "attach": return await cmdAttach(rest);
550
551
  case "stop": return await cmdStop(rest);
552
+ case "exit": return await cmdExit(rest);
551
553
  case "restart": return await cmdRestart(rest);
552
554
  case "note": return await cmdNote(rest);
553
555
  case "serve": {
554
- const { cmdServe } = await import("./serve-D8UEMOas.js");
556
+ const { cmdServe } = await import("./serve-Bathp8Fu.js");
555
557
  return cmdServe(rest);
556
558
  }
557
559
  case "setup": {
558
- const { cmdSetup } = await import("./setup-BPAdHGDG.js");
560
+ const { cmdSetup } = await import("./setup-ygcLSFBT.js");
559
561
  return cmdSetup(rest);
560
562
  }
561
563
  case "schedule": {
562
- const { cmdSchedule } = await import("./schedule-PVnlPldE.js");
564
+ const { cmdSchedule } = await import("./schedule-C8QKzvq-.js");
563
565
  return cmdSchedule(rest);
564
566
  }
565
567
  case "remote": {
@@ -580,7 +582,7 @@ async function runSubcommand(argv) {
580
582
  }
581
583
  function cmdHelp(managerCommands = true) {
582
584
  const setupLine = managerCommands ? ` ay setup guided setup: pick a workspace, share to agent-yes.com\n` : ``;
583
- process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\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 at https://github.com/snomiao/agent-yes/tree/main/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");
585
+ process.stdout.write("ay - agent-yes CLI\n\nManagement:\n ay ls [keyword] list running agents\n ay tail [-f] [-n N] <keyword> last N lines (96), -f to follow\n ay read <keyword> [page opts] paginate: --last/--head N, --range A:B,\n --before-line L [--limit N]\n ay cat <keyword> full log\n ay head <keyword> first N lines\n ay send <keyword> <msg> send a message\n ay attach <keyword> interactive attach (detach: Ctrl-\\)\n ay stop <keyword> graceful shutdown (/exit for claude/codex)\n ay exit <keyword> [reason] graceful shutdown, recording who/why (= 'ay send <kw> exit')\n ay status <keyword> agent status snapshot\n ay result <keyword> [--wait] pull an agent's structured result envelope\n ay result set '<json>' (inside an agent) deposit your result envelope\n ay reap kill process groups leaked by dead agents\n\nRemote:\n" + setupLine + " ay schedule <when> <cli> -- <msg> run an agent on a schedule (HH:MM or cron)\n ay serve [--port N] start HTTP API server (prints token)\n ay serve status show serve daemon/server status\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 at https://github.com/snomiao/agent-yes/tree/main/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");
584
586
  return 0;
585
587
  }
586
588
  function matchKeyword(record, keyword) {
@@ -1824,6 +1826,12 @@ async function cmdSend(rest) {
1824
1826
  if (sender.agent) throw new Error(`${what}.\n Confirm it's the right agent first: ay tail ${record.pid}\n then resend, or pass --force to override.`);
1825
1827
  process.stderr.write(`warning: ${what} — make sure this is the agent you meant (ay tail ${record.pid}).\n`);
1826
1828
  }
1829
+ if (isExitRequest(body)) {
1830
+ const reason = sender.agent ? `requested by ${sender.agent.cli} #${sender.agent.pid} @ ${shortenPath(sender.agent.cwd)}` : `requested via 'ay send ${keyword} exit'`;
1831
+ const { strategy } = await gracefulExitAgent(record, reason);
1832
+ process.stdout.write(`pid ${record.pid} (${record.cli}): exit requested — sent ${strategy} (${reason})\n`);
1833
+ return 0;
1834
+ }
1827
1835
  const fullBody = (sender.agent ? `[from ${sender.agent.cli} #${sender.agent.pid} @ ${shortenPath(sender.agent.cwd)} — reply: ay send ${sender.agent.pid} "..."]\n` : "") + body;
1828
1836
  if (fullBody && trailing) {
1829
1837
  await writeToIpc(fifoPath, fullBody);
@@ -1986,6 +1994,74 @@ async function cmdStop(rest) {
1986
1994
  process.stderr.write(`\n ay status ${record.pid} # confirm it exited\n ay ls --all # see exit codes\n`);
1987
1995
  return 0;
1988
1996
  }
1997
+ /** A `send` body that is exactly the exit word (not a sentence that merely
1998
+ * contains it). Bare "exit" and the literal "/exit" both qualify. */
1999
+ function isExitRequest(body) {
2000
+ const t = body.trim().toLowerCase();
2001
+ return t === "exit" || t === "/exit";
2002
+ }
2003
+ /**
2004
+ * Gracefully terminate a live agent and record WHY in its note (the audit trail
2005
+ * shown by `ay ls`). Sends the CLI's graceful-exit command (e.g. claude's
2006
+ * `/exit`) or a double-Ctrl+C fallback. `reason` is agent-yes metadata — claude's
2007
+ * `/exit` takes no argument, so the reason is the note, not appended to `/exit`.
2008
+ * Shared by `ay exit` and by `ay send <kw> exit`'s routing.
2009
+ */
2010
+ async function gracefulExitAgent(record, reason) {
2011
+ if (!record.fifo_file) throw new Error(`pid ${record.pid}: no fifo_file — cannot send shutdown command`);
2012
+ await writeNote(record.pid, `↩ exit — ${reason}`).catch(() => {});
2013
+ const fifoPath = record.fifo_file;
2014
+ const graceful = GRACEFUL_EXIT_COMMANDS[record.cli];
2015
+ if (graceful) {
2016
+ await writeToIpc(fifoPath, graceful);
2017
+ await new Promise((r) => setTimeout(r, 200));
2018
+ await writeToIpc(fifoPath, "\r");
2019
+ return { strategy: `'${graceful}' + Enter` };
2020
+ }
2021
+ await writeToIpc(fifoPath, "");
2022
+ await new Promise((r) => setTimeout(r, 200));
2023
+ await writeToIpc(fifoPath, "");
2024
+ return { strategy: `double Ctrl+C (no known /exit for cli "${record.cli}")` };
2025
+ }
2026
+ async function cmdExit(rest) {
2027
+ const argv = await yargs(rest).usage("Usage: ay exit <keyword> [reason]").option("all", {
2028
+ type: "boolean",
2029
+ default: false,
2030
+ description: "Include exited agents"
2031
+ }).option("latest", {
2032
+ type: "boolean",
2033
+ default: false,
2034
+ description: "Use most recent match"
2035
+ }).option("cwd", {
2036
+ type: "string",
2037
+ description: "Restrict to agents under this dir"
2038
+ }).help(false).version(false).exitProcess(false).parseAsync();
2039
+ const opts = {
2040
+ all: argv.all,
2041
+ active: false,
2042
+ json: false,
2043
+ latest: argv.latest,
2044
+ cwdScope: typeof argv.cwd === "string" ? path.resolve(argv.cwd) : null
2045
+ };
2046
+ const keyword = argv._[0] !== void 0 ? String(argv._[0]) : void 0;
2047
+ if (!keyword) throw new Error("usage: ay exit <keyword> [reason]");
2048
+ const reasonArg = argv._.slice(1).map(String).join(" ").trim();
2049
+ const record = await resolveOne(keyword, opts);
2050
+ if (!isPidAlive(record.pid)) {
2051
+ await updateGlobalPidStatus(record.pid, {
2052
+ status: "exited",
2053
+ exit_reason: "already-stopped"
2054
+ }).catch(() => {});
2055
+ process.stdout.write(`pid ${record.pid} (${record.cli}) already stopped — marked exited\n`);
2056
+ return 0;
2057
+ }
2058
+ const sender = await senderContext();
2059
+ const reason = reasonArg || (sender.agent ? `requested by ${sender.agent.cli} #${sender.agent.pid} @ ${shortenPath(sender.agent.cwd)}` : "manual");
2060
+ const { strategy } = await gracefulExitAgent(record, reason);
2061
+ process.stdout.write(`exiting pid ${record.pid} (${record.cli}) via ${strategy} — ${reason}\n`);
2062
+ process.stderr.write(`\n ay status ${record.pid} # confirm it exited\n`);
2063
+ return 0;
2064
+ }
1989
2065
  async function cmdAttach(rest) {
1990
2066
  const argv = await yargs(rest).usage("Usage: ay attach <keyword> [--escape ctrl-\\]").option("escape", {
1991
2067
  type: "string",
@@ -2504,5 +2580,5 @@ async function cmdResultSet(rest) {
2504
2580
  }
2505
2581
 
2506
2582
  //#endregion
2507
- export { writeToIpc as C, stopTipForCli as S, renderRawLogLines as _, cursorAbs as a, runSubcommand as b, extractTaskCounts as c, isPidAlive as d, isSubcommand as f, renderRawLog as g, readNotes as h, controlCodeFromName as i, finalizedLines as l, matchKeyword as m, READ_PAGE_DEFAULT as n, deriveLiveStatus as o, listRecords as p, cmdHelp as r, extractNeedsInput as s, GRACEFUL_EXIT_COMMANDS as t, isAgentStuck as u, resolveOne as v, snapshotStatus as x, resolveReadWindow as y };
2508
- //# sourceMappingURL=subcommands-dbx7EoK-.js.map
2583
+ 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 };
2584
+ //# sourceMappingURL=subcommands-BthznoYM.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-DMpCuaZe.js";
2
+ import { r as getInstalledPackage } from "./versionChecker-DatX-QMn.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";
@@ -1216,6 +1216,16 @@ async function agentYes({ cli, cliArgs = [], skipPermissions = false, prompt, ro
1216
1216
  const inheritedAyPid = Number(ptyEnv.AGENT_YES_PID);
1217
1217
  const parentPid = Number.isInteger(inheritedAyPid) && inheritedAyPid > 0 ? inheritedAyPid : void 0;
1218
1218
  ptyEnv.AGENT_YES_PID = String(process.pid);
1219
+ if (cliConf?.env) for (const [key, raw] of Object.entries(cliConf.env)) {
1220
+ let unresolved = false;
1221
+ const value = raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => {
1222
+ const v = ptyEnv[name];
1223
+ if (v === void 0 || v === "") unresolved = true;
1224
+ return v ?? "";
1225
+ });
1226
+ if (unresolved) continue;
1227
+ ptyEnv[key] = value;
1228
+ }
1219
1229
  const ptyOptions = {
1220
1230
  name: "xterm-color",
1221
1231
  ...getTerminalDimensions(),
@@ -1788,4 +1798,4 @@ function sleep(ms) {
1788
1798
 
1789
1799
  //#endregion
1790
1800
  export { removeControlCharacters as a, AgentContext as i, agentYes as n, config as r, CLIS_CONFIG as t };
1791
- //# sourceMappingURL=ts-CMMwNq--.js.map
1801
+ //# sourceMappingURL=ts-CV4m7CUC.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.141.0";
10
+ var version = "1.143.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-DMpCuaZe.js.map
218
+ //# sourceMappingURL=versionChecker-DatX-QMn.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-yes",
3
- "version": "1.141.0",
3
+ "version": "1.143.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",
@@ -39,9 +39,12 @@
39
39
  "cursor-yes": "./dist/cursor-yes.js",
40
40
  "cy": "./dist/cy.js",
41
41
  "gemini-yes": "./dist/gemini-yes.js",
42
+ "glm-yes": "./dist/glm-yes.js",
42
43
  "grok-yes": "./dist/grok-yes.js",
43
44
  "opencode-yes": "./dist/opencode-yes.js",
44
- "qwen-yes": "./dist/qwen-yes.js"
45
+ "qwen-yes": "./dist/qwen-yes.js",
46
+ "pi-yes": "./dist/pi-yes.js",
47
+ "openrouter-yes": "./dist/openrouter-yes.js"
45
48
  },
46
49
  "directories": {
47
50
  "doc": "docs"
@@ -4,6 +4,9 @@ import { buildRustArgs } from "./buildRustArgs";
4
4
 
5
5
  const SUPPORTED_CLIS = [
6
6
  "claude",
7
+ "glm",
8
+ "openrouter",
9
+ "pi",
7
10
  "gemini",
8
11
  "codex",
9
12
  "copilot",
package/ts/index.ts CHANGED
@@ -43,6 +43,10 @@ export type AgentCliConfig = {
43
43
  | { powershell?: string; bash?: string; npm?: string; unix?: string; windows?: string }; // hint user for install command if not installed
44
44
  version?: string; // hint user for version command to check if installed
45
45
  binary?: string; // actual binary name if different from cli, e.g. cursor -> cursor-agent
46
+ // Env vars injected into the spawned agent (e.g. glm points `claude` at Z.AI).
47
+ // Values support ${VAR} expansion against the launching env; an entry whose
48
+ // variable is unset is skipped so it can't blank out an inherited value.
49
+ env?: Record<string, string>;
46
50
  defaultArgs?: string[]; // function to ensure certain args are present
47
51
  yesArgs?: string[]; // appended when `-y`/--yes is passed: the per-CLI "yolo" flag (claude: --dangerously-skip-permissions; codex: --dangerously-bypass-approvals-and-sandbox)
48
52
  help?: string; // documentation/help URL for the CLI
@@ -369,6 +373,21 @@ export default async function agentYes({
369
373
  const parentPid =
370
374
  Number.isInteger(inheritedAyPid) && inheritedAyPid > 0 ? inheritedAyPid : undefined;
371
375
  ptyEnv.AGENT_YES_PID = String(process.pid);
376
+ // Inject per-CLI env (e.g. glm → Z.AI endpoint). Expand ${VAR} against the
377
+ // launching env; skip entries whose vars are unset so we never blank out an
378
+ // inherited value (e.g. ANTHROPIC_AUTH_TOKEN when ZAI_API_KEY isn't exported).
379
+ if (cliConf?.env) {
380
+ for (const [key, raw] of Object.entries(cliConf.env)) {
381
+ let unresolved = false;
382
+ const value = raw.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, name) => {
383
+ const v = ptyEnv[name];
384
+ if (v === undefined || v === "") unresolved = true;
385
+ return v ?? "";
386
+ });
387
+ if (unresolved) continue;
388
+ ptyEnv[key] = value;
389
+ }
390
+ }
372
391
  const ptyOptions = {
373
392
  name: "xterm-color",
374
393
  ...getTerminalDimensions(),
package/ts/serve.ts CHANGED
@@ -111,6 +111,13 @@ const SESSION_PIN_ENV = new Set([
111
111
  "CLAUDE_CODE_SESSION_ID",
112
112
  "CLAUDE_CODE_CHILD_SESSION",
113
113
  "CLAUDE_CODE_ENTRYPOINT",
114
+ // The agent-yes wrapper pid of the agent that launched `ay serve`. A daemon
115
+ // started from inside an agent's shell carries that agent's AGENT_YES_PID for
116
+ // its whole lifetime; without stripping it, every console-spawned agent would
117
+ // inherit it and be recorded with parent_pid = that stale agent, mis-rooting
118
+ // the whole subagent tree under an unrelated agent. Dropping it makes console
119
+ // spawns clean top-level agents (parent_pid = None).
120
+ "AGENT_YES_PID",
114
121
  ]);
115
122
 
116
123
  // Env for a console-spawned agent, minus only the session-pinning vars above. If
@@ -731,6 +738,14 @@ export async function cmdServe(rest: string[]): Promise<number> {
731
738
  );
732
739
  }
733
740
 
741
+ // Drop the AGENT_YES_PID we may have inherited from the shell/agent that
742
+ // launched us. A serve daemon outlives that agent, but the env var sticks for
743
+ // our whole lifetime; if left in place, freshAgentEnv() aside, any descendant
744
+ // we spawn would be recorded with parent_pid = that long-dead agent, mis-rooting
745
+ // the subagent tree. Clearing it once at startup makes console spawns clean
746
+ // top-level agents regardless of the spawn path.
747
+ delete process.env.AGENT_YES_PID;
748
+
734
749
  const port = (argv.port as number) ?? DEFAULT_PORT;
735
750
  const host = (argv.host as string) ?? "127.0.0.1";
736
751
  const tokenFlag = typeof argv.token === "string" ? argv.token : undefined;
@@ -717,6 +717,82 @@ describe("subcommands.cmdSend writes bytes to FIFO", () => {
717
717
  const { controlCodeFromName } = await loadModule();
718
718
  expect(controlCodeFromName("none")).toBe("");
719
719
  });
720
+
721
+ it.skipIf(!itUnix)(
722
+ "routes a bare 'exit' to the graceful /exit, not the literal word",
723
+ async () => {
724
+ const { runSubcommand } = await loadModule();
725
+ const { appendGlobalPid } = await import("./globalPidIndex.ts");
726
+ const { spawnSync } = await import("child_process");
727
+ const tmp = await mkdtemp(path.join(tmpdir(), "ay-fifo-"));
728
+ try {
729
+ const fifo = path.join(tmp, "exit.fifo");
730
+ if (spawnSync("mkfifo", [fifo]).status !== 0) return;
731
+ const fs = await import("fs");
732
+ const rdwrFd = fs.openSync(fifo, fs.constants.O_RDWR);
733
+ await appendGlobalPid({
734
+ pid: process.pid,
735
+ cli: "claude",
736
+ prompt: null,
737
+ cwd: process.cwd(),
738
+ log_file: null,
739
+ fifo_file: fifo,
740
+ status: "active",
741
+ exit_code: null,
742
+ exit_reason: null,
743
+ started_at: Date.now(),
744
+ });
745
+ const stdout: string[] = [];
746
+ const orig = process.stdout.write.bind(process.stdout);
747
+ (process.stdout as any).write = (s: any) => (stdout.push(String(s)), true);
748
+ const savedAyPid = process.env.AGENT_YES_PID;
749
+ delete process.env.AGENT_YES_PID;
750
+ try {
751
+ const code = await runSubcommand([
752
+ "bun",
753
+ "cli.js",
754
+ "send",
755
+ String(process.pid),
756
+ "exit",
757
+ "--force",
758
+ ]);
759
+ expect(code).toBe(0);
760
+ expect(stdout.join("")).toMatch(/exit requested/);
761
+ } finally {
762
+ process.stdout.write = orig;
763
+ if (savedAyPid !== undefined) process.env.AGENT_YES_PID = savedAyPid;
764
+ }
765
+ const buf = Buffer.alloc(4096);
766
+ const n = fs.readSync(rdwrFd, buf, 0, buf.length, null);
767
+ // The real `/exit` command + Enter — NOT the literal "exit\r" that claude ignores.
768
+ expect(buf.subarray(0, n).toString()).toBe("/exit\r");
769
+ fs.closeSync(rdwrFd);
770
+ } finally {
771
+ await rm(tmp, { recursive: true, force: true }).catch(() => null);
772
+ }
773
+ },
774
+ );
775
+ });
776
+
777
+ describe("subcommands.isExitRequest", () => {
778
+ it("matches the bare exit word and the literal /exit (any case, trimmed)", async () => {
779
+ const { isExitRequest } = await loadModule();
780
+ for (const s of ["exit", "/exit", " exit ", "EXIT", "/Exit", "\nexit\n"]) {
781
+ expect(isExitRequest(s)).toBe(true);
782
+ }
783
+ });
784
+ it("does NOT match a sentence that merely contains 'exit'", async () => {
785
+ const { isExitRequest } = await loadModule();
786
+ for (const s of [
787
+ "please exit now",
788
+ "exit the loop after step 3",
789
+ "do not exit",
790
+ "exiting",
791
+ "",
792
+ ]) {
793
+ expect(isExitRequest(s)).toBe(false);
794
+ }
795
+ });
720
796
  });
721
797
 
722
798
  describe("subcommands.writeToIpc reliable delivery", () => {
package/ts/subcommands.ts CHANGED
Binary file
@@ -1,8 +0,0 @@
1
- import "./ts-CMMwNq--.js";
2
- import "./logger-CDIsZ-Pp.js";
3
- import "./versionChecker-DMpCuaZe.js";
4
- import "./pidStore-fqXqTKkh.js";
5
- import "./globalPidIndex-DlmmJlO8.js";
6
- import { t as SUPPORTED_CLIS } from "./SUPPORTED_CLIS-CnBoRIDN.js";
7
-
8
- export { SUPPORTED_CLIS };
@@ -1,7 +0,0 @@
1
- import "./logger-CDIsZ-Pp.js";
2
- import "./globalPidIndex-DlmmJlO8.js";
3
- import "./configShared-C1C04bbq.js";
4
- import "./remotes-PKKjfTI1.js";
5
- import { C as writeToIpc, S as stopTipForCli, _ as renderRawLogLines, a as cursorAbs, b as runSubcommand, c as extractTaskCounts, d as isPidAlive, f as isSubcommand, g as renderRawLog, h as readNotes, i as controlCodeFromName, l as finalizedLines, m as matchKeyword, n as READ_PAGE_DEFAULT, o as deriveLiveStatus, p as listRecords, r as cmdHelp, s as extractNeedsInput, t as GRACEFUL_EXIT_COMMANDS, u as isAgentStuck, v as resolveOne, x as snapshotStatus, y as resolveReadWindow } from "./subcommands-dbx7EoK-.js";
6
-
7
- export { cmdHelp, isSubcommand, runSubcommand };