@cluesmith/codev 3.3.3 → 3.3.4

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.
Files changed (176) hide show
  1. package/dashboard-dist/assets/{index-CUIjS_Vb.js → index-DZf6pNPE.js} +2 -2
  2. package/dashboard-dist/assets/{index-CUIjS_Vb.js.map → index-DZf6pNPE.js.map} +1 -1
  3. package/dashboard-dist/index.html +1 -1
  4. package/dist/agent-farm/cli.d.ts.map +1 -1
  5. package/dist/agent-farm/cli.js +48 -0
  6. package/dist/agent-farm/cli.js.map +1 -1
  7. package/dist/agent-farm/commands/ide.d.ts +20 -0
  8. package/dist/agent-farm/commands/ide.d.ts.map +1 -0
  9. package/dist/agent-farm/commands/ide.js +138 -0
  10. package/dist/agent-farm/commands/ide.js.map +1 -0
  11. package/dist/agent-farm/commands/inbox.d.ts.map +1 -1
  12. package/dist/agent-farm/commands/inbox.js +15 -3
  13. package/dist/agent-farm/commands/inbox.js.map +1 -1
  14. package/dist/agent-farm/commands/send.d.ts.map +1 -1
  15. package/dist/agent-farm/commands/send.js +16 -1
  16. package/dist/agent-farm/commands/send.js.map +1 -1
  17. package/dist/agent-farm/commands/spawn-worktree.d.ts.map +1 -1
  18. package/dist/agent-farm/commands/spawn-worktree.js +16 -7
  19. package/dist/agent-farm/commands/spawn-worktree.js.map +1 -1
  20. package/dist/agent-farm/commands/tower.d.ts +21 -9
  21. package/dist/agent-farm/commands/tower.d.ts.map +1 -1
  22. package/dist/agent-farm/commands/tower.js +90 -30
  23. package/dist/agent-farm/commands/tower.js.map +1 -1
  24. package/dist/agent-farm/db/tower-owner.d.ts +168 -0
  25. package/dist/agent-farm/db/tower-owner.d.ts.map +1 -0
  26. package/dist/agent-farm/db/tower-owner.js +369 -0
  27. package/dist/agent-farm/db/tower-owner.js.map +1 -0
  28. package/dist/agent-farm/db/types.d.ts +30 -5
  29. package/dist/agent-farm/db/types.d.ts.map +1 -1
  30. package/dist/agent-farm/db/types.js.map +1 -1
  31. package/dist/agent-farm/lib/ide-record.d.ts +65 -0
  32. package/dist/agent-farm/lib/ide-record.d.ts.map +1 -0
  33. package/dist/agent-farm/lib/ide-record.js +118 -0
  34. package/dist/agent-farm/lib/ide-record.js.map +1 -0
  35. package/dist/agent-farm/lib/tunnel-client.d.ts +22 -0
  36. package/dist/agent-farm/lib/tunnel-client.d.ts.map +1 -1
  37. package/dist/agent-farm/lib/tunnel-client.js +53 -5
  38. package/dist/agent-farm/lib/tunnel-client.js.map +1 -1
  39. package/dist/agent-farm/servers/gate-profiles.d.ts +23 -2
  40. package/dist/agent-farm/servers/gate-profiles.d.ts.map +1 -1
  41. package/dist/agent-farm/servers/gate-profiles.js +43 -2
  42. package/dist/agent-farm/servers/gate-profiles.js.map +1 -1
  43. package/dist/agent-farm/servers/ide-forward.d.ts +62 -0
  44. package/dist/agent-farm/servers/ide-forward.d.ts.map +1 -0
  45. package/dist/agent-farm/servers/ide-forward.js +274 -0
  46. package/dist/agent-farm/servers/ide-forward.js.map +1 -0
  47. package/dist/agent-farm/servers/ide-server.d.ts +68 -0
  48. package/dist/agent-farm/servers/ide-server.d.ts.map +1 -0
  49. package/dist/agent-farm/servers/ide-server.js +281 -0
  50. package/dist/agent-farm/servers/ide-server.js.map +1 -0
  51. package/dist/agent-farm/servers/mailbox-delivery.d.ts +213 -4
  52. package/dist/agent-farm/servers/mailbox-delivery.d.ts.map +1 -1
  53. package/dist/agent-farm/servers/mailbox-delivery.js +489 -68
  54. package/dist/agent-farm/servers/mailbox-delivery.js.map +1 -1
  55. package/dist/agent-farm/servers/mailbox-wiring.d.ts +18 -4
  56. package/dist/agent-farm/servers/mailbox-wiring.d.ts.map +1 -1
  57. package/dist/agent-farm/servers/mailbox-wiring.js +94 -21
  58. package/dist/agent-farm/servers/mailbox-wiring.js.map +1 -1
  59. package/dist/agent-farm/servers/message-write.d.ts +138 -11
  60. package/dist/agent-farm/servers/message-write.d.ts.map +1 -1
  61. package/dist/agent-farm/servers/message-write.js +144 -35
  62. package/dist/agent-farm/servers/message-write.js.map +1 -1
  63. package/dist/agent-farm/servers/overview.d.ts +28 -12
  64. package/dist/agent-farm/servers/overview.d.ts.map +1 -1
  65. package/dist/agent-farm/servers/overview.js +130 -79
  66. package/dist/agent-farm/servers/overview.js.map +1 -1
  67. package/dist/agent-farm/servers/render-gate.d.ts +53 -0
  68. package/dist/agent-farm/servers/render-gate.d.ts.map +1 -1
  69. package/dist/agent-farm/servers/render-gate.js +135 -24
  70. package/dist/agent-farm/servers/render-gate.js.map +1 -1
  71. package/dist/agent-farm/servers/session-submit.d.ts +7 -4
  72. package/dist/agent-farm/servers/session-submit.d.ts.map +1 -1
  73. package/dist/agent-farm/servers/session-submit.js +7 -4
  74. package/dist/agent-farm/servers/session-submit.js.map +1 -1
  75. package/dist/agent-farm/servers/tower-cron.d.ts.map +1 -1
  76. package/dist/agent-farm/servers/tower-cron.js +4 -1
  77. package/dist/agent-farm/servers/tower-cron.js.map +1 -1
  78. package/dist/agent-farm/servers/tower-instances.d.ts.map +1 -1
  79. package/dist/agent-farm/servers/tower-instances.js +6 -7
  80. package/dist/agent-farm/servers/tower-instances.js.map +1 -1
  81. package/dist/agent-farm/servers/tower-routes.d.ts.map +1 -1
  82. package/dist/agent-farm/servers/tower-routes.js +53 -13
  83. package/dist/agent-farm/servers/tower-routes.js.map +1 -1
  84. package/dist/agent-farm/servers/tower-server.js +64 -7
  85. package/dist/agent-farm/servers/tower-server.js.map +1 -1
  86. package/dist/agent-farm/servers/tower-terminals.d.ts.map +1 -1
  87. package/dist/agent-farm/servers/tower-terminals.js +53 -8
  88. package/dist/agent-farm/servers/tower-terminals.js.map +1 -1
  89. package/dist/agent-farm/servers/tower-websocket.d.ts.map +1 -1
  90. package/dist/agent-farm/servers/tower-websocket.js +18 -1
  91. package/dist/agent-farm/servers/tower-websocket.js.map +1 -1
  92. package/dist/agent-farm/utils/message-format.d.ts.map +1 -1
  93. package/dist/agent-farm/utils/message-format.js +5 -6
  94. package/dist/agent-farm/utils/message-format.js.map +1 -1
  95. package/dist/agent-farm/utils/port.d.ts +22 -0
  96. package/dist/agent-farm/utils/port.d.ts.map +1 -0
  97. package/dist/agent-farm/utils/port.js +35 -0
  98. package/dist/agent-farm/utils/port.js.map +1 -0
  99. package/dist/agent-farm/utils/server-utils.d.ts +26 -0
  100. package/dist/agent-farm/utils/server-utils.d.ts.map +1 -1
  101. package/dist/agent-farm/utils/server-utils.js +36 -0
  102. package/dist/agent-farm/utils/server-utils.js.map +1 -1
  103. package/dist/cli.js +2 -2
  104. package/dist/cli.js.map +1 -1
  105. package/dist/commands/consult/index.d.ts.map +1 -1
  106. package/dist/commands/consult/index.js +61 -1
  107. package/dist/commands/consult/index.js.map +1 -1
  108. package/dist/commands/consult/tree-tripwire.d.ts +177 -0
  109. package/dist/commands/consult/tree-tripwire.d.ts.map +1 -0
  110. package/dist/commands/consult/tree-tripwire.js +356 -0
  111. package/dist/commands/consult/tree-tripwire.js.map +1 -0
  112. package/dist/commands/doctor.d.ts +69 -0
  113. package/dist/commands/doctor.d.ts.map +1 -1
  114. package/dist/commands/doctor.js +163 -0
  115. package/dist/commands/doctor.js.map +1 -1
  116. package/dist/commands/generate-image.d.ts +5 -0
  117. package/dist/commands/generate-image.d.ts.map +1 -1
  118. package/dist/commands/generate-image.js +225 -4
  119. package/dist/commands/generate-image.js.map +1 -1
  120. package/dist/lib/agent-env.d.ts +86 -0
  121. package/dist/lib/agent-env.d.ts.map +1 -0
  122. package/dist/lib/agent-env.js +126 -0
  123. package/dist/lib/agent-env.js.map +1 -0
  124. package/dist/lib/forge-contracts.d.ts +10 -0
  125. package/dist/lib/forge-contracts.d.ts.map +1 -1
  126. package/dist/lib/forge-contracts.js.map +1 -1
  127. package/dist/lib/forge-rate-limit.d.ts +45 -0
  128. package/dist/lib/forge-rate-limit.d.ts.map +1 -0
  129. package/dist/lib/forge-rate-limit.js +89 -0
  130. package/dist/lib/forge-rate-limit.js.map +1 -0
  131. package/dist/lib/forge.d.ts +28 -0
  132. package/dist/lib/forge.d.ts.map +1 -1
  133. package/dist/lib/forge.js +63 -10
  134. package/dist/lib/forge.js.map +1 -1
  135. package/dist/lib/github.d.ts +13 -1
  136. package/dist/lib/github.d.ts.map +1 -1
  137. package/dist/lib/github.js +58 -15
  138. package/dist/lib/github.js.map +1 -1
  139. package/dist/lib/test-env.d.ts +17 -0
  140. package/dist/lib/test-env.d.ts.map +1 -1
  141. package/dist/lib/test-env.js +20 -0
  142. package/dist/lib/test-env.js.map +1 -1
  143. package/dist/terminal/pty-session.d.ts +122 -4
  144. package/dist/terminal/pty-session.d.ts.map +1 -1
  145. package/dist/terminal/pty-session.js +142 -7
  146. package/dist/terminal/pty-session.js.map +1 -1
  147. package/dist/terminal/session-manager.d.ts +23 -2
  148. package/dist/terminal/session-manager.d.ts.map +1 -1
  149. package/dist/terminal/session-manager.js +115 -36
  150. package/dist/terminal/session-manager.js.map +1 -1
  151. package/dist/terminal/session-screen.d.ts +34 -5
  152. package/dist/terminal/session-screen.d.ts.map +1 -1
  153. package/dist/terminal/session-screen.js +52 -6
  154. package/dist/terminal/session-screen.js.map +1 -1
  155. package/dist/terminal/terminal-replies.d.ts +102 -0
  156. package/dist/terminal/terminal-replies.d.ts.map +1 -0
  157. package/dist/terminal/terminal-replies.js +181 -0
  158. package/dist/terminal/terminal-replies.js.map +1 -0
  159. package/package.json +4 -4
  160. package/scripts/forge/github/pr-list.sh +6 -2
  161. package/scripts/forge/github/pr-search.sh +4 -2
  162. package/scripts/forge/gitlab/issue-search.sh +1 -0
  163. package/scripts/forge/gitlab/pr-search.sh +35 -2
  164. package/scripts/forge/linear/auth-status.sh +1 -0
  165. package/scripts/forge/linear/issue-comment.sh +1 -0
  166. package/scripts/forge/linear/issue-list.sh +1 -0
  167. package/scripts/forge/linear/issue-search.sh +1 -0
  168. package/scripts/forge/linear/issue-view.sh +1 -0
  169. package/scripts/forge/linear/recently-closed.sh +1 -0
  170. package/scripts/forge/linear/user-identity.sh +1 -0
  171. package/skeleton/.claude/skills/generate-image/SKILL.md +19 -2
  172. package/skeleton/.codex/skills/generate-image/SKILL.md +19 -2
  173. package/skeleton/resources/commands/agent-farm.md +27 -2
  174. package/skeleton/roles/consultant.md +32 -0
  175. package/skeleton/templates/AGENTS.md +1 -1
  176. package/skeleton/templates/CLAUDE.md +1 -1
@@ -69,6 +69,10 @@ export class SessionScreen {
69
69
  // parses asynchronously and processes writes FIFO, so awaiting the LATEST write's
70
70
  // callback guarantees every earlier write is parsed too — the flush {@link read} needs.
71
71
  pending = Promise.resolve();
72
+ // Chunk counters backing {@link hasUnparsedOutput}: `feed` numbers each chunk, its parse
73
+ // callback records the number. Equal means the grid shows every byte fed so far.
74
+ fedSeq = 0;
75
+ parsedSeq = 0;
72
76
  disposed = false;
73
77
  constructor(cols, rows) {
74
78
  this._cols = cols;
@@ -90,7 +94,20 @@ export class SessionScreen {
90
94
  feed(data) {
91
95
  if (this.disposed)
92
96
  return;
93
- this.pending = new Promise((resolve) => this.term.write(data, () => resolve()));
97
+ // Number every chunk and record the number its parse callback completes, so a synchronous
98
+ // caller can tell whether the grid is CURRENT (Issue #1664, CMAP round 2 — codex). xterm
99
+ // parses asynchronously and FIFO, so `parsedSeq !== fedSeq` means bytes have been fed that
100
+ // the grid does not show yet — and a composer redraw hiding in them is invisible to
101
+ // {@link peek}.
102
+ const seq = ++this.fedSeq;
103
+ this.pending = new Promise((resolve) => this.term.write(data, () => {
104
+ this.parsedSeq = seq;
105
+ resolve();
106
+ }));
107
+ }
108
+ /** Have bytes been fed that the grid has not parsed yet? See {@link feed} and {@link peek}. */
109
+ get hasUnparsedOutput() {
110
+ return this.parsedSeq !== this.fedSeq;
94
111
  }
95
112
  /**
96
113
  * Resize the mirror to match the live session (Spec 1313: the gate renders at the
@@ -110,11 +127,14 @@ export class SessionScreen {
110
127
  * `await this.pending` drains every byte fed up to this call into the grid, so the returned
111
128
  * buffer reflects AT LEAST the output counted by `ringBuffer.bytesWritten` at the moment the
112
129
  * caller sampled its change-token (xterm parses writes FIFO but may run ahead into later queued
113
- * writes, so the buffer can reflect *more* — never less). That lower bound is the property the
114
- * delivery path's token-before/after TOCTOU relies on: the caller MUST read the returned buffer
115
- * with no intervening `await` (the classifier is synchronous), so no `feed` can interleave the
116
- * read; any output that landed during THIS flush already advanced `bytesWritten`, so the caller's
117
- * post-classify token re-check trips (→ hold) and nothing is delivered onto it.
130
+ * writes, so the buffer can reflect *more* — never less). The caller MUST read the returned
131
+ * buffer with no intervening `await` (the classifier is synchronous), so no `feed` can
132
+ * interleave the read.
133
+ *
134
+ * Issue #1664 retired the `bytesWritten` re-check this note used to point at — the delivery
135
+ * path no longer holds on whole-screen output, because a working agent always has some. What
136
+ * covers output landing after the flush is now {@link peek}, which refuses to answer at all
137
+ * while any fed byte is still unparsed.
118
138
  *
119
139
  * After {@link dispose} the term is freed and its parse callback may never fire, so this returns
120
140
  * the current view WITHOUT awaiting `pending` — a disposed screen has no coherent frame, the
@@ -127,6 +147,32 @@ export class SessionScreen {
127
147
  await this.pending;
128
148
  return { term: this.term, cols: this._cols, rows: this._rows };
129
149
  }
150
+ /**
151
+ * The live buffer WITHOUT flushing the parser (Issue #1664) — the synchronous counterpart of
152
+ * {@link read}, for a caller that cannot await.
153
+ *
154
+ * The delivery path's in-lock precheck runs inside the per-terminal submission lock,
155
+ * immediately before the first byte, and is synchronous by contract; it re-reads the composer
156
+ * region there to confirm the screen it measured is still the screen it is about to write
157
+ * onto. Awaiting a parse callback in that position is not available to it.
158
+ *
159
+ * `null` when the grid is not CURRENT — any byte fed but not yet parsed, or a disposed mirror
160
+ * (CMAP round 2 — codex). Without that, this would hand back a grid missing exactly the
161
+ * composer redraw the caller is checking for: output arriving after the stable sample and
162
+ * immediately before the in-lock precheck is queued for parsing, absent from the buffer, and
163
+ * the fingerprint would compare EQUAL and permit the write — the corruption race #1521 is
164
+ * about, which the retired `bytesWritten` comparison used to catch. Refusing to answer is the
165
+ * fail-safe direction: every caller treats `null` as "the composer moved" and holds, and the
166
+ * hold is self-clearing on the next pass a parse-turn later.
167
+ *
168
+ * This is why `peek()` is not simply "read without awaiting": a synchronous caller cannot
169
+ * flush, so the only honest answers are a coherent frame or none.
170
+ */
171
+ peek() {
172
+ if (this.disposed || this.hasUnparsedOutput)
173
+ return null;
174
+ return { term: this.term, cols: this._cols, rows: this._rows };
175
+ }
130
176
  /**
131
177
  * Serialize the current screen state (both buffers, colors/attrs, cursor, alt-screen
132
178
  * mode, plus up to {@link SCREEN_SCROLLBACK} lines of history) back into a terminal
@@ -1 +1 @@
1
- {"version":3,"file":"session-screen.js","sourceRoot":"","sources":["../../src/terminal/session-screen.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"session-screen.js","sourceRoot":"","sources":["../../src/terminal/session-screen.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,102 @@
1
+ /**
2
+ * Terminal REPLY stripping for the delivery gate's input signal (Issue #1473).
3
+ *
4
+ * The render gate needs to know when a HUMAN has put something on a session's line, so a
5
+ * gated delivery never lands on a composer somebody has started typing into. The only
6
+ * server-side observation point is {@link PtySession.write}'s external branch — every live
7
+ * client (the web terminal, the VS Code webview, mobile, the raw
8
+ * `POST /api/terminals/:id/write` route) funnels through it.
9
+ *
10
+ * But that stream is not keystrokes. xterm forwards terminal REPLIES — DA, DSR, CPR,
11
+ * XTWINOPS, DECRPM, DECRQSS, OSC colour queries, focus reports — through the very same
12
+ * `onData` event as typed characters, because from the client's point of view they are all
13
+ * "bytes to send upstream". The web client strips a few of them, but ONLY while
14
+ * `rc.initialPhase` is true (`Terminal.tsx`) — for a session's entire steady-state life that
15
+ * filter is off, and the VS Code webview and mobile clients never had one at all. (`afx
16
+ * attach` is not in scope either way: it writes the shellper socket directly, so neither its
17
+ * keystrokes nor its replies ever pass through `PtySession.write()` — see arch.md §5.)
18
+ * Counting a reply as human input would produce a hold with nobody at the keyboard, and
19
+ * worse, a SELF-TRIP: our own delivery write repaints the TUI, the TUI queries geometry, the
20
+ * browser answers, and the answer reads as a keystroke that blocks the next delivery. So the
21
+ * filter is a precondition of the whole design, and it lives here — server-side, where every
22
+ * client is covered, rather than in one cooperative client.
23
+ *
24
+ * SIGNAL-ONLY. Nothing here changes what reaches the PTY: the caller still writes the full
25
+ * chunk verbatim, because the application ASKED for the reply and blocks waiting on it.
26
+ * Swallowing a DA reply would hang every attached terminal.
27
+ *
28
+ * ## Failure directions
29
+ *
30
+ * - **Over-strip** (a real keystroke eaten from the signal) → that keystroke goes uncounted,
31
+ * which is exactly today's behaviour, so never a regression — but it silently re-opens the
32
+ * race this issue exists to close, which is why the survival tests below are the strict half.
33
+ * - **Under-strip** (a reply counted as input) → a spurious `busy:recent-input` hold that
34
+ * clears on the next settle. Fail-safe, and now visible rather than silent.
35
+ *
36
+ * The filter is **chunk-local**: it sees exactly the string one `write()` was handed, so a
37
+ * reply that arrives split across two calls (a short socket read, a coalescing client) matches
38
+ * no pattern in either half and is under-stripped. That is the fail-safe direction — a hold
39
+ * that clears itself a settle later — and it is why no pattern here is written to span calls:
40
+ * buffering across chunks to catch it would put a real keystroke's arrival at the mercy of the
41
+ * NEXT chunk, which is the over-strip direction, and that one is silent.
42
+ *
43
+ * ## The table is derived from a PINNED dependency
44
+ *
45
+ * Every pattern below was read out of `@xterm/xterm@{@link XTERM_REPLY_TABLE_VERSION}`'s
46
+ * bundle — each `triggerDataEvent` call site that emits a reply rather than a keystroke. A
47
+ * version bump is therefore a REVIEW TRIGGER for this file: a newly-answered query (kitty
48
+ * keyboard, XTVERSION) becomes an uncounted-reply hold until its shape is added here. Two
49
+ * patterns below are pre-emptive for that reason and match nothing stock xterm 5.5 emits.
50
+ *
51
+ * ## Case sensitivity is load-bearing
52
+ *
53
+ * `ESC[C` is Right-arrow and `ESC[1;5C` is Ctrl-Right. The CSI family pattern keys on a
54
+ * LOWERCASE final byte set (`c`/`n`/`t`/`y`); an `i` flag on it would strip every arrow key,
55
+ * Home/End and shift-Tab out of the signal. No pattern here carries `i`, and
56
+ * `terminal-replies.test.ts` pins the survival of those exact sequences.
57
+ */
58
+ /**
59
+ * The `@xterm/xterm` version whose emission sites this table was enumerated from. Bumping the
60
+ * dependency past it means re-reading its `triggerDataEvent` call sites; a test asserts this
61
+ * constant against the installed package's version so the bump cannot pass silently.
62
+ */
63
+ export declare const XTERM_REPLY_TABLE_VERSION = "5.5.0";
64
+ /**
65
+ * Every recognized terminal reply in `data`, in the order the patterns match them.
66
+ *
67
+ * Diagnostics only — {@link stripTerminalReplies} does the real work and never calls this. It
68
+ * backs the `AF_LOG_INPUT_SIGNAL` trace, whose whole purpose is to let a human SEE which bytes
69
+ * were treated as a reply and which survived as input, rather than infer it from a hold that
70
+ * did or did not happen.
71
+ */
72
+ export declare function terminalReplyMatches(data: string): string[];
73
+ /**
74
+ * Render a byte run so it can be read in a log line: ESC as `\e`, other C0 controls and DEL as
75
+ * `\xNN`, everything else literal.
76
+ *
77
+ * Printing raw ESC to a terminal log would make the log itself move the cursor and repaint —
78
+ * the diagnostic would corrupt the thing it is diagnosing, and the reply bytes under
79
+ * investigation would be the ones that vanished.
80
+ */
81
+ export declare function escapeBytes(data: string): string;
82
+ /**
83
+ * The human-input residue of one upstream chunk: `data` with every recognized terminal reply
84
+ * removed. Returns `''` when the chunk was nothing but replies — the caller reads that as "no
85
+ * human input here" and leaves the gate's input signal untouched.
86
+ *
87
+ * Pure and allocation-cheap; called on every keystroke, so the common case (plain text with
88
+ * no ESC at all) short-circuits before touching a regex.
89
+ *
90
+ * The short-circuit does NOT cover a bracketed paste: `ESC[200~ … ESC[201~` always contains
91
+ * ESC, so a pasted body — up to the 1 MiB a `/write` request body may carry — runs every
92
+ * pattern's `replace` over its full length. This is an EXTERNAL paste only (a human pasting
93
+ * into the browser terminal, or a caller POSTing one to `/api/terminals/:id/write`); a mail
94
+ * delivery's own bracketed paste (Issue #1567) is written with origin `'delivery'` and never
95
+ * reaches this function at all, so no long send pays the scan. Left as is deliberately: an
96
+ * external paste is a rare, human-paced event next to the per-keystroke path this function is
97
+ * tuned for, and any length guard cheap enough to be worth adding would have to decide where a
98
+ * reply may legitimately sit inside the payload — which is the over-strip direction, the
99
+ * silent one.
100
+ */
101
+ export declare function stripTerminalReplies(data: string): string;
102
+ //# sourceMappingURL=terminal-replies.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-replies.d.ts","sourceRoot":"","sources":["../../src/terminal/terminal-replies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH;;;;GAIG;AACH,eAAO,MAAM,yBAAyB,UAAU,CAAC;AAqDjD;;;;;;;GAOG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAM3D;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAShD;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAQzD"}
@@ -0,0 +1,181 @@
1
+ /**
2
+ * Terminal REPLY stripping for the delivery gate's input signal (Issue #1473).
3
+ *
4
+ * The render gate needs to know when a HUMAN has put something on a session's line, so a
5
+ * gated delivery never lands on a composer somebody has started typing into. The only
6
+ * server-side observation point is {@link PtySession.write}'s external branch — every live
7
+ * client (the web terminal, the VS Code webview, mobile, the raw
8
+ * `POST /api/terminals/:id/write` route) funnels through it.
9
+ *
10
+ * But that stream is not keystrokes. xterm forwards terminal REPLIES — DA, DSR, CPR,
11
+ * XTWINOPS, DECRPM, DECRQSS, OSC colour queries, focus reports — through the very same
12
+ * `onData` event as typed characters, because from the client's point of view they are all
13
+ * "bytes to send upstream". The web client strips a few of them, but ONLY while
14
+ * `rc.initialPhase` is true (`Terminal.tsx`) — for a session's entire steady-state life that
15
+ * filter is off, and the VS Code webview and mobile clients never had one at all. (`afx
16
+ * attach` is not in scope either way: it writes the shellper socket directly, so neither its
17
+ * keystrokes nor its replies ever pass through `PtySession.write()` — see arch.md §5.)
18
+ * Counting a reply as human input would produce a hold with nobody at the keyboard, and
19
+ * worse, a SELF-TRIP: our own delivery write repaints the TUI, the TUI queries geometry, the
20
+ * browser answers, and the answer reads as a keystroke that blocks the next delivery. So the
21
+ * filter is a precondition of the whole design, and it lives here — server-side, where every
22
+ * client is covered, rather than in one cooperative client.
23
+ *
24
+ * SIGNAL-ONLY. Nothing here changes what reaches the PTY: the caller still writes the full
25
+ * chunk verbatim, because the application ASKED for the reply and blocks waiting on it.
26
+ * Swallowing a DA reply would hang every attached terminal.
27
+ *
28
+ * ## Failure directions
29
+ *
30
+ * - **Over-strip** (a real keystroke eaten from the signal) → that keystroke goes uncounted,
31
+ * which is exactly today's behaviour, so never a regression — but it silently re-opens the
32
+ * race this issue exists to close, which is why the survival tests below are the strict half.
33
+ * - **Under-strip** (a reply counted as input) → a spurious `busy:recent-input` hold that
34
+ * clears on the next settle. Fail-safe, and now visible rather than silent.
35
+ *
36
+ * The filter is **chunk-local**: it sees exactly the string one `write()` was handed, so a
37
+ * reply that arrives split across two calls (a short socket read, a coalescing client) matches
38
+ * no pattern in either half and is under-stripped. That is the fail-safe direction — a hold
39
+ * that clears itself a settle later — and it is why no pattern here is written to span calls:
40
+ * buffering across chunks to catch it would put a real keystroke's arrival at the mercy of the
41
+ * NEXT chunk, which is the over-strip direction, and that one is silent.
42
+ *
43
+ * ## The table is derived from a PINNED dependency
44
+ *
45
+ * Every pattern below was read out of `@xterm/xterm@{@link XTERM_REPLY_TABLE_VERSION}`'s
46
+ * bundle — each `triggerDataEvent` call site that emits a reply rather than a keystroke. A
47
+ * version bump is therefore a REVIEW TRIGGER for this file: a newly-answered query (kitty
48
+ * keyboard, XTVERSION) becomes an uncounted-reply hold until its shape is added here. Two
49
+ * patterns below are pre-emptive for that reason and match nothing stock xterm 5.5 emits.
50
+ *
51
+ * ## Case sensitivity is load-bearing
52
+ *
53
+ * `ESC[C` is Right-arrow and `ESC[1;5C` is Ctrl-Right. The CSI family pattern keys on a
54
+ * LOWERCASE final byte set (`c`/`n`/`t`/`y`); an `i` flag on it would strip every arrow key,
55
+ * Home/End and shift-Tab out of the signal. No pattern here carries `i`, and
56
+ * `terminal-replies.test.ts` pins the survival of those exact sequences.
57
+ */
58
+ /**
59
+ * The `@xterm/xterm` version whose emission sites this table was enumerated from. Bumping the
60
+ * dependency past it means re-reading its `triggerDataEvent` call sites; a test asserts this
61
+ * constant against the installed package's version so the bump cannot pass silently.
62
+ */
63
+ export const XTERM_REPLY_TABLE_VERSION = '5.5.0';
64
+ /**
65
+ * Sequences a terminal emulator sends UPSTREAM in answer to the application, never as a
66
+ * human action. Anchored one shape at a time — never a blanket "starts with ESC[" — so a
67
+ * keystroke can only be eaten by a pattern that names its exact form.
68
+ *
69
+ * What each covers, in xterm 5.5.0's own emission order:
70
+ *
71
+ * 1. The CSI answer family, keyed on a lowercase final byte:
72
+ * - DA1 `ESC[?1;2c`, `ESC[?6c` (`sendDeviceAttributesPrimary`)
73
+ * - DA2 `ESC[>0;276;0c`, `ESC[>85;95;0c`, `ESC[>83;40003;0c`
74
+ * - DSR `ESC[0n` (`deviceStatus`, param 5)
75
+ * - XTWINOPS `ESC[4;h;wt`, `ESC[6;h;wt`, `ESC[8;rows;colst`
76
+ * - DECRPM `ESC[?2004$y`, `ESC[1;2$y` (`_reportMode`)
77
+ * No key sequence xterm can produce ends in `c`, `n`, `t` or `y` — its key finals are
78
+ * `~ A B C D F H I O Z R M m`, all outside this class.
79
+ * 2. CPR `ESC[12;40R` and DECXCPR `ESC[?12;40R` — TWO params, not three. (An earlier
80
+ * three-param pattern would have missed both.)
81
+ * 3. The kitty-keyboard query answer, for a fork that grows one. Its literal `?` is what
82
+ * keeps it off real kitty-ENCODED keystrokes, which carry no `?`.
83
+ * 4. Focus in/out `ESC[I` / `ESC[O`. Stripped deliberately: a focus report cannot change
84
+ * composer CONTENT, whereas a click that could carries its own mouse report — which is
85
+ * preserved (see below). So this costs no coverage and stops an alt-tab holding mail.
86
+ * 5. DECRQSS `ESC P 1 $ r … ESC \` (`requestStatusString`).
87
+ * 6. XTVERSION `ESC P > | … ESC \` — pre-emptive; stock 5.5.0 never answers it.
88
+ * 7. OSC colour reports `ESC ] 10;rgb:…` / `ESC ] 4;12;rgb:…`, terminated by ST or BEL.
89
+ *
90
+ * NOT stripped, on purpose:
91
+ *
92
+ * - **Mouse reports** (`ESC[<0;10;5M`, `ESC[<0;10;5m`, and the X10 `ESC[M` + 3 bytes). These
93
+ * are built from a DOM event by the mouse ENCODERS and pushed through the generic data
94
+ * path — they are a human ACTION that can change the composer (a click moves the cursor,
95
+ * a middle-click pastes, a drag selects), not a parser answer. Stripping them would
96
+ * re-open the race for every mouse-driven TUI. Motion tracking can be chatty, but that
97
+ * only holds delivery while the mouse is actually moving, and it clears one settle after
98
+ * it stops — the fail-safe direction.
99
+ * - **Bracketed paste markers** (`ESC[200~` / `ESC[201~`). A paste is human input.
100
+ *
101
+ * Content-blind by construction: a reply-SHAPED byte run inside a bracketed paste is stripped
102
+ * from the signal while the surrounding pasted text still counts. Intended (over-strip is the
103
+ * safe direction) and pinned by a test.
104
+ */
105
+ const TERMINAL_REPLY_PATTERNS = [
106
+ /\x1b\[[?>=]?[0-9;]*\$?[cnty]/g,
107
+ /\x1b\[\??[0-9]+;[0-9]+R/g,
108
+ /\x1b\[\?[0-9;]*u/g,
109
+ /\x1b\[[IO]/g,
110
+ /\x1bP[0-9]\$r[^\x1b]*\x1b\\/g,
111
+ /\x1bP>\|[^\x1b]*\x1b\\/g,
112
+ /\x1b\][0-9;]+;rgb:[0-9a-fA-F/]+(?:\x07|\x1b\\)/g,
113
+ ];
114
+ /**
115
+ * Every recognized terminal reply in `data`, in the order the patterns match them.
116
+ *
117
+ * Diagnostics only — {@link stripTerminalReplies} does the real work and never calls this. It
118
+ * backs the `AF_LOG_INPUT_SIGNAL` trace, whose whole purpose is to let a human SEE which bytes
119
+ * were treated as a reply and which survived as input, rather than infer it from a hold that
120
+ * did or did not happen.
121
+ */
122
+ export function terminalReplyMatches(data) {
123
+ const found = [];
124
+ for (const pattern of TERMINAL_REPLY_PATTERNS) {
125
+ for (const m of data.matchAll(pattern))
126
+ found.push(m[0]);
127
+ }
128
+ return found;
129
+ }
130
+ /**
131
+ * Render a byte run so it can be read in a log line: ESC as `\e`, other C0 controls and DEL as
132
+ * `\xNN`, everything else literal.
133
+ *
134
+ * Printing raw ESC to a terminal log would make the log itself move the cursor and repaint —
135
+ * the diagnostic would corrupt the thing it is diagnosing, and the reply bytes under
136
+ * investigation would be the ones that vanished.
137
+ */
138
+ export function escapeBytes(data) {
139
+ let out = '';
140
+ for (const ch of data) {
141
+ const code = ch.codePointAt(0) ?? 0;
142
+ if (ch === '\x1b')
143
+ out += '\\e';
144
+ else if (code < 0x20 || code === 0x7f)
145
+ out += `\\x${code.toString(16).padStart(2, '0')}`;
146
+ else
147
+ out += ch;
148
+ }
149
+ return out;
150
+ }
151
+ /**
152
+ * The human-input residue of one upstream chunk: `data` with every recognized terminal reply
153
+ * removed. Returns `''` when the chunk was nothing but replies — the caller reads that as "no
154
+ * human input here" and leaves the gate's input signal untouched.
155
+ *
156
+ * Pure and allocation-cheap; called on every keystroke, so the common case (plain text with
157
+ * no ESC at all) short-circuits before touching a regex.
158
+ *
159
+ * The short-circuit does NOT cover a bracketed paste: `ESC[200~ … ESC[201~` always contains
160
+ * ESC, so a pasted body — up to the 1 MiB a `/write` request body may carry — runs every
161
+ * pattern's `replace` over its full length. This is an EXTERNAL paste only (a human pasting
162
+ * into the browser terminal, or a caller POSTing one to `/api/terminals/:id/write`); a mail
163
+ * delivery's own bracketed paste (Issue #1567) is written with origin `'delivery'` and never
164
+ * reaches this function at all, so no long send pays the scan. Left as is deliberately: an
165
+ * external paste is a rare, human-paced event next to the per-keystroke path this function is
166
+ * tuned for, and any length guard cheap enough to be worth adding would have to decide where a
167
+ * reply may legitimately sit inside the payload — which is the over-strip direction, the
168
+ * silent one.
169
+ */
170
+ export function stripTerminalReplies(data) {
171
+ if (!data.includes('\x1b'))
172
+ return data;
173
+ let out = data;
174
+ for (const pattern of TERMINAL_REPLY_PATTERNS) {
175
+ out = out.replace(pattern, '');
176
+ if (out === '')
177
+ return '';
178
+ }
179
+ return out;
180
+ }
181
+ //# sourceMappingURL=terminal-replies.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-replies.js","sourceRoot":"","sources":["../../src/terminal/terminal-replies.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwDG;AAEH;;;;GAIG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,OAAO,CAAC;AAEjD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AACH,MAAM,uBAAuB,GAAsB;IACjD,+BAA+B;IAC/B,0BAA0B;IAC1B,mBAAmB;IACnB,aAAa;IACb,8BAA8B;IAC9B,yBAAyB;IACzB,iDAAiD;CAClD,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,uBAAuB,EAAE,CAAC;QAC9C,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;QACtB,MAAM,IAAI,GAAG,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,EAAE,KAAK,MAAM;YAAE,GAAG,IAAI,KAAK,CAAC;aAC3B,IAAI,IAAI,GAAG,IAAI,IAAI,IAAI,KAAK,IAAI;YAAE,GAAG,IAAI,MAAM,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,CAAC;;YACpF,GAAG,IAAI,EAAE,CAAC;IACjB,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,UAAU,oBAAoB,CAAC,IAAY;IAC/C,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,IAAI,GAAG,GAAG,IAAI,CAAC;IACf,KAAK,MAAM,OAAO,IAAI,uBAAuB,EAAE,CAAC;QAC9C,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;QAC/B,IAAI,GAAG,KAAK,EAAE;YAAE,OAAO,EAAE,CAAC;IAC5B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cluesmith/codev",
3
- "version": "3.3.3",
3
+ "version": "3.3.4",
4
4
  "description": "Codev CLI - AI-assisted software development framework",
5
5
  "type": "module",
6
6
  "bin": {
@@ -35,9 +35,9 @@
35
35
  "open": "^10.1.0",
36
36
  "tree-kill": "^1.2.2",
37
37
  "ws": "^8.18.0",
38
- "@cluesmith/codev-core": "3.3.3",
39
- "@cluesmith/codev-sdk": "3.3.3",
40
- "@cluesmith/codev-types": "3.3.3"
38
+ "@cluesmith/codev-core": "3.3.4",
39
+ "@cluesmith/codev-types": "3.3.4",
40
+ "@cluesmith/codev-sdk": "3.3.4"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@playwright/test": "^1.58.0",
@@ -7,5 +7,9 @@
7
7
  # as objects (users carry `login`; teams carry `slug`/`name` and no `login`), so
8
8
  # `.login // empty` keeps user reviewers and drops team reviewers — matching the
9
9
  # `reviewRequests: string[]` contract in PrListItem (forge-contracts.ts).
10
- exec gh pr list --json number,title,url,reviewDecision,body,createdAt,author,reviewRequests,isDraft \
11
- | jq '[.[] | .reviewRequests = [.reviewRequests[].login // empty]]'
10
+ #
11
+ # Not `gh … | jq`: POSIX sh has no pipefail, so the caller would see jq's exit
12
+ # status and a rate-limited gh would look like a successful empty list (#1645).
13
+ # forge-executable: gh
14
+ out="$(gh pr list --json number,title,url,reviewDecision,body,createdAt,author,reviewRequests,isDraft)" || exit 1
15
+ printf '%s' "$out" | jq '[.[] | .reviewRequests = [.reviewRequests[].login // empty]]'
@@ -1,7 +1,9 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: pr-search (GitHub via gh CLI)
3
3
  # Input: CODEV_SEARCH_QUERY
4
- # Output: JSON [{number, headRefName, baseRefName}]
4
+ # Output: JSON [{number, headRefName, baseRefName, state}]
5
5
  # --state all is required so the search includes merged/closed PRs; without it
6
6
  # `gh pr list` defaults to --state open and post-merge lookups return nothing (#759).
7
- exec gh pr list --state all --search "$CODEV_SEARCH_QUERY" --json number,headRefName,baseRefName
7
+ # `state` (OPEN/MERGED/CLOSED) lets consumers that only want open PRs filter
8
+ # client-side — the spawn collision guard must ignore merged/closed ones (#1637).
9
+ exec gh pr list --state all --search "$CODEV_SEARCH_QUERY" --json number,headRefName,baseRefName,state
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-search (GitLab via glab CLI)
3
+ # forge-executable: glab
3
4
  #
4
5
  # ⚠️ UNVERIFIED — mirrors gitlab/issue-list.sh (raw `glab ... --output json`,
5
6
  # no field normalization) plus a state flag and a `body` field surfaced from
@@ -1,5 +1,38 @@
1
1
  #!/bin/sh
2
- # Forge concept: pr-search (GitLab via glab CLI)
2
+ # Forge concept: pr-search (GitLab via glab CLI — merge requests)
3
+ # Output: JSON [{number, headRefName, baseRefName, state, ...glab fields}]
4
+ #
5
+ # ⚠️ UNVERIFIED — `glab` is not available in the authoring environment (#920);
6
+ # written against glab's documented `mr list --output json` shape (confirmed
7
+ # against the field mappings already documented in gitlab/pr-list.sh and
8
+ # gitlab/pr-exists.sh). Smoke-test before relying on it.
9
+ #
3
10
  # --all is required so the search includes merged/closed MRs; without it
4
11
  # `glab mr list` defaults to opened only and post-merge lookups return nothing (#759).
5
- exec glab mr list --all --search "$CODEV_SEARCH_QUERY" --output json
12
+ #
13
+ # Maps glab's GitLab-shaped fields into the forge-neutral PrSearchItem contract
14
+ # (forge-contracts.ts); `. + {…}` is non-destructive for keys we don't touch:
15
+ # iid -> number (the MR number glab mr view/merge expects)
16
+ # source_branch -> headRefName
17
+ # target_branch -> baseRefName
18
+ # Without this the spawn guard's message reads `PR #undefined` and consult's
19
+ # findPRForIssue can't resolve the MR's base branch on GitLab.
20
+ #
21
+ # glab reports state lowercase (opened/merged/closed/locked); normalize to the
22
+ # GitHub convention (OPEN/MERGED/CLOSED) so consumers filter with one comparison.
23
+ # The spawn collision guard keys off state === "OPEN" to ignore merged MRs (#1637).
24
+ # `locked` (a transient merging state) maps to OPEN too: the guard should err
25
+ # toward the recoverable --force prompt, never silently skip a live collision.
26
+ #
27
+ # Not `glab … | jq`: POSIX sh has no pipefail, so the caller would see jq's exit
28
+ # status and a failed glab would look like a successful empty list (#1645).
29
+ # The capture-then-pipe shape hides `glab` from extractExecutable's first-line
30
+ # heuristic (it lands on the `printf` builtin), so declare the backend explicitly
31
+ # — same as github/pr-list.sh (#1645).
32
+ # forge-executable: glab
33
+ out="$(glab mr list --all --search "$CODEV_SEARCH_QUERY" --output json)" || exit 1
34
+ printf '%s' "$out" | jq 'map(.
35
+ + {number: .iid}
36
+ + {headRefName: .source_branch}
37
+ + {baseRefName: .target_branch}
38
+ + {state: ((.state // "") | ascii_downcase as $s | if $s == "opened" or $s == "locked" then "OPEN" else ($s | ascii_upcase) end)})'
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: auth-status (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Output: exit code (0 = authenticated)
4
5
  set -e
5
6
 
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-comment (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_ISSUE_ID, CODEV_COMMENT_BODY
4
5
  # Output: exit code only
5
6
  set -e
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-list (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_LINEAR_TEAM (team key, e.g. "ENG")
4
5
  # Output: JSON [{number, title, url, labels, createdAt, author, assignees}]
5
6
  set -e
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-search (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  #
4
5
  # ⚠️ UNVERIFIED — mirrors linear/issue-list.sh with `description` added to the
5
6
  # query (mapped to `body`) and the state filter parameterized. No Linear
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: issue-view (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_ISSUE_ID (e.g. "ENG-123")
4
5
  # Output: JSON {title, body, state, url, author, createdAt, assignees, labels, milestone, comments[]}
5
6
  #
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: recently-closed (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Input: CODEV_LINEAR_TEAM, CODEV_SINCE_DATE (optional, ISO date)
4
5
  # Output: JSON [{number, title, url, labels, createdAt, closedAt}]
5
6
  set -e
@@ -1,5 +1,6 @@
1
1
  #!/bin/sh
2
2
  # Forge concept: user-identity (Linear via GraphQL API)
3
+ # forge-executable: curl
3
4
  # Output: plain text display name
4
5
  set -e
5
6
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generate-image
3
- description: AI image generation via Gemini, or Atlas Cloud with `-p atlas`. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY, or ATLASCLOUD_API_KEY when using `-p atlas`.
3
+ description: AI image generation via Gemini with optional async providers. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires the API key for the selected provider.
4
4
  ---
5
5
 
6
6
  # generate-image - AI Image Generation
@@ -22,7 +22,7 @@ Note: this is a `codev` subcommand, not standalone.
22
22
  -r, --resolution <res> Resolution: 1K, 2K, 4K (default: 1K)
23
23
  -a, --aspect <ratio> Aspect ratio (default: 1:1)
24
24
  --ref <image> Reference image (repeatable, max 14)
25
- -p, --provider <name> Provider: gemini (default) or atlas
25
+ -p, --provider <name> Provider name (default: gemini)
26
26
  ```
27
27
 
28
28
  ## Aspect ratios
@@ -64,3 +64,20 @@ Measured differences on that path:
64
64
  than silently ignoring the images.
65
65
  - The model returns JPEG, so the output file is named after its actual bytes
66
66
  instead of writing JPEG into the default `output.png`.
67
+
68
+ ## MuAPI provider
69
+
70
+ `-p muapi` uses MuAPI's Nano Banana Pro endpoint with the same aspect-ratio and
71
+ resolution controls. Set `MUAPI_API_KEY` (or `MU_API_KEY`) using the [MuAPI
72
+ access keys page](https://muapi.ai/access-keys). Reference images are uploaded
73
+ to MuAPI and sent to the edit endpoint; returned media is downloaded only over
74
+ HTTPS.
75
+
76
+ ```bash
77
+ codev generate-image "A sunset over mountains" -p muapi -a 16:9
78
+ codev generate-image "Keep the subject, change the lighting" -p muapi --ref subject.png
79
+ ```
80
+
81
+ The provider submits a job, polls the [MuAPI image API](https://muapi.ai/ai-image-api),
82
+ and writes the returned image locally. Polling and individual requests are
83
+ bounded, and the API key is never sent to the returned media host.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: generate-image
3
- description: AI image generation via Gemini, or Atlas Cloud with `-p atlas`. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires GEMINI_API_KEY or GOOGLE_API_KEY, or ATLASCLOUD_API_KEY when using `-p atlas`.
3
+ description: AI image generation via Gemini with optional async providers. Use when the user wants to generate, create, or make an image, or when you need to create visual assets like logos, diagrams, or illustrations. Requires the API key for the selected provider.
4
4
  ---
5
5
 
6
6
  # generate-image - AI Image Generation
@@ -22,7 +22,7 @@ Note: this is a `codev` subcommand, not standalone.
22
22
  -r, --resolution <res> Resolution: 1K, 2K, 4K (default: 1K)
23
23
  -a, --aspect <ratio> Aspect ratio (default: 1:1)
24
24
  --ref <image> Reference image (repeatable, max 14)
25
- -p, --provider <name> Provider: gemini (default) or atlas
25
+ -p, --provider <name> Provider name (default: gemini)
26
26
  ```
27
27
 
28
28
  ## Aspect ratios
@@ -64,3 +64,20 @@ Measured differences on that path:
64
64
  than silently ignoring the images.
65
65
  - The model returns JPEG, so the output file is named after its actual bytes
66
66
  instead of writing JPEG into the default `output.png`.
67
+
68
+ ## MuAPI provider
69
+
70
+ `-p muapi` uses MuAPI's Nano Banana Pro endpoint with the same aspect-ratio and
71
+ resolution controls. Set `MUAPI_API_KEY` (or `MU_API_KEY`) using the [MuAPI
72
+ access keys page](https://muapi.ai/access-keys). Reference images are uploaded
73
+ to MuAPI and sent to the edit endpoint; returned media is downloaded only over
74
+ HTTPS.
75
+
76
+ ```bash
77
+ codev generate-image "A sunset over mountains" -p muapi -a 16:9
78
+ codev generate-image "Keep the subject, change the lighting" -p muapi --ref subject.png
79
+ ```
80
+
81
+ The provider submits a job, polls the [MuAPI image API](https://muapi.ai/ai-image-api),
82
+ and writes the returned image locally. Polling and individual requests are
83
+ bounded, and the API key is never sent to the returned media host.