shadok-ai 0.17.2 → 0.17.3
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/setup-token.d.ts +44 -32
- package/dist/setup-token.js +172 -159
- package/dist/setup-token.js.map +1 -1
- package/package.json +1 -1
package/dist/setup-token.d.ts
CHANGED
|
@@ -4,53 +4,64 @@
|
|
|
4
4
|
* its privilege-dropped group agents (they have no keychain). This module drives
|
|
5
5
|
* `claude setup-token` and hands the minted token to a server-supplied store.
|
|
6
6
|
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
7
|
+
* Driven inside a detached **tmux** session — the same engine as every agent
|
|
8
|
+
* (`src/tmux.ts`, `TmuxPilot`). tmux owns a real terminal, so the `isatty` gate
|
|
9
|
+
* `setup-token` enforces is satisfied, AND we read the terminal **already
|
|
10
|
+
* rendered** (`capture-pane -p`) instead of reverse-engineering a raw
|
|
11
|
+
* OSC-8/CSI escape stream. That removed an entire class of parsing fragility —
|
|
12
|
+
* the exact class that made the first (node-pty) implementation silently miss
|
|
13
|
+
* the minted token at the success screen.
|
|
14
14
|
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
15
|
+
* Behaviours measured live against the real CLI (v2.1.317, 2026-11-03), driving
|
|
16
|
+
* it through tmux exactly as this module does:
|
|
17
|
+
* - The URL prints as plain text on its own line (pane set wide so it never
|
|
18
|
+
* wraps): "Browser didn't open? Use the url below…", then the authorize URL.
|
|
19
|
+
* - The code box is a MASKED field ("Paste code here if prompted > *****"), so
|
|
20
|
+
* we wait for the typed code to land before pressing Enter.
|
|
21
|
+
* - A bad code renders "OAuth error: Invalid code. Please make sure the full
|
|
22
|
+
* code was copied" followed by "Press Enter to retry" — the retry needs an
|
|
23
|
+
* explicit Enter to return to the paste prompt (it does not re-prompt alone).
|
|
24
|
+
*
|
|
25
|
+
* The token value must never be logged, returned, or surfaced in an error: the
|
|
26
|
+
* flow stores it server-side and reports only ok/err.
|
|
18
27
|
*
|
|
19
28
|
* See docs/superpowers/specs/2026-11-03-mt-token-provisioning-design.md.
|
|
20
29
|
*/
|
|
21
30
|
/**
|
|
22
|
-
* Strip terminal escapes
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
31
|
+
* Strip terminal escapes. `capture-pane -p` is already rendered plain text, so
|
|
32
|
+
* this is a defensive no-op there — but it also lets the parsers accept a raw
|
|
33
|
+
* stream (OSC-8 links, CSI cursor moves, two-char escapes) in tests and if a
|
|
34
|
+
* caller ever feeds one.
|
|
26
35
|
*/
|
|
27
36
|
export declare function stripTokenEscapes(s: string): string;
|
|
28
37
|
/**
|
|
29
|
-
* Pure: the OAuth URL
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
* link has been printed.
|
|
38
|
+
* Pure: the OAuth URL. From the rendered pane it is a bare `https://` on its own
|
|
39
|
+
* line; the OSC-8 target form is still accepted (a raw stream, or a future CLI
|
|
40
|
+
* that emits a hyperlink). Null until the link has been printed.
|
|
33
41
|
*/
|
|
34
42
|
export declare function parseSetupTokenUrl(chunk: string): string | null;
|
|
35
43
|
/**
|
|
36
|
-
* Pure: the refusal the CLI printed for a submitted code, if any.
|
|
37
|
-
*
|
|
38
|
-
* the
|
|
44
|
+
* Pure: the refusal the CLI printed for a submitted code, if any. There is
|
|
45
|
+
* deliberately NO guessed success string — success is the token appearing (see
|
|
46
|
+
* the flow). The real wording is "OAuth error: Invalid code. …"; matching the
|
|
47
|
+
* "Invalid code" substring is enough and robust to the surrounding text.
|
|
39
48
|
*/
|
|
40
49
|
export declare function parseSetupTokenOutcome(chunk: string): "invalid-code" | null;
|
|
41
50
|
/**
|
|
42
|
-
* Pure: the minted token (`sk-ant-oat01-…`) from the
|
|
43
|
-
* Stripping first rejoins a token
|
|
44
|
-
*
|
|
45
|
-
* line-wraps (a hard wrap inserts a real
|
|
51
|
+
* Pure: the minted token (`sk-ant-oat01-…`) from the rendered text, or null.
|
|
52
|
+
* Stripping first rejoins a token a raw stream split with cursor escapes; from a
|
|
53
|
+
* `capture-pane` the text is already clean. The flow renders the pane wide
|
|
54
|
+
* enough that the ~108-char token never line-wraps (a hard wrap inserts a real
|
|
55
|
+
* newline no strip can undo).
|
|
46
56
|
*/
|
|
47
57
|
export declare function parseSetupToken(chunk: string): string | null;
|
|
48
58
|
export declare function setupTokenPending(): boolean;
|
|
49
59
|
export declare function cancelSetupToken(): void;
|
|
50
60
|
/**
|
|
51
|
-
* Start `claude setup-token`
|
|
52
|
-
* flow that already printed a URL (a reload or a second door
|
|
53
|
-
* link the operator is looking at — each flow carries its own
|
|
61
|
+
* Start `claude setup-token` in a detached tmux session and resolve its OAuth
|
|
62
|
+
* URL. Reuses a live flow that already printed a URL (a reload or a second door
|
|
63
|
+
* must not kill the link the operator is looking at — each flow carries its own
|
|
64
|
+
* PKCE challenge).
|
|
54
65
|
*/
|
|
55
66
|
export declare function startSetupToken(): Promise<{
|
|
56
67
|
url: string;
|
|
@@ -58,10 +69,11 @@ export declare function startSetupToken(): Promise<{
|
|
|
58
69
|
error: string;
|
|
59
70
|
}>;
|
|
60
71
|
/**
|
|
61
|
-
* Submit the pasted code.
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
72
|
+
* Submit the pasted code. Types it into the masked box, waits for it to land,
|
|
73
|
+
* presses Enter, then watches the rendered pane for the token or the
|
|
74
|
+
* invalid-code refusal. Success = the token appears; it is handed to `store`
|
|
75
|
+
* and the flow is torn down. The token is NEVER returned, logged, or surfaced
|
|
76
|
+
* in an error.
|
|
65
77
|
*/
|
|
66
78
|
export declare function submitSetupTokenCode(code: string, store: (token: string) => void): Promise<{
|
|
67
79
|
ok: true;
|
package/dist/setup-token.js
CHANGED
|
@@ -1,47 +1,53 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
2
|
import { claudeCommand } from "./claude-bin.js";
|
|
3
|
+
import { tmuxAvailable, tmuxHasSession, tmuxKillSession } from "./tmux.js";
|
|
3
4
|
/**
|
|
4
5
|
* In-cockpit provisioning of the long-lived subscription token
|
|
5
6
|
* (`MT_CLAUDE_OAUTH_TOKEN`), the credential a multi-tenant instance injects into
|
|
6
7
|
* its privilege-dropped group agents (they have no keychain). This module drives
|
|
7
8
|
* `claude setup-token` and hands the minted token to a server-supplied store.
|
|
8
9
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* `
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
10
|
+
* Driven inside a detached **tmux** session — the same engine as every agent
|
|
11
|
+
* (`src/tmux.ts`, `TmuxPilot`). tmux owns a real terminal, so the `isatty` gate
|
|
12
|
+
* `setup-token` enforces is satisfied, AND we read the terminal **already
|
|
13
|
+
* rendered** (`capture-pane -p`) instead of reverse-engineering a raw
|
|
14
|
+
* OSC-8/CSI escape stream. That removed an entire class of parsing fragility —
|
|
15
|
+
* the exact class that made the first (node-pty) implementation silently miss
|
|
16
|
+
* the minted token at the success screen.
|
|
16
17
|
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
18
|
+
* Behaviours measured live against the real CLI (v2.1.317, 2026-11-03), driving
|
|
19
|
+
* it through tmux exactly as this module does:
|
|
20
|
+
* - The URL prints as plain text on its own line (pane set wide so it never
|
|
21
|
+
* wraps): "Browser didn't open? Use the url below…", then the authorize URL.
|
|
22
|
+
* - The code box is a MASKED field ("Paste code here if prompted > *****"), so
|
|
23
|
+
* we wait for the typed code to land before pressing Enter.
|
|
24
|
+
* - A bad code renders "OAuth error: Invalid code. Please make sure the full
|
|
25
|
+
* code was copied" followed by "Press Enter to retry" — the retry needs an
|
|
26
|
+
* explicit Enter to return to the paste prompt (it does not re-prompt alone).
|
|
27
|
+
*
|
|
28
|
+
* The token value must never be logged, returned, or surfaced in an error: the
|
|
29
|
+
* flow stores it server-side and reports only ok/err.
|
|
20
30
|
*
|
|
21
31
|
* See docs/superpowers/specs/2026-11-03-mt-token-provisioning-design.md.
|
|
22
32
|
*/
|
|
33
|
+
// ─── Pure parsers (fed the rendered pane text) ───────────────────────────────
|
|
23
34
|
/**
|
|
24
|
-
* Strip terminal escapes
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
35
|
+
* Strip terminal escapes. `capture-pane -p` is already rendered plain text, so
|
|
36
|
+
* this is a defensive no-op there — but it also lets the parsers accept a raw
|
|
37
|
+
* stream (OSC-8 links, CSI cursor moves, two-char escapes) in tests and if a
|
|
38
|
+
* caller ever feeds one.
|
|
28
39
|
*/
|
|
29
40
|
export function stripTokenEscapes(s) {
|
|
30
41
|
return s
|
|
31
|
-
// OSC-8 open (params ; url) and close (;;), ST or BEL terminated.
|
|
32
42
|
.replace(/\x1b\]8;[^;]*;[^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
|
|
33
|
-
// Any other OSC (window title, etc.), ST or BEL terminated.
|
|
34
43
|
.replace(/\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)/g, "")
|
|
35
|
-
// CSI: ESC [ <params 0x30-3f> <intermediates 0x20-2f> <final 0x40-7e>.
|
|
36
44
|
.replace(/\x1b\[[\x30-\x3f]*[\x20-\x2f]*[\x40-\x7e]/g, "")
|
|
37
|
-
// Two-char escapes (ESC 7/8 save/restore, ESC =/> keypad, etc.).
|
|
38
45
|
.replace(/\x1b[=>78MD]/g, "");
|
|
39
46
|
}
|
|
40
47
|
/**
|
|
41
|
-
* Pure: the OAuth URL
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* link has been printed.
|
|
48
|
+
* Pure: the OAuth URL. From the rendered pane it is a bare `https://` on its own
|
|
49
|
+
* line; the OSC-8 target form is still accepted (a raw stream, or a future CLI
|
|
50
|
+
* that emits a hyperlink). Null until the link has been printed.
|
|
45
51
|
*/
|
|
46
52
|
export function parseSetupTokenUrl(chunk) {
|
|
47
53
|
const osc = chunk.match(/\x1b\]8;[^;]*;(https:\/\/[^\x07\x1b]+?)(?:\x07|\x1b\\)/);
|
|
@@ -51,189 +57,196 @@ export function parseSetupTokenUrl(chunk) {
|
|
|
51
57
|
return bare ? bare[0] : null;
|
|
52
58
|
}
|
|
53
59
|
/**
|
|
54
|
-
* Pure: the refusal the CLI printed for a submitted code, if any.
|
|
55
|
-
*
|
|
56
|
-
* the
|
|
60
|
+
* Pure: the refusal the CLI printed for a submitted code, if any. There is
|
|
61
|
+
* deliberately NO guessed success string — success is the token appearing (see
|
|
62
|
+
* the flow). The real wording is "OAuth error: Invalid code. …"; matching the
|
|
63
|
+
* "Invalid code" substring is enough and robust to the surrounding text.
|
|
57
64
|
*/
|
|
58
65
|
export function parseSetupTokenOutcome(chunk) {
|
|
59
66
|
return /Invalid code/i.test(stripTokenEscapes(chunk)) ? "invalid-code" : null;
|
|
60
67
|
}
|
|
61
68
|
/**
|
|
62
|
-
* Pure: the minted token (`sk-ant-oat01-…`) from the
|
|
63
|
-
* Stripping first rejoins a token
|
|
64
|
-
*
|
|
65
|
-
* line-wraps (a hard wrap inserts a real
|
|
69
|
+
* Pure: the minted token (`sk-ant-oat01-…`) from the rendered text, or null.
|
|
70
|
+
* Stripping first rejoins a token a raw stream split with cursor escapes; from a
|
|
71
|
+
* `capture-pane` the text is already clean. The flow renders the pane wide
|
|
72
|
+
* enough that the ~108-char token never line-wraps (a hard wrap inserts a real
|
|
73
|
+
* newline no strip can undo).
|
|
66
74
|
*/
|
|
67
75
|
export function parseSetupToken(chunk) {
|
|
68
76
|
const m = stripTokenEscapes(chunk).match(/sk-ant-oat01-[A-Za-z0-9_-]+/);
|
|
69
77
|
return m ? m[0] : null;
|
|
70
78
|
}
|
|
71
|
-
|
|
79
|
+
// ─── The flow (impure, tmux-driven) ──────────────────────────────────────────
|
|
80
|
+
/** The OAuth URL expires anyway; a child holding the session forever is worse. */
|
|
72
81
|
const FLOW_IDLE_MS = 10 * 60_000;
|
|
73
|
-
/** Wide enough that the ~
|
|
74
|
-
* real newline no escape-strip can undo). */
|
|
75
|
-
const
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
* pasted from either finishes the other's.
|
|
80
|
-
*/
|
|
82
|
+
/** Wide enough that neither the ~346-char URL nor the ~108-char token wraps (a
|
|
83
|
+
* hard wrap inserts a real newline no escape-strip can undo). */
|
|
84
|
+
const PANE_COLS = 400;
|
|
85
|
+
const PANE_ROWS = 50;
|
|
86
|
+
/** One flow at a time for the whole instance — the tmux session name is fixed. */
|
|
87
|
+
const SESSION = "sk-mttoken";
|
|
81
88
|
let flow = null;
|
|
82
|
-
|
|
83
|
-
return
|
|
89
|
+
function tmux(args, input) {
|
|
90
|
+
return execFileSync("tmux", args, {
|
|
91
|
+
encoding: "utf8",
|
|
92
|
+
input,
|
|
93
|
+
timeout: 10_000,
|
|
94
|
+
maxBuffer: 8 * 1024 * 1024,
|
|
95
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
96
|
+
});
|
|
84
97
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
clearTimeout(flow.timer);
|
|
98
|
+
/** The rendered pane. `scrollback` also pulls history, so a line that scrolled
|
|
99
|
+
* off the visible screen (e.g. the token above a trailing prompt) is caught. */
|
|
100
|
+
function capture(scrollback = false) {
|
|
89
101
|
try {
|
|
90
|
-
|
|
102
|
+
const args = ["capture-pane", "-t", SESSION, "-p"];
|
|
103
|
+
if (scrollback)
|
|
104
|
+
args.push("-S", "-");
|
|
105
|
+
return tmux(args);
|
|
91
106
|
}
|
|
92
107
|
catch {
|
|
93
|
-
|
|
108
|
+
return "";
|
|
94
109
|
}
|
|
110
|
+
}
|
|
111
|
+
function sleep(ms) {
|
|
112
|
+
return new Promise((r) => setTimeout(r, ms));
|
|
113
|
+
}
|
|
114
|
+
export function setupTokenPending() {
|
|
115
|
+
return flow !== null && !flow.ended && tmuxHasSession(SESSION);
|
|
116
|
+
}
|
|
117
|
+
export function cancelSetupToken() {
|
|
118
|
+
if (flow)
|
|
119
|
+
clearTimeout(flow.timer);
|
|
120
|
+
tmuxKillSession(SESSION);
|
|
95
121
|
flow = null;
|
|
96
122
|
}
|
|
97
123
|
/**
|
|
98
|
-
* Start `claude setup-token`
|
|
99
|
-
* flow that already printed a URL (a reload or a second door
|
|
100
|
-
* link the operator is looking at — each flow carries its own
|
|
124
|
+
* Start `claude setup-token` in a detached tmux session and resolve its OAuth
|
|
125
|
+
* URL. Reuses a live flow that already printed a URL (a reload or a second door
|
|
126
|
+
* must not kill the link the operator is looking at — each flow carries its own
|
|
127
|
+
* PKCE challenge).
|
|
101
128
|
*/
|
|
102
129
|
export async function startSetupToken() {
|
|
103
|
-
if (flow && !flow.ended && flow.url)
|
|
130
|
+
if (flow && !flow.ended && flow.url && tmuxHasSession(SESSION))
|
|
104
131
|
return { url: flow.url };
|
|
105
132
|
cancelSetupToken();
|
|
106
|
-
|
|
133
|
+
if (!tmuxAvailable())
|
|
134
|
+
return { error: "tmux is not available on this host — it is required to provision the token" };
|
|
135
|
+
// Build the command the way TmuxPilot does: `env` — unsetting any inherited
|
|
136
|
+
// CLAUDE*/CLAUDECODE/AI_AGENT vars (a parent session's token or child-session
|
|
137
|
+
// marker must not reach this nested claude) — then the assignments and the
|
|
138
|
+
// binary, each token single-quoted so values with specials are safe. BROWSER
|
|
139
|
+
// is neutralised so the CLI never tries to open a tab on the SERVER's machine.
|
|
140
|
+
const bin = claudeCommand();
|
|
141
|
+
const unset = Object.keys(process.env)
|
|
142
|
+
.filter((k) => /^(CLAUDE|CLAUDECODE|AI_AGENT)/.test(k))
|
|
143
|
+
.flatMap((k) => ["-u", k]);
|
|
144
|
+
const cmd = ["env", ...unset, "TERM=xterm-256color", "BROWSER=/usr/bin/true", bin, "setup-token"]
|
|
145
|
+
.map((a) => `'${String(a).replace(/'/g, "'\\''")}'`)
|
|
146
|
+
.join(" ");
|
|
107
147
|
try {
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
cols: PTY_COLS,
|
|
111
|
-
rows: 40,
|
|
112
|
-
cwd: process.cwd(),
|
|
113
|
-
// Neutralise BROWSER so the CLI never opens a tab on the SERVER's machine.
|
|
114
|
-
env: { ...process.env, BROWSER: "/usr/bin/true" },
|
|
115
|
-
});
|
|
148
|
+
tmuxKillSession(SESSION); // clear any stale session of the same name
|
|
149
|
+
tmux(["new-session", "-d", "-s", SESSION, "-x", String(PANE_COLS), "-y", String(PANE_ROWS), "-c", process.cwd(), cmd]);
|
|
116
150
|
}
|
|
117
151
|
catch (e) {
|
|
118
152
|
return { error: "could not start claude setup-token: " + (e instanceof Error ? e.message : String(e)) };
|
|
119
153
|
}
|
|
120
|
-
const f = {
|
|
121
|
-
proc,
|
|
122
|
-
url: null,
|
|
123
|
-
out: "",
|
|
124
|
-
pending: null,
|
|
125
|
-
ended: false,
|
|
126
|
-
timer: setTimeout(() => cancelSetupToken(), FLOW_IDLE_MS),
|
|
127
|
-
};
|
|
154
|
+
const f = { url: null, ended: false, timer: setTimeout(() => cancelSetupToken(), FLOW_IDLE_MS) };
|
|
128
155
|
flow = f;
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
f
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
// scrub. The token flows solely into `f.out` (scrubbed on settle), and only
|
|
137
|
-
// while `f.pending` is set, i.e. strictly after the URL and the code.
|
|
138
|
-
let all = "";
|
|
139
|
-
proc.onData((d) => {
|
|
140
|
-
if (!f.url) {
|
|
141
|
-
all += d;
|
|
142
|
-
const u = parseSetupTokenUrl(all);
|
|
143
|
-
if (u) {
|
|
144
|
-
f.url = u;
|
|
145
|
-
all = ""; // done with it — never hold the minted token here
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
if (f.pending) {
|
|
149
|
-
f.out += d;
|
|
150
|
-
const tok = parseSetupToken(f.out);
|
|
151
|
-
if (tok) {
|
|
152
|
-
settle({ kind: "success", token: tok });
|
|
153
|
-
}
|
|
154
|
-
else if (parseSetupTokenOutcome(f.out) === "invalid-code") {
|
|
155
|
-
settle({ kind: "invalid-code" });
|
|
156
|
-
}
|
|
156
|
+
const deadline = Date.now() + 30_000;
|
|
157
|
+
while (Date.now() < deadline) {
|
|
158
|
+
if (flow !== f || f.ended)
|
|
159
|
+
return { error: "the setup-token process ended before printing a URL" };
|
|
160
|
+
if (!tmuxHasSession(SESSION)) {
|
|
161
|
+
cancelSetupToken();
|
|
162
|
+
return { error: "the setup-token process ended before printing a URL" };
|
|
157
163
|
}
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const tok = parseSetupToken(f.out);
|
|
163
|
-
settle(tok ? { kind: "success", token: tok } : { kind: "ended" });
|
|
164
|
+
const url = parseSetupTokenUrl(capture());
|
|
165
|
+
if (url) {
|
|
166
|
+
f.url = url;
|
|
167
|
+
return { url };
|
|
164
168
|
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
resolve({ error: "claude setup-token printed no URL within 30s" });
|
|
170
|
-
}, 30_000);
|
|
171
|
-
const poll = setInterval(() => {
|
|
172
|
-
if (f.url) {
|
|
173
|
-
clearInterval(poll);
|
|
174
|
-
clearTimeout(deadline);
|
|
175
|
-
resolve({ url: f.url });
|
|
176
|
-
}
|
|
177
|
-
else if (flow !== f || f.ended) {
|
|
178
|
-
clearInterval(poll);
|
|
179
|
-
clearTimeout(deadline);
|
|
180
|
-
resolve({ error: "the setup-token process ended before printing a URL" });
|
|
181
|
-
}
|
|
182
|
-
}, 100);
|
|
183
|
-
});
|
|
169
|
+
await sleep(300);
|
|
170
|
+
}
|
|
171
|
+
cancelSetupToken();
|
|
172
|
+
return { error: "claude setup-token printed no URL within 30s" };
|
|
184
173
|
}
|
|
185
174
|
/**
|
|
186
|
-
* Submit the pasted code.
|
|
187
|
-
*
|
|
188
|
-
*
|
|
189
|
-
*
|
|
175
|
+
* Submit the pasted code. Types it into the masked box, waits for it to land,
|
|
176
|
+
* presses Enter, then watches the rendered pane for the token or the
|
|
177
|
+
* invalid-code refusal. Success = the token appears; it is handed to `store`
|
|
178
|
+
* and the flow is torn down. The token is NEVER returned, logged, or surfaced
|
|
179
|
+
* in an error.
|
|
190
180
|
*/
|
|
191
181
|
export async function submitSetupTokenCode(code, store) {
|
|
192
182
|
const f = flow;
|
|
193
|
-
if (!f || f.ended)
|
|
183
|
+
if (!f || f.ended || !tmuxHasSession(SESSION))
|
|
194
184
|
return { ok: false, error: "no setup-token flow is in progress — start one first" };
|
|
195
185
|
const trimmed = code.trim();
|
|
196
186
|
if (!trimmed)
|
|
197
187
|
return { ok: false, error: "empty code" };
|
|
198
188
|
clearTimeout(f.timer);
|
|
199
189
|
f.timer = setTimeout(() => cancelSetupToken(), FLOW_IDLE_MS);
|
|
200
|
-
//
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
// The
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
190
|
+
// Type the code (literal — the code may contain '#', '-', etc.), then WAIT for
|
|
191
|
+
// it to appear in the masked box before Enter: a no-delay Enter submits an
|
|
192
|
+
// empty field and nothing happens (measured).
|
|
193
|
+
try {
|
|
194
|
+
tmux(["send-keys", "-t", SESSION, "-l", "--", trimmed]);
|
|
195
|
+
}
|
|
196
|
+
catch {
|
|
197
|
+
return { ok: false, error: "the setup-token flow ended — start a new one and try again" };
|
|
198
|
+
}
|
|
199
|
+
const entered = Date.now() + 4_000;
|
|
200
|
+
while (Date.now() < entered) {
|
|
201
|
+
// The prompt line gains masked chars (or the code) after the ">".
|
|
202
|
+
const line = capture().split("\n").find((l) => /Paste code here/i.test(l)) ?? "";
|
|
203
|
+
if (/Paste code here[^>]*>\s*\S/i.test(line))
|
|
204
|
+
break;
|
|
205
|
+
await sleep(200);
|
|
206
|
+
}
|
|
207
|
+
try {
|
|
208
|
+
tmux(["send-keys", "-t", SESSION, "Enter"]);
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
return { ok: false, error: "the setup-token flow ended — start a new one and try again" };
|
|
212
|
+
}
|
|
213
|
+
const deadline = Date.now() + 30_000;
|
|
214
|
+
while (Date.now() < deadline) {
|
|
215
|
+
// Scrollback included: on success the token may sit above a trailing line.
|
|
216
|
+
// NB: this buffer can contain the token — it must never be logged.
|
|
217
|
+
const screen = capture(true);
|
|
218
|
+
const tok = parseSetupToken(screen);
|
|
219
|
+
if (tok) {
|
|
220
|
+
try {
|
|
221
|
+
store(tok);
|
|
222
|
+
}
|
|
223
|
+
catch (e) {
|
|
224
|
+
// Never leak the token in the error. Tear down (the token was already
|
|
225
|
+
// printed; there is no live prompt to retry) and ask for a fresh flow.
|
|
226
|
+
cancelSetupToken();
|
|
227
|
+
return { ok: false, error: "storing the token failed — start a new one: " + (e instanceof Error ? e.message : "unknown") };
|
|
228
|
+
}
|
|
229
|
+
cancelSetupToken();
|
|
230
|
+
return { ok: true };
|
|
217
231
|
}
|
|
218
|
-
|
|
219
|
-
//
|
|
220
|
-
//
|
|
221
|
-
|
|
222
|
-
|
|
232
|
+
if (parseSetupTokenOutcome(screen) === "invalid-code") {
|
|
233
|
+
// The CLI shows "Press Enter to retry" → press Enter to return to the
|
|
234
|
+
// paste prompt, keeping the flow (and its PKCE URL) alive for a retry.
|
|
235
|
+
try {
|
|
236
|
+
tmux(["send-keys", "-t", SESSION, "Enter"]);
|
|
237
|
+
}
|
|
238
|
+
catch {
|
|
239
|
+
/* session gone — the next submit will report it */
|
|
240
|
+
}
|
|
241
|
+
return { ok: false, error: "Invalid code. Please make sure the full code was copied." };
|
|
242
|
+
}
|
|
243
|
+
if (!tmuxHasSession(SESSION)) {
|
|
244
|
+
f.ended = true;
|
|
223
245
|
cancelSetupToken();
|
|
224
|
-
return {
|
|
225
|
-
ok: false,
|
|
226
|
-
error: "storing the token failed — start a new one: " + (e instanceof Error ? e.message : "unknown"),
|
|
227
|
-
};
|
|
246
|
+
return { ok: false, error: "the setup-token flow ended — start a new one and try again" };
|
|
228
247
|
}
|
|
229
|
-
|
|
230
|
-
cancelSetupToken();
|
|
231
|
-
return { ok: true };
|
|
248
|
+
await sleep(300);
|
|
232
249
|
}
|
|
233
|
-
if (verdict.kind === "invalid-code")
|
|
234
|
-
return { ok: false, error: "Invalid code. Please make sure the full code was copied." };
|
|
235
|
-
if (verdict.kind === "ended")
|
|
236
|
-
return { ok: false, error: "the setup-token flow ended — start a new one and try again" };
|
|
237
250
|
return { ok: false, error: "the CLI did not answer within 30s" };
|
|
238
251
|
}
|
|
239
252
|
//# sourceMappingURL=setup-token.js.map
|
package/dist/setup-token.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"setup-token.js","sourceRoot":"","sources":["../src/setup-token.ts"],"names":[],"mappings":"AAAA,OAAO,
|
|
1
|
+
{"version":3,"file":"setup-token.js","sourceRoot":"","sources":["../src/setup-token.ts"],"names":[],"mappings":"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"}
|