@vibe-commit/capture 0.2.2 → 0.3.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/dist/agent_hooks.d.ts +53 -8
- package/dist/agent_hooks.js +180 -19
- package/dist/agent_hooks.js.map +1 -1
- package/dist/agent_install.d.ts +0 -17
- package/dist/agent_install.js +1 -24
- package/dist/agent_install.js.map +1 -1
- package/dist/agents/cursor.js +54 -18
- package/dist/agents/cursor.js.map +1 -1
- package/dist/commands/auth.d.ts +24 -9
- package/dist/commands/auth.js +38 -10
- package/dist/commands/auth.js.map +1 -1
- package/dist/commands/browser_credential.d.ts +89 -0
- package/dist/commands/browser_credential.js +181 -0
- package/dist/commands/browser_credential.js.map +1 -0
- package/dist/commands/connect.d.ts +26 -9
- package/dist/commands/connect.js +49 -90
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/status.js +35 -2
- package/dist/commands/status.js.map +1 -1
- package/dist/copy/strings.d.ts +42 -2
- package/dist/copy/strings.js +52 -1
- package/dist/copy/strings.js.map +1 -1
- package/dist/hooks/entry.js +106 -24
- package/dist/hooks/entry.js.map +1 -1
- package/dist/index.js +9 -1
- package/dist/index.js.map +1 -1
- package/dist/oauth/mint.d.ts +97 -0
- package/dist/oauth/mint.js +120 -0
- package/dist/oauth/mint.js.map +1 -0
- package/dist/oauth/signin.d.ts +25 -0
- package/dist/oauth/signin.js +30 -0
- package/dist/oauth/signin.js.map +1 -1
- package/dist/post.d.ts +42 -0
- package/dist/post.js +64 -3
- package/dist/post.js.map +1 -1
- package/dist/spool.d.ts +20 -0
- package/dist/spool.js +37 -0
- package/dist/spool.js.map +1 -1
- package/dist/state.d.ts +77 -0
- package/dist/state.js +111 -2
- package/dist/state.js.map +1 -1
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/agent_hooks.d.ts
CHANGED
|
@@ -22,13 +22,26 @@
|
|
|
22
22
|
*
|
|
23
23
|
* 1. **APPEND, never replace.** A foreign entry is never removed, rewritten or
|
|
24
24
|
* reordered. Ours goes last.
|
|
25
|
-
* 2. **IDEMPOTENT
|
|
26
|
-
*
|
|
25
|
+
* 2. **IDEMPOTENT — by the marker when it survives, and by OUR EXACT COMMAND
|
|
26
|
+
* when it does not.** JSON has nowhere to put a comment, so ours carries
|
|
27
|
+
* `_vibecommit`. Without an identity, `connect` run twice appends twice and
|
|
27
28
|
* the hook fires twice per event — two processes racing on one session
|
|
28
29
|
* state file. ⚠ The marker field is TOLERATED by all three readers, and
|
|
29
30
|
* that is measured rather than assumed: Codex parses and clamps with the
|
|
30
31
|
* field present on the inner and outer object alike, and Cursor's validator
|
|
31
32
|
* checks only known members (`HWu`/`jWu`/`$Wu` in its bundle).
|
|
33
|
+
*
|
|
34
|
+
* ⛔ **BUT TOLERATED ON READ IS NOT KEPT ON WRITE — `CR-224`, `TODOS[138]`.**
|
|
35
|
+
* Claude Code's own settings writer STRIPS unknown keys from hook entries
|
|
36
|
+
* the next time it saves `settings.json` for any reason. MEASURED against
|
|
37
|
+
* Claude Code 2.1.285 with a throwaway `CLAUDE_CONFIG_DIR`: an entry
|
|
38
|
+
* carrying `_vibecommit`, a second foreign key on the entry and a third on
|
|
39
|
+
* the inner hook went through `claude plugin marketplace add` (which writes
|
|
40
|
+
* `extraKnownMarketplaces`) and came back with ALL THREE gone — while an
|
|
41
|
+
* unknown TOP-LEVEL key survived, so it is the hooks schema that strips,
|
|
42
|
+
* not the file writer wholesale. So on the one machine that matters most the
|
|
43
|
+
* marker is gone by the second `connect`, and a marker-only identity
|
|
44
|
+
* appended a duplicate every run. See `isOurs` for what replaced it.
|
|
32
45
|
* 3. ⛔ **REFUSE on unparseable, never overwrite.** A file we could not read is
|
|
33
46
|
* a file whose contents we cannot preserve. Overwriting it would be the
|
|
34
47
|
* destructive outcome wearing a success message — the exact defect class
|
|
@@ -57,17 +70,42 @@
|
|
|
57
70
|
* registers nothing, and reports success — the same matches-nothing failure as
|
|
58
71
|
* an unrecognised event name, relocated to the document level.
|
|
59
72
|
*/
|
|
73
|
+
import type { AgentId } from "./agents/types.js";
|
|
60
74
|
/**
|
|
61
|
-
* What marks an entry as ours.
|
|
75
|
+
* What marks an entry as ours — the FAST PATH, not the whole identity.
|
|
62
76
|
*
|
|
63
|
-
* ⚠ **A FIELD,
|
|
64
|
-
*
|
|
65
|
-
*
|
|
66
|
-
*
|
|
77
|
+
* ⚠ **A FIELD, never a substring of the command.** A substring match would claim
|
|
78
|
+
* a user's own hook that happens to mention this product. ⛔ But a field is only
|
|
79
|
+
* as durable as the host lets it be, and Claude Code's writer drops it (rule 2
|
|
80
|
+
* above, measured). So an entry WITHOUT the marker is still ours when its
|
|
81
|
+
* command is one we could have written — see `isOurs`, which anchors on the
|
|
82
|
+
* WHOLE command string and never on part of it.
|
|
67
83
|
*/
|
|
68
84
|
export declare const AGENT_HOOK_MARKER = "vibecommit-capture-hook v1";
|
|
69
85
|
/** Which document shape an agent's hook config uses. See the module docblock. */
|
|
70
86
|
export type HookDocShape = "claude" | "cursor";
|
|
87
|
+
/**
|
|
88
|
+
* The command an agent's config invokes.
|
|
89
|
+
*
|
|
90
|
+
* ⛔ **`hook` LEADS, AND THE FLAG NEVER DOES.** `invocationMode` returns `"hook"`
|
|
91
|
+
* only when `argv[0] === "hook"`, so a registered command whose first argument
|
|
92
|
+
* is `--agent=` runs the binary INTERACTIVELY — inside a hook, printing to a
|
|
93
|
+
* stream the agent reads back into the user's turn. `registry.ts` names this as
|
|
94
|
+
* the trap the wave was most likely to ship, and `test/agent-registry.test.ts`
|
|
95
|
+
* holds both arms.
|
|
96
|
+
*
|
|
97
|
+
* ⚠ **The flag is passed explicitly for every agent, Claude Code included**,
|
|
98
|
+
* even though `DEFAULT_AGENT_ID` makes it redundant there. A config that names
|
|
99
|
+
* its own agent is self-describing, and — since Cursor reads Claude Code's
|
|
100
|
+
* settings and maps them onto its own events — it is the only thing that tells
|
|
101
|
+
* the two invocations apart when both fire.
|
|
102
|
+
*
|
|
103
|
+
* ⚠ **It lives HERE, beside its own inverse (`HOOK_COMMAND`), since `CR-224`.**
|
|
104
|
+
* The merge below recognises our entry by this exact shape once the host has
|
|
105
|
+
* stripped the marker, so the builder and the recogniser are one grammar and
|
|
106
|
+
* must not be able to drift apart across two files.
|
|
107
|
+
*/
|
|
108
|
+
export declare function hookCommand(binPath: string, agentId: AgentId): string;
|
|
71
109
|
/** One event we want registered, already in the agent's own vocabulary. */
|
|
72
110
|
export interface HookRegistration {
|
|
73
111
|
/** The agent's OWN spelling — `SessionEnd` or `sessionEnd`, never canonical. */
|
|
@@ -100,7 +138,14 @@ export type MergeOutcome = {
|
|
|
100
138
|
* That split is deliberate: every rule in the module docblock is a property of
|
|
101
139
|
* this function, and none of them needs a real `~/.claude` to check.
|
|
102
140
|
*/
|
|
103
|
-
export declare function mergeHookDocument(raw: string | null, shape: HookDocShape, registrations: readonly HookRegistration[]
|
|
141
|
+
export declare function mergeHookDocument(raw: string | null, shape: HookDocShape, registrations: readonly HookRegistration[],
|
|
142
|
+
/**
|
|
143
|
+
* Does the binary an existing entry points at still exist? Consulted ONLY by
|
|
144
|
+
* the moved-directory arm of `commandIsOurs` (`TODOS[144]`). ⚠ The default
|
|
145
|
+
* answers "yes" — the direction in which a doubt leaves a WORKING hook alone
|
|
146
|
+
* and costs at most a duplicate. `writeAgentHooks` passes `existsSync`.
|
|
147
|
+
*/
|
|
148
|
+
binaryExists?: (path: string) => boolean): MergeOutcome;
|
|
104
149
|
export type AgentHookInstall = {
|
|
105
150
|
readonly kind: "installed";
|
|
106
151
|
readonly path: string;
|
package/dist/agent_hooks.js
CHANGED
|
@@ -22,13 +22,26 @@
|
|
|
22
22
|
*
|
|
23
23
|
* 1. **APPEND, never replace.** A foreign entry is never removed, rewritten or
|
|
24
24
|
* reordered. Ours goes last.
|
|
25
|
-
* 2. **IDEMPOTENT
|
|
26
|
-
*
|
|
25
|
+
* 2. **IDEMPOTENT — by the marker when it survives, and by OUR EXACT COMMAND
|
|
26
|
+
* when it does not.** JSON has nowhere to put a comment, so ours carries
|
|
27
|
+
* `_vibecommit`. Without an identity, `connect` run twice appends twice and
|
|
27
28
|
* the hook fires twice per event — two processes racing on one session
|
|
28
29
|
* state file. ⚠ The marker field is TOLERATED by all three readers, and
|
|
29
30
|
* that is measured rather than assumed: Codex parses and clamps with the
|
|
30
31
|
* field present on the inner and outer object alike, and Cursor's validator
|
|
31
32
|
* checks only known members (`HWu`/`jWu`/`$Wu` in its bundle).
|
|
33
|
+
*
|
|
34
|
+
* ⛔ **BUT TOLERATED ON READ IS NOT KEPT ON WRITE — `CR-224`, `TODOS[138]`.**
|
|
35
|
+
* Claude Code's own settings writer STRIPS unknown keys from hook entries
|
|
36
|
+
* the next time it saves `settings.json` for any reason. MEASURED against
|
|
37
|
+
* Claude Code 2.1.285 with a throwaway `CLAUDE_CONFIG_DIR`: an entry
|
|
38
|
+
* carrying `_vibecommit`, a second foreign key on the entry and a third on
|
|
39
|
+
* the inner hook went through `claude plugin marketplace add` (which writes
|
|
40
|
+
* `extraKnownMarketplaces`) and came back with ALL THREE gone — while an
|
|
41
|
+
* unknown TOP-LEVEL key survived, so it is the hooks schema that strips,
|
|
42
|
+
* not the file writer wholesale. So on the one machine that matters most the
|
|
43
|
+
* marker is gone by the second `connect`, and a marker-only identity
|
|
44
|
+
* appended a duplicate every run. See `isOurs` for what replaced it.
|
|
32
45
|
* 3. ⛔ **REFUSE on unparseable, never overwrite.** A file we could not read is
|
|
33
46
|
* a file whose contents we cannot preserve. Overwriting it would be the
|
|
34
47
|
* destructive outcome wearing a success message — the exact defect class
|
|
@@ -57,21 +70,68 @@
|
|
|
57
70
|
* registers nothing, and reports success — the same matches-nothing failure as
|
|
58
71
|
* an unrecognised event name, relocated to the document level.
|
|
59
72
|
*/
|
|
60
|
-
import { chmodSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
61
|
-
import { dirname, join } from "node:path";
|
|
73
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, renameSync, statSync, writeFileSync } from "node:fs";
|
|
74
|
+
import { basename, dirname, join } from "node:path";
|
|
62
75
|
/**
|
|
63
|
-
* What marks an entry as ours.
|
|
76
|
+
* What marks an entry as ours — the FAST PATH, not the whole identity.
|
|
64
77
|
*
|
|
65
|
-
* ⚠ **A FIELD,
|
|
66
|
-
*
|
|
67
|
-
*
|
|
68
|
-
*
|
|
78
|
+
* ⚠ **A FIELD, never a substring of the command.** A substring match would claim
|
|
79
|
+
* a user's own hook that happens to mention this product. ⛔ But a field is only
|
|
80
|
+
* as durable as the host lets it be, and Claude Code's writer drops it (rule 2
|
|
81
|
+
* above, measured). So an entry WITHOUT the marker is still ours when its
|
|
82
|
+
* command is one we could have written — see `isOurs`, which anchors on the
|
|
83
|
+
* WHOLE command string and never on part of it.
|
|
69
84
|
*/
|
|
70
85
|
export const AGENT_HOOK_MARKER = "vibecommit-capture-hook v1";
|
|
71
86
|
/** The field carrying it. Leading underscore: a hint that it is not the agent's. */
|
|
72
87
|
const MARKER_KEY = "_vibecommit";
|
|
73
88
|
/** The `version` we mint when CREATING a `cursor` document. */
|
|
74
89
|
const CURSOR_CONFIG_VERSION = 1;
|
|
90
|
+
/**
|
|
91
|
+
* The command an agent's config invokes.
|
|
92
|
+
*
|
|
93
|
+
* ⛔ **`hook` LEADS, AND THE FLAG NEVER DOES.** `invocationMode` returns `"hook"`
|
|
94
|
+
* only when `argv[0] === "hook"`, so a registered command whose first argument
|
|
95
|
+
* is `--agent=` runs the binary INTERACTIVELY — inside a hook, printing to a
|
|
96
|
+
* stream the agent reads back into the user's turn. `registry.ts` names this as
|
|
97
|
+
* the trap the wave was most likely to ship, and `test/agent-registry.test.ts`
|
|
98
|
+
* holds both arms.
|
|
99
|
+
*
|
|
100
|
+
* ⚠ **The flag is passed explicitly for every agent, Claude Code included**,
|
|
101
|
+
* even though `DEFAULT_AGENT_ID` makes it redundant there. A config that names
|
|
102
|
+
* its own agent is self-describing, and — since Cursor reads Claude Code's
|
|
103
|
+
* settings and maps them onto its own events — it is the only thing that tells
|
|
104
|
+
* the two invocations apart when both fire.
|
|
105
|
+
*
|
|
106
|
+
* ⚠ **It lives HERE, beside its own inverse (`HOOK_COMMAND`), since `CR-224`.**
|
|
107
|
+
* The merge below recognises our entry by this exact shape once the host has
|
|
108
|
+
* stripped the marker, so the builder and the recogniser are one grammar and
|
|
109
|
+
* must not be able to drift apart across two files.
|
|
110
|
+
*/
|
|
111
|
+
export function hookCommand(binPath, agentId) {
|
|
112
|
+
return `${quote(binPath)} hook --agent=${agentId}`;
|
|
113
|
+
}
|
|
114
|
+
/** Double-quote for the shells these configs are read by. */
|
|
115
|
+
function quote(value) {
|
|
116
|
+
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* ⛔ `hookCommand`'s output, recognised — ANCHORED AT BOTH ENDS.
|
|
120
|
+
*
|
|
121
|
+
* The quoted path is `quote`'s grammar exactly: any character except the four it
|
|
122
|
+
* escapes, or one of those four behind a backslash. `^…$` is the whole point:
|
|
123
|
+
* `time "<ours>" hook --agent=x`, `"<ours>" hook --agent=x && echo done` and
|
|
124
|
+
* `echo "<ours>" hook --agent=x` are all the USER's commands, and a pattern
|
|
125
|
+
* without both anchors would claim them.
|
|
126
|
+
*/
|
|
127
|
+
const HOOK_COMMAND = /^"((?:[^"\\$`]|\\["\\$`])*)" hook --agent=([a-z][a-z0-9-]*)$/;
|
|
128
|
+
/** `hookCommand`, inverted. `null` for anything it could not have produced. */
|
|
129
|
+
function parseHookCommand(command) {
|
|
130
|
+
const match = HOOK_COMMAND.exec(command);
|
|
131
|
+
if (match === null)
|
|
132
|
+
return null;
|
|
133
|
+
return { binPath: match[1].replace(/\\(["\\$`])/g, "$1"), agentId: match[2] };
|
|
134
|
+
}
|
|
75
135
|
/**
|
|
76
136
|
* ⛔ **THE WHOLE MERGE, AS A PURE FUNCTION ON TEXT.**
|
|
77
137
|
*
|
|
@@ -81,7 +141,14 @@ const CURSOR_CONFIG_VERSION = 1;
|
|
|
81
141
|
* That split is deliberate: every rule in the module docblock is a property of
|
|
82
142
|
* this function, and none of them needs a real `~/.claude` to check.
|
|
83
143
|
*/
|
|
84
|
-
export function mergeHookDocument(raw, shape, registrations
|
|
144
|
+
export function mergeHookDocument(raw, shape, registrations,
|
|
145
|
+
/**
|
|
146
|
+
* Does the binary an existing entry points at still exist? Consulted ONLY by
|
|
147
|
+
* the moved-directory arm of `commandIsOurs` (`TODOS[144]`). ⚠ The default
|
|
148
|
+
* answers "yes" — the direction in which a doubt leaves a WORKING hook alone
|
|
149
|
+
* and costs at most a duplicate. `writeAgentHooks` passes `existsSync`.
|
|
150
|
+
*/
|
|
151
|
+
binaryExists = () => true) {
|
|
85
152
|
let doc;
|
|
86
153
|
if (raw === null || raw.trim() === "") {
|
|
87
154
|
doc = {};
|
|
@@ -133,30 +200,124 @@ export function mergeHookDocument(raw, shape, registrations) {
|
|
|
133
200
|
}
|
|
134
201
|
const entries = [...(current ?? [])];
|
|
135
202
|
const ours = entryFor(shape, registration);
|
|
136
|
-
const
|
|
203
|
+
const mine = (entry) => isOurs(entry, shape, registration, binaryExists);
|
|
204
|
+
const at = entries.findIndex(mine);
|
|
137
205
|
if (at >= 0) {
|
|
138
206
|
// ⛔ REFRESH IN PLACE, and the position is kept. The binary path moves on
|
|
139
207
|
// every reinstall, so this arm is the ordinary one on a re-`connect` — and
|
|
140
208
|
// appending instead would grow the array without bound.
|
|
141
209
|
entries[at] = ours;
|
|
210
|
+
// ⛔ AND EVERY OTHER COPY OF OURS GOES (`CR-224`). A machine that ran a
|
|
211
|
+
// marker-only `connect` N times after its host stripped the marker holds N
|
|
212
|
+
// copies, each spawning a process on every event. Refreshing only the first
|
|
213
|
+
// would leave N-1 of them firing forever; a re-run has to CONVERGE to one.
|
|
214
|
+
// Only entries `isOurs` claims are removed — rule 1 is about foreign ones,
|
|
215
|
+
// and the claim is anchored on the whole command, so this cannot reach one.
|
|
216
|
+
for (let i = entries.length - 1; i > at; i -= 1) {
|
|
217
|
+
if (mine(entries[i]))
|
|
218
|
+
entries.splice(i, 1);
|
|
219
|
+
}
|
|
142
220
|
updated.push(registration.event);
|
|
143
221
|
}
|
|
144
222
|
else {
|
|
145
223
|
entries.push(ours);
|
|
146
224
|
added.push(registration.event);
|
|
147
225
|
}
|
|
148
|
-
foreign += entries.filter((entry) => !
|
|
226
|
+
foreign += entries.filter((entry) => !mine(entry)).length;
|
|
149
227
|
hooks[registration.event] = entries;
|
|
150
228
|
}
|
|
151
229
|
doc.hooks = hooks;
|
|
152
230
|
return { kind: "merged", text: `${JSON.stringify(doc, null, 2)}\n`, added, updated, foreign };
|
|
153
231
|
}
|
|
154
|
-
/**
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
232
|
+
/**
|
|
233
|
+
* Ours? ⛔ **By the marker, or by being EXACTLY an entry we could have written.**
|
|
234
|
+
*
|
|
235
|
+
* The marker is the fast path and is enough on its own — it is the only field
|
|
236
|
+
* nothing but this module writes. Without it (`CR-224`: Claude Code's writer
|
|
237
|
+
* strips it), an entry is ours only when ALL of these hold:
|
|
238
|
+
*
|
|
239
|
+
* 1. **Its keys are ours and nobody else's.** `{ hooks: [one hook] }` for the
|
|
240
|
+
* `claude` shape, `{ command, timeout }` for `cursor`, and the one inner
|
|
241
|
+
* hook carries only `type`/`command`/`timeout`. A `matcher`, a second
|
|
242
|
+
* inner hook or any other key means someone EDITED it, and refreshing it
|
|
243
|
+
* would silently delete their edit.
|
|
244
|
+
* 2. **Its command is ours** — see `commandIsOurs`.
|
|
245
|
+
*
|
|
246
|
+
* ⚠ **THE FAILURE DIRECTION IS DELIBERATE.** A false "not ours" costs one
|
|
247
|
+
* duplicate entry, which `claimFiring` (`CR-195`/U4a) already turns into a
|
|
248
|
+
* single capture. A false "ours" overwrites a user's hook — the one outcome this
|
|
249
|
+
* module exists to make impossible. Every doubt resolves to "not ours".
|
|
250
|
+
*/
|
|
251
|
+
function isOurs(entry, shape, registration, binaryExists) {
|
|
252
|
+
if (!isRecord(entry))
|
|
253
|
+
return false;
|
|
254
|
+
if (entry[MARKER_KEY] === AGENT_HOOK_MARKER)
|
|
255
|
+
return true;
|
|
256
|
+
if (shape === "cursor") {
|
|
257
|
+
return (hasOnlyKeys(entry, ["command", "timeout"]) &&
|
|
258
|
+
typeof entry.command === "string" &&
|
|
259
|
+
commandIsOurs(entry.command, registration.command, binaryExists));
|
|
260
|
+
}
|
|
261
|
+
if (!hasOnlyKeys(entry, ["hooks"]) || !Array.isArray(entry.hooks) || entry.hooks.length !== 1) {
|
|
262
|
+
return false;
|
|
263
|
+
}
|
|
264
|
+
const inner = entry.hooks[0];
|
|
265
|
+
return (isRecord(inner) &&
|
|
266
|
+
hasOnlyKeys(inner, ["type", "command", "timeout"]) &&
|
|
267
|
+
inner.type === "command" &&
|
|
268
|
+
typeof inner.command === "string" &&
|
|
269
|
+
commandIsOurs(inner.command, registration.command, binaryExists));
|
|
270
|
+
}
|
|
271
|
+
/**
|
|
272
|
+
* ⛔ **THE WHOLE STRING, NEVER A SUBSTRING — the trap `TODOS[138]` names.**
|
|
273
|
+
*
|
|
274
|
+
* Two arms, and only two:
|
|
275
|
+
*
|
|
276
|
+
* - **Identical** to the command we are about to write. The ordinary re-run.
|
|
277
|
+
* - **`hookCommand`'s exact shape with only the binary's DIRECTORY changed.**
|
|
278
|
+
* This is the reinstall — an nvm switch or a new npm prefix moves
|
|
279
|
+
* `<prefix>/bin/vibecommit`, the command text moves with it, and an
|
|
280
|
+
* identical-only rule would append a second entry beside one that now
|
|
281
|
+
* points at a binary that may not exist (so fails on every event). Both
|
|
282
|
+
* sides must parse as the whole `"<path>" hook --agent=<id>` grammar, name
|
|
283
|
+
* the SAME agent (Cursor reads Claude Code's config, so the `--agent=` tail
|
|
284
|
+
* is what tells the two apart), and end in the SAME binary name. What a
|
|
285
|
+
* foreign tool would need to be claimed is our exact argv AND a binary with
|
|
286
|
+
* our name — which is our binary.
|
|
287
|
+
*
|
|
288
|
+
* ⚠ Invoking the same install by a different NAME (`…/bin/vibecommit` vs
|
|
289
|
+
* `…/dist/index.js`) is not matched and leaves one duplicate. That is the safe
|
|
290
|
+
* direction (see `isOurs`), and `index.js` is too generic a name to claim on.
|
|
291
|
+
*
|
|
292
|
+
* ⛔ **AND THE MOVED-DIRECTORY ARM CLAIMS ONLY A DEAD PATH (`TODOS[144]`).** A
|
|
293
|
+
* claimed entry is REPLACED, so this arm decides which binary the user's hooks
|
|
294
|
+
* run. Claiming a LIVE one hands the hook to whatever binary ran `connect` —
|
|
295
|
+
* and a throwaway install (`npx`, a temp prefix, a test harness's artifacts
|
|
296
|
+
* dir) is exactly that: Gate A repointed a machine's working capture at a
|
|
297
|
+
* directory about to be deleted, after which every hook fails quietly and
|
|
298
|
+
* capture stops. Without this arm the same run appends a second entry and the
|
|
299
|
+
* working one keeps firing (`claimFiring` makes it one capture). So: a moved
|
|
300
|
+
* path is claimed only when the binary it names is GONE — the reinstall this
|
|
301
|
+
* arm exists for (an uninstalled prefix, a removed nvm version) — and a live
|
|
302
|
+
* one is left alone. ⚠ Cost, accepted: two live installs leave two entries
|
|
303
|
+
* until one is removed; a duplicate is the safe failure, a hijack is not.
|
|
304
|
+
*/
|
|
305
|
+
function commandIsOurs(candidate, ours, binaryExists) {
|
|
306
|
+
if (candidate === ours)
|
|
307
|
+
return true;
|
|
308
|
+
const theirs = parseHookCommand(candidate);
|
|
309
|
+
const mine = parseHookCommand(ours);
|
|
310
|
+
if (theirs === null || mine === null)
|
|
311
|
+
return false;
|
|
312
|
+
return (theirs.agentId === mine.agentId &&
|
|
313
|
+
basename(theirs.binPath) === basename(mine.binPath) &&
|
|
314
|
+
!binaryExists(theirs.binPath));
|
|
315
|
+
}
|
|
316
|
+
function isRecord(value) {
|
|
317
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
318
|
+
}
|
|
319
|
+
function hasOnlyKeys(value, allowed) {
|
|
320
|
+
return Object.keys(value).every((key) => allowed.includes(key));
|
|
160
321
|
}
|
|
161
322
|
/** Our entry, in the shape that agent's reader actually validates. */
|
|
162
323
|
function entryFor(shape, registration) {
|
|
@@ -204,7 +365,7 @@ export function writeAgentHooks(path, shape, registrations) {
|
|
|
204
365
|
// the write below rather than being guessed at here.
|
|
205
366
|
raw = null;
|
|
206
367
|
}
|
|
207
|
-
const merged = mergeHookDocument(raw, shape, registrations);
|
|
368
|
+
const merged = mergeHookDocument(raw, shape, registrations, existsSync);
|
|
208
369
|
if (merged.kind === "refused")
|
|
209
370
|
return { kind: "refused", path, why: merged.why };
|
|
210
371
|
const temp = join(dirname(path), `.${AGENT_HOOK_MARKER.replace(/\W+/g, "-")}.tmp`);
|
package/dist/agent_hooks.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent_hooks.js","sourceRoot":"","sources":["../src/agent_hooks.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"agent_hooks.js","sourceRoot":"","sources":["../src/agent_hooks.ts"],"names":[],"mappings":"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"}
|
package/dist/agent_install.d.ts
CHANGED
|
@@ -46,23 +46,6 @@ export interface AgentHookOutcome {
|
|
|
46
46
|
readonly path: string;
|
|
47
47
|
readonly install: AgentHookInstall | null;
|
|
48
48
|
}
|
|
49
|
-
/**
|
|
50
|
-
* The command an agent's config invokes.
|
|
51
|
-
*
|
|
52
|
-
* ⛔ **`hook` LEADS, AND THE FLAG NEVER DOES.** `invocationMode` returns `"hook"`
|
|
53
|
-
* only when `argv[0] === "hook"`, so a registered command whose first argument
|
|
54
|
-
* is `--agent=` runs the binary INTERACTIVELY — inside a hook, printing to a
|
|
55
|
-
* stream the agent reads back into the user's turn. `registry.ts` names this as
|
|
56
|
-
* the trap the wave was most likely to ship, and `test/agent-registry.test.ts`
|
|
57
|
-
* holds both arms.
|
|
58
|
-
*
|
|
59
|
-
* ⚠ **The flag is passed explicitly for every agent, Claude Code included**,
|
|
60
|
-
* even though `DEFAULT_AGENT_ID` makes it redundant there. A config that names
|
|
61
|
-
* its own agent is self-describing, and — since Cursor reads Claude Code's
|
|
62
|
-
* settings and maps them onto its own events — it is the only thing that tells
|
|
63
|
-
* the two invocations apart when both fire.
|
|
64
|
-
*/
|
|
65
|
-
export declare function hookCommand(binPath: string, agentId: AgentId): string;
|
|
66
49
|
/** Every event this client registers, in one agent's own vocabulary. */
|
|
67
50
|
export declare function registrationsFor(dialect: TranscriptDialect, binPath: string): readonly HookRegistration[];
|
|
68
51
|
/** Is this agent on the machine at all? The DIRECTORY, never the config file. */
|
package/dist/agent_install.js
CHANGED
|
@@ -40,32 +40,9 @@
|
|
|
40
40
|
*/
|
|
41
41
|
import { existsSync } from "node:fs";
|
|
42
42
|
import { dirname } from "node:path";
|
|
43
|
-
import { writeAgentHooks } from "./agent_hooks.js";
|
|
43
|
+
import { hookCommand, writeAgentHooks, } from "./agent_hooks.js";
|
|
44
44
|
import { DIALECTS } from "./agents/registry.js";
|
|
45
45
|
import { HOOK_EVENTS } from "./agents/types.js";
|
|
46
|
-
/**
|
|
47
|
-
* The command an agent's config invokes.
|
|
48
|
-
*
|
|
49
|
-
* ⛔ **`hook` LEADS, AND THE FLAG NEVER DOES.** `invocationMode` returns `"hook"`
|
|
50
|
-
* only when `argv[0] === "hook"`, so a registered command whose first argument
|
|
51
|
-
* is `--agent=` runs the binary INTERACTIVELY — inside a hook, printing to a
|
|
52
|
-
* stream the agent reads back into the user's turn. `registry.ts` names this as
|
|
53
|
-
* the trap the wave was most likely to ship, and `test/agent-registry.test.ts`
|
|
54
|
-
* holds both arms.
|
|
55
|
-
*
|
|
56
|
-
* ⚠ **The flag is passed explicitly for every agent, Claude Code included**,
|
|
57
|
-
* even though `DEFAULT_AGENT_ID` makes it redundant there. A config that names
|
|
58
|
-
* its own agent is self-describing, and — since Cursor reads Claude Code's
|
|
59
|
-
* settings and maps them onto its own events — it is the only thing that tells
|
|
60
|
-
* the two invocations apart when both fire.
|
|
61
|
-
*/
|
|
62
|
-
export function hookCommand(binPath, agentId) {
|
|
63
|
-
return `${quote(binPath)} hook --agent=${agentId}`;
|
|
64
|
-
}
|
|
65
|
-
/** Double-quote for the shells these configs are read by. */
|
|
66
|
-
function quote(value) {
|
|
67
|
-
return `"${value.replace(/(["\\$`])/g, "\\$1")}"`;
|
|
68
|
-
}
|
|
69
46
|
/** Every event this client registers, in one agent's own vocabulary. */
|
|
70
47
|
export function registrationsFor(dialect, binPath) {
|
|
71
48
|
return HOOK_EVENTS.map((event) => ({
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent_install.js","sourceRoot":"","sources":["../src/agent_install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,
|
|
1
|
+
{"version":3,"file":"agent_install.js","sourceRoot":"","sources":["../src/agent_install.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,OAAO,EACL,WAAW,EACX,eAAe,GAGhB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAC;AAChD,OAAO,EAAE,WAAW,EAAwC,MAAM,mBAAmB,CAAC;AAStF,wEAAwE;AACxE,MAAM,UAAU,gBAAgB,CAC9B,OAA0B,EAC1B,OAAe;IAEf,OAAO,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC;QACjC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,CAAC;QAChC,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;QACzC,sEAAsE;QACtE,sEAAsE;QACtE,4EAA4E;QAC5E,4EAA4E;QAC5E,mDAAmD;QACnD,UAAU,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,mBAAmB,GAAG,IAAI,CAAC,CAAC;KACtF,CAAC,CAAC,CAAC;AACN,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,YAAY,CAAC,OAA0B,EAAE,IAAY,EAAE,GAAsB;IAC3F,OAAO,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC;AACjE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAY,EACZ,GAAsB,EACtB,OAAe;IAEf,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QAChD,IAAI,CAAC,YAAY,CAAC,OAAO,EAAE,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;YACtC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACtD,CAAC;QACD,OAAO;YACL,OAAO,EAAE,OAAO,CAAC,EAAE;YACnB,IAAI;YACJ,OAAO,EAAE,eAAe,CAAC,IAAI,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,EAAE,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;SAC7F,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/agents/cursor.js
CHANGED
|
@@ -19,27 +19,63 @@
|
|
|
19
19
|
*/
|
|
20
20
|
import { cursorHooksPath, cursorTranscriptRoot, isCursorTranscript } from "../paths.js";
|
|
21
21
|
/**
|
|
22
|
-
*
|
|
22
|
+
* ⭐ **MEASURED. `D5`'s SECOND HALF IS ANSWERED (`TODOS[130]`), AND THE ANSWER IS
|
|
23
|
+
* THAT CURSOR IMPOSES NO NUMBER OF ITS OWN.**
|
|
23
24
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
* `
|
|
28
|
-
*
|
|
29
|
-
* and are deliberately left as the floor they always were rather than quietly
|
|
30
|
-
* promoted on the strength of the other half. Nothing installs a Cursor hook
|
|
31
|
-
* today, so no Cursor hook has ever fired and these numbers remain unreachable
|
|
32
|
-
* in production.
|
|
25
|
+
* `D5` asked for the exact `hook_event_name` strings Cursor sends *and* the
|
|
26
|
+
* timeouts its `~/.cursor/hooks.json` registers. D205 discharged the strings
|
|
27
|
+
* from the shipped app bundle. The timeouts are now read the same way, out of
|
|
28
|
+
* `workbench.desktop.main.js` — the validator `Fed` and the executor
|
|
29
|
+
* `_executeCommandHookScript`:
|
|
33
30
|
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
31
|
+
* | question | measured |
|
|
32
|
+
* |---|---|
|
|
33
|
+
* | is there a per-hook `timeout`? | **yes**, and it is in **SECONDS** |
|
|
34
|
+
* | is it clamped or capped? | **NO** — above `3600` Cursor only `console.warn`s |
|
|
35
|
+
* | rejected when? | non-number, or `<= 0`. Nothing else. |
|
|
36
|
+
* | default when omitted | **60 s** — `(t.timeout ?? ROi) * 1e3`, `ROi = 60` |
|
|
37
|
+
* | enforcement | real: killed, **exit code 124**, logged `timed out after Ns` |
|
|
38
|
+
* | on timeout | `failClosed: true` BLOCKS the tool; otherwise the step proceeds |
|
|
40
39
|
*
|
|
41
|
-
* ⛔ **
|
|
42
|
-
* owns the real numbers once `D5` comes
|
|
40
|
+
* ⛔ **SO THERE WAS NEVER A CURSOR NUMBER TO DISCOVER. Cursor honours whatever we
|
|
41
|
+
* register.** The old header said `CR-195` "owns the real numbers once `D5` comes
|
|
42
|
+
* back"; `D5` is back, and the real numbers are ours to choose.
|
|
43
|
+
*
|
|
44
|
+
* ## ⚠ THE NUMBERS BELOW DO NOT CHANGE — BUT THEIR JUSTIFICATION DOES
|
|
45
|
+
*
|
|
46
|
+
* They were a defensive floor against a cap that might exist. They are now a
|
|
47
|
+
* DELIBERATE PAIR, and the reasoning is no longer conditional:
|
|
48
|
+
*
|
|
49
|
+
* - We register **3 s**, so Cursor kills the hook at 3 s and not before.
|
|
50
|
+
* - Our own watchdog is **2.2 s**, so it always fires **800 ms FIRST**.
|
|
51
|
+
*
|
|
52
|
+
* That ordering is the whole point. `DESIGN.md §13.7` forbids one outcome — the
|
|
53
|
+
* agent killing us before we can exit 0 with silent stdout — and the old header
|
|
54
|
+
* could only *hope* the registered value held. It holds: the client watchdog
|
|
55
|
+
* leads the registered timeout by construction, on a value Cursor does not
|
|
56
|
+
* override.
|
|
57
|
+
*
|
|
58
|
+
* ⚠ And note the direction against Cursor's own default: **60 s**. Registering
|
|
59
|
+
* 3 s TIGHTENS by twenty-fold. An omitted `timeout` would leave a wedged hook
|
|
60
|
+
* stalling an agent step for a full minute, so writing this field is not
|
|
61
|
+
* ceremony — it is the difference between a 2.2 s worst case and a 60 s one.
|
|
62
|
+
*
|
|
63
|
+
* ## ⛔ WHAT THIS MEASUREMENT IS, AND WHAT IT IS NOT
|
|
64
|
+
*
|
|
65
|
+
* It is a reading of **shipped code**, not an observed firing — the same
|
|
66
|
+
* standing as D205's strings, and the same limit. It proves what Cursor's
|
|
67
|
+
* validator accepts and what its executor does with the value. It does **not**
|
|
68
|
+
* prove a hook of ours has been fired by a real Cursor session and killed at
|
|
69
|
+
* 3 s. That confirmation is cheap now that the signal is known — **exit code
|
|
70
|
+
* 124** and `Hook … timed out after 3s` in Cursor's log — and it is owed.
|
|
71
|
+
*
|
|
72
|
+
* ⚠ **Both clauses of the old header were false when it was written, which is
|
|
73
|
+
* why this row existed.** It said *"Nothing installs a Cursor hook today, so no
|
|
74
|
+
* Cursor hook has ever fired and these numbers remain unreachable in
|
|
75
|
+
* production."* `CURSOR` carries a `hookConfig`, so `installAgentHooks` writes
|
|
76
|
+
* `~/.cursor/hooks.json` on any machine with Cursor present — and that shipped
|
|
77
|
+
* in `0.2.0` and is live in `0.2.3`. The numbers were reachable, in users'
|
|
78
|
+
* hands, and documented as unreachable.
|
|
43
79
|
*/
|
|
44
80
|
const CURSOR_REGISTERED_MS = 3_000;
|
|
45
81
|
const CURSOR_CLIENT_MS = 2_200;
|
|
@@ -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
|
|
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"}
|
package/dist/commands/auth.d.ts
CHANGED
|
@@ -36,19 +36,27 @@
|
|
|
36
36
|
* that discipline theatre. It is refused loudly rather than quietly ignored,
|
|
37
37
|
* because a user who typed it needs to be told it is now in their history.
|
|
38
38
|
*
|
|
39
|
-
* ##
|
|
39
|
+
* ## ⚠ Browser FIRST since `CR-226`; the paste is the fallback, not gone
|
|
40
40
|
*
|
|
41
|
-
*
|
|
42
|
-
* `vibecommit-mcp`
|
|
43
|
-
* plaintext
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
41
|
+
* This note used to say the verb "is not sign-in, and it does not mint", because
|
|
42
|
+
* `mintIngestCredential` in `vibecommit-mcp` had no route in front of it and the
|
|
43
|
+
* only plaintext producer was the web app's `POST /api/ingest-credentials`. The
|
|
44
|
+
* route now exists (`POST /oauth/ingest-credential`), so on a terminal this verb
|
|
45
|
+
* signs in through the browser and mints the credential there
|
|
46
|
+
* (`browser_credential.ts`). The paste below is unchanged and still reached:
|
|
47
47
|
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
48
|
+
* - `--stdin`, or no TTY — the CI shape. Never a browser, exactly as before.
|
|
49
|
+
* - `--paste` — a human who already holds a credential from the dashboard.
|
|
50
|
+
* - any browser step failing — its reason is printed, then the prompt runs.
|
|
51
|
+
*
|
|
52
|
+
* ⚠ So there is no path on which `CR-226` makes the pre-existing flow worse:
|
|
53
|
+
* every failure lands on the prompt that was the whole verb before it.
|
|
54
|
+
*
|
|
55
|
+
* @provenance vibecommit-mcp src/transport/server.ts — POST /oauth/ingest-credential is the mint route, verified
|
|
56
|
+
* @provenance vibecommit-web app/api/ingest-credentials/route.ts — the dashboard producer the paste path still serves, verified
|
|
50
57
|
*/
|
|
51
58
|
import type { CommandContext } from "./context.js";
|
|
59
|
+
import type { BrowserCredential, Retry } from "./browser_credential.js";
|
|
52
60
|
export interface AuthDeps {
|
|
53
61
|
/**
|
|
54
62
|
* Read the secret from a terminal WITHOUT ECHOING IT. Injected for the same
|
|
@@ -62,5 +70,12 @@ export interface AuthDeps {
|
|
|
62
70
|
readonly readSecret?: (prompt: string) => Promise<string>;
|
|
63
71
|
/** Read the whole of stdin. The pipe path, and the only path on a non-TTY. */
|
|
64
72
|
readonly readStdin?: () => Promise<string>;
|
|
73
|
+
/**
|
|
74
|
+
* The browser mint — `CR-226`. Injected like the two readers above, and for
|
|
75
|
+
* the same reason: left out, this verb is the paste-only verb it was before,
|
|
76
|
+
* so a test that forgot to stub it cannot reach a server or open a browser.
|
|
77
|
+
* `src/index.ts` passes the real one.
|
|
78
|
+
*/
|
|
79
|
+
readonly browser?: (ctx: CommandContext, retry: Retry) => Promise<BrowserCredential>;
|
|
65
80
|
}
|
|
66
81
|
export declare function auth(ctx: CommandContext, argv: readonly string[], deps?: AuthDeps): Promise<number>;
|