@vibe-commit/capture 0.6.1 → 0.8.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.
Files changed (172) hide show
  1. package/README.md +191 -9
  2. package/dist/account_not_here.d.ts +15 -0
  3. package/dist/account_not_here.js +25 -0
  4. package/dist/account_not_here.js.map +1 -0
  5. package/dist/agent_hooks.d.ts +8 -0
  6. package/dist/agent_hooks.js +31 -1
  7. package/dist/agent_hooks.js.map +1 -1
  8. package/dist/agents/claude_code.d.ts +34 -0
  9. package/dist/agents/claude_code.js +141 -8
  10. package/dist/agents/claude_code.js.map +1 -1
  11. package/dist/agents/codex_cli.d.ts +13 -0
  12. package/dist/agents/codex_cli.js +241 -2
  13. package/dist/agents/codex_cli.js.map +1 -1
  14. package/dist/agents/cursor.d.ts +34 -0
  15. package/dist/agents/cursor.js +115 -1
  16. package/dist/agents/cursor.js.map +1 -1
  17. package/dist/agents/session_listing.d.ts +29 -0
  18. package/dist/agents/session_listing.js +49 -0
  19. package/dist/agents/session_listing.js.map +1 -0
  20. package/dist/agents/types.d.ts +118 -8
  21. package/dist/agents/types.js +17 -3
  22. package/dist/agents/types.js.map +1 -1
  23. package/dist/commands/approval_pending.d.ts +9 -0
  24. package/dist/commands/approval_pending.js +23 -0
  25. package/dist/commands/approval_pending.js.map +1 -0
  26. package/dist/commands/auth.d.ts +7 -0
  27. package/dist/commands/auth.js +52 -11
  28. package/dist/commands/auth.js.map +1 -1
  29. package/dist/commands/backfill.d.ts +160 -0
  30. package/dist/commands/backfill.js +748 -0
  31. package/dist/commands/backfill.js.map +1 -0
  32. package/dist/commands/browser_credential.d.ts +6 -2
  33. package/dist/commands/browser_credential.js +34 -14
  34. package/dist/commands/browser_credential.js.map +1 -1
  35. package/dist/commands/connect.d.ts +146 -0
  36. package/dist/commands/connect.js +216 -117
  37. package/dist/commands/connect.js.map +1 -1
  38. package/dist/commands/connect_account.d.ts +135 -0
  39. package/dist/commands/connect_account.js +321 -0
  40. package/dist/commands/connect_account.js.map +1 -0
  41. package/dist/commands/connect_picker.d.ts +27 -0
  42. package/dist/commands/connect_picker.js +549 -0
  43. package/dist/commands/connect_picker.js.map +1 -0
  44. package/dist/commands/context.d.ts +16 -0
  45. package/dist/commands/context.js +27 -0
  46. package/dist/commands/context.js.map +1 -1
  47. package/dist/commands/credential_problem.d.ts +15 -4
  48. package/dist/commands/credential_problem.js +29 -22
  49. package/dist/commands/credential_problem.js.map +1 -1
  50. package/dist/commands/device_credential.d.ts +5 -1
  51. package/dist/commands/device_credential.js +17 -14
  52. package/dist/commands/device_credential.js.map +1 -1
  53. package/dist/commands/finalize.d.ts +95 -0
  54. package/dist/commands/finalize.js +249 -74
  55. package/dist/commands/finalize.js.map +1 -1
  56. package/dist/commands/finish_mint.d.ts +30 -6
  57. package/dist/commands/finish_mint.js +26 -6
  58. package/dist/commands/finish_mint.js.map +1 -1
  59. package/dist/commands/report.js +38 -30
  60. package/dist/commands/report.js.map +1 -1
  61. package/dist/commands/scan.d.ts +162 -0
  62. package/dist/commands/scan.js +507 -0
  63. package/dist/commands/scan.js.map +1 -0
  64. package/dist/commands/status.d.ts +19 -1
  65. package/dist/commands/status.js +232 -26
  66. package/dist/commands/status.js.map +1 -1
  67. package/dist/commands/status_hooks.d.ts +14 -0
  68. package/dist/commands/status_hooks.js +81 -0
  69. package/dist/commands/status_hooks.js.map +1 -0
  70. package/dist/commands/status_machine.d.ts +27 -0
  71. package/dist/commands/status_machine.js +210 -0
  72. package/dist/commands/status_machine.js.map +1 -0
  73. package/dist/commands/sync_reason.d.ts +17 -0
  74. package/dist/commands/sync_reason.js +25 -0
  75. package/dist/commands/sync_reason.js.map +1 -0
  76. package/dist/commands/why.js +44 -31
  77. package/dist/commands/why.js.map +1 -1
  78. package/dist/consent.d.ts +217 -0
  79. package/dist/consent.js +400 -1
  80. package/dist/consent.js.map +1 -1
  81. package/dist/copy/error_codes.d.ts +222 -0
  82. package/dist/copy/error_codes.js +105 -0
  83. package/dist/copy/error_codes.js.map +1 -0
  84. package/dist/copy/index.d.ts +2 -2
  85. package/dist/copy/index.js +2 -2
  86. package/dist/copy/index.js.map +1 -1
  87. package/dist/copy/strings.d.ts +617 -30
  88. package/dist/copy/strings.js +741 -36
  89. package/dist/copy/strings.js.map +1 -1
  90. package/dist/copy/time.d.ts +11 -0
  91. package/dist/copy/time.js +18 -0
  92. package/dist/copy/time.js.map +1 -1
  93. package/dist/credential.d.ts +5 -1
  94. package/dist/credential.js +2 -2
  95. package/dist/credential.js.map +1 -1
  96. package/dist/git.d.ts +43 -0
  97. package/dist/git.js +95 -0
  98. package/dist/git.js.map +1 -1
  99. package/dist/grants_sync.d.ts +165 -0
  100. package/dist/grants_sync.js +289 -0
  101. package/dist/grants_sync.js.map +1 -0
  102. package/dist/grants_verb.d.ts +70 -0
  103. package/dist/grants_verb.js +121 -0
  104. package/dist/grants_verb.js.map +1 -0
  105. package/dist/hooks/entry.d.ts +18 -1
  106. package/dist/hooks/entry.js +117 -14
  107. package/dist/hooks/entry.js.map +1 -1
  108. package/dist/hooks/post_commit.js +3 -3
  109. package/dist/hooks/post_commit.js.map +1 -1
  110. package/dist/hooks/post_rewrite.js +6 -4
  111. package/dist/hooks/post_rewrite.js.map +1 -1
  112. package/dist/hooks/session_grants.d.ts +59 -0
  113. package/dist/hooks/session_grants.js +91 -0
  114. package/dist/hooks/session_grants.js.map +1 -0
  115. package/dist/hooks/session_start.d.ts +114 -0
  116. package/dist/hooks/session_start.js +243 -0
  117. package/dist/hooks/session_start.js.map +1 -0
  118. package/dist/index.d.ts +1 -1
  119. package/dist/index.js +76 -11
  120. package/dist/index.js.map +1 -1
  121. package/dist/install.d.ts +57 -0
  122. package/dist/install.js +54 -20
  123. package/dist/install.js.map +1 -1
  124. package/dist/json.d.ts +32 -9
  125. package/dist/json.js +35 -8
  126. package/dist/json.js.map +1 -1
  127. package/dist/machine_state.d.ts +52 -0
  128. package/dist/machine_state.js +63 -0
  129. package/dist/machine_state.js.map +1 -0
  130. package/dist/oauth/retire.d.ts +10 -0
  131. package/dist/oauth/retire.js +13 -0
  132. package/dist/oauth/retire.js.map +1 -1
  133. package/dist/oauth/session.d.ts +1 -0
  134. package/dist/org_pending.d.ts +52 -0
  135. package/dist/org_pending.js +109 -0
  136. package/dist/org_pending.js.map +1 -0
  137. package/dist/paths.d.ts +70 -0
  138. package/dist/paths.js +90 -0
  139. package/dist/paths.js.map +1 -1
  140. package/dist/pending_revoke.d.ts +117 -0
  141. package/dist/pending_revoke.js +233 -0
  142. package/dist/pending_revoke.js.map +1 -0
  143. package/dist/pending_revoke_file.d.ts +55 -0
  144. package/dist/pending_revoke_file.js +74 -0
  145. package/dist/pending_revoke_file.js.map +1 -0
  146. package/dist/picker.d.ts +208 -0
  147. package/dist/picker.js +526 -0
  148. package/dist/picker.js.map +1 -0
  149. package/dist/picker_checklist.d.ts +141 -0
  150. package/dist/picker_checklist.js +411 -0
  151. package/dist/picker_checklist.js.map +1 -0
  152. package/dist/policy.d.ts +70 -1
  153. package/dist/policy.js +68 -2
  154. package/dist/policy.js.map +1 -1
  155. package/dist/post.d.ts +91 -18
  156. package/dist/post.js +259 -27
  157. package/dist/post.js.map +1 -1
  158. package/dist/state.d.ts +6 -0
  159. package/dist/state.js +38 -1
  160. package/dist/state.js.map +1 -1
  161. package/dist/system_message.d.ts +32 -2
  162. package/dist/system_message.js +47 -5
  163. package/dist/system_message.js.map +1 -1
  164. package/dist/term.d.ts +28 -2
  165. package/dist/term.js +23 -2
  166. package/dist/term.js.map +1 -1
  167. package/dist/verb_help.d.ts +13 -0
  168. package/dist/verb_help.js +40 -0
  169. package/dist/verb_help.js.map +1 -0
  170. package/dist/version.d.ts +1 -1
  171. package/dist/version.js +1 -1
  172. package/package.json +5 -9
@@ -17,7 +17,9 @@
17
17
  * ⚠ **What is DATA here and what is MEASURED is not the same set**, and the
18
18
  * difference is marked on each member below rather than left to a reader.
19
19
  */
20
+ import { join } from "node:path";
20
21
  import { cursorHooksPath, cursorTranscriptRoot, isCursorTranscript } from "../paths.js";
22
+ import { readEntries, sessionFile, subdirectories } from "./session_listing.js";
21
23
  /**
22
24
  * ⭐ **MEASURED. `D5`'s SECOND HALF IS ANSWERED (`TODOS[130]`), AND THE ANSWER IS
23
25
  * THAT CURSOR IMPOSES NO NUMBER OF ITS OWN.**
@@ -79,6 +81,97 @@ import { cursorHooksPath, cursorTranscriptRoot, isCursorTranscript } from "../pa
79
81
  */
80
82
  const CURSOR_REGISTERED_MS = 3_000;
81
83
  const CURSOR_CLIENT_MS = 2_200;
84
+ /**
85
+ * The longest folder name Cursor writes verbatim.
86
+ *
87
+ * ⛔ **MEASURED (Open Question 3, VS, 2026-10-24), with `cursor-agent`, which
88
+ * creates the project folder even when it is not logged in.** A name up to 61
89
+ * characters is kept; a longer one becomes its first 53 characters, `-`, and 7
90
+ * hex characters (the hash varied with the input, so it is not a constant).
91
+ */
92
+ export const CURSOR_NAME_LIMIT = 61;
93
+ /**
94
+ * The folder name Cursor gives a workspace path — Open Question 3.
95
+ *
96
+ * ⛔ **THIS IS NOT CLAUDE CODE'S RULE, and the difference is the measurement.**
97
+ * Every RUN of non-alphanumerics, a literal `--` included, collapses to ONE `-`;
98
+ * leading and trailing separators are dropped (so `/tmp/q/trail.` is `…-trail`
99
+ * and there is no leading `-`); case is kept. An emoji and `é'` each gave a single
100
+ * dash where Claude Code gives two.
101
+ *
102
+ * ⚠ **Which makes Cursor's names collide far more:** `a-b`, `a.b`, `a--b` and
103
+ * `a.b.` are all one folder. The scan's walk encodes every directory entry with
104
+ * THIS function and matches on the result, so the collapsing applies to every
105
+ * candidate rather than being assumed away.
106
+ */
107
+ export function encodeProjectPath(path) {
108
+ return path.replace(/[^A-Za-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
109
+ }
110
+ /**
111
+ * Is this folder name one Cursor cut and hashed? See `CURSOR_NAME_LIMIT`.
112
+ *
113
+ * ⚠ **A hashed name is always exactly 61 long, and so is a verbatim name that
114
+ * happens to be 61 long** — length alone cannot tell them apart. The test is the
115
+ * hash's shape too (`-` and 7 hex). A verbatim 61-character name that ends the
116
+ * same way is read as hashed, which is the safe error: it is skipped and
117
+ * counted, never matched to a path it might not be.
118
+ */
119
+ export function isHashedProjectName(name) {
120
+ return name.length === CURSOR_NAME_LIMIT && /-[0-9a-f]{7}$/.test(name);
121
+ }
122
+ /**
123
+ * The folder Cursor uses for a window with no workspace open. It is not a path,
124
+ * so it can never resolve to one and is not worth a "folder no longer exists"
125
+ * count. Seen by NAME on the author's machine; nothing in it was read.
126
+ */
127
+ const CURSOR_NO_WORKSPACE = "empty-window";
128
+ /**
129
+ * Cursor's sessions, by folder — `D11 · A`, `scan`.
130
+ *
131
+ * `<root>/<encoded workspace>/agent-transcripts/<cid>/<cid>.jsonl`. Only the
132
+ * conversation's own file counts: `<cid>/subagents/…` is delegated work that
133
+ * belongs to the parent (`delegatedTranscripts: "announce"`). A flat
134
+ * `agent-transcripts/<cid>.jsonl` is accepted too, because `isCursorTranscript`
135
+ * admits any `.jsonl` at that depth and a count that disagreed with the gate
136
+ * would be a count of nothing.
137
+ *
138
+ * ⛔ Names and times only; see `claudeSessionSource` for the rule and its reason.
139
+ */
140
+ function cursorSessionSource(home) {
141
+ const root = cursorTranscriptRoot(home);
142
+ const folders = [];
143
+ try {
144
+ for (const folder of subdirectories(root)) {
145
+ if (folder.name === CURSOR_NO_WORKSPACE)
146
+ continue;
147
+ const transcripts = join(folder.path, "agent-transcripts");
148
+ const sessions = [];
149
+ for (const entry of readEntries(transcripts) ?? []) {
150
+ const file = entry.isDirectory()
151
+ ? join(transcripts, entry.name, `${entry.name}.jsonl`)
152
+ : entry.name.endsWith(".jsonl")
153
+ ? join(transcripts, entry.name)
154
+ : null;
155
+ const session = file === null ? null : sessionFile(file);
156
+ if (session !== null)
157
+ sessions.push(session);
158
+ }
159
+ if (sessions.length > 0)
160
+ folders.push({ name: folder.name, sessions });
161
+ }
162
+ }
163
+ catch {
164
+ return { kind: "unreadable", reason: "unreadable" };
165
+ }
166
+ // Cursor keeps no record of the folders it has run in, so there is no second signal.
167
+ return {
168
+ kind: "folders",
169
+ folders,
170
+ encode: encodeProjectPath,
171
+ isHashed: isHashedProjectName,
172
+ recorded: [],
173
+ };
174
+ }
82
175
  export const CURSOR = {
83
176
  id: "cursor",
84
177
  transport: "ndjson",
@@ -103,7 +196,22 @@ export const CURSOR = {
103
196
  // ⛔ Against the client's `SessionEnd`, `sessionEnd` matched NOTHING — not an
104
197
  // error, a `false` — so a Cursor session took no settle on its final turn and
105
198
  // lost it permanently. That is the whole of the bug.
106
- eventNames: { Stop: "stop", PreCompact: "preCompact", SessionEnd: "sessionEnd" },
199
+ //
200
+ // ⛔ **`sessionStart` is STATIC, NOT OBSERVED** (Open Question 1, 2026-10-24).
201
+ // `cursor-agent` was not logged in, so no session ran; the spelling and the
202
+ // payload (`conversation_id`, `generation_id`, `model`, `is_background_agent`,
203
+ // `composer_mode`) come from the CLI bundle 2026.08.11-e8db854, which runs it
204
+ // for a NEW chat before the first turn. It reads `env` and `additional_context`
205
+ // from the output — and `additional_context` goes into the agent's context and
206
+ // is NOT shown to the user. There is no user-visible field, so this client
207
+ // never uses it to carry a notice: Cursor users learn the state from `status`,
208
+ // and the first-`Stop` install is kept as the fallback for exactly this reason.
209
+ eventNames: {
210
+ Stop: "stop",
211
+ PreCompact: "preCompact",
212
+ SessionEnd: "sessionEnd",
213
+ SessionStart: "sessionStart",
214
+ },
107
215
  // ⛔ `~/.cursor/hooks.json`, and the `cursor` SHAPE — the two are a pair.
108
216
  // Read out of the shipped bundle's validator: a top-level numeric `version`
109
217
  // is REQUIRED, entries are a FLAT array of hook scripts each needing a
@@ -125,6 +233,11 @@ export const CURSOR = {
125
233
  registeredTimeoutMs: CURSOR_REGISTERED_MS,
126
234
  clientBudgetMs: CURSOR_CLIENT_MS,
127
235
  },
236
+ // gstack-shortcut(dec-e650afbf): `Stop`'s clocks, no fast path (eng review D7 · B).
237
+ SessionStart: {
238
+ registeredTimeoutMs: CURSOR_REGISTERED_MS, // gstack-shortcut(dec-e650afbf)
239
+ clientBudgetMs: CURSOR_CLIENT_MS, // gstack-shortcut(dec-e650afbf)
240
+ },
128
241
  },
129
242
  // ⛔ DECLARED EXPLICITLY, and it is load-bearing now in a way it was not
130
243
  // before (D177 §2, §9). `subagentsDir()` would compute
@@ -142,6 +255,7 @@ export const CURSOR = {
142
255
  // measurement, not as a fact about the agent. Measuring it is a change to this
143
256
  // line and nothing else.
144
257
  sessionIdFromEnv: () => null,
258
+ sessionSource: (home) => cursorSessionSource(home),
145
259
  // ⛔ NO `locateCurrentTranscript`, and the omission is the decision — the same
146
260
  // one Codex made. Finding the conversation for THIS project would mean munging
147
261
  // a workspace path this client is never told, or reading a `cwd` out of the
@@ -1 +1 @@
1
- {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAEH,OAAO,EAAE,eAAe,EAAE,oBAAoB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAGxF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA0DG;AACH,MAAM,oBAAoB,GAAG,KAAK,CAAC;AACnC,MAAM,gBAAgB,GAAG,KAAK,CAAC;AAE/B,MAAM,CAAC,MAAM,MAAM,GAAsB;IACvC,EAAE,EAAE,QAAQ;IACZ,SAAS,EAAE,QAAQ;IACnB,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,uDAAuD;IACvD,cAAc,EAAE,oBAAoB;IACpC,wEAAwE;IACxE,uEAAuE;IACvE,UAAU,EAAE,kBAAkB;IAC9B,8EAA8E;IAC9E,6EAA6E;IAC7E,4EAA4E;IAC5E,iEAAiE;IACjE,+EAA+E;IAC/E,mEAAmE;IACnE,EAAE;IACF,8EAA8E;IAC9E,4EAA4E;IAC5E,EAAE;IACF,6EAA6E;IAC7E,8EAA8E;IAC9E,qDAAqD;IACrD,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE;IAChF,yEAAyE;IACzE,4EAA4E;IAC5E,uEAAuE;IACvE,6EAA6E;IAC7E,2EAA2E;IAC3E,uDAAuD;IACvD,yEAAyE;IACzE,UAAU,EAAE,EAAE,IAAI,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,QAAQ,EAAE;IACtE,MAAM,EAAE;QACN,IAAI,EAAE;YACJ,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;QACD,UAAU,EAAE;YACV,mBAAmB,EAAE,oBAAoB;YACzC,cAAc,EAAE,gBAAgB;SACjC;KACF;IACD,wEAAwE;IACxE,uDAAuD;IACvD,+EAA+E;IAC/E,+EAA+E;IAC/E,8EAA8E;IAC9E,8EAA8E;IAC9E,+DAA+D;IAC/D,oBAAoB,EAAE,UAAU;IAChC,SAAS,EAAE,QAAQ;IACnB,+EAA+E;IAC/E,6EAA6E;IAC7E,6EAA6E;IAC7E,6EAA6E;IAC7E,+EAA+E;IAC/E,yBAAyB;IACzB,gBAAgB,EAAE,GAAG,EAAE,CAAC,IAAI;IAC5B,8EAA8E;IAC9E,+EAA+E;IAC/E,4EAA4E;IAC5E,6EAA6E;IAC7E,0CAA0C;CAC3C,CAAC"}
1
+ {"version":3,"file":"cursor.js","sourceRoot":"","sources":["../../src/agents/cursor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Listing a folder-keyed agent's sessions — `D11 · A`, `scan`.
3
+ *
4
+ * The two folder-keyed dialects (Claude Code, Cursor) differ in how a path is
5
+ * encoded and where a session sits under the folder, and agree on everything
6
+ * this file does: read directory ENTRIES, `lstat` a candidate for its time, and
7
+ * never open it.
8
+ *
9
+ * ⛔ **`lstat`, REGULAR FILES ONLY.** A symlink named `x.jsonl` is not a session
10
+ * (Cursor's gate resolves for exactly that reason), and a FIFO or socket under a
11
+ * root this client does not own is a thing a later `readFileSync` hangs on or
12
+ * throws on. A listing that only ever `lstat`s cannot be made to block, which is
13
+ * the property `scan` needs from the one command that walks the whole root.
14
+ */
15
+ import { type Dirent } from "node:fs";
16
+ import type { SourceSession } from "./types.js";
17
+ /**
18
+ * The entries of a directory, `null` when it is simply not there, and a THROW
19
+ * for anything else (permissions, I/O) — the caller turns a throw into the
20
+ * agent's `unreadable` answer, so an absent store and a broken one stay apart.
21
+ */
22
+ export declare function readEntries(dir: string): Dirent[] | null;
23
+ /** One regular file's times, or null for anything that is not one. Never opens it. */
24
+ export declare function sessionFile(path: string): SourceSession | null;
25
+ /** `<dir>/<name>` for each entry that is a real directory (a symlink is not one). */
26
+ export declare function subdirectories(dir: string): {
27
+ name: string;
28
+ path: string;
29
+ }[];
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Listing a folder-keyed agent's sessions — `D11 · A`, `scan`.
3
+ *
4
+ * The two folder-keyed dialects (Claude Code, Cursor) differ in how a path is
5
+ * encoded and where a session sits under the folder, and agree on everything
6
+ * this file does: read directory ENTRIES, `lstat` a candidate for its time, and
7
+ * never open it.
8
+ *
9
+ * ⛔ **`lstat`, REGULAR FILES ONLY.** A symlink named `x.jsonl` is not a session
10
+ * (Cursor's gate resolves for exactly that reason), and a FIFO or socket under a
11
+ * root this client does not own is a thing a later `readFileSync` hangs on or
12
+ * throws on. A listing that only ever `lstat`s cannot be made to block, which is
13
+ * the property `scan` needs from the one command that walks the whole root.
14
+ */
15
+ import { lstatSync, readdirSync } from "node:fs";
16
+ import { join } from "node:path";
17
+ /**
18
+ * The entries of a directory, `null` when it is simply not there, and a THROW
19
+ * for anything else (permissions, I/O) — the caller turns a throw into the
20
+ * agent's `unreadable` answer, so an absent store and a broken one stay apart.
21
+ */
22
+ export function readEntries(dir) {
23
+ try {
24
+ return readdirSync(dir, { withFileTypes: true });
25
+ }
26
+ catch (err) {
27
+ const code = err.code;
28
+ if (code === "ENOENT" || code === "ENOTDIR")
29
+ return null;
30
+ throw err;
31
+ }
32
+ }
33
+ /** One regular file's times, or null for anything that is not one. Never opens it. */
34
+ export function sessionFile(path) {
35
+ try {
36
+ const stat = lstatSync(path);
37
+ return stat.isFile() ? { path, mtimeMs: stat.mtimeMs } : null;
38
+ }
39
+ catch {
40
+ return null;
41
+ }
42
+ }
43
+ /** `<dir>/<name>` for each entry that is a real directory (a symlink is not one). */
44
+ export function subdirectories(dir) {
45
+ return (readEntries(dir) ?? [])
46
+ .filter((entry) => entry.isDirectory())
47
+ .map((entry) => ({ name: entry.name, path: join(dir, entry.name) }));
48
+ }
49
+ //# sourceMappingURL=session_listing.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session_listing.js","sourceRoot":"","sources":["../../src/agents/session_listing.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,SAAS,EAAE,WAAW,EAAe,MAAM,SAAS,CAAC;AAC9D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAIjC;;;;GAIG;AACH,MAAM,UAAU,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,OAAO,WAAW,CAAC,GAAG,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,CAAC;IACnD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,IAAI,GAAI,GAA6B,CAAC,IAAI,CAAC;QACjD,IAAI,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QACzD,MAAM,GAAG,CAAC;IACZ,CAAC;AACH,CAAC;AAED,sFAAsF;AACtF,MAAM,UAAU,WAAW,CAAC,IAAY;IACtC,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;QAC7B,OAAO,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,qFAAqF;AACrF,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,CAAC;SAC5B,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;SACtC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC,CAAC;AACzE,CAAC"}
@@ -137,13 +137,27 @@ export type RedactionProfile = "claude-code" | "codex-cli" | "cursor";
137
137
  */
138
138
  export type DelegatedTranscripts = "discover" | "announce";
139
139
  /**
140
- * The three hook events DESIGN.md §13.7 names.
140
+ * The four hook events this client registers.
141
141
  *
142
- * Both members of this lane fire the same three; that is exactly why
142
+ * Every member of this lane fires the same four; that is exactly why
143
143
  * `hook_event_name` was REJECTED as an adapter selector (`CR-183`) — the events
144
144
  * are shared, so they discriminate nothing.
145
+ *
146
+ * ⛔ **`SessionStart` is the fourth, and it is the one that never uploads** (0.7.0,
147
+ * eng review D3 · A). `Stop`, `PreCompact` and `SessionEnd` flow into the code
148
+ * that reads a transcript and sends it; `SessionStart` must only install git
149
+ * hooks and say so, so `hooks/entry.ts` resolves the canonical event FIRST and
150
+ * branches to its own handler before the consent gate and before anything reads
151
+ * `transcript_path`. It sits in this list, and not in a table of its own, so the
152
+ * compiler makes each dialect declare its budget and its spelling.
153
+ *
154
+ * event Claude Code Codex Cursor reads a transcript
155
+ * Stop Stop Stop stop yes
156
+ * PreCompact PreCompact PreCompact preCompact yes
157
+ * SessionEnd SessionEnd SessionEnd sessionEnd yes
158
+ * SessionStart SessionStart SessionStart sessionStart NEVER
145
159
  */
146
- export declare const HOOK_EVENTS: readonly ["Stop", "PreCompact", "SessionEnd"];
160
+ export declare const HOOK_EVENTS: readonly ["Stop", "PreCompact", "SessionEnd", "SessionStart"];
147
161
  export type HookEventName = (typeof HOOK_EVENTS)[number];
148
162
  /**
149
163
  * One event's two clocks, and the relation between them is the point.
@@ -173,6 +187,77 @@ export interface AgentHookConfig {
173
187
  readonly path: (home: string, env: NodeJS.ProcessEnv) => string;
174
188
  readonly shape: HookDocShape;
175
189
  }
190
+ /**
191
+ * One transcript file a source adapter saw — `D11 · A`, `scan`.
192
+ *
193
+ * ⛔ **`stat`ed for its times and NEVER OPENED.** Everything the picker, the
194
+ * offer and the backfill count comes from names and times, because opening a
195
+ * transcript of a repo nobody granted is the read the consent design forbids. A
196
+ * field that could only be filled by reading the file does not belong here.
197
+ */
198
+ export interface SourceSession {
199
+ readonly path: string;
200
+ readonly mtimeMs: number;
201
+ /**
202
+ * When the agent itself says the session began, where it keeps that. Codex's
203
+ * index does (`created_at`); the folder-keyed agents do not, and for them the
204
+ * file's mtime stands in everywhere a date is shown.
205
+ */
206
+ readonly startedMs?: number;
207
+ }
208
+ /** One agent project folder: its encoded name, and the sessions found in it. */
209
+ export interface SourceFolder {
210
+ readonly name: string;
211
+ readonly sessions: readonly SourceSession[];
212
+ }
213
+ /** One index row: a session whose agent already recorded the folder it ran in. */
214
+ export interface SourceRow {
215
+ readonly cwd: string;
216
+ readonly origin: string | null;
217
+ readonly session: SourceSession;
218
+ }
219
+ /** Why a store could not be listed. Each is a different line in `Not listed here`. */
220
+ export type UnreadableReason = "unrecognised index" | "index locked" | "unreadable";
221
+ /**
222
+ * What one agent's store yields, without a transcript being opened.
223
+ *
224
+ * The two shapes are the two ways the three agents keep sessions, and they are
225
+ * not interchangeable: a FOLDER-keyed agent (Claude Code, Cursor) names the
226
+ * folder by lossy encoding, so the engine has to resolve it against the
227
+ * filesystem and can fail to (`encode` and `isHashed` are what that walk needs);
228
+ * an INDEXED agent (Codex) records the real `cwd`, so it is never ambiguous.
229
+ *
230
+ * ⛔ **`unreadable` is an answer, not an exception.** One agent's store failing
231
+ * hides that agent's rows and nothing else's, and `scan` reports it; a thrown
232
+ * error here would take the other two agents' rows down with it.
233
+ */
234
+ export type SessionSource = {
235
+ readonly kind: "folders";
236
+ readonly folders: readonly SourceFolder[];
237
+ /** This dialect's `encodeProjectPath`, handed over so the walk uses the same one. */
238
+ readonly encode: (path: string) => string;
239
+ /** Is this folder name a truncated-and-hashed one, which cannot be inverted? */
240
+ readonly isHashed: (name: string) => boolean;
241
+ /**
242
+ * Paths the agent ITSELF recorded having run in — a second skip signal. A
243
+ * folder whose name encodes to a recorded path that no longer exists is one
244
+ * whose sessions may belong to that deleted path, whatever else now matches.
245
+ * Empty for an agent that keeps no such record.
246
+ */
247
+ readonly recorded: readonly string[];
248
+ } | {
249
+ readonly kind: "rows";
250
+ readonly rows: readonly SourceRow[];
251
+ /**
252
+ * Rollout files on disk that no index row names — counted, never opened. Only
253
+ * the index's own agent has the notion; a folder-keyed agent has no index to
254
+ * be missing from.
255
+ */
256
+ readonly unindexed: number;
257
+ } | {
258
+ readonly kind: "unreadable";
259
+ readonly reason: UnreadableReason;
260
+ };
176
261
  /** Where `connect` found the session running right now. */
177
262
  export interface LocatedTranscript {
178
263
  readonly path: string;
@@ -260,11 +345,14 @@ export interface TranscriptDialect {
260
345
  * (lost permanently — that event has no next invocation) and no gap-stamp
261
346
  * reserve. Nothing raises and the hook still exits 0.
262
347
  *
263
- * ⚠ **Widening `HookEventName` was the obvious fix and it is the wrong one.**
264
- * The three map 1:1, so a rename belongs in a per-dialect table; widening the
265
- * union would make `Readonly<Record<HookEventName, EventBudget>>` non-total
266
- * over the real event set and force every dialect to declare budgets for
267
- * events it can never receive.
348
+ * ⚠ **Widening `HookEventName` was the wrong fix for a RENAME, and it is the right
349
+ * one for a new event** (0.7.0, eng review D3 · A). The first three map 1:1, so a
350
+ * rename belongs in a per-dialect table. `SessionStart` is a new event that ALL
351
+ * THREE agents deliver (Claude Code and Codex measured live, Cursor read out of
352
+ * the shipped bundle — see each dialect), so the argument that stopped the
353
+ * widening — "force every dialect to declare budgets for events it can never
354
+ * receive" — no longer holds, and the compiler is now the thing that makes a
355
+ * dialect added later declare its `SessionStart` spelling and budget.
268
356
  *
269
357
  * ## ⚠ MEASURED per dialect, and the two measurements differ in kind
270
358
  *
@@ -304,6 +392,28 @@ export interface TranscriptDialect {
304
392
  * is exactly the cap Codex silently clamps to — measured, not inferred.
305
393
  */
306
394
  readonly hookConfig: AgentHookConfig;
395
+ /**
396
+ * ⛔ **WHAT SESSIONS DOES THIS AGENT HAVE ON DISK — without opening one.**
397
+ * `D11 · A`, `scan`, the picker and the backfill offer.
398
+ *
399
+ * Listed by NAME and TIME only (`SourceSession`). The consent design is that no
400
+ * path opens a transcript of an ungranted repo, so the thing that decides what
401
+ * to offer cannot be the thing that reads the transcripts. ⚠ Each adapter fails
402
+ * closed on its own: an unreadable store is `{ kind: "unreadable" }` and hides
403
+ * that agent's rows only.
404
+ *
405
+ * ⛔ **REQUIRED, not optional, and that is `admitsFile`'s device.** A dialect
406
+ * added later must ANSWER how its sessions are found, and the compiler is what
407
+ * asks; an agent that cannot answer without opening a transcript returns
408
+ * `unreadable` and says so, which is a decision a reviewer sees.
409
+ *
410
+ * ⚠ A MEASURED requirement and not a speculative one: Open Question 3 found the
411
+ * two folder-keyed agents encode a path DIFFERENTLY (Claude Code per UTF-16
412
+ * unit with no collapsing; Cursor collapsing runs and trimming the ends), and
413
+ * the shipped `connect` looked in the wrong folder for any repo path holding an
414
+ * `_` or a space because it assumed one rule.
415
+ */
416
+ readonly sessionSource: (home: string, env: NodeJS.ProcessEnv) => SessionSource;
307
417
  /** Per-event clocks. See `EventBudget` for the relation that binds them. */
308
418
  readonly events: Readonly<Record<HookEventName, EventBudget>>;
309
419
  readonly delegatedTranscripts: DelegatedTranscripts;
@@ -57,11 +57,25 @@
57
57
  */
58
58
  export const UNKNOWN_AGENT_ID = "unknown";
59
59
  /**
60
- * The three hook events DESIGN.md §13.7 names.
60
+ * The four hook events this client registers.
61
61
  *
62
- * Both members of this lane fire the same three; that is exactly why
62
+ * Every member of this lane fires the same four; that is exactly why
63
63
  * `hook_event_name` was REJECTED as an adapter selector (`CR-183`) — the events
64
64
  * are shared, so they discriminate nothing.
65
+ *
66
+ * ⛔ **`SessionStart` is the fourth, and it is the one that never uploads** (0.7.0,
67
+ * eng review D3 · A). `Stop`, `PreCompact` and `SessionEnd` flow into the code
68
+ * that reads a transcript and sends it; `SessionStart` must only install git
69
+ * hooks and say so, so `hooks/entry.ts` resolves the canonical event FIRST and
70
+ * branches to its own handler before the consent gate and before anything reads
71
+ * `transcript_path`. It sits in this list, and not in a table of its own, so the
72
+ * compiler makes each dialect declare its budget and its spelling.
73
+ *
74
+ * event Claude Code Codex Cursor reads a transcript
75
+ * Stop Stop Stop stop yes
76
+ * PreCompact PreCompact PreCompact preCompact yes
77
+ * SessionEnd SessionEnd SessionEnd sessionEnd yes
78
+ * SessionStart SessionStart SessionStart sessionStart NEVER
65
79
  */
66
- export const HOOK_EVENTS = ["Stop", "PreCompact", "SessionEnd"];
80
+ export const HOOK_EVENTS = ["Stop", "PreCompact", "SessionEnd", "SessionStart"];
67
81
  //# sourceMappingURL=types.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AA4BH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAC;AA6D1C;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,CAAU,CAAC"}
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/agents/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6CG;AA4BH;;;;;;;;;;GAUG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,SAAS,CAAC;AA6D1C;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,cAAc,CAAU,CAAC"}
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The org-approval-pending block — style guide §10.2's "held" beat, D65 §DR6, D81. One definition for the two places that
3
+ * print it: `connect`, on the 403 its own test capture got, and `status`, for a repo whose last upload got that same 403.
4
+ *
5
+ * ⚠ EXISTING COPY ONLY. `status` adds no sentence of its own; it prints what `connect` prints, so the two cannot drift
6
+ * (`org_pending.ts` records that the 403 happened). The shared clause renders on EVERY branch and `owners_notified` `> 0`
7
+ * ADDS to it (D81); `null` and `0` never claim a notification.
8
+ */
9
+ export declare function approvalPendingBlock(colour: boolean, repoSlug: string, ownersNotified: number | null): string[];
@@ -0,0 +1,23 @@
1
+ /**
2
+ * The org-approval-pending block — style guide §10.2's "held" beat, D65 §DR6, D81. One definition for the two places that
3
+ * print it: `connect`, on the 403 its own test capture got, and `status`, for a repo whose last upload got that same 403.
4
+ *
5
+ * ⚠ EXISTING COPY ONLY. `status` adds no sentence of its own; it prints what `connect` prints, so the two cannot drift
6
+ * (`org_pending.ts` records that the 403 happened). The shared clause renders on EVERY branch and `owners_notified` `> 0`
7
+ * ADDS to it (D81); `null` and `0` never claim a notification.
8
+ */
9
+ import { CONNECT, URLS } from "../copy/index.js";
10
+ import { renderErrorBlock } from "../term.js";
11
+ export function approvalPendingBlock(colour, repoSlug, ownersNotified) {
12
+ const why = ownersNotified !== null && ownersNotified > 0
13
+ ? `${CONNECT.pendingRecorded} ${CONNECT.pendingNotified(ownersNotified)}`
14
+ : CONNECT.pendingRecorded;
15
+ return renderErrorBlock({
16
+ kind: "warn",
17
+ what: CONNECT.pendingWhat,
18
+ why: [CONNECT.pendingWhy(repoSlug), why, CONNECT.pendingBuffered],
19
+ fixLabel: CONNECT.pendingTrackLabel,
20
+ fixes: [URLS.captureAccess],
21
+ }, colour);
22
+ }
23
+ //# sourceMappingURL=approval_pending.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"approval_pending.js","sourceRoot":"","sources":["../../src/commands/approval_pending.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,gBAAgB,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,UAAU,oBAAoB,CAAC,MAAe,EAAE,QAAgB,EAAE,cAA6B;IACnG,MAAM,GAAG,GACP,cAAc,KAAK,IAAI,IAAI,cAAc,GAAG,CAAC;QAC3C,CAAC,CAAC,GAAG,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,eAAe,CAAC,cAAc,CAAC,EAAE;QACzE,CAAC,CAAC,OAAO,CAAC,eAAe,CAAC;IAC9B,OAAO,gBAAgB,CACrB;QACE,IAAI,EAAE,MAAM;QACZ,IAAI,EAAE,OAAO,CAAC,WAAW;QACzB,GAAG,EAAE,CAAC,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,GAAG,EAAE,OAAO,CAAC,eAAe,CAAC;QACjE,QAAQ,EAAE,OAAO,CAAC,iBAAiB;QACnC,KAAK,EAAE,CAAC,IAAI,CAAC,aAAa,CAAC;KAC5B,EACD,MAAM,CACP,CAAC;AACJ,CAAC"}
@@ -75,6 +75,7 @@
75
75
  */
76
76
  import type { CommandContext } from "./context.js";
77
77
  import type { BrowserCredential, BrowserOptions, Retry } from "./browser_credential.js";
78
+ import type { VerbSyncDeps } from "../grants_verb.js";
78
79
  export interface AuthDeps {
79
80
  /**
80
81
  * Read the secret from a terminal WITHOUT ECHOING IT. Injected for the same
@@ -101,5 +102,11 @@ export interface AuthDeps {
101
102
  * `src/index.ts` passes one that carries NO `browser` (never a second sign-in).
102
103
  */
103
104
  readonly connect?: (ctx: CommandContext) => Promise<number>;
105
+ /**
106
+ * Slice 2, R3-7. The account's network, injected like `connect`: a credential this verb saves on a machine that has synced before
107
+ * is synced before the verb returns, so the new credential row is synced from its first upload. Left out, the verb makes no
108
+ * request, as before. `src/index.ts` passes the real `fetch`.
109
+ */
110
+ readonly account?: VerbSyncDeps;
104
111
  }
105
112
  export declare function auth(ctx: CommandContext, argv: readonly string[], deps?: AuthDeps): Promise<number>;
@@ -73,11 +73,14 @@
73
73
  * @provenance vibecommit-mcp src/transport/server.ts — POST /oauth/ingest-credential is the mint route, verified
74
74
  * @provenance vibecommit-web app/api/ingest-credentials/route.ts — the dashboard producer the paste path still serves, verified
75
75
  */
76
- import { AUTH, COMMANDS, DEVICE, HELP, URLS } from "../copy/index.js";
77
- import { authCommandFor } from "../install.js";
76
+ import { AUTH, DEVICE, PICKER, URLS } from "../copy/index.js";
77
+ import { renderVerbHelp } from "../verb_help.js";
78
+ import { authCommandFor, channelCommand } from "../install.js";
78
79
  import { writeLines } from "./context.js";
79
80
  import { EXIT } from "../exit.js";
80
81
  import { INGEST_TOKEN_PREFIX, IngestCredential, saveCredential } from "../credential.js";
82
+ import { readGrants } from "../consent.js";
83
+ import { machineCapture, machineSummary } from "../machine_state.js";
81
84
  import { credentialsPath } from "../paths.js";
82
85
  import { resolveProjectKeys } from "../project.js";
83
86
  import { paint, renderErrorBlock, tildePath, wrap } from "../term.js";
@@ -98,10 +101,11 @@ export async function auth(ctx, argv, deps = {}) {
98
101
  if (positional.length > 0) {
99
102
  writeLines(ctx.stderr, renderErrorBlock({
100
103
  kind: "bad",
104
+ code: "CREDENTIAL_IN_ARGV",
101
105
  what: AUTH.argvWhat,
102
106
  why: [AUTH.argvWhy],
103
107
  fixLabel: AUTH.argvFix,
104
- fixes: [AUTH.argvFixPrompt, AUTH.argvFixStdin],
108
+ fixes: [channelCommand(ctx.env, ctx.selfPath, AUTH.argvFixPrompt), AUTH.argvFixStdin(authCommandFor(ctx.env, ctx.selfPath))],
105
109
  }, ctx.colour));
106
110
  return EXIT.usage;
107
111
  }
@@ -110,7 +114,8 @@ export async function auth(ctx, argv, deps = {}) {
110
114
  // exactly that a usage error) runs first. By the time this is reached, `positional.length === 0`, so
111
115
  // `--help` still has no side effects — nothing below has read or written anything yet either way.
112
116
  if (argv.includes("--help") || argv.includes("-h")) {
113
- writeLines(ctx.stdout, [HELP.commands.auth]);
117
+ const helpVerb = "auth"; // an identifier, not a literal, at the writer: `copy-inline-literals` follows argument positions
118
+ writeLines(ctx.stdout, renderVerbHelp(helpVerb));
114
119
  return EXIT.ok;
115
120
  }
116
121
  const piped = argv.includes(STDIN_FLAG) || !ctx.stdinIsTty;
@@ -119,6 +124,7 @@ export async function auth(ctx, argv, deps = {}) {
119
124
  writeLines(ctx.stderr, renderErrorBlock(piped
120
125
  ? {
121
126
  kind: "bad",
127
+ code: "SIGNIN_CODE_NEEDS_TTY",
122
128
  what: DEVICE.needsTerminalWhat,
123
129
  why: [DEVICE.needsTerminalWhy],
124
130
  fixLabel: DEVICE.needsTerminalFix,
@@ -126,10 +132,11 @@ export async function auth(ctx, argv, deps = {}) {
126
132
  }
127
133
  : {
128
134
  kind: "bad",
135
+ code: "AUTH_FLAGS_CONFLICT",
129
136
  what: DEVICE.conflictWhat,
130
137
  why: [DEVICE.conflictWhy],
131
138
  fixLabel: DEVICE.conflictFix,
132
- fixes: [COMMANDS.auth, `${COMMANDS.auth} ${PASTE_FLAG}`],
139
+ fixes: [authCommandFor(ctx.env, ctx.selfPath), `${authCommandFor(ctx.env, ctx.selfPath)} ${PASTE_FLAG}`],
133
140
  }, ctx.colour));
134
141
  return EXIT.usage;
135
142
  }
@@ -161,10 +168,11 @@ export async function auth(ctx, argv, deps = {}) {
161
168
  if (secret === "") {
162
169
  writeLines(ctx.stderr, renderErrorBlock({
163
170
  kind: "bad",
171
+ code: "CREDENTIAL_EMPTY",
164
172
  what: AUTH.emptyWhat,
165
173
  why: [AUTH.emptyWhy],
166
- fixLabel: AUTH.emptyFix,
167
- fixes: [URLS.settings],
174
+ fixLabel: AUTH.mintFix(URLS.settings),
175
+ fixes: AUTH.mintCommands(authCommandFor(ctx.env, ctx.selfPath)),
168
176
  }, ctx.colour));
169
177
  return EXIT.failure;
170
178
  }
@@ -175,10 +183,11 @@ export async function auth(ctx, argv, deps = {}) {
175
183
  if (!secret.startsWith(INGEST_TOKEN_PREFIX)) {
176
184
  writeLines(ctx.stderr, renderErrorBlock({
177
185
  kind: "bad",
186
+ code: "CREDENTIAL_NOT_INGEST",
178
187
  what: AUTH.wrongClassWhat,
179
188
  why: [AUTH.wrongClassWhy],
180
- fixLabel: AUTH.wrongClassFix,
181
- fixes: [URLS.settings],
189
+ fixLabel: AUTH.mintFix(URLS.settings),
190
+ fixes: AUTH.mintCommands(authCommandFor(ctx.env, ctx.selfPath)),
182
191
  }, ctx.colour));
183
192
  return EXIT.failure;
184
193
  }
@@ -194,17 +203,49 @@ export async function auth(ctx, argv, deps = {}) {
194
203
  * block and the exit are exactly what they were.
195
204
  */
196
205
  async function saved(ctx, deps, piped, notes = []) {
197
- const shouldConnect = !piped && deps.connect !== undefined && resolveProjectKeys(ctx.cwd) !== null;
206
+ // 0.7.0, D32 · 26A (supersedes only L6's "outside a repository" clause). Inside a work tree the chain is unchanged (L2).
207
+ // Outside one, the continuation is the picker flow, and only on a machine that has nothing captured yet; a machine that
208
+ // already captures prints one line and no picker. ⛔ BOTH of 0.6.0's other guards stay: `piped` (`--stdin`, or no
209
+ // terminal) NEVER continues, so a CI runner with no grant still saves the credential and exits 0 instead of reaching the
210
+ // non-terminal refusal and exiting 2; and the continuation gets no `browser` (`index.ts` hands `connect` none), so a
211
+ // second sign-in cannot start.
212
+ const inRepo = resolveProjectKeys(ctx.cwd) !== null;
213
+ const capture = inRepo || piped || deps.connect === undefined ? null : machineCapture(ctx.home);
214
+ const shouldConnect = !piped && deps.connect !== undefined && (inRepo || capture?.kind === "none");
215
+ const staysOn = capture?.kind === "on";
198
216
  writeLines(ctx.stdout, [
199
217
  ...renderErrorBlock({
200
218
  kind: "ok",
201
219
  what: AUTH.savedWhat,
202
220
  why: [AUTH.savedWhy(tildePath(credentialsPath(ctx.home), ctx.home)), ...notes],
203
- ...(shouldConnect ? {} : { fixLabel: AUTH.nextLabel, fixes: [AUTH.nextCommand] }),
221
+ ...(shouldConnect || staysOn
222
+ ? {}
223
+ : { fixLabel: AUTH.nextLabel, fixes: [channelCommand(ctx.env, ctx.selfPath, AUTH.nextCommand)] }),
204
224
  }, ctx.colour),
225
+ ...(capture?.kind === "on" ? ["", ...wrap(PICKER.authStays(machineSummary(capture)), 2)] : []),
205
226
  ]);
206
227
  if (shouldConnect && deps.connect !== undefined)
207
228
  return await deps.connect(ctx);
229
+ // Not chaining (the chained `connect` syncs for itself): this is the only other place a credential is saved, so it is the other
230
+ // place a new credential row is synced. ⛔ It makes no prompt and prints nothing, so a piped or no-terminal `auth` (which never
231
+ // continues, D32 guard 1) is as quiet as before; a failure is silent and the exit stays what it was.
232
+ // ⛔ A piped, `--stdin` or no-terminal `auth` does NOT sync (L10: "with `--stdin`, or with no terminal, it only saves the credential"): a
233
+ // sync sends this machine's grants, which is more than saving. R3-7 is for a person at a terminal.
234
+ if (!piped)
235
+ await syncNewCredential(ctx, deps);
208
236
  return EXIT.ok;
209
237
  }
238
+ /**
239
+ * R3-7, for a TERMINAL `auth` only (the caller skips a piped one). Only a machine that HAS synced (`grants.json`'s `synced_at`) syncs here: a machine that never has would be making a FIRST
240
+ * sync, which prints what it is saving and belongs to `connect`/`status`/`off`, not to a verb whose job is to save a credential.
241
+ * Bounded at 3 s and silent on failure (`syncForVerb` never throws).
242
+ */
243
+ async function syncNewCredential(ctx, deps) {
244
+ if (deps.account === undefined)
245
+ return;
246
+ const read = readGrants(ctx.home);
247
+ if (read.kind !== "ok" || read.grants.synced_at === null)
248
+ return;
249
+ await (await import("../grants_verb.js")).syncForVerb(ctx, false, deps.account);
250
+ }
210
251
  //# sourceMappingURL=auth.js.map