@songsid/agend 2.1.5-beta.1 → 2.1.5-beta.2

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.
@@ -0,0 +1,450 @@
1
+ /**
2
+ * Remote `/login` in web-terminal mode (v2.1.5, design docs/design/login-relay-v2).
3
+ *
4
+ * The fleet starts ONE command (`<backend> login`, optionally preceded by a
5
+ * deterministic pre-command such as `kiro-cli logout`) in a dedicated tmux
6
+ * server and opens a time-boxed, token-gated browser terminal onto that pane
7
+ * (src/web-terminal.ts). The human drives whatever TUI the CLI shows; the
8
+ * fleet only observes the pane to post the device URL/code, judge success and
9
+ * restart the backend's instances afterwards. Zero LLM involvement.
10
+ *
11
+ * Authorization surface owned here (sol reviews this as a new surface):
12
+ * - admin-only, re-checked inside start() — callers are not trusted
13
+ * - one login/install window fleet-wide, claimed SYNCHRONOUSLY before the
14
+ * first await (LoginWindowLock, shared with relay login and install)
15
+ * - only flows reviewed as having no shell escape (`noShellEscape: true`)
16
+ * may open a terminal; per-requester rate limit 3 starts / 5 min
17
+ * - every start goes through a risk confirmation button (design §3.2); the
18
+ * kiro variant also states that the CLI will be logged out first
19
+ * - the link (no secret) goes to the requesting chat; the one-time access
20
+ * token goes ONLY to the requester privately (adapter.sendDirect). If the
21
+ * private route fails the token is never posted in the channel: the
22
+ * admin gets a requester-only "resend" button. If neither the link nor a
23
+ * token route can be delivered, the session is cancelled — never left
24
+ * half-delivered.
25
+ * - errors from secret-bearing sends are logged as name/code only; every
26
+ * step is audited to the event log without token/cookie/URL secrets
27
+ * - fleet shutdown cancels the active session and waits for the confirmed
28
+ * tmux kill (no orphaned login CLI)
29
+ */
30
+ import { homedir } from "node:os";
31
+ import { LOGIN_BACKEND_ALIASES, LOGIN_FLOWS, checkAuthStatus } from "./login-flows.js";
32
+ import { t } from "./locale.js";
33
+ import { MAX_TTL_MS, TmuxTerminalBackend, WebTerminalSession, } from "./web-terminal.js";
34
+ import { WebTerminalHttpServer } from "./web-terminal-http.js";
35
+ export const LOGIN_TOKEN_RESEND_PREFIX = "login-token:";
36
+ export const DEFAULT_WEB_TERMINAL_TTL_MINUTES = 10;
37
+ /** Design §3.1: a requester may create at most this many sessions per window. */
38
+ export const START_RATE_LIMIT = 3;
39
+ export const START_RATE_WINDOW_MS = 5 * 60_000;
40
+ /**
41
+ * Describe an error from a secret-bearing operation WITHOUT trusting anything
42
+ * the error carries (sol B2/B3): message, name and code are all
43
+ * provider-controlled and any of them may echo the payload (the token). Only
44
+ * a bounded, errno-like code survives; everything else is a constant.
45
+ */
46
+ const KNOWN_ERRNO = new Set([
47
+ "EPIPE", "ECONNRESET", "ECONNREFUSED", "ETIMEDOUT", "ENOTFOUND", "EAI_AGAIN", "ENETUNREACH", "EHOSTUNREACH",
48
+ "EACCES", "EPERM", "ENOENT", "ECONNABORTED", "ERR_NETWORK", "ABORT_ERR", "ETELEGRAM", "UND_ERR_CONNECT_TIMEOUT",
49
+ ]);
50
+ function safeErr(err) {
51
+ const code = err?.code;
52
+ if (typeof code === "number" && Number.isFinite(code))
53
+ return { errorKind: "error", errno: code };
54
+ // Exact set only — a shape-based allowlist overlaps the token's own shape (sol round 3 B2).
55
+ if (typeof code === "string" && KNOWN_ERRNO.has(code))
56
+ return { errorKind: "error", errno: code };
57
+ return { errorKind: "error" };
58
+ }
59
+ export class LoginController {
60
+ deps;
61
+ active = null;
62
+ /** True between shutdown() and reopen(): no new work is accepted. */
63
+ stopping = false;
64
+ /**
65
+ * Monotonic: incremented by every shutdown(), never rolled back by reopen().
66
+ * A continuation captures it at start and treats any change as "the world
67
+ * I started in is gone" — so a stopAll→startAll cannot resurrect old work.
68
+ */
69
+ shutdownGeneration = 0;
70
+ backendFactory = new TmuxTerminalBackend();
71
+ startTimes = new Map();
72
+ constructor(deps) {
73
+ this.deps = deps;
74
+ }
75
+ isActive() { return this.active !== null; }
76
+ get activeBackend() { return this.active?.backend ?? null; }
77
+ /** Configured mode; web is the default, relay is the 2.1.5-only rollback. */
78
+ mode() {
79
+ return this.deps.fleetConfig()?.login?.mode === "relay" ? "relay" : "web";
80
+ }
81
+ /**
82
+ * Start a web-terminal login. Returns a status line, or null when the
83
+ * confirmation buttons were posted instead (every start is confirmed once;
84
+ * the button calls back with skipAuthCheck + tokenPresent).
85
+ */
86
+ async start(backendArg, chat, opts = {}) {
87
+ const backend = LOGIN_BACKEND_ALIASES[backendArg.toLowerCase()] ?? backendArg.toLowerCase();
88
+ const flow = LOGIN_FLOWS[backend];
89
+ if (!flow)
90
+ return t("login.unsupported", backendArg);
91
+ if (flow.remoteLogin === "unsupported") {
92
+ this.audit("declined_unsupported", { backend, requester: chat.userId ?? null });
93
+ return t("login.remote_unsupported_agent_cli", backend, flow.command);
94
+ }
95
+ if (this.stopping)
96
+ return t("login.web_shutting_down");
97
+ const generation = this.shutdownGeneration;
98
+ // Authorization is decided HERE, not by whoever called us.
99
+ if (!chat.userId || !this.deps.isFleetAdmin(chat.userId, chat.adapterId)) {
100
+ this.audit("denied", { backend, requester: chat.userId ?? null, adapterId: chat.adapterId });
101
+ return t("permission.denied");
102
+ }
103
+ const cfg = this.deps.fleetConfig();
104
+ if (cfg?.web_terminal?.enabled === false)
105
+ return t("login.web_disabled");
106
+ if (flow.noShellEscape !== true) {
107
+ this.audit("flow_not_allowed", { backend, requester: chat.userId });
108
+ return t("login.web_flow_not_allowed", backend);
109
+ }
110
+ if (!this.admitRate(chat.userId)) {
111
+ this.audit("rate_limited", { backend, requester: chat.userId });
112
+ return t("login.web_rate_limited", String(START_RATE_LIMIT), String(START_RATE_WINDOW_MS / 60_000));
113
+ }
114
+ // Reserve the fleet-wide window NOW, before any await (sol B1). From here
115
+ // on the claim is owned by this region: every exit releases it unless it
116
+ // was transferred to a published session (`transferred`), and every
117
+ // resumption after an await re-checks that the claim is still current —
118
+ // a fleet shutdown in between must stop us (sol round 2 B1/B2).
119
+ const claim = this.deps.claimWindow(backend);
120
+ if (!claim)
121
+ return this.deps.windowBusyMessage();
122
+ let transferred = false;
123
+ try {
124
+ return await this.startClaimed(flow, backend, chat, opts, cfg, claim, generation, () => { transferred = true; });
125
+ }
126
+ finally {
127
+ if (!transferred)
128
+ this.deps.releaseWindow(claim);
129
+ }
130
+ }
131
+ /** The fence every continuation applies after an await: stopping, a newer shutdown generation, or a lost claim. */
132
+ stale(generation, claim) {
133
+ return this.stopping || this.shutdownGeneration !== generation || (claim !== null && !this.deps.isClaimCurrent(claim));
134
+ }
135
+ async startClaimed(flow, backend, chat, opts, cfg, claim, generation, markTransferred) {
136
+ if (!opts.skipAuthCheck) {
137
+ // First pass: find out whether the CLI still holds a token, then ask for
138
+ // the explicit go-ahead (design §3.2). The button re-enters with the
139
+ // answer; the window is released (by the caller's finally) meanwhile.
140
+ let tokenPresent = false;
141
+ if (flow.authCheck)
142
+ tokenPresent = (await (this.deps.checkAuth ?? checkAuthStatus)(flow.authCheck)) === "valid";
143
+ if (this.stale(generation, claim))
144
+ return t("login.web_shutting_down");
145
+ this.deps.releaseWindow(claim); // nothing runs until the button is pressed
146
+ const logoutFirst = tokenPresent && flow.preCommand?.when === "token-present";
147
+ try {
148
+ await this.deps.postButtons({
149
+ prefix: "login-confirm:",
150
+ instanceName: backend,
151
+ chat,
152
+ message: logoutFirst
153
+ ? `${t("login.web_confirm", backend)}\n${t("login.still_valid_precommand", backend, flow.preCommand.command)}`
154
+ : t("login.web_confirm", backend),
155
+ choices: [
156
+ { action: tokenPresent ? "go-relogin" : "go", label: t("login.web_confirm_go") },
157
+ { action: "cancel", label: t("login.relogin_cancel") },
158
+ ],
159
+ expiredText: t("buttons.stale"),
160
+ });
161
+ }
162
+ catch (err) {
163
+ this.deps.logger.warn({ ...safeErr(err), backend }, "Failed to post login confirmation");
164
+ return t("login.failed", backend, t("login.web_confirm_failed"));
165
+ }
166
+ if (this.stale(generation, null)) {
167
+ // The prompt went out while (or after) we stopped. It cannot be
168
+ // withdrawn from here; a click on it starts a FRESH start() that runs
169
+ // every check again (admin, allowlist, rate, claim, generation).
170
+ this.audit("stale_confirmation", { backend, requester: chat.userId });
171
+ }
172
+ return null;
173
+ }
174
+ const userId = chat.userId;
175
+ const command = this.buildCommand(flow, opts.tokenPresent === true);
176
+ const ttlMs = this.ttlMs(cfg);
177
+ const spec = {
178
+ kind: "login",
179
+ backend,
180
+ command,
181
+ cwd: process.env.HOME ?? homedir(),
182
+ ttlMs,
183
+ observe: {
184
+ urlPattern: flow.urlPattern,
185
+ codePattern: flow.codePattern,
186
+ successPattern: flow.successPattern,
187
+ failures: flow.failures,
188
+ },
189
+ requester: { adapterId: chat.adapterId, userId, chatId: chat.chatId, threadId: chat.threadId },
190
+ };
191
+ const logger = this.deps.logger;
192
+ const entry = {
193
+ claim, session: null, http: null, backend, chat,
194
+ requesterUserId: userId, url: "", tokenDelivered: false, silent: false,
195
+ };
196
+ const events = {
197
+ onHint: (url, code) => this.sendHint(chat, backend, url, code),
198
+ onDone: async (result) => {
199
+ this.releaseEntry(entry);
200
+ if (!entry.silent)
201
+ await this.reportDone(chat, backend, result);
202
+ },
203
+ onAudit: (event, fields) => this.audit(event.replace(/^web_terminal_/, ""), fields),
204
+ };
205
+ // A throwing factory must not leak the claim: the caller's finally covers
206
+ // it because `markTransferred` is only called once the entry is published.
207
+ entry.session = (this.deps.createSession ?? ((s, e, l) => new WebTerminalSession(s, e, this.backendFactory, l)))(spec, events, logger);
208
+ this.active = entry;
209
+ markTransferred(); // from here the entry owns the claim (onDone/abort/shutdown release it)
210
+ this.noteStart(userId);
211
+ try {
212
+ await entry.session.start();
213
+ if (this.stale(generation, claim))
214
+ return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
215
+ const http = (this.deps.createHttp ?? ((s, l, o) => new WebTerminalHttpServer(s, l, o)))(entry.session, logger, {
216
+ bind: cfg?.web_terminal?.bind,
217
+ hostname: cfg?.hostname || "localhost",
218
+ });
219
+ entry.http = http;
220
+ entry.url = (await http.listen()).url;
221
+ if (this.stale(generation, claim))
222
+ return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
223
+ }
224
+ catch (err) {
225
+ return this.abort(entry, t("login.failed", backend, err.message), "startup failed");
226
+ }
227
+ // Delivery is part of starting: a link nobody received, or a token that
228
+ // reached neither the requester nor a resend button, means the session
229
+ // must not stay open (sol M1). A shutdown landing during any of these
230
+ // awaits aborts instead of announcing a terminal that no longer exists.
231
+ if (this.stale(generation, claim))
232
+ return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
233
+ if (!(await this.sendLink(entry, Math.round(ttlMs / 60_000), command))) {
234
+ return this.abort(entry, t("login.failed", backend, t("login.web_link_failed")), "link delivery failed");
235
+ }
236
+ if (this.stale(generation, claim))
237
+ return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
238
+ if (!(await this.sendToken(entry, { offerResend: true }))) {
239
+ return this.abort(entry, t("login.failed", backend, t("login.web_token_failed")), "token delivery failed");
240
+ }
241
+ if (this.stale(generation, claim))
242
+ return this.abort(entry, t("login.web_shutting_down"), "fleet shutdown");
243
+ return t("login.web_started", backend);
244
+ }
245
+ /** `/login cancel` in web mode. */
246
+ async cancel() {
247
+ if (!this.active)
248
+ return t("login.no_session");
249
+ const backend = this.active.backend;
250
+ await this.active.session.cancel("cancelled");
251
+ return t("login.cancelled", backend);
252
+ }
253
+ /**
254
+ * Fleet shutdown: end the active session and wait for the confirmed tmux
255
+ * kill (sol B3). Pre-active continuations (a start parked in its pre-check)
256
+ * are fenced by the lock being closed by FleetManager before this call —
257
+ * they observe !isClaimCurrent and stop without posting or starting.
258
+ */
259
+ async shutdown() {
260
+ this.stopping = true;
261
+ this.shutdownGeneration++;
262
+ const entry = this.active;
263
+ if (!entry)
264
+ return;
265
+ entry.silent = true;
266
+ // No timer race here: cancel() is single-flight and bounded by the
267
+ // engine's own per-op timeouts (abort → ≤1 more 10 s stage → confirmed
268
+ // kill ≤31 s). Releasing ownership early would let the fleet exit while
269
+ // the dedicated tmux server is still being torn down.
270
+ await entry.session.cancel("fleet shutdown").catch(err => this.deps.logger.warn(safeErr(err), "web login shutdown failed"));
271
+ await entry.http?.close().catch(() => { });
272
+ this.releaseEntry(entry);
273
+ }
274
+ /** In-process restart after shutdown(): accept work again. */
275
+ reopen() { this.stopping = false; }
276
+ /** The "resend token" button: only the requester, only while the token is unredeemed. */
277
+ async resendToken(requesterUserId) {
278
+ const entry = this.active;
279
+ if (!entry)
280
+ return t("login.no_session");
281
+ if (!requesterUserId || requesterUserId !== entry.requesterUserId) {
282
+ this.audit("token_resend_denied", { backend: entry.backend, requester: requesterUserId ?? null });
283
+ return t("permission.denied");
284
+ }
285
+ if (entry.session.peekAccessToken() === null)
286
+ return t("login.web_token_already_used");
287
+ const ok = await this.sendToken(entry, { offerResend: false, isResend: true });
288
+ if (ok)
289
+ return t("login.web_token_resent");
290
+ // The one recovery route failed too: do not leave a token-less session
291
+ // holding the fleet-wide window until TTL (sol M1). Close it now.
292
+ return this.abort(entry, t("login.web_token_resend_failed", entry.backend), "token resend failed");
293
+ }
294
+ // ── Internals ──
295
+ admitRate(userId) {
296
+ const now = (this.deps.now ?? Date.now)();
297
+ const recent = (this.startTimes.get(userId) ?? []).filter(ts => now - ts < START_RATE_WINDOW_MS);
298
+ this.startTimes.set(userId, recent);
299
+ return recent.length < START_RATE_LIMIT;
300
+ }
301
+ noteStart(userId) {
302
+ const now = (this.deps.now ?? Date.now)();
303
+ this.startTimes.set(userId, [...(this.startTimes.get(userId) ?? []), now]);
304
+ }
305
+ releaseEntry(entry) {
306
+ if (this.active === entry)
307
+ this.active = null; // identity guard: never clear a newer owner
308
+ this.deps.releaseWindow(entry.claim);
309
+ }
310
+ /** Startup/delivery failure: end the session quietly and hand the caller the one report. */
311
+ async abort(entry, report, detail) {
312
+ entry.silent = true;
313
+ if (entry.session.state === "running") {
314
+ await entry.session.cancel(detail).catch(err => this.deps.logger.warn(safeErr(err), "web login abort failed"));
315
+ }
316
+ await entry.http?.close().catch(() => { });
317
+ this.releaseEntry(entry);
318
+ this.audit("aborted", { backend: entry.backend, requester: entry.requesterUserId, detail });
319
+ return report;
320
+ }
321
+ buildCommand(flow, tokenPresent) {
322
+ const pre = flow.preCommand;
323
+ const runPre = pre && (pre.when === "always" || (pre.when === "token-present" && tokenPresent));
324
+ return runPre ? `${pre.command}; ${flow.command}` : flow.command;
325
+ }
326
+ ttlMs(cfg) {
327
+ const minutes = cfg?.web_terminal?.ttl_minutes ?? DEFAULT_WEB_TERMINAL_TTL_MINUTES;
328
+ const clamped = Math.min(Math.max(1, Math.floor(minutes)), MAX_TTL_MS / 60_000);
329
+ return clamped * 60_000;
330
+ }
331
+ /** The link carries no secret: it may go to the requesting chat. Returns delivery success. */
332
+ async sendLink(entry, ttlMinutes, command) {
333
+ const { chat } = entry;
334
+ const text = t("login.web_link", entry.backend, String(ttlMinutes), command, entry.url);
335
+ try {
336
+ await chat.adapter.sendText(chat.chatId, text, { threadId: chat.threadId, disablePreview: true });
337
+ this.audit("link_sent", { backend: entry.backend, requester: entry.requesterUserId, host: hostOf(entry.url) });
338
+ return true;
339
+ }
340
+ catch (err) {
341
+ this.deps.logger.warn({ ...safeErr(err), backend: entry.backend }, "Failed to deliver web terminal link");
342
+ this.audit("link_failed", { backend: entry.backend, requester: entry.requesterUserId, ...safeErr(err) });
343
+ return false;
344
+ }
345
+ }
346
+ /**
347
+ * The access token goes ONLY to the requester, privately. Never into the
348
+ * channel: if the private route fails, offer a resend button instead.
349
+ * Returns true when the token was delivered OR the resend button was posted.
350
+ */
351
+ async sendToken(entry, o) {
352
+ const { chat } = entry;
353
+ const token = entry.session.peekAccessToken();
354
+ if (token === null)
355
+ return true; // already redeemed: nothing to deliver
356
+ const body = chat.adapter.type === "telegram"
357
+ ? `${escapeHtml(t("login.web_token", entry.backend))}\n<tg-spoiler>${escapeHtml(token)}</tg-spoiler>`
358
+ : `${t("login.web_token", entry.backend)}\n\`${token}\``;
359
+ if (typeof chat.adapter.sendDirect === "function") {
360
+ try {
361
+ await chat.adapter.sendDirect(entry.requesterUserId, body, {
362
+ format: chat.adapter.type === "telegram" ? "html" : "text",
363
+ disablePreview: true,
364
+ });
365
+ entry.tokenDelivered = true;
366
+ this.audit(o.isResend ? "token_resent" : "token_sent", { backend: entry.backend, requester: entry.requesterUserId, via: "dm" });
367
+ return true;
368
+ }
369
+ catch (err) {
370
+ // Never the message: a provider error may echo the payload (the token).
371
+ this.deps.logger.warn({ ...safeErr(err), backend: entry.backend }, "web terminal token DM failed");
372
+ }
373
+ }
374
+ entry.tokenDelivered = false;
375
+ this.audit("token_dm_failed", { backend: entry.backend, requester: entry.requesterUserId });
376
+ if (!o.offerResend)
377
+ return false;
378
+ try {
379
+ await this.deps.postButtons({
380
+ prefix: LOGIN_TOKEN_RESEND_PREFIX,
381
+ instanceName: entry.backend,
382
+ chat,
383
+ message: t("login.web_token_dm_failed"),
384
+ choices: [{ action: "resend", label: t("login.web_resend") }],
385
+ expiredText: t("buttons.stale"),
386
+ });
387
+ return true;
388
+ }
389
+ catch (err) {
390
+ this.deps.logger.warn({ ...safeErr(err), backend: entry.backend }, "Failed to post token resend button");
391
+ return false;
392
+ }
393
+ }
394
+ /** Device URL (+ code) observed in the pane: same spoiler treatment as before. Errors logged without the message. */
395
+ async sendHint(chat, backend, url, code) {
396
+ const codeLine = code ? `\n${t("login.auth_code", code)}` : "";
397
+ try {
398
+ if (chat.adapter.type === "telegram") {
399
+ await chat.adapter.sendText(chat.chatId, `${escapeHtml(t("login.auth_hint", backend))}\n<tg-spoiler>${escapeHtml(url)}${escapeHtml(codeLine)}</tg-spoiler>`, { threadId: chat.threadId, format: "html" });
400
+ }
401
+ else {
402
+ await chat.adapter.sendText(chat.chatId, `${t("login.auth_hint", backend)}\n${url}${codeLine}`, { threadId: chat.threadId });
403
+ }
404
+ }
405
+ catch (err) {
406
+ this.deps.logger.warn({ ...safeErr(err), backend }, "Failed to deliver login URL");
407
+ }
408
+ }
409
+ async reportDone(chat, backend, result) {
410
+ let text;
411
+ if (result.ok) {
412
+ const { woken, restarted } = await this.deps.recoverBackendInstances(backend);
413
+ const none = t("login.none");
414
+ text = t("login.success", backend, woken.length ? woken.join(", ") : none, restarted.length ? restarted.join(", ") : none);
415
+ }
416
+ else if (result.reason === "cancel" && result.detail === "cancelled") {
417
+ text = ""; // the cancel command's own reply already announced this
418
+ }
419
+ else {
420
+ text = t("login.failed", backend, result.detail);
421
+ if (result.suggest === "relogin")
422
+ text += `\n${t("login.web_suggest_relogin")}`;
423
+ else if (result.suggest === "check-args")
424
+ text += `\n${t("login.web_suggest_check_args")}`;
425
+ }
426
+ if (result.cleanupFailed)
427
+ text += `${text ? "\n" : ""}${t("login.web_cleanup_failed", backend)}`;
428
+ if (!text)
429
+ return;
430
+ await chat.adapter.sendText(chat.chatId, text, { threadId: chat.threadId }).catch(() => { });
431
+ }
432
+ audit(event, fields) {
433
+ try {
434
+ this.deps.eventLog()?.insert("login", `login_web_${event}`, fields);
435
+ }
436
+ catch { /* never break the flow */ }
437
+ }
438
+ }
439
+ function escapeHtml(s) {
440
+ return s.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;");
441
+ }
442
+ function hostOf(url) {
443
+ try {
444
+ return new URL(url).host;
445
+ }
446
+ catch {
447
+ return "?";
448
+ }
449
+ }
450
+ //# sourceMappingURL=login-controller.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"login-controller.js","sourceRoot":"","sources":["../src/login-controller.ts"],"names":[],"mappings":"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"}
@@ -4,9 +4,18 @@
4
4
  * Every command, menu label, prompt, and success string here was verified
5
5
  * against the installed CLI (`--help` output or strings extracted from the
6
6
  * release binary) rather than written from memory:
7
- * - codex: `codex login --device-auth`, success "Successfully logged in"
8
- * - grok: `grok login --device-auth`, success "Login successful!"
9
- * - kiro: `kiro-cli login --use-device-flow`; free/pro selector is the
7
+ * - codex: `codex login --device-auth`, success "Successfully logged in".
8
+ * The flag is KEPT on purpose (live-verified codex 0.153.4,
9
+ * headless): plain `codex login` starts a callback server on the
10
+ * HOST's localhost:1455 and waits for a browser redirect the
11
+ * admin's browser can never deliver; codex itself prints "On a
12
+ * remote or headless machine? Use `codex login --device-auth`".
13
+ * The flag is hidden from `--help` but still accepted.
14
+ * - grok: `grok login` — plain login already IS device-code on grok 1.0.5
15
+ * (identical output to `--device-auth`), success "Login successful!"
16
+ * - kiro: `kiro-cli login` (plain, per user request): shows the four-way
17
+ * selector incl. "Your Organization"; Identity Center is device-code
18
+ * regardless of `--use-device-flow` (live-verified 2.21.1). The
10
19
  * arrow-key menu "Select login method" with exactly the four
11
20
  * options below; Identity Center then asks "Enter Start URL" /
12
21
  * "Enter Region"; success "Logged in successfully"/"Logged in with"
@@ -57,6 +66,35 @@ export interface LoginFlow {
57
66
  successPattern: RegExp;
58
67
  /** Hard cap for the whole login session. */
59
68
  timeoutMs: number;
69
+ /**
70
+ * Deterministic shell command run in the same window right before `command`
71
+ * (`pre; command`). `token-present`: only when the auth pre-check said the
72
+ * CLI still holds a token and the admin confirmed a re-login — kiro refuses
73
+ * `login` outright while any token record exists (live-verified 2.21.1).
74
+ */
75
+ preCommand?: {
76
+ command: string;
77
+ when: "always" | "token-present";
78
+ };
79
+ /** Known failure strings in the dead pane → human wording + suggested next step (web mode). */
80
+ failures?: Array<{
81
+ pattern: RegExp;
82
+ message: string;
83
+ suggest?: "relogin" | "check-args" | "retry";
84
+ }>;
85
+ /**
86
+ * Explicit allowlist for the web terminal (design §2.3/§3): set only after a
87
+ * human reviewed that this CLI's login TUI offers no shell escape. A flow
88
+ * without it never gets a browser terminal — the scope guarantee "one
89
+ * command, no shell" depends on it.
90
+ */
91
+ noShellEscape?: true;
92
+ /**
93
+ * Remote /login is declined outright for this backend (every mode), with a
94
+ * user-facing reason. Kept in LOGIN_FLOWS only for authCheck /
95
+ * loginScreenPattern, which the daemon still uses.
96
+ */
97
+ remoteLogin?: "unsupported";
60
98
  }
61
99
  /**
62
100
  * Cheap auth probes for backends that do not have an AgEnD-managed /login
@@ -4,9 +4,18 @@
4
4
  * Every command, menu label, prompt, and success string here was verified
5
5
  * against the installed CLI (`--help` output or strings extracted from the
6
6
  * release binary) rather than written from memory:
7
- * - codex: `codex login --device-auth`, success "Successfully logged in"
8
- * - grok: `grok login --device-auth`, success "Login successful!"
9
- * - kiro: `kiro-cli login --use-device-flow`; free/pro selector is the
7
+ * - codex: `codex login --device-auth`, success "Successfully logged in".
8
+ * The flag is KEPT on purpose (live-verified codex 0.153.4,
9
+ * headless): plain `codex login` starts a callback server on the
10
+ * HOST's localhost:1455 and waits for a browser redirect the
11
+ * admin's browser can never deliver; codex itself prints "On a
12
+ * remote or headless machine? Use `codex login --device-auth`".
13
+ * The flag is hidden from `--help` but still accepted.
14
+ * - grok: `grok login` — plain login already IS device-code on grok 1.0.5
15
+ * (identical output to `--device-auth`), success "Login successful!"
16
+ * - kiro: `kiro-cli login` (plain, per user request): shows the four-way
17
+ * selector incl. "Your Organization"; Identity Center is device-code
18
+ * regardless of `--use-device-flow` (live-verified 2.21.1). The
10
19
  * arrow-key menu "Select login method" with exactly the four
11
20
  * options below; Identity Center then asks "Enter Start URL" /
12
21
  * "Enter Region"; success "Logged in successfully"/"Logged in with"
@@ -43,7 +52,10 @@ const GENERIC_URL = /https:\/\/[^\s"'<>\])]+/;
43
52
  const STANDALONE_DEVICE_CODE = /^\s*([A-Z0-9]{4,10}-[A-Z0-9]{4,10})\s*$/m;
44
53
  export const LOGIN_FLOWS = {
45
54
  "codex": {
55
+ noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
46
56
  backend: "codex",
57
+ // Keep --device-auth: plain login needs a browser redirect to THIS host's
58
+ // localhost:1455, which a remote admin's browser cannot deliver (see header).
47
59
  command: "codex login --device-auth",
48
60
  authCheck: { argv: ["codex", "login", "status"] },
49
61
  loginScreenPattern: /Sign in with ChatGPT/,
@@ -52,8 +64,9 @@ export const LOGIN_FLOWS = {
52
64
  timeoutMs: LOGIN_TIMEOUT_MS,
53
65
  },
54
66
  "grok": {
67
+ noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
55
68
  backend: "grok",
56
- command: "grok login --device-auth",
69
+ command: "grok login",
57
70
  authCheck: { argv: ["grok", "models"] },
58
71
  loginScreenPattern: /Run `grok login`/,
59
72
  // Binary template is "enter code: $CODE"; the standalone form is a fallback.
@@ -62,8 +75,9 @@ export const LOGIN_FLOWS = {
62
75
  timeoutMs: LOGIN_TIMEOUT_MS,
63
76
  },
64
77
  "kiro-cli": {
78
+ noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
65
79
  backend: "kiro-cli",
66
- command: "kiro-cli login --use-device-flow",
80
+ command: "kiro-cli login",
67
81
  authCheck: { argv: ["kiro-cli", "whoami", "--format", "json"] },
68
82
  loginScreenPattern: /Select login method/,
69
83
  menu: {
@@ -76,8 +90,18 @@ export const LOGIN_FLOWS = {
76
90
  codePattern: /Code:\s*([A-Z0-9][A-Z0-9-]{3,})/,
77
91
  successPattern: /Logged in successfully|Logged in with /,
78
92
  timeoutMs: LOGIN_TIMEOUT_MS,
93
+ // kiro-cli 2.21.1 (live-verified): any stored token record — even an
94
+ // expired one with a refresh token — makes `login` exit 1 with "Already
95
+ // logged in"; only `logout` clears it. The saved Identity Center start
96
+ // URL/region survive logout and pre-fill the prompts in the terminal.
97
+ preCommand: { command: "kiro-cli logout", when: "token-present" },
98
+ failures: [
99
+ { pattern: /Already logged in, please logout/, message: "kiro-cli still holds a token — use Re-login (it logs out first)", suggest: "relogin" },
100
+ { pattern: /error: dispatch failure/, message: "Identity Center rejected the request — check the Start URL and Region", suggest: "check-args" },
101
+ ],
79
102
  },
80
103
  "claude-code": {
104
+ noShellEscape: true, // login TUI reviewed: menu/prompts/device code only, no shell
81
105
  backend: "claude-code",
82
106
  command: "claude auth login",
83
107
  authCheck: { argv: ["claude", "auth", "status"], validPattern: /"loggedIn":\s*true/ },
@@ -87,6 +111,14 @@ export const LOGIN_FLOWS = {
87
111
  timeoutMs: LOGIN_TIMEOUT_MS,
88
112
  },
89
113
  "antigravity": {
114
+ // Remote login is UNSUPPORTED (user decision, v2.1.5): bare `agy` is the
115
+ // full agent CLI (tools, permission prompts, MCP) and has no isolated
116
+ // login sub-command — "logging in" means running the whole agent, which
117
+ // violates the web terminal's "one login command, no shell" boundary.
118
+ // /login agy is declined in every mode (no relay fallback) until upstream
119
+ // ships a dedicated login command. authCheck / loginScreenPattern stay for
120
+ // the daemon's own use.
121
+ remoteLogin: "unsupported",
90
122
  backend: "antigravity",
91
123
  command: "agy",
92
124
  authCheck: { argv: ["agy", "models"] },