@ait-co/console-cli 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -22
- package/dist/cli.mjs +831 -412
- package/dist/cli.mjs.map +1 -1
- package/package.json +2 -2
package/dist/cli.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.mjs","names":[],"sources":["../src/browser.ts","../src/exit.ts","../src/flush.ts","../src/oauth.ts","../src/paths.ts","../src/session.ts","../src/commands/login.ts","../src/commands/logout.ts","../src/github.ts","../src/platform.ts","../src/semver.ts","../src/version.ts","../src/commands/upgrade.ts","../src/commands/whoami.ts","../src/cli.ts"],"sourcesContent":["import { spawn } from 'node:child_process';\n\n// Best-effort cross-platform \"open this URL in the default browser\". On\n// failure the caller prints the URL so the user can copy it manually. We\n// deliberately avoid pulling in an `open`-package dependency — the matrix we\n// care about (macOS / Linux / Windows) is tiny.\n\nexport interface OpenBrowserResult {\n readonly launched: boolean;\n}\n\nexport function openBrowser(url: string): Promise<OpenBrowserResult> {\n // Allow tests and headless environments to skip the spawn entirely.\n if (process.env.AIT_CONSOLE_NO_BROWSER === '1') {\n return Promise.resolve({ launched: false });\n }\n\n // Refuse anything that isn't a plain http(s) URL — belt-and-suspenders\n // against accidentally feeding `file://`, `javascript:`, or a shell\n // metacharacter-laden input into the platform opener.\n try {\n const parsed = new URL(url);\n if (parsed.protocol !== 'http:' && parsed.protocol !== 'https:') {\n return Promise.resolve({ launched: false });\n }\n } catch {\n return Promise.resolve({ launched: false });\n }\n\n return new Promise((resolve) => {\n try {\n if (process.platform === 'win32') {\n // `cmd /c start` needs the `\"\"` window-title placeholder so a URL\n // containing `&` isn't reinterpreted. `windowsVerbatimArguments`\n // keeps Node from re-quoting our already-quoted arguments, which\n // would otherwise corrupt URLs with special characters. We also\n // wrap the URL in double-quotes so an intervening literal space\n // (rare but legal through redirects/proxies) is treated as part\n // of the argument.\n const quotedUrl = `\"${url.replace(/\"/g, '%22')}\"`;\n const child = spawn('cmd', ['/c', 'start', '\"\"', quotedUrl], {\n stdio: 'ignore',\n detached: true,\n windowsHide: true,\n windowsVerbatimArguments: true,\n });\n child.once('error', () => resolve({ launched: false }));\n child.once('spawn', () => {\n child.unref();\n resolve({ launched: true });\n });\n return;\n }\n const command = process.platform === 'darwin' ? 'open' : 'xdg-open';\n const child = spawn(command, [url], {\n stdio: 'ignore',\n detached: true,\n });\n child.once('error', () => resolve({ launched: false }));\n child.once('spawn', () => {\n child.unref();\n resolve({ launched: true });\n });\n } catch {\n resolve({ launched: false });\n }\n });\n}\n","// Centralized exit codes so every command and the agent-plugin side agree.\n\nexport const ExitCode = {\n Ok: 0,\n Generic: 1,\n Usage: 2,\n NotAuthenticated: 10,\n NetworkError: 11,\n LoginTimeout: 12,\n LoginStateMismatch: 13,\n UpgradeUnavailable: 20,\n UpgradeAlreadyLatest: 21,\n} as const;\n\nexport type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];\n","// Flush-safe exit: drain stdout before calling `process.exit` so a piped\n// consumer never loses the final JSON line. Callers typically write the\n// JSON payload (or plain-text result) to stdout immediately before\n// calling `return exitAfterFlush(code)`.\n\nexport async function exitAfterFlush(code: number): Promise<never> {\n await new Promise<void>((resolve) => process.stdout.write('', () => resolve()));\n process.exit(code);\n}\n","import { randomBytes, timingSafeEqual } from 'node:crypto';\nimport { createServer, type IncomingMessage, type Server, type ServerResponse } from 'node:http';\nimport type { AddressInfo } from 'node:net';\n\n// Localhost OAuth callback server. Binds to 127.0.0.1 on an ephemeral port,\n// waits for exactly one request to `/callback`, validates the `state`\n// parameter, and resolves with the query fields. The server is single-use —\n// subsequent requests receive 410 Gone and the server closes on settle.\n//\n// The Toss developer console OAuth URL and token-exchange flow are not\n// publicly documented (see CLAUDE.md § \"Open questions\"), so this module is\n// deliberately generic: it only knows how to receive a redirect, not how to\n// shape the outbound authorize URL. The caller composes that URL.\n\nexport interface CallbackQuery {\n readonly code: string;\n readonly state: string;\n readonly raw: Record<string, string>;\n}\n\nexport interface CallbackServer {\n readonly port: number;\n readonly redirectUri: string;\n readonly expectedState: string;\n waitForCallback(): Promise<CallbackQuery>;\n close(): Promise<void>;\n}\n\nexport interface StartCallbackServerOptions {\n // Overall timeout for waitForCallback, in ms. Defaults to 5 minutes.\n readonly timeoutMs?: number;\n // Preferred port. 0 = OS-assigned ephemeral. If a preferred port is in use,\n // the server transparently falls back to 0.\n readonly preferredPort?: number;\n}\n\nexport class CallbackTimeoutError extends Error {\n constructor(seconds: number) {\n super(`Login timed out after ${seconds}s`);\n this.name = 'CallbackTimeoutError';\n }\n}\n\nexport class CallbackStateMismatchError extends Error {\n constructor() {\n super('Invalid or missing state parameter');\n this.name = 'CallbackStateMismatchError';\n }\n}\n\nexport class CallbackMissingCodeError extends Error {\n constructor() {\n super('Missing code parameter');\n this.name = 'CallbackMissingCodeError';\n }\n}\n\nexport function randomState(): string {\n // 32 bytes → 43 chars base64url. Sufficient for CSRF.\n return randomBytes(32).toString('base64url');\n}\n\n// Constant-time string comparison. Falls back to a simple `false` when the\n// lengths differ, matching `crypto.timingSafeEqual`'s own precondition.\nfunction constantTimeStringEqual(a: string, b: string): boolean {\n const bufA = Buffer.from(a, 'utf8');\n const bufB = Buffer.from(b, 'utf8');\n if (bufA.length !== bufB.length) return false;\n return timingSafeEqual(bufA, bufB);\n}\n\nfunction escapeHtml(s: string): string {\n return s\n .replace(/&/g, '&')\n .replace(/</g, '<')\n .replace(/>/g, '>')\n .replace(/\"/g, '"')\n .replace(/'/g, ''')\n .replace(/\\//g, '/')\n .replace(/`/g, '`');\n}\n\ntype ParseResult =\n | { kind: 'ok'; query: CallbackQuery }\n | { kind: 'state-mismatch' }\n | { kind: 'missing-code' }\n | { kind: 'malformed' }\n | { kind: 'not-found' };\n\nfunction parseCallbackUrl(reqUrl: string | undefined, expectedState: string): ParseResult {\n if (!reqUrl) return { kind: 'malformed' };\n let parsed: URL;\n try {\n parsed = new URL(reqUrl, 'http://127.0.0.1');\n } catch {\n return { kind: 'malformed' };\n }\n if (parsed.pathname !== '/callback') {\n return { kind: 'not-found' };\n }\n const raw: Record<string, string> = {};\n for (const [k, v] of parsed.searchParams) raw[k] = v;\n const state = raw.state ?? '';\n const code = raw.code ?? '';\n if (!state || !constantTimeStringEqual(state, expectedState)) {\n return { kind: 'state-mismatch' };\n }\n if (!code) {\n return { kind: 'missing-code' };\n }\n return { kind: 'ok', query: { code, state, raw } };\n}\n\nconst ERROR_MESSAGES: Record<Exclude<ParseResult['kind'], 'ok'>, string> = {\n 'state-mismatch': 'Invalid or missing state parameter',\n 'missing-code': 'Missing code parameter',\n malformed: 'Malformed request URL',\n 'not-found': 'Not found',\n};\n\nconst ERROR_STATUS: Record<Exclude<ParseResult['kind'], 'ok'>, number> = {\n 'state-mismatch': 400,\n 'missing-code': 400,\n malformed: 400,\n 'not-found': 404,\n};\n\nconst SUCCESS_HTML = `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><title>ait-console</title>\n<style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1rem;color:#222}h1{font-size:1.25rem}</style>\n</head>\n<body>\n<h1>Logged in to ait-console</h1>\n<p>You can close this window and return to your terminal.</p>\n</body></html>`;\n\nconst GONE_HTML = `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><title>ait-console</title>\n<style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1rem;color:#222}h1{font-size:1.25rem}</style>\n</head>\n<body>\n<h1>This login flow is already complete</h1>\n<p>Return to your terminal.</p>\n</body></html>`;\n\nfunction errorHtml(message: string): string {\n // Messages are currently a fixed enum (see ERROR_MESSAGES) but we escape\n // unconditionally so future callers can't introduce a reflected-XSS hole.\n return `<!doctype html>\n<html lang=\"en\">\n<head><meta charset=\"utf-8\"><title>ait-console — error</title>\n<style>body{font-family:system-ui,sans-serif;max-width:32rem;margin:4rem auto;padding:0 1rem;color:#222}h1{font-size:1.25rem;color:#b00020}</style>\n</head>\n<body>\n<h1>Login failed</h1>\n<p>${escapeHtml(message)}</p>\n<p>Return to your terminal for details.</p>\n</body></html>`;\n}\n\nasync function bindServer(server: Server, preferredPort: number | undefined): Promise<number> {\n const tryListen = (port: number): Promise<number> =>\n new Promise((resolve, reject) => {\n const onError = (err: Error) => {\n server.removeListener('error', onError);\n reject(err);\n };\n server.once('error', onError);\n server.listen(port, '127.0.0.1', () => {\n server.removeListener('error', onError);\n const addr = server.address() as AddressInfo | null;\n if (!addr) reject(new Error('Failed to bind callback server'));\n else resolve(addr.port);\n });\n });\n\n if (preferredPort && preferredPort !== 0) {\n try {\n return await tryListen(preferredPort);\n } catch (err) {\n if ((err as NodeJS.ErrnoException).code !== 'EADDRINUSE') throw err;\n // Fall through to ephemeral port.\n }\n }\n return tryListen(0);\n}\n\nexport async function startCallbackServer(\n options: StartCallbackServerOptions = {},\n): Promise<CallbackServer> {\n const timeoutMs = options.timeoutMs ?? 5 * 60 * 1000;\n const expectedState = randomState();\n\n const server: Server = createServer();\n // Bind to 127.0.0.1 only — never expose the callback on a routable address.\n // Try `preferredPort` first; on EADDRINUSE fall back to ephemeral (0).\n const boundPort = await bindServer(server, options.preferredPort);\n\n const redirectUri = `http://127.0.0.1:${boundPort}/callback`;\n\n let settled = false;\n let closed = false;\n let resolveCb!: (q: CallbackQuery) => void;\n let rejectCb!: (e: Error) => void;\n const waiter = new Promise<CallbackQuery>((resolve, reject) => {\n resolveCb = resolve;\n rejectCb = reject;\n });\n // Attach a noop catch so an early rejection (e.g. state mismatch fired\n // before the caller calls waitForCallback) is not treated as unhandled.\n // The real error surface is the Promise returned from waitForCallback(),\n // which re-receives the same rejection via its rejectCb. Don't route\n // diagnostics through this handler — it exists solely to appease the\n // runtime's unhandled-rejection tracker.\n waiter.catch(() => {});\n\n const finish = (outcome: { kind: 'ok'; q: CallbackQuery } | { kind: 'err'; e: Error }) => {\n if (settled) return;\n settled = true;\n if (outcome.kind === 'ok') resolveCb(outcome.q);\n else rejectCb(outcome.e);\n };\n\n server.on('request', (req: IncomingMessage, res: ServerResponse) => {\n // Once the flow is settled, further hits (duplicate redirects, noisy\n // extensions) get 410 Gone rather than another success page. This\n // prevents a late attacker-crafted callback from rendering as \"logged in\".\n if (settled) {\n res.statusCode = 410;\n res.setHeader('content-type', 'text/html; charset=utf-8');\n res.end(GONE_HTML);\n return;\n }\n const result = parseCallbackUrl(req.url, expectedState);\n if (result.kind === 'ok') {\n res.statusCode = 200;\n res.setHeader('content-type', 'text/html; charset=utf-8');\n // Settle only after the response body has actually been flushed to\n // the client — otherwise `server.close()` / `closeAllConnections()`\n // on the caller's side can tear the socket down mid-write and the\n // user sees \"connection reset\" instead of the success page.\n res.end(SUCCESS_HTML, () => finish({ kind: 'ok', q: result.query }));\n return;\n }\n const status = ERROR_STATUS[result.kind];\n const message = ERROR_MESSAGES[result.kind];\n res.statusCode = status;\n res.setHeader('content-type', 'text/html; charset=utf-8');\n // Don't settle on arbitrary 404s — the user might have a noisy\n // extension or favicon probe. Only settle on structural errors at the\n // /callback path itself. Once we settle (success or structural error),\n // every subsequent request — including a legitimate-looking redirect —\n // gets 410 Gone via the `settled` branch above. The first-wins contract\n // is intentional: a CSRF attacker can't race a real redirect by firing\n // a bad callback first (it'll reject with state-mismatch), and a noisy\n // browser reload after success can't re-render a login page.\n const onFlushed = () => {\n switch (result.kind) {\n case 'state-mismatch':\n finish({ kind: 'err', e: new CallbackStateMismatchError() });\n return;\n case 'missing-code':\n finish({ kind: 'err', e: new CallbackMissingCodeError() });\n return;\n case 'malformed':\n case 'not-found':\n // Don't settle on malformed or non-callback paths — a noisy\n // extension probing `/\\x00` or `/favicon.ico` shouldn't end\n // the flow. The real redirect will still resolve it.\n return;\n default:\n // Exhaustiveness check — a new ParseResult kind will fail to\n // compile here because `result` is narrowed to `never`.\n ((_: never) => {})(result);\n }\n };\n res.end(errorHtml(message), onFlushed);\n });\n\n const timer = setTimeout(() => {\n // Ceil so the reported number is an upper bound on the real cap —\n // a `timeoutMs` of 1500 ms reports \"after 2s\", never \"after 1s\".\n finish({ kind: 'err', e: new CallbackTimeoutError(Math.ceil(timeoutMs / 1000)) });\n }, timeoutMs);\n if (typeof timer.unref === 'function') timer.unref();\n\n const close = async (): Promise<void> => {\n if (closed) return;\n closed = true;\n clearTimeout(timer);\n // Race `server.close()` against a 1s timeout — a misbehaving keep-alive\n // client shouldn't be able to hold the CLI from exiting.\n await new Promise<void>((resolve) => {\n let done = false;\n const finishClose = () => {\n if (done) return;\n done = true;\n resolve();\n };\n server.close(() => finishClose());\n server.closeAllConnections?.();\n const fallback = setTimeout(finishClose, 1000);\n if (typeof fallback.unref === 'function') fallback.unref();\n });\n };\n\n return {\n port: boundPort,\n redirectUri,\n expectedState,\n waitForCallback: () => waiter,\n close,\n };\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n// Resolve the config directory following the XDG Base Directory spec on\n// POSIX systems and using %APPDATA% on Windows. Falls back gracefully if\n// environment variables are missing (e.g. minimal containers without HOME).\n\nconst APP_NAME = 'ait-console';\n\nexport function configDir(): string {\n if (process.platform === 'win32') {\n const appData = process.env.APPDATA;\n if (appData && appData.length > 0) return join(appData, APP_NAME);\n return join(homedir() || '.', 'AppData', 'Roaming', APP_NAME);\n }\n const xdg = process.env.XDG_CONFIG_HOME;\n if (xdg && xdg.length > 0) return join(xdg, APP_NAME);\n return join(homedir() || '.', '.config', APP_NAME);\n}\n\nexport function sessionFilePath(): string {\n return join(configDir(), 'session.json');\n}\n","import { chmod, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { configDir, sessionFilePath } from './paths.js';\n\n// Minimal, forward-compatible session shape. `cookies` and `origins` mirror\n// Playwright's `storageState` so a future `login` command can hand them\n// straight to a headless context.\n//\n// SECURITY: this module is the only place that touches the secret material.\n// - Never log raw cookies / origins.\n// - Treat file IO errors as \"no session\" in user-facing commands.\n\nexport interface SessionUser {\n id: string;\n email: string;\n displayName?: string;\n}\n\nexport interface Session {\n schemaVersion: 1;\n user: SessionUser;\n // Opaque Playwright storageState slots. Kept `unknown` to discourage\n // destructuring / logging anywhere except the login/deploy code paths.\n cookies: unknown[];\n origins: unknown[];\n capturedAt: string; // ISO-8601\n}\n\n// Public-safe projection for `whoami` and other diagnostics.\nexport interface SessionSummary {\n user: SessionUser;\n capturedAt: string;\n}\n\nfunction summarize(session: Session): SessionSummary {\n return { user: session.user, capturedAt: session.capturedAt };\n}\n\nexport async function readSession(): Promise<Session | null> {\n try {\n const raw = await readFile(sessionFilePath(), 'utf8');\n const parsed = JSON.parse(raw) as Session;\n if (parsed.schemaVersion !== 1) return null;\n if (!parsed.user || typeof parsed.user.id !== 'string') return null;\n if (typeof parsed.user.email !== 'string') return null;\n if (parsed.user.displayName !== undefined && typeof parsed.user.displayName !== 'string') {\n return null;\n }\n return parsed;\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return null;\n // Malformed / unreadable file — treat as no session so commands emit a\n // clean \"not logged in\" error instead of a stack trace.\n return null;\n }\n}\n\nexport async function readSessionSummary(): Promise<SessionSummary | null> {\n const s = await readSession();\n return s ? summarize(s) : null;\n}\n\nexport async function writeSession(session: Session): Promise<void> {\n const dir = dirname(sessionFilePath());\n await mkdir(dir, { recursive: true, mode: 0o700 });\n await writeFile(sessionFilePath(), JSON.stringify(session, null, 2), {\n mode: 0o600,\n });\n // writeFile's mode only applies on creation; tighten existing files too.\n try {\n await chmod(sessionFilePath(), 0o600);\n } catch {\n // Windows / exotic FS: best-effort only.\n }\n}\n\nexport async function clearSession(): Promise<{ existed: boolean }> {\n try {\n await unlink(sessionFilePath());\n return { existed: true };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return { existed: false };\n throw err;\n }\n}\n\nexport function sessionPathForDiagnostics(): string {\n return sessionFilePath();\n}\n\nexport function configDirForDiagnostics(): string {\n return configDir();\n}\n","import { defineCommand } from 'citty';\nimport { openBrowser } from '../browser.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport {\n CallbackMissingCodeError,\n type CallbackQuery,\n CallbackStateMismatchError,\n CallbackTimeoutError,\n startCallbackServer,\n} from '../oauth.js';\nimport { type Session, writeSession } from '../session.js';\n\n// The Toss developer console OAuth authorize URL and scope are not publicly\n// documented as of 2026-04. Override with `AIT_CONSOLE_OAUTH_URL` (and\n// optionally `AIT_CONSOLE_OAUTH_CLIENT_ID` / `AIT_CONSOLE_OAUTH_SCOPE`) while\n// discovery is in progress. Without the env var we refuse to run rather than\n// silently hit a placeholder endpoint.\n\n// Cap raw callback-query fields before writing them to the session file.\n// The real flow will replace this with a token-endpoint POST; until then,\n// accept only short, control-char-free strings for the user label. 128 is\n// well above the 254-char RFC 5321 email length cap and plausible display\n// names, so anything longer is presumed garbage.\nconst MAX_FIELD_LENGTH = 128;\n\n// Sentinel used when the (still-in-discovery) callback does not carry a\n// user identity. Using a fixed label avoids storing the raw OAuth `code`\n// as a user id, which would then leak through `whoami`.\nconst PENDING_USER_ID = 'pending:oauth-discovery';\n\nfunction sanitizeField(raw: string | undefined): string | undefined {\n if (typeof raw !== 'string') return undefined;\n if (raw.length === 0) return undefined;\n if (raw.length > MAX_FIELD_LENGTH) return undefined;\n // Reject control chars including CR/LF so a pasted value can't forge a log\n // line or break JSON emission.\n // biome-ignore lint/suspicious/noControlCharactersInRegex: explicit control-char filter\n if (/[\\x00-\\x1f\\x7f]/.test(raw)) return undefined;\n return raw;\n}\n\nfunction buildAuthorizeUrl(params: {\n readonly authorizeUrl: string;\n readonly redirectUri: string;\n readonly state: string;\n readonly clientId: string | undefined;\n readonly scope: string | undefined;\n}): string {\n const url = new URL(params.authorizeUrl);\n url.searchParams.set('response_type', 'code');\n url.searchParams.set('redirect_uri', params.redirectUri);\n url.searchParams.set('state', params.state);\n if (params.clientId) url.searchParams.set('client_id', params.clientId);\n if (params.scope) url.searchParams.set('scope', params.scope);\n return url.toString();\n}\n\nfunction classifyCallbackError(err: Error): {\n reason: 'timeout' | 'state-mismatch' | 'missing-code' | 'other';\n exitCode: ExitCode;\n} {\n if (err instanceof CallbackTimeoutError) {\n return { reason: 'timeout', exitCode: ExitCode.LoginTimeout };\n }\n if (err instanceof CallbackStateMismatchError) {\n return { reason: 'state-mismatch', exitCode: ExitCode.LoginStateMismatch };\n }\n if (err instanceof CallbackMissingCodeError) {\n return { reason: 'missing-code', exitCode: ExitCode.Generic };\n }\n return { reason: 'other', exitCode: ExitCode.Generic };\n}\n\nexport const loginCommand = defineCommand({\n meta: {\n name: 'login',\n description: 'Log in via the browser; starts a localhost callback server.',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n 'no-browser': {\n type: 'boolean',\n description: \"Don't auto-open the browser; print the URL for manual copy.\",\n default: false,\n },\n timeout: {\n type: 'string',\n description: 'Abort the login if no callback arrives within N seconds (default 300).',\n default: '300',\n },\n },\n async run({ args }) {\n const rawOauthUrl = process.env.AIT_CONSOLE_OAUTH_URL;\n const authorizeUrl = rawOauthUrl && rawOauthUrl.length > 0 ? rawOauthUrl : null;\n const clientId = process.env.AIT_CONSOLE_OAUTH_CLIENT_ID;\n const scope = process.env.AIT_CONSOLE_OAUTH_SCOPE;\n\n const emitError = (payload: Record<string, unknown>, human: string) => {\n if (args.json) {\n process.stdout.write(`${JSON.stringify({ ok: false, ...payload })}\\n`);\n }\n process.stderr.write(`${human}\\n`);\n };\n\n const timeoutNum = Number(args.timeout);\n // Require ≥ 1 s so `CallbackTimeoutError`'s rounded-to-seconds message\n // can't produce misleading \"Login timed out after 1s\" for a 500 ms cap.\n if (!Number.isFinite(timeoutNum) || timeoutNum < 1) {\n emitError(\n { reason: 'invalid-timeout', given: args.timeout },\n `Invalid --timeout value: ${args.timeout}`,\n );\n return exitAfterFlush(ExitCode.Usage);\n }\n const timeoutMs = timeoutNum * 1000;\n\n if (!authorizeUrl) {\n emitError(\n { reason: 'oauth-url-not-configured', hint: 'set AIT_CONSOLE_OAUTH_URL' },\n [\n 'The Toss developer console OAuth URL is not configured.',\n 'Discovery is pending — set AIT_CONSOLE_OAUTH_URL to override,',\n 'or track the TODO in CLAUDE.md § \"Open questions\".',\n ].join('\\n'),\n );\n return exitAfterFlush(ExitCode.Usage);\n }\n\n const server = await startCallbackServer({ timeoutMs });\n const authUrl = buildAuthorizeUrl({\n authorizeUrl,\n redirectUri: server.redirectUri,\n state: server.expectedState,\n clientId,\n scope,\n });\n\n // Per the --json contract, stdout in JSON mode is strictly a single\n // JSON document. Progress/diagnostic chatter always goes to stderr so\n // behavior is consistent between modes — in JSON mode it still helps\n // humans watching a terminal and gives tests a way to recover the\n // server's port.\n process.stderr.write(`Listening for the OAuth callback on ${server.redirectUri}\\n`);\n\n let launched = false;\n if (!args['no-browser']) {\n const result = await openBrowser(authUrl);\n launched = result.launched;\n }\n if (launched) {\n process.stderr.write('Opened your browser. Complete the login there.\\n');\n } else {\n process.stderr.write(`Open this URL in your browser to continue:\\n ${authUrl}\\n`);\n }\n\n let query: CallbackQuery;\n try {\n try {\n query = await server.waitForCallback();\n } catch (err) {\n // Emit diagnostics first; the (idempotent) close lands in finally.\n const { reason, exitCode } = classifyCallbackError(err as Error);\n emitError(\n { reason, message: (err as Error).message },\n `Login failed: ${(err as Error).message}`,\n );\n return exitAfterFlush(exitCode);\n }\n } finally {\n await server.close();\n }\n\n // Token exchange / session capture is pending Toss console OAuth\n // discovery (tracked in TODO.md and CLAUDE.md § \"Open questions\").\n // Until then we accept optional user-label fields from the callback\n // query string but validate them strictly. If no identity field is\n // present we refuse to write the session rather than smuggling the\n // raw OAuth `code` (a secret) into the user-visible identifier. The\n // real flow will POST to a token endpoint and capture a Playwright\n // `storageState` — at which point `cookies` and `origins` become\n // non-empty and the identity fallback goes away. Field sanitization\n // (length + control-char guards) stays regardless, since display\n // names and emails still need to be safe to serialize and print.\n const rawUserId = sanitizeField(query.raw.user_id);\n const rawEmail = sanitizeField(query.raw.email);\n const displayName = sanitizeField(query.raw.display_name);\n if (!rawUserId && !rawEmail) {\n // Refuse to write a phantom session during the OAuth-discovery\n // placeholder phase: with no identity we'd have nothing meaningful\n // to show in `whoami`. The real flow will always provide an\n // identity via the token endpoint; this branch goes away then.\n emitError(\n { reason: 'oauth-identity-missing' },\n [\n 'The callback did not carry user_id or email.',\n 'This is expected until Toss console OAuth discovery lands.',\n 'No session was written.',\n ].join('\\n'),\n );\n return exitAfterFlush(ExitCode.Generic);\n }\n const userId = rawUserId ?? PENDING_USER_ID;\n const email = rawEmail ?? '';\n\n const session: Session = {\n schemaVersion: 1,\n user: displayName ? { id: userId, email, displayName } : { id: userId, email },\n // Left empty pending real token-exchange + Playwright storageState.\n cookies: [],\n origins: [],\n capturedAt: new Date().toISOString(),\n };\n try {\n await writeSession(session);\n } catch (err) {\n emitError(\n { reason: 'session-write-failed', message: (err as Error).message },\n `Failed to write session file: ${(err as Error).message}`,\n );\n return exitAfterFlush(ExitCode.Generic);\n }\n\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({\n ok: true,\n status: 'logged-in',\n user: session.user,\n capturedAt: session.capturedAt,\n })}\\n`,\n );\n } else {\n const label = displayName ? `${displayName} <${email}>` : email || userId;\n process.stdout.write(`Logged in as ${label}\\n`);\n }\n // Flush on the success path too — pipe consumers reading the JSON line\n // shouldn't be subject to truncation from a natural-exit race.\n return exitAfterFlush(ExitCode.Ok);\n },\n});\n","import { defineCommand } from 'citty';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { clearSession, sessionPathForDiagnostics } from '../session.js';\n\nexport const logoutCommand = defineCommand({\n meta: {\n name: 'logout',\n description: 'Delete the local session file.',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n },\n async run({ args }) {\n const path = sessionPathForDiagnostics();\n\n let existed: boolean;\n try {\n const result = await clearSession();\n existed = result.existed;\n } catch (err) {\n const message = (err as Error).message;\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({ ok: false, reason: 'unlink-failed', path, message })}\\n`,\n );\n }\n process.stderr.write(`Failed to remove session file at ${path}: ${message}\\n`);\n return exitAfterFlush(ExitCode.Generic);\n }\n\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({ ok: true, status: existed ? 'logged-out' : 'no-session', path })}\\n`,\n );\n } else if (existed) {\n process.stdout.write(`Logged out. Session removed from ${path}\\n`);\n } else {\n process.stdout.write(`No active session at ${path}.\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n },\n});\n","// Thin GitHub Releases API client. Only reads public endpoints, never writes.\n\nconst REPO_OWNER = 'apps-in-toss-community';\nconst REPO_NAME = 'console-cli';\n\nexport interface ReleaseAsset {\n name: string;\n browser_download_url: string;\n size: number;\n}\n\nexport interface Release {\n tag_name: string;\n name: string | null;\n html_url: string;\n assets: ReleaseAsset[];\n}\n\nfunction defaultHeaders(): HeadersInit {\n const headers: Record<string, string> = {\n Accept: 'application/vnd.github+json',\n 'User-Agent': 'ait-console',\n 'X-GitHub-Api-Version': '2022-11-28',\n };\n const token = process.env.GITHUB_TOKEN;\n if (token && token.length > 0) {\n headers.Authorization = `Bearer ${token}`;\n }\n return headers;\n}\n\nexport async function fetchLatestRelease(): Promise<Release> {\n const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;\n const res = await fetch(url, { headers: defaultHeaders() });\n if (!res.ok) {\n throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as Release;\n}\n\n// Parse `tag_name` into a comparable semver string. Changesets tags this repo\n// as `@ait-co/console-cli@0.1.2`; older ad-hoc tags may be `v0.1.2`. We\n// accept both.\nexport function versionFromTag(tag: string): string {\n const at = tag.lastIndexOf('@');\n const candidate = at >= 0 ? tag.slice(at + 1) : tag;\n return candidate.startsWith('v') ? candidate.slice(1) : candidate;\n}\n","// Map Node's `process.platform` / `process.arch` to the binary asset names\n// produced by `scripts/build-bin.ts` and attached to GitHub Releases.\n\nexport interface PlatformTarget {\n os: 'linux' | 'darwin' | 'windows';\n arch: 'x64' | 'arm64';\n assetName: string;\n}\n\nexport function detectPlatform(): PlatformTarget | null {\n let os: PlatformTarget['os'];\n switch (process.platform) {\n case 'linux':\n os = 'linux';\n break;\n case 'darwin':\n os = 'darwin';\n break;\n case 'win32':\n os = 'windows';\n break;\n default:\n return null;\n }\n\n let arch: PlatformTarget['arch'];\n switch (process.arch) {\n case 'x64':\n arch = 'x64';\n break;\n case 'arm64':\n arch = 'arm64';\n break;\n default:\n return null;\n }\n\n // We don't ship windows-arm64 yet — Bun's `--compile` target support is still partial.\n if (os === 'windows' && arch === 'arm64') return null;\n\n const suffix = os === 'windows' ? '.exe' : '';\n return { os, arch, assetName: `ait-console-${os}-${arch}${suffix}` };\n}\n","// Minimal semver comparator. We only need \"is A strictly newer than B?\" for\n// the upgrade check. Pulling the full `semver` package would bloat the\n// compiled binary for one function.\n\nexport function parseSemver(\n v: string,\n): { major: number; minor: number; patch: number; pre: string } | null {\n const m = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);\n if (!m) return null;\n return { major: +m[1]!, minor: +m[2]!, patch: +m[3]!, pre: m[4] ?? '' };\n}\n\n// Returns 1 if a > b, -1 if a < b, 0 if equal. Returns 0 if either is\n// unparseable (defensive — upgrade will treat that as \"already latest\").\nexport function compareSemver(a: string, b: string): number {\n const pa = parseSemver(a);\n const pb = parseSemver(b);\n if (!pa || !pb) return 0;\n if (pa.major !== pb.major) return pa.major > pb.major ? 1 : -1;\n if (pa.minor !== pb.minor) return pa.minor > pb.minor ? 1 : -1;\n if (pa.patch !== pb.patch) return pa.patch > pb.patch ? 1 : -1;\n // Treat \"no prerelease\" as greater than \"has prerelease\" (1.0.0 > 1.0.0-rc).\n if (pa.pre === pb.pre) return 0;\n if (pa.pre === '') return 1;\n if (pb.pre === '') return -1;\n return pa.pre > pb.pre ? 1 : -1;\n}\n","// Single source of truth for the embedded CLI version.\n//\n// The value is replaced at build time:\n// - tsdown → via `--define AIT_CONSOLE_VERSION=...` (see `scripts/build-define.ts`)\n// - bun → via `--define AIT_CONSOLE_VERSION=...` (see `scripts/build-bin.ts`)\n//\n// During `pnpm test` / `ts-node` execution the define isn't applied, so we fall\n// back to reading `package.json` at runtime. That path is never hit in the\n// shipped artifacts.\n\ndeclare const AIT_CONSOLE_VERSION: string | undefined;\n\nfunction resolveVersion(): string {\n try {\n // biome-ignore lint/suspicious/noExplicitAny: globalThis lookup for optional build-time define\n const injected = (globalThis as any).AIT_CONSOLE_VERSION as string | undefined;\n if (typeof injected === 'string' && injected.length > 0) return injected;\n } catch {\n // ignore\n }\n try {\n if (typeof AIT_CONSOLE_VERSION === 'string' && AIT_CONSOLE_VERSION.length > 0) {\n return AIT_CONSOLE_VERSION;\n }\n } catch {\n // ignore\n }\n return '0.0.0-dev';\n}\n\nexport const VERSION = resolveVersion();\n","import { chmod, rename, writeFile } from 'node:fs/promises';\nimport { basename, dirname } from 'node:path';\nimport { defineCommand } from 'citty';\nimport { ExitCode } from '../exit.js';\nimport { fetchLatestRelease, versionFromTag } from '../github.js';\nimport { detectPlatform } from '../platform.js';\nimport { compareSemver } from '../semver.js';\nimport { VERSION } from '../version.js';\n\n// Distinguishes a Bun-compiled standalone (where `process.execPath` points at\n// the binary itself) from a Node-hosted install (where it points at `node`).\n// Only the former can atomically replace itself; the latter should upgrade\n// via npm.\nfunction isStandaloneBinary(): boolean {\n const exe = basename(process.execPath).toLowerCase();\n return exe.startsWith('ait-console');\n}\n\nexport const upgradeCommand = defineCommand({\n meta: {\n name: 'upgrade',\n description: 'Download the latest release binary from GitHub and replace the current one.',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n force: {\n type: 'boolean',\n description: 'Re-install even if already on the latest version.',\n default: false,\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Check for updates without downloading or replacing.',\n default: false,\n },\n },\n async run({ args }) {\n const emit = (payload: Record<string, unknown>, human: string) => {\n if (args.json) {\n process.stdout.write(`${JSON.stringify(payload)}\\n`);\n } else {\n process.stdout.write(`${human}\\n`);\n }\n };\n const emitError = (payload: Record<string, unknown>, human: string) => {\n if (args.json) {\n process.stdout.write(`${JSON.stringify({ ok: false, ...payload })}\\n`);\n } else {\n process.stderr.write(`${human}\\n`);\n }\n };\n\n let release: Awaited<ReturnType<typeof fetchLatestRelease>>;\n try {\n release = await fetchLatestRelease();\n } catch (err) {\n emitError(\n { reason: 'network-error', message: (err as Error).message },\n `Failed to query GitHub releases: ${(err as Error).message}`,\n );\n process.exit(ExitCode.NetworkError);\n }\n\n const latest = versionFromTag(release.tag_name);\n const current = VERSION;\n const cmp = compareSemver(latest, current);\n const needsUpdate = cmp > 0 || args.force;\n\n if (!needsUpdate) {\n emit(\n { ok: true, status: 'already-latest', current, latest },\n `Already on the latest version (${current}).`,\n );\n process.exit(ExitCode.UpgradeAlreadyLatest);\n }\n\n if (args['dry-run']) {\n emit(\n { ok: true, status: 'update-available', current, latest, url: release.html_url },\n `Update available: ${current} → ${latest}\\n${release.html_url}`,\n );\n return;\n }\n\n if (!isStandaloneBinary()) {\n emitError(\n {\n reason: 'not-standalone',\n current,\n latest,\n hint: 'npm i -g @ait-co/console-cli@latest',\n },\n [\n 'This install was launched via Node, not the standalone binary.',\n 'Self-upgrade is only supported for the compiled binary.',\n `Run: npm i -g @ait-co/console-cli@latest (currently ${current}, latest ${latest})`,\n ].join('\\n'),\n );\n process.exit(ExitCode.UpgradeUnavailable);\n }\n\n const platform = detectPlatform();\n if (!platform) {\n emitError(\n {\n reason: 'unsupported-platform',\n platform: process.platform,\n arch: process.arch,\n },\n `No prebuilt binary for ${process.platform}/${process.arch}.`,\n );\n process.exit(ExitCode.UpgradeUnavailable);\n }\n\n const asset = release.assets.find((a) => a.name === platform.assetName);\n if (!asset) {\n emitError(\n { reason: 'asset-missing', assetName: platform.assetName, tag: release.tag_name },\n `Release ${release.tag_name} has no asset named ${platform.assetName}. It may still be uploading.`,\n );\n process.exit(ExitCode.UpgradeUnavailable);\n }\n\n const exePath = process.execPath;\n const stagingPath = `${exePath}.new.${Date.now()}`;\n\n if (!args.json) {\n process.stdout.write(`Downloading ${asset.name} (${latest})...\\n`);\n }\n\n try {\n const res = await fetch(asset.browser_download_url);\n if (!res.ok || !res.body) {\n throw new Error(`Download failed: ${res.status} ${res.statusText}`);\n }\n const buf = new Uint8Array(await res.arrayBuffer());\n await writeFile(stagingPath, buf, { mode: 0o755 });\n await chmod(stagingPath, 0o755);\n } catch (err) {\n emitError(\n { reason: 'download-failed', message: (err as Error).message },\n `Failed to download new binary: ${(err as Error).message}`,\n );\n process.exit(ExitCode.NetworkError);\n }\n\n // Atomic replace. POSIX `rename(2)` on the same filesystem is atomic.\n // On Windows a running exe can't be overwritten directly; the staging\n // path is in the same dir, so rename-over works on most shells, and we\n // leave `<exe>.old` handling to a future refinement.\n try {\n if (process.platform === 'win32') {\n await rename(exePath, `${exePath}.old`);\n await rename(stagingPath, exePath);\n } else {\n await rename(stagingPath, exePath);\n }\n } catch (err) {\n emitError(\n { reason: 'replace-failed', message: (err as Error).message, exePath, stagingPath },\n `Failed to replace binary at ${exePath}: ${(err as Error).message}`,\n );\n process.exit(ExitCode.Generic);\n }\n\n emit(\n {\n ok: true,\n status: 'upgraded',\n from: current,\n to: latest,\n installedAt: exePath,\n installedIn: dirname(exePath),\n },\n `Upgraded ait-console: ${current} → ${latest}`,\n );\n },\n});\n","import { defineCommand } from 'citty';\nimport { ExitCode } from '../exit.js';\nimport { readSessionSummary, sessionPathForDiagnostics } from '../session.js';\n\nexport const whoamiCommand = defineCommand({\n meta: {\n name: 'whoami',\n description: 'Show the currently authenticated user from the local session.',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n },\n async run({ args }) {\n const summary = await readSessionSummary();\n\n if (!summary) {\n if (args.json) {\n process.stdout.write(`${JSON.stringify({ authenticated: false })}\\n`);\n } else {\n process.stderr.write('Not logged in. Run `ait-console login` to start a session.\\n');\n process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\\n`);\n }\n process.exit(ExitCode.NotAuthenticated);\n }\n\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({\n authenticated: true,\n user: summary.user,\n capturedAt: summary.capturedAt,\n })}\\n`,\n );\n return;\n }\n\n const label = summary.user.displayName\n ? `${summary.user.displayName} <${summary.user.email}>`\n : summary.user.email;\n process.stdout.write(`Logged in as ${label}\\n`);\n process.stdout.write(`Session captured: ${summary.capturedAt}\\n`);\n },\n});\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty';\nimport { loginCommand } from './commands/login.js';\nimport { logoutCommand } from './commands/logout.js';\nimport { upgradeCommand } from './commands/upgrade.js';\nimport { whoamiCommand } from './commands/whoami.js';\nimport { VERSION } from './version.js';\n\nconst main = defineCommand({\n meta: {\n name: 'ait-console',\n version: VERSION,\n description:\n 'Community CLI for the Apps in Toss developer console (unofficial; not affiliated with Toss).',\n },\n subCommands: {\n whoami: whoamiCommand,\n login: loginCommand,\n logout: logoutCommand,\n upgrade: upgradeCommand,\n },\n});\n\nrunMain(main);\n"],"mappings":";;;;;;;;;AAWA,SAAgB,YAAY,KAAyC;AAEnE,KAAI,QAAQ,IAAI,2BAA2B,IACzC,QAAO,QAAQ,QAAQ,EAAE,UAAU,OAAO,CAAC;AAM7C,KAAI;EACF,MAAM,SAAS,IAAI,IAAI,IAAI;AAC3B,MAAI,OAAO,aAAa,WAAW,OAAO,aAAa,SACrD,QAAO,QAAQ,QAAQ,EAAE,UAAU,OAAO,CAAC;SAEvC;AACN,SAAO,QAAQ,QAAQ,EAAE,UAAU,OAAO,CAAC;;AAG7C,QAAO,IAAI,SAAS,YAAY;AAC9B,MAAI;AACF,OAAI,QAAQ,aAAa,SAAS;IAShC,MAAM,QAAQ,MAAM,OAAO;KAAC;KAAM;KAAS;KADzB,IAAI,IAAI,QAAQ,MAAM,MAAM,CAAC;KACY,EAAE;KAC3D,OAAO;KACP,UAAU;KACV,aAAa;KACb,0BAA0B;KAC3B,CAAC;AACF,UAAM,KAAK,eAAe,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AACvD,UAAM,KAAK,eAAe;AACxB,WAAM,OAAO;AACb,aAAQ,EAAE,UAAU,MAAM,CAAC;MAC3B;AACF;;GAGF,MAAM,QAAQ,MADE,QAAQ,aAAa,WAAW,SAAS,YAC5B,CAAC,IAAI,EAAE;IAClC,OAAO;IACP,UAAU;IACX,CAAC;AACF,SAAM,KAAK,eAAe,QAAQ,EAAE,UAAU,OAAO,CAAC,CAAC;AACvD,SAAM,KAAK,eAAe;AACxB,UAAM,OAAO;AACb,YAAQ,EAAE,UAAU,MAAM,CAAC;KAC3B;UACI;AACN,WAAQ,EAAE,UAAU,OAAO,CAAC;;GAE9B;;;;AChEJ,MAAa,WAAW;CACtB,IAAI;CACJ,SAAS;CACT,OAAO;CACP,kBAAkB;CAClB,cAAc;CACd,cAAc;CACd,oBAAoB;CACpB,oBAAoB;CACpB,sBAAsB;CACvB;;;ACPD,eAAsB,eAAe,MAA8B;AACjE,OAAM,IAAI,SAAe,YAAY,QAAQ,OAAO,MAAM,UAAU,SAAS,CAAC,CAAC;AAC/E,SAAQ,KAAK,KAAK;;;;AC6BpB,IAAa,uBAAb,cAA0C,MAAM;CAC9C,YAAY,SAAiB;AAC3B,QAAM,yBAAyB,QAAQ,GAAG;AAC1C,OAAK,OAAO;;;AAIhB,IAAa,6BAAb,cAAgD,MAAM;CACpD,cAAc;AACZ,QAAM,qCAAqC;AAC3C,OAAK,OAAO;;;AAIhB,IAAa,2BAAb,cAA8C,MAAM;CAClD,cAAc;AACZ,QAAM,yBAAyB;AAC/B,OAAK,OAAO;;;AAIhB,SAAgB,cAAsB;AAEpC,QAAO,YAAY,GAAG,CAAC,SAAS,YAAY;;AAK9C,SAAS,wBAAwB,GAAW,GAAoB;CAC9D,MAAM,OAAO,OAAO,KAAK,GAAG,OAAO;CACnC,MAAM,OAAO,OAAO,KAAK,GAAG,OAAO;AACnC,KAAI,KAAK,WAAW,KAAK,OAAQ,QAAO;AACxC,QAAO,gBAAgB,MAAM,KAAK;;AAGpC,SAAS,WAAW,GAAmB;AACrC,QAAO,EACJ,QAAQ,MAAM,QAAQ,CACtB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,OAAO,CACrB,QAAQ,MAAM,SAAS,CACvB,QAAQ,MAAM,QAAQ,CACtB,QAAQ,OAAO,QAAQ,CACvB,QAAQ,MAAM,QAAQ;;AAU3B,SAAS,iBAAiB,QAA4B,eAAoC;AACxF,KAAI,CAAC,OAAQ,QAAO,EAAE,MAAM,aAAa;CACzC,IAAI;AACJ,KAAI;AACF,WAAS,IAAI,IAAI,QAAQ,mBAAmB;SACtC;AACN,SAAO,EAAE,MAAM,aAAa;;AAE9B,KAAI,OAAO,aAAa,YACtB,QAAO,EAAE,MAAM,aAAa;CAE9B,MAAM,MAA8B,EAAE;AACtC,MAAK,MAAM,CAAC,GAAG,MAAM,OAAO,aAAc,KAAI,KAAK;CACnD,MAAM,QAAQ,IAAI,SAAS;CAC3B,MAAM,OAAO,IAAI,QAAQ;AACzB,KAAI,CAAC,SAAS,CAAC,wBAAwB,OAAO,cAAc,CAC1D,QAAO,EAAE,MAAM,kBAAkB;AAEnC,KAAI,CAAC,KACH,QAAO,EAAE,MAAM,gBAAgB;AAEjC,QAAO;EAAE,MAAM;EAAM,OAAO;GAAE;GAAM;GAAO;GAAK;EAAE;;AAGpD,MAAM,iBAAqE;CACzE,kBAAkB;CAClB,gBAAgB;CAChB,WAAW;CACX,aAAa;CACd;AAED,MAAM,eAAmE;CACvE,kBAAkB;CAClB,gBAAgB;CAChB,WAAW;CACX,aAAa;CACd;AAED,MAAM,eAAe;;;;;;;;;AAUrB,MAAM,YAAY;;;;;;;;;AAUlB,SAAS,UAAU,SAAyB;AAG1C,QAAO;;;;;;;KAOJ,WAAW,QAAQ,CAAC;;;;AAKzB,eAAe,WAAW,QAAgB,eAAoD;CAC5F,MAAM,aAAa,SACjB,IAAI,SAAS,SAAS,WAAW;EAC/B,MAAM,WAAW,QAAe;AAC9B,UAAO,eAAe,SAAS,QAAQ;AACvC,UAAO,IAAI;;AAEb,SAAO,KAAK,SAAS,QAAQ;AAC7B,SAAO,OAAO,MAAM,mBAAmB;AACrC,UAAO,eAAe,SAAS,QAAQ;GACvC,MAAM,OAAO,OAAO,SAAS;AAC7B,OAAI,CAAC,KAAM,wBAAO,IAAI,MAAM,iCAAiC,CAAC;OACzD,SAAQ,KAAK,KAAK;IACvB;GACF;AAEJ,KAAI,iBAAiB,kBAAkB,EACrC,KAAI;AACF,SAAO,MAAM,UAAU,cAAc;UAC9B,KAAK;AACZ,MAAK,IAA8B,SAAS,aAAc,OAAM;;AAIpE,QAAO,UAAU,EAAE;;AAGrB,eAAsB,oBACpB,UAAsC,EAAE,EACf;CACzB,MAAM,YAAY,QAAQ,aAAa,MAAS;CAChD,MAAM,gBAAgB,aAAa;CAEnC,MAAM,SAAiB,cAAc;CAGrC,MAAM,YAAY,MAAM,WAAW,QAAQ,QAAQ,cAAc;CAEjE,MAAM,cAAc,oBAAoB,UAAU;CAElD,IAAI,UAAU;CACd,IAAI,SAAS;CACb,IAAI;CACJ,IAAI;CACJ,MAAM,SAAS,IAAI,SAAwB,SAAS,WAAW;AAC7D,cAAY;AACZ,aAAW;GACX;AAOF,QAAO,YAAY,GAAG;CAEtB,MAAM,UAAU,YAA0E;AACxF,MAAI,QAAS;AACb,YAAU;AACV,MAAI,QAAQ,SAAS,KAAM,WAAU,QAAQ,EAAE;MAC1C,UAAS,QAAQ,EAAE;;AAG1B,QAAO,GAAG,YAAY,KAAsB,QAAwB;AAIlE,MAAI,SAAS;AACX,OAAI,aAAa;AACjB,OAAI,UAAU,gBAAgB,2BAA2B;AACzD,OAAI,IAAI,UAAU;AAClB;;EAEF,MAAM,SAAS,iBAAiB,IAAI,KAAK,cAAc;AACvD,MAAI,OAAO,SAAS,MAAM;AACxB,OAAI,aAAa;AACjB,OAAI,UAAU,gBAAgB,2BAA2B;AAKzD,OAAI,IAAI,oBAAoB,OAAO;IAAE,MAAM;IAAM,GAAG,OAAO;IAAO,CAAC,CAAC;AACpE;;EAEF,MAAM,SAAS,aAAa,OAAO;EACnC,MAAM,UAAU,eAAe,OAAO;AACtC,MAAI,aAAa;AACjB,MAAI,UAAU,gBAAgB,2BAA2B;EASzD,MAAM,kBAAkB;AACtB,WAAQ,OAAO,MAAf;IACE,KAAK;AACH,YAAO;MAAE,MAAM;MAAO,GAAG,IAAI,4BAA4B;MAAE,CAAC;AAC5D;IACF,KAAK;AACH,YAAO;MAAE,MAAM;MAAO,GAAG,IAAI,0BAA0B;MAAE,CAAC;AAC1D;IACF,KAAK;IACL,KAAK,YAIH;IACF,QAGE,GAAE,MAAa,IAAI,OAAO;;;AAGhC,MAAI,IAAI,UAAU,QAAQ,EAAE,UAAU;GACtC;CAEF,MAAM,QAAQ,iBAAiB;AAG7B,SAAO;GAAE,MAAM;GAAO,GAAG,IAAI,qBAAqB,KAAK,KAAK,YAAY,IAAK,CAAC;GAAE,CAAC;IAChF,UAAU;AACb,KAAI,OAAO,MAAM,UAAU,WAAY,OAAM,OAAO;CAEpD,MAAM,QAAQ,YAA2B;AACvC,MAAI,OAAQ;AACZ,WAAS;AACT,eAAa,MAAM;AAGnB,QAAM,IAAI,SAAe,YAAY;GACnC,IAAI,OAAO;GACX,MAAM,oBAAoB;AACxB,QAAI,KAAM;AACV,WAAO;AACP,aAAS;;AAEX,UAAO,YAAY,aAAa,CAAC;AACjC,UAAO,uBAAuB;GAC9B,MAAM,WAAW,WAAW,aAAa,IAAK;AAC9C,OAAI,OAAO,SAAS,UAAU,WAAY,UAAS,OAAO;IAC1D;;AAGJ,QAAO;EACL,MAAM;EACN;EACA;EACA,uBAAuB;EACvB;EACD;;;;ACnTH,MAAM,WAAW;AAEjB,SAAgB,YAAoB;AAClC,KAAI,QAAQ,aAAa,SAAS;EAChC,MAAM,UAAU,QAAQ,IAAI;AAC5B,MAAI,WAAW,QAAQ,SAAS,EAAG,QAAO,KAAK,SAAS,SAAS;AACjE,SAAO,KAAK,SAAS,IAAI,KAAK,WAAW,WAAW,SAAS;;CAE/D,MAAM,MAAM,QAAQ,IAAI;AACxB,KAAI,OAAO,IAAI,SAAS,EAAG,QAAO,KAAK,KAAK,SAAS;AACrD,QAAO,KAAK,SAAS,IAAI,KAAK,WAAW,SAAS;;AAGpD,SAAgB,kBAA0B;AACxC,QAAO,KAAK,WAAW,EAAE,eAAe;;;;ACa1C,SAAS,UAAU,SAAkC;AACnD,QAAO;EAAE,MAAM,QAAQ;EAAM,YAAY,QAAQ;EAAY;;AAG/D,eAAsB,cAAuC;AAC3D,KAAI;EACF,MAAM,MAAM,MAAM,SAAS,iBAAiB,EAAE,OAAO;EACrD,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,OAAO,kBAAkB,EAAG,QAAO;AACvC,MAAI,CAAC,OAAO,QAAQ,OAAO,OAAO,KAAK,OAAO,SAAU,QAAO;AAC/D,MAAI,OAAO,OAAO,KAAK,UAAU,SAAU,QAAO;AAClD,MAAI,OAAO,KAAK,gBAAgB,KAAA,KAAa,OAAO,OAAO,KAAK,gBAAgB,SAC9E,QAAO;AAET,SAAO;UACA,KAAK;AAEZ,MADc,IAA8B,SAC/B,SAAU,QAAO;AAG9B,SAAO;;;AAIX,eAAsB,qBAAqD;CACzE,MAAM,IAAI,MAAM,aAAa;AAC7B,QAAO,IAAI,UAAU,EAAE,GAAG;;AAG5B,eAAsB,aAAa,SAAiC;AAElE,OAAM,MADM,QAAQ,iBAAiB,CAAC,EACrB;EAAE,WAAW;EAAM,MAAM;EAAO,CAAC;AAClD,OAAM,UAAU,iBAAiB,EAAE,KAAK,UAAU,SAAS,MAAM,EAAE,EAAE,EACnE,MAAM,KACP,CAAC;AAEF,KAAI;AACF,QAAM,MAAM,iBAAiB,EAAE,IAAM;SAC/B;;AAKV,eAAsB,eAA8C;AAClE,KAAI;AACF,QAAM,OAAO,iBAAiB,CAAC;AAC/B,SAAO,EAAE,SAAS,MAAM;UACjB,KAAK;AAEZ,MADc,IAA8B,SAC/B,SAAU,QAAO,EAAE,SAAS,OAAO;AAChD,QAAM;;;AAIV,SAAgB,4BAAoC;AAClD,QAAO,iBAAiB;;;;ACjE1B,MAAM,mBAAmB;AAKzB,MAAM,kBAAkB;AAExB,SAAS,cAAc,KAA6C;AAClE,KAAI,OAAO,QAAQ,SAAU,QAAO,KAAA;AACpC,KAAI,IAAI,WAAW,EAAG,QAAO,KAAA;AAC7B,KAAI,IAAI,SAAS,iBAAkB,QAAO,KAAA;AAI1C,KAAI,kBAAkB,KAAK,IAAI,CAAE,QAAO,KAAA;AACxC,QAAO;;AAGT,SAAS,kBAAkB,QAMhB;CACT,MAAM,MAAM,IAAI,IAAI,OAAO,aAAa;AACxC,KAAI,aAAa,IAAI,iBAAiB,OAAO;AAC7C,KAAI,aAAa,IAAI,gBAAgB,OAAO,YAAY;AACxD,KAAI,aAAa,IAAI,SAAS,OAAO,MAAM;AAC3C,KAAI,OAAO,SAAU,KAAI,aAAa,IAAI,aAAa,OAAO,SAAS;AACvE,KAAI,OAAO,MAAO,KAAI,aAAa,IAAI,SAAS,OAAO,MAAM;AAC7D,QAAO,IAAI,UAAU;;AAGvB,SAAS,sBAAsB,KAG7B;AACA,KAAI,eAAe,qBACjB,QAAO;EAAE,QAAQ;EAAW,UAAU,SAAS;EAAc;AAE/D,KAAI,eAAe,2BACjB,QAAO;EAAE,QAAQ;EAAkB,UAAU,SAAS;EAAoB;AAE5E,KAAI,eAAe,yBACjB,QAAO;EAAE,QAAQ;EAAgB,UAAU,SAAS;EAAS;AAE/D,QAAO;EAAE,QAAQ;EAAS,UAAU,SAAS;EAAS;;AAGxD,MAAa,eAAe,cAAc;CACxC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,cAAc;GACZ,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,SAAS;GACP,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,cAAc,QAAQ,IAAI;EAChC,MAAM,eAAe,eAAe,YAAY,SAAS,IAAI,cAAc;EAC3E,MAAM,WAAW,QAAQ,IAAI;EAC7B,MAAM,QAAQ,QAAQ,IAAI;EAE1B,MAAM,aAAa,SAAkC,UAAkB;AACrE,OAAI,KAAK,KACP,SAAQ,OAAO,MAAM,GAAG,KAAK,UAAU;IAAE,IAAI;IAAO,GAAG;IAAS,CAAC,CAAC,IAAI;AAExE,WAAQ,OAAO,MAAM,GAAG,MAAM,IAAI;;EAGpC,MAAM,aAAa,OAAO,KAAK,QAAQ;AAGvC,MAAI,CAAC,OAAO,SAAS,WAAW,IAAI,aAAa,GAAG;AAClD,aACE;IAAE,QAAQ;IAAmB,OAAO,KAAK;IAAS,EAClD,4BAA4B,KAAK,UAClC;AACD,UAAO,eAAe,SAAS,MAAM;;EAEvC,MAAM,YAAY,aAAa;AAE/B,MAAI,CAAC,cAAc;AACjB,aACE;IAAE,QAAQ;IAA4B,MAAM;IAA6B,EACzE;IACE;IACA;IACA;IACD,CAAC,KAAK,KAAK,CACb;AACD,UAAO,eAAe,SAAS,MAAM;;EAGvC,MAAM,SAAS,MAAM,oBAAoB,EAAE,WAAW,CAAC;EACvD,MAAM,UAAU,kBAAkB;GAChC;GACA,aAAa,OAAO;GACpB,OAAO,OAAO;GACd;GACA;GACD,CAAC;AAOF,UAAQ,OAAO,MAAM,uCAAuC,OAAO,YAAY,IAAI;EAEnF,IAAI,WAAW;AACf,MAAI,CAAC,KAAK,cAER,aADe,MAAM,YAAY,QAAQ,EACvB;AAEpB,MAAI,SACF,SAAQ,OAAO,MAAM,mDAAmD;MAExE,SAAQ,OAAO,MAAM,iDAAiD,QAAQ,IAAI;EAGpF,IAAI;AACJ,MAAI;AACF,OAAI;AACF,YAAQ,MAAM,OAAO,iBAAiB;YAC/B,KAAK;IAEZ,MAAM,EAAE,QAAQ,aAAa,sBAAsB,IAAa;AAChE,cACE;KAAE;KAAQ,SAAU,IAAc;KAAS,EAC3C,iBAAkB,IAAc,UACjC;AACD,WAAO,eAAe,SAAS;;YAEzB;AACR,SAAM,OAAO,OAAO;;EActB,MAAM,YAAY,cAAc,MAAM,IAAI,QAAQ;EAClD,MAAM,WAAW,cAAc,MAAM,IAAI,MAAM;EAC/C,MAAM,cAAc,cAAc,MAAM,IAAI,aAAa;AACzD,MAAI,CAAC,aAAa,CAAC,UAAU;AAK3B,aACE,EAAE,QAAQ,0BAA0B,EACpC;IACE;IACA;IACA;IACD,CAAC,KAAK,KAAK,CACb;AACD,UAAO,eAAe,SAAS,QAAQ;;EAEzC,MAAM,SAAS,aAAa;EAC5B,MAAM,QAAQ,YAAY;EAE1B,MAAM,UAAmB;GACvB,eAAe;GACf,MAAM,cAAc;IAAE,IAAI;IAAQ;IAAO;IAAa,GAAG;IAAE,IAAI;IAAQ;IAAO;GAE9E,SAAS,EAAE;GACX,SAAS,EAAE;GACX,6BAAY,IAAI,MAAM,EAAC,aAAa;GACrC;AACD,MAAI;AACF,SAAM,aAAa,QAAQ;WACpB,KAAK;AACZ,aACE;IAAE,QAAQ;IAAwB,SAAU,IAAc;IAAS,EACnE,iCAAkC,IAAc,UACjD;AACD,UAAO,eAAe,SAAS,QAAQ;;AAGzC,MAAI,KAAK,KACP,SAAQ,OAAO,MACb,GAAG,KAAK,UAAU;GAChB,IAAI;GACJ,QAAQ;GACR,MAAM,QAAQ;GACd,YAAY,QAAQ;GACrB,CAAC,CAAC,IACJ;OACI;GACL,MAAM,QAAQ,cAAc,GAAG,YAAY,IAAI,MAAM,KAAK,SAAS;AACnE,WAAQ,OAAO,MAAM,gBAAgB,MAAM,IAAI;;AAIjD,SAAO,eAAe,SAAS,GAAG;;CAErC,CAAC;;;AC/OF,MAAa,gBAAgB,cAAc;CACzC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,aAAa;EACb,SAAS;EACV,EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,OAAO,2BAA2B;EAExC,IAAI;AACJ,MAAI;AAEF,cADe,MAAM,cAAc,EAClB;WACV,KAAK;GACZ,MAAM,UAAW,IAAc;AAC/B,OAAI,KAAK,KACP,SAAQ,OAAO,MACb,GAAG,KAAK,UAAU;IAAE,IAAI;IAAO,QAAQ;IAAiB;IAAM;IAAS,CAAC,CAAC,IAC1E;AAEH,WAAQ,OAAO,MAAM,oCAAoC,KAAK,IAAI,QAAQ,IAAI;AAC9E,UAAO,eAAe,SAAS,QAAQ;;AAGzC,MAAI,KAAK,KACP,SAAQ,OAAO,MACb,GAAG,KAAK,UAAU;GAAE,IAAI;GAAM,QAAQ,UAAU,eAAe;GAAc;GAAM,CAAC,CAAC,IACtF;WACQ,QACT,SAAQ,OAAO,MAAM,oCAAoC,KAAK,IAAI;MAElE,SAAQ,OAAO,MAAM,wBAAwB,KAAK,KAAK;AAEzD,SAAO,eAAe,SAAS,GAAG;;CAErC,CAAC;;;AC5CF,MAAM,aAAa;AACnB,MAAM,YAAY;AAelB,SAAS,iBAA8B;CACrC,MAAM,UAAkC;EACtC,QAAQ;EACR,cAAc;EACd,wBAAwB;EACzB;CACD,MAAM,QAAQ,QAAQ,IAAI;AAC1B,KAAI,SAAS,MAAM,SAAS,EAC1B,SAAQ,gBAAgB,UAAU;AAEpC,QAAO;;AAGT,eAAsB,qBAAuC;CAC3D,MAAM,MAAM,gCAAgC,WAAW,GAAG,UAAU;CACpE,MAAM,MAAM,MAAM,MAAM,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;AAC3D,KAAI,CAAC,IAAI,GACP,OAAM,IAAI,MAAM,mCAAmC,IAAI,OAAO,GAAG,IAAI,aAAa;AAEpF,QAAQ,MAAM,IAAI,MAAM;;AAM1B,SAAgB,eAAe,KAAqB;CAClD,MAAM,KAAK,IAAI,YAAY,IAAI;CAC/B,MAAM,YAAY,MAAM,IAAI,IAAI,MAAM,KAAK,EAAE,GAAG;AAChD,QAAO,UAAU,WAAW,IAAI,GAAG,UAAU,MAAM,EAAE,GAAG;;;;ACrC1D,SAAgB,iBAAwC;CACtD,IAAI;AACJ,SAAQ,QAAQ,UAAhB;EACE,KAAK;AACH,QAAK;AACL;EACF,KAAK;AACH,QAAK;AACL;EACF,KAAK;AACH,QAAK;AACL;EACF,QACE,QAAO;;CAGX,IAAI;AACJ,SAAQ,QAAQ,MAAhB;EACE,KAAK;AACH,UAAO;AACP;EACF,KAAK;AACH,UAAO;AACP;EACF,QACE,QAAO;;AAIX,KAAI,OAAO,aAAa,SAAS,QAAS,QAAO;AAGjD,QAAO;EAAE;EAAI;EAAM,WAAW,eAAe,GAAG,GAAG,OADpC,OAAO,YAAY,SAAS;EACyB;;;;ACrCtE,SAAgB,YACd,GACqE;CACrE,MAAM,IAAI,6CAA6C,KAAK,EAAE;AAC9D,KAAI,CAAC,EAAG,QAAO;AACf,QAAO;EAAE,OAAO,CAAC,EAAE;EAAK,OAAO,CAAC,EAAE;EAAK,OAAO,CAAC,EAAE;EAAK,KAAK,EAAE,MAAM;EAAI;;AAKzE,SAAgB,cAAc,GAAW,GAAmB;CAC1D,MAAM,KAAK,YAAY,EAAE;CACzB,MAAM,KAAK,YAAY,EAAE;AACzB,KAAI,CAAC,MAAM,CAAC,GAAI,QAAO;AACvB,KAAI,GAAG,UAAU,GAAG,MAAO,QAAO,GAAG,QAAQ,GAAG,QAAQ,IAAI;AAC5D,KAAI,GAAG,UAAU,GAAG,MAAO,QAAO,GAAG,QAAQ,GAAG,QAAQ,IAAI;AAC5D,KAAI,GAAG,UAAU,GAAG,MAAO,QAAO,GAAG,QAAQ,GAAG,QAAQ,IAAI;AAE5D,KAAI,GAAG,QAAQ,GAAG,IAAK,QAAO;AAC9B,KAAI,GAAG,QAAQ,GAAI,QAAO;AAC1B,KAAI,GAAG,QAAQ,GAAI,QAAO;AAC1B,QAAO,GAAG,MAAM,GAAG,MAAM,IAAI;;;;ACb/B,SAAS,iBAAyB;AAChC,KAAI;EAEF,MAAM,WAAY,WAAmB;AACrC,MAAI,OAAO,aAAa,YAAY,SAAS,SAAS,EAAG,QAAO;SAC1D;AAGR,KAAI;AAEA,SAAA;SAEI;AAGR,QAAO;;AAGT,MAAa,UAAU,gBAAgB;;;ACjBvC,SAAS,qBAA8B;AAErC,QADY,SAAS,QAAQ,SAAS,CAAC,aAAa,CACzC,WAAW,cAAc;;AAGtC,MAAa,iBAAiB,cAAc;CAC1C,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM;EACJ,MAAM;GACJ,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,OAAO;GACL,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACD,WAAW;GACT,MAAM;GACN,aAAa;GACb,SAAS;GACV;EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,QAAQ,SAAkC,UAAkB;AAChE,OAAI,KAAK,KACP,SAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,QAAQ,CAAC,IAAI;OAEpD,SAAQ,OAAO,MAAM,GAAG,MAAM,IAAI;;EAGtC,MAAM,aAAa,SAAkC,UAAkB;AACrE,OAAI,KAAK,KACP,SAAQ,OAAO,MAAM,GAAG,KAAK,UAAU;IAAE,IAAI;IAAO,GAAG;IAAS,CAAC,CAAC,IAAI;OAEtE,SAAQ,OAAO,MAAM,GAAG,MAAM,IAAI;;EAItC,IAAI;AACJ,MAAI;AACF,aAAU,MAAM,oBAAoB;WAC7B,KAAK;AACZ,aACE;IAAE,QAAQ;IAAiB,SAAU,IAAc;IAAS,EAC5D,oCAAqC,IAAc,UACpD;AACD,WAAQ,KAAK,SAAS,aAAa;;EAGrC,MAAM,SAAS,eAAe,QAAQ,SAAS;EAC/C,MAAM,UAAU;AAIhB,MAAI,EAHQ,cAAc,QAAQ,QAAQ,GAChB,KAAK,KAAK,QAElB;AAChB,QACE;IAAE,IAAI;IAAM,QAAQ;IAAkB;IAAS;IAAQ,EACvD,kCAAkC,QAAQ,IAC3C;AACD,WAAQ,KAAK,SAAS,qBAAqB;;AAG7C,MAAI,KAAK,YAAY;AACnB,QACE;IAAE,IAAI;IAAM,QAAQ;IAAoB;IAAS;IAAQ,KAAK,QAAQ;IAAU,EAChF,qBAAqB,QAAQ,KAAK,OAAO,IAAI,QAAQ,WACtD;AACD;;AAGF,MAAI,CAAC,oBAAoB,EAAE;AACzB,aACE;IACE,QAAQ;IACR;IACA;IACA,MAAM;IACP,EACD;IACE;IACA;IACA,wDAAwD,QAAQ,WAAW,OAAO;IACnF,CAAC,KAAK,KAAK,CACb;AACD,WAAQ,KAAK,SAAS,mBAAmB;;EAG3C,MAAM,WAAW,gBAAgB;AACjC,MAAI,CAAC,UAAU;AACb,aACE;IACE,QAAQ;IACR,UAAU,QAAQ;IAClB,MAAM,QAAQ;IACf,EACD,0BAA0B,QAAQ,SAAS,GAAG,QAAQ,KAAK,GAC5D;AACD,WAAQ,KAAK,SAAS,mBAAmB;;EAG3C,MAAM,QAAQ,QAAQ,OAAO,MAAM,MAAM,EAAE,SAAS,SAAS,UAAU;AACvE,MAAI,CAAC,OAAO;AACV,aACE;IAAE,QAAQ;IAAiB,WAAW,SAAS;IAAW,KAAK,QAAQ;IAAU,EACjF,WAAW,QAAQ,SAAS,sBAAsB,SAAS,UAAU,8BACtE;AACD,WAAQ,KAAK,SAAS,mBAAmB;;EAG3C,MAAM,UAAU,QAAQ;EACxB,MAAM,cAAc,GAAG,QAAQ,OAAO,KAAK,KAAK;AAEhD,MAAI,CAAC,KAAK,KACR,SAAQ,OAAO,MAAM,eAAe,MAAM,KAAK,IAAI,OAAO,QAAQ;AAGpE,MAAI;GACF,MAAM,MAAM,MAAM,MAAM,MAAM,qBAAqB;AACnD,OAAI,CAAC,IAAI,MAAM,CAAC,IAAI,KAClB,OAAM,IAAI,MAAM,oBAAoB,IAAI,OAAO,GAAG,IAAI,aAAa;AAGrE,SAAM,UAAU,aADJ,IAAI,WAAW,MAAM,IAAI,aAAa,CAAC,EACjB,EAAE,MAAM,KAAO,CAAC;AAClD,SAAM,MAAM,aAAa,IAAM;WACxB,KAAK;AACZ,aACE;IAAE,QAAQ;IAAmB,SAAU,IAAc;IAAS,EAC9D,kCAAmC,IAAc,UAClD;AACD,WAAQ,KAAK,SAAS,aAAa;;AAOrC,MAAI;AACF,OAAI,QAAQ,aAAa,SAAS;AAChC,UAAM,OAAO,SAAS,GAAG,QAAQ,MAAM;AACvC,UAAM,OAAO,aAAa,QAAQ;SAElC,OAAM,OAAO,aAAa,QAAQ;WAE7B,KAAK;AACZ,aACE;IAAE,QAAQ;IAAkB,SAAU,IAAc;IAAS;IAAS;IAAa,EACnF,+BAA+B,QAAQ,IAAK,IAAc,UAC3D;AACD,WAAQ,KAAK,SAAS,QAAQ;;AAGhC,OACE;GACE,IAAI;GACJ,QAAQ;GACR,MAAM;GACN,IAAI;GACJ,aAAa;GACb,aAAa,QAAQ,QAAQ;GAC9B,EACD,yBAAyB,QAAQ,KAAK,SACvC;;CAEJ,CAAC;;;ACjLF,MAAa,gBAAgB,cAAc;CACzC,MAAM;EACJ,MAAM;EACN,aAAa;EACd;CACD,MAAM,EACJ,MAAM;EACJ,MAAM;EACN,aAAa;EACb,SAAS;EACV,EACF;CACD,MAAM,IAAI,EAAE,QAAQ;EAClB,MAAM,UAAU,MAAM,oBAAoB;AAE1C,MAAI,CAAC,SAAS;AACZ,OAAI,KAAK,KACP,SAAQ,OAAO,MAAM,GAAG,KAAK,UAAU,EAAE,eAAe,OAAO,CAAC,CAAC,IAAI;QAChE;AACL,YAAQ,OAAO,MAAM,+DAA+D;AACpF,YAAQ,OAAO,MAAM,yBAAyB,2BAA2B,CAAC,IAAI;;AAEhF,WAAQ,KAAK,SAAS,iBAAiB;;AAGzC,MAAI,KAAK,MAAM;AACb,WAAQ,OAAO,MACb,GAAG,KAAK,UAAU;IAChB,eAAe;IACf,MAAM,QAAQ;IACd,YAAY,QAAQ;IACrB,CAAC,CAAC,IACJ;AACD;;EAGF,MAAM,QAAQ,QAAQ,KAAK,cACvB,GAAG,QAAQ,KAAK,YAAY,IAAI,QAAQ,KAAK,MAAM,KACnD,QAAQ,KAAK;AACjB,UAAQ,OAAO,MAAM,gBAAgB,MAAM,IAAI;AAC/C,UAAQ,OAAO,MAAM,qBAAqB,QAAQ,WAAW,IAAI;;CAEpE,CAAC;;;ACvBF,QAfa,cAAc;CACzB,MAAM;EACJ,MAAM;EACN,SAAS;EACT,aACE;EACH;CACD,aAAa;EACX,QAAQ;EACR,OAAO;EACP,QAAQ;EACR,SAAS;EACV;CACF,CAAC,CAEW"}
|
|
1
|
+
{"version":3,"file":"cli.mjs","names":["winPath","fsConstants"],"sources":["../src/api/http.ts","../src/api/me.ts","../src/cdp.ts","../src/chrome.ts","../src/exit.ts","../src/flush.ts","../src/paths.ts","../src/session.ts","../src/commands/login.ts","../src/commands/logout.ts","../src/github.ts","../src/platform.ts","../src/semver.ts","../src/version.ts","../src/commands/upgrade.ts","../src/commands/whoami.ts","../src/cli.ts"],"sourcesContent":["// Thin HTTP layer for driving the Apps in Toss console API.\n//\n// Two concerns live here:\n// 1. Serialising the session's captured cookies into a `Cookie` header\n// per request origin (we drop cookies whose Domain/Path don't match\n// the target URL — feeding `apps-in-toss.toss.im` session cookies to\n// `business-accounts.toss.im` would be either ignored or rejected).\n// 2. Unwrapping the Toss `{ resultType, success, error? }` envelope that\n// every console endpoint uses. Upstream callers get `T` on success or\n// a typed `TossApiError` on failure — no need to repeat envelope\n// dispatch in every command.\n//\n// We don't try to be a full cookie jar. The cookie set we care about is\n// captured in one shot at login time and replayed verbatim thereafter;\n// Set-Cookie responses from API calls are ignored. A later PR will add\n// refresh logic once we see whether the console issues sliding sessions.\n\nimport type { CdpCookie } from '../cdp.js';\n\nexport interface TossEnvelopeSuccess<T> {\n readonly resultType: 'SUCCESS';\n readonly success: T;\n}\n\nexport interface TossEnvelopeFailure {\n readonly resultType: 'FAIL';\n readonly success: null;\n readonly error: {\n readonly errorType: number;\n readonly errorCode: string;\n readonly reason: string;\n readonly data?: unknown;\n readonly title?: string | null;\n };\n}\n\nexport type TossEnvelope<T> = TossEnvelopeSuccess<T> | TossEnvelopeFailure;\n\nexport class TossApiError extends Error {\n constructor(\n readonly status: number,\n readonly errorCode: string,\n readonly reason: string,\n readonly errorType: number,\n ) {\n super(`Toss API error ${errorCode}: ${reason} (HTTP ${status})`);\n this.name = 'TossApiError';\n }\n\n /** Cookie-based auth rejected — session missing/expired/invalidated. */\n get isAuthError(): boolean {\n return this.status === 401 || this.errorCode === '4010';\n }\n}\n\nexport class NetworkError extends Error {\n constructor(\n readonly url: string,\n cause: Error,\n ) {\n super(`Network request to ${url} failed: ${cause.message}`);\n this.name = 'NetworkError';\n this.cause = cause;\n }\n}\n\nexport class MalformedResponseError extends Error {\n constructor(\n readonly url: string,\n readonly status: number,\n message: string,\n readonly bodyPreview?: string,\n ) {\n const suffix = bodyPreview ? ` (body: ${bodyPreview})` : '';\n super(`Malformed response from ${url} (HTTP ${status}): ${message}${suffix}`);\n this.name = 'MalformedResponseError';\n }\n}\n\n// --- Cookie matching ---\n\n/**\n * RFC 6265-ish domain match. We accept the bare hostname case plus the\n * standard suffix match (`.example.com` cookie matches `foo.example.com`),\n * because CDP `Network.getAllCookies` normalises cookie Domain to a form\n * with a leading dot for host-matching cookies but without for explicit-host\n * cookies. Either form should round-trip correctly.\n */\nexport function domainMatches(cookieDomain: string, hostname: string): boolean {\n if (cookieDomain.length === 0) return false;\n const lower = cookieDomain.toLowerCase();\n const host = hostname.toLowerCase();\n if (lower === host) return true;\n if (lower.startsWith('.') && host.endsWith(lower)) return true;\n // Host cookies without a leading dot: cookie Domain must equal the host.\n // Suffix-match only applies when there's an explicit leading dot.\n if (!lower.startsWith('.') && host.endsWith(`.${lower}`)) return true;\n return false;\n}\n\n/**\n * RFC 6265 §5.1.4 path match. A cookie Path C matches a request path P iff\n * C and P are identical, OR C is a prefix of P and either C already ends\n * with '/' or the character in P immediately after C is '/'. Notably\n * \"/foo\" does NOT match \"/foobar\".\n */\nexport function pathMatches(cookiePath: string, requestPath: string): boolean {\n if (!cookiePath) return true;\n if (cookiePath === requestPath) return true;\n if (!requestPath.startsWith(cookiePath)) return false;\n return cookiePath.endsWith('/') || requestPath.charAt(cookiePath.length) === '/';\n}\n\n// Cookie name/value must not contain control characters or separators that\n// would let an attacker smuggle header fields via CRLF injection. CDP\n// returns cookie values verbatim, so we defend at the serialisation edge.\nfunction isSafeCookiePart(s: string): boolean {\n // Reject CR, LF, NUL, and ';' (cookie separator). Tabs and spaces are\n // technically allowed but we block them too to avoid header confusion.\n // biome-ignore lint/suspicious/noControlCharactersInRegex: explicit control-char filter\n return !/[\\x00-\\x1f;\\x7f]/.test(s);\n}\n\n/**\n * Build a `Cookie:` header value for the given URL from a captured cookie\n * set. Returns `null` when no cookies match — the caller should skip the\n * header entirely rather than emit `Cookie: ` with an empty value.\n *\n * Ordering follows RFC 6265 §5.4: cookies with longer paths come before\n * cookies with shorter paths; ties break on earliest creation time, which\n * we don't track, so we preserve capture order as a stable tiebreaker.\n */\nexport function cookieHeaderFor(url: URL, cookies: readonly CdpCookie[]): string | null {\n const matching = cookies\n .map((c, i) => ({ c, i }))\n .filter(({ c }) => {\n if (!isSafeCookiePart(c.name) || !isSafeCookiePart(c.value)) return false;\n if (!domainMatches(c.domain, url.hostname)) return false;\n if (!pathMatches(c.path, url.pathname)) return false;\n if (c.secure && url.protocol !== 'https:') return false;\n return true;\n })\n .sort((a, b) => {\n const byPath = b.c.path.length - a.c.path.length;\n return byPath !== 0 ? byPath : a.i - b.i;\n })\n .map(({ c }) => c);\n if (matching.length === 0) return null;\n return matching.map((c) => `${c.name}=${c.value}`).join('; ');\n}\n\n// --- Request helper ---\n\n// Narrow fetch signature that callers (and tests) can satisfy without\n// implementing Bun-specific extensions like `fetch.preconnect`.\nexport type FetchLike = (input: URL | string, init?: RequestInit) => Promise<Response>;\n\nexport interface RequestOptions {\n readonly method?: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';\n readonly url: string;\n readonly cookies: readonly CdpCookie[];\n readonly body?: unknown;\n readonly fetchImpl?: FetchLike;\n readonly headers?: Record<string, string>;\n}\n\n/**\n * Perform a request against the console API and unwrap the Toss envelope.\n *\n * Always sets `Accept: application/json` and propagates the captured cookie\n * set. Callers may pass additional headers (useful for CSRF tokens that\n * later endpoints turn out to require — discovery is per-feature).\n */\nexport async function requestConsoleApi<T>(options: RequestOptions): Promise<T> {\n const url = new URL(options.url);\n const cookieHeader = cookieHeaderFor(url, options.cookies);\n const headers: Record<string, string> = {\n Accept: 'application/json, text/plain, */*',\n ...options.headers,\n };\n if (cookieHeader) headers.Cookie = cookieHeader;\n\n const init: RequestInit = {\n method: options.method ?? 'GET',\n headers,\n // Cookies handled manually; disable any built-in cookie jar behaviour.\n redirect: 'follow',\n };\n if (options.body !== undefined) {\n headers['Content-Type'] = 'application/json';\n init.body = JSON.stringify(options.body);\n }\n\n const fetchImpl: FetchLike = options.fetchImpl ?? ((input, init) => fetch(input, init));\n let res: Response;\n try {\n res = await fetchImpl(url, init);\n } catch (err) {\n throw new NetworkError(url.toString(), err as Error);\n }\n\n // Read the body as text first so a parse failure can include a preview\n // in the error. Empty responses or non-JSON WAF pages are a lot easier\n // to diagnose when you can see the first few hundred bytes.\n let text: string;\n try {\n text = await res.text();\n } catch (err) {\n throw new MalformedResponseError(url.toString(), res.status, (err as Error).message);\n }\n let parsed: TossEnvelope<T>;\n try {\n parsed = JSON.parse(text) as TossEnvelope<T>;\n } catch (err) {\n const preview = text.slice(0, 200).replace(/\\s+/g, ' ').trim();\n throw new MalformedResponseError(url.toString(), res.status, (err as Error).message, preview);\n }\n\n if (parsed.resultType === 'SUCCESS') {\n return parsed.success;\n }\n throw new TossApiError(\n res.status,\n parsed.error.errorCode,\n parsed.error.reason,\n parsed.error.errorType,\n );\n}\n","import type { CdpCookie } from '../cdp.js';\nimport { type FetchLike, requestConsoleApi } from './http.js';\n\n// Console-scoped \"who am I\" endpoint, discovered by observing the console UI\n// boot requests. Returned shape is stable across the sample workspace; new\n// fields may appear but we read it conservatively.\n\nexport interface ConsoleMemberWorkspace {\n readonly workspaceId: number;\n readonly workspaceName: string;\n readonly role: string;\n readonly isOwnerDelegationRequested: boolean;\n}\n\nexport interface ConsoleMemberUserInfo {\n readonly id: number;\n readonly bizUserNo: number;\n readonly name: string;\n readonly email: string;\n readonly role: string;\n readonly workspaces: readonly ConsoleMemberWorkspace[];\n readonly isAdult: boolean;\n readonly isOverseasBusiness: boolean;\n}\n\nconst MEMBER_USER_INFO_URL =\n 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole/members/me/user-info';\n\nexport async function fetchConsoleMemberUserInfo(\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<ConsoleMemberUserInfo> {\n return requestConsoleApi<ConsoleMemberUserInfo>({\n url: MEMBER_USER_INFO_URL,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n}\n","// Tiny Chrome DevTools Protocol client. Enough to navigate a tab, listen for\n// frame-navigation events, and dump cookies via `Network.getAllCookies`.\n//\n// Deliberately does NOT pull in `ws` or any WebSocket userland lib: Node 22+\n// and Bun both expose `globalThis.WebSocket` with the standard interface,\n// which is what we use. Keeps `bun build --compile` tiny and avoids the\n// optional-native-deps dance.\n//\n// Threading model: one `CdpClient` wraps one WebSocket connection to the\n// *browser* (the URL printed on Chrome's stderr). Per-target sessions are\n// attached lazily via `Target.attachToTarget` — we shuttle messages over\n// the single connection using `sessionId` routing, the same way the DevTools\n// frontend and `chrome-remote-interface` do. Only the APIs we actually need\n// for login capture are wrapped; everything else is available through the\n// raw `send(method, params, sessionId?)` escape hatch.\n\ntype JsonValue =\n | null\n | string\n | number\n | boolean\n | readonly JsonValue[]\n | { readonly [k: string]: JsonValue };\n\ninterface CdpSuccess {\n readonly id: number;\n readonly result: Record<string, unknown>;\n readonly sessionId?: string;\n}\n\ninterface CdpError {\n readonly id: number;\n readonly error: { readonly code: number; readonly message: string; readonly data?: unknown };\n readonly sessionId?: string;\n}\n\ninterface CdpEvent {\n readonly method: string;\n readonly params: Record<string, unknown>;\n readonly sessionId?: string;\n}\n\ntype CdpMessage = CdpSuccess | CdpError | CdpEvent;\n\nfunction isResponse(m: CdpMessage): m is CdpSuccess | CdpError {\n return 'id' in m;\n}\n\nexport class CdpProtocolError extends Error {\n constructor(\n readonly method: string,\n readonly code: number,\n message: string,\n ) {\n super(`CDP error for ${method}: ${message} (code=${code})`);\n this.name = 'CdpProtocolError';\n }\n}\n\nexport class CdpConnectionClosedError extends Error {\n constructor() {\n super('CDP connection closed before the response arrived.');\n this.name = 'CdpConnectionClosedError';\n }\n}\n\nexport interface CdpCookie {\n readonly name: string;\n readonly value: string;\n readonly domain: string;\n readonly path: string;\n readonly expires: number;\n readonly httpOnly: boolean;\n readonly secure: boolean;\n readonly session: boolean;\n readonly sameSite?: 'Strict' | 'Lax' | 'None';\n}\n\nexport type CdpEventListener = (event: CdpEvent) => void;\n\nexport interface ConnectCdpOptions {\n readonly url: string;\n // Injected for tests. Must match the subset of WebSocket we use: `onopen`,\n // `onmessage`, `onerror`, `onclose`, `send`, `close`, and the `readyState`\n // constants (OPEN, CLOSED).\n readonly webSocketFactory?: (url: string) => WebSocket;\n}\n\nexport class CdpClient {\n private readonly socket: WebSocket;\n private nextId = 1;\n private readonly pending = new Map<\n number,\n { resolve(result: Record<string, unknown>): void; reject(err: Error): void; method: string }\n >();\n private readonly listeners = new Set<CdpEventListener>();\n private closed = false;\n\n private constructor(socket: WebSocket) {\n this.socket = socket;\n socket.addEventListener('message', (ev: MessageEvent) => this.handleMessage(ev));\n socket.addEventListener('close', () => this.handleClose());\n socket.addEventListener('error', () => {\n // Let the `close` event handle pending-promise rejection. Surfacing the\n // error here as well would double-reject; browsers emit both events in\n // quick succession on a failed handshake.\n });\n }\n\n static async connect(options: ConnectCdpOptions): Promise<CdpClient> {\n const factory = options.webSocketFactory ?? ((url: string) => new WebSocket(url));\n const socket = factory(options.url);\n await new Promise<void>((resolve, reject) => {\n const onOpen = () => {\n cleanup();\n resolve();\n };\n const onError = () => {\n cleanup();\n reject(new Error(`Failed to open CDP WebSocket at ${options.url}`));\n };\n const onClose = () => {\n cleanup();\n reject(new Error(`CDP WebSocket closed before opening (${options.url})`));\n };\n const cleanup = () => {\n socket.removeEventListener('open', onOpen);\n socket.removeEventListener('error', onError);\n socket.removeEventListener('close', onClose);\n };\n socket.addEventListener('open', onOpen);\n socket.addEventListener('error', onError);\n socket.addEventListener('close', onClose);\n });\n return new CdpClient(socket);\n }\n\n on(listener: CdpEventListener): () => void {\n this.listeners.add(listener);\n return () => this.listeners.delete(listener);\n }\n\n async send<T = Record<string, unknown>>(\n method: string,\n params?: Record<string, JsonValue>,\n sessionId?: string,\n ): Promise<T> {\n if (this.closed) throw new CdpConnectionClosedError();\n const id = this.nextId++;\n // Assemble as a plain record and let the JSON serialiser drop keys\n // that are absent — matches the exactOptionalPropertyTypes contract\n // without the triple-nested ternary.\n const req: Record<string, unknown> = { id, method };\n if (params) req.params = params;\n if (sessionId) req.sessionId = sessionId;\n const waiter = new Promise<Record<string, unknown>>((resolve, reject) => {\n this.pending.set(id, { resolve, reject, method });\n });\n this.socket.send(JSON.stringify(req));\n const result = await waiter;\n return result as T;\n }\n\n async close(): Promise<void> {\n if (this.closed) return;\n this.closed = true;\n // Reject any outstanding requests so callers don't hang forever.\n for (const [, pending] of this.pending) {\n pending.reject(new CdpConnectionClosedError());\n }\n this.pending.clear();\n try {\n this.socket.close();\n } catch {\n // already closed\n }\n }\n\n private handleMessage(ev: MessageEvent): void {\n let parsed: CdpMessage;\n try {\n const raw =\n typeof ev.data === 'string' ? ev.data : new TextDecoder().decode(ev.data as ArrayBuffer);\n parsed = JSON.parse(raw) as CdpMessage;\n } catch {\n // Non-JSON payload — shouldn't happen on CDP, ignore.\n return;\n }\n if (isResponse(parsed)) {\n const pending = this.pending.get(parsed.id);\n if (!pending) return;\n this.pending.delete(parsed.id);\n if ('error' in parsed) {\n pending.reject(\n new CdpProtocolError(pending.method, parsed.error.code, parsed.error.message),\n );\n } else {\n pending.resolve(parsed.result);\n }\n return;\n }\n for (const listener of this.listeners) {\n try {\n listener(parsed);\n } catch {\n // Listener errors should not crash the dispatch loop.\n }\n }\n }\n\n private handleClose(): void {\n if (this.closed) return;\n this.closed = true;\n for (const [, pending] of this.pending) {\n pending.reject(new CdpConnectionClosedError());\n }\n this.pending.clear();\n }\n}\n\n// --- High-level helpers ---\n\nexport interface AttachedTarget {\n readonly sessionId: string;\n readonly targetId: string;\n}\n\n/**\n * Attach to the first \"page\" target exposed by the browser. Chrome always\n * opens at least one page target when launched with an initial URL, so this\n * is a reliable way to grab a session without guessing target IDs.\n */\nexport async function attachToFirstPage(client: CdpClient): Promise<AttachedTarget> {\n const { targetInfos } = await client.send<{\n targetInfos: Array<{ targetId: string; type: string }>;\n }>('Target.getTargets');\n const page = targetInfos.find((t) => t.type === 'page');\n if (!page) {\n throw new Error('No page target found; Chrome launched without an initial tab.');\n }\n const { sessionId } = await client.send<{ sessionId: string }>('Target.attachToTarget', {\n targetId: page.targetId,\n flatten: true,\n });\n return { sessionId, targetId: page.targetId };\n}\n\nexport interface FrameNavigatedEvent {\n readonly url: string;\n readonly frameId: string;\n readonly isMainFrame: boolean;\n}\n\n/**\n * Subscribe to main-frame navigations on the attached page session. Returns\n * an unsubscribe function.\n *\n * Chrome emits `Page.frameNavigated` for every frame — we filter to the main\n * frame (top-level document) since auxiliary iframes (analytics, chat\n * widgets) would otherwise trigger false matches.\n */\nexport async function watchMainFrameNavigations(\n client: CdpClient,\n sessionId: string,\n onNavigate: (ev: FrameNavigatedEvent) => void,\n): Promise<() => void> {\n await client.send('Page.enable', {}, sessionId);\n const off = client.on((event) => {\n if (event.sessionId !== sessionId) return;\n if (event.method !== 'Page.frameNavigated') return;\n const frame = event.params.frame as\n | { url?: string; id?: string; parentId?: string }\n | undefined;\n if (!frame?.url || !frame.id) return;\n onNavigate({\n url: frame.url,\n frameId: frame.id,\n isMainFrame: frame.parentId === undefined,\n });\n });\n return off;\n}\n\n/**\n * `Network.getAllCookies` is scoped to a target session — Chrome rejects it\n * on the browser-level endpoint with `method not found`. Requiring sessionId\n * here surfaces that constraint at compile time.\n *\n * The response shape is fixed in the CDP spec, but we still validate every\n * cookie's required string/number fields at runtime so a malformed entry\n * (from a future Chrome change, say) fails loud instead of propagating\n * `undefined` into the Cookie: header or the on-disk session file.\n */\nexport async function getAllCookies(\n client: CdpClient,\n sessionId: string,\n): Promise<readonly CdpCookie[]> {\n const result = await client.send<{ cookies: unknown }>('Network.getAllCookies', {}, sessionId);\n if (!Array.isArray(result.cookies)) {\n throw new Error('Network.getAllCookies returned a non-array payload');\n }\n return result.cookies.map((raw, index) => validateCookie(raw, index));\n}\n\nfunction validateCookie(raw: unknown, index: number): CdpCookie {\n if (!raw || typeof raw !== 'object') {\n throw new Error(`Cookie #${index} is not an object`);\n }\n const c = raw as Record<string, unknown>;\n const str = (field: string): string => {\n const v = c[field];\n if (typeof v !== 'string') throw new Error(`Cookie #${index}.${field} is not a string`);\n return v;\n };\n const num = (field: string): number => {\n const v = c[field];\n if (typeof v !== 'number') throw new Error(`Cookie #${index}.${field} is not a number`);\n return v;\n };\n const bool = (field: string): boolean => {\n const v = c[field];\n if (typeof v !== 'boolean') throw new Error(`Cookie #${index}.${field} is not a boolean`);\n return v;\n };\n const base = {\n name: str('name'),\n value: str('value'),\n domain: str('domain'),\n path: str('path'),\n expires: num('expires'),\n httpOnly: bool('httpOnly'),\n secure: bool('secure'),\n session: bool('session'),\n };\n const sameSite = c.sameSite;\n if (sameSite === 'Strict' || sameSite === 'Lax' || sameSite === 'None') {\n return { ...base, sameSite };\n }\n return base;\n}\n","import { type ChildProcess, spawn } from 'node:child_process';\nimport { constants as fsConstants } from 'node:fs';\nimport { mkdtemp, rm } from 'node:fs/promises';\nimport { homedir, tmpdir } from 'node:os';\nimport { join, win32 as winPath } from 'node:path';\n\n// Thin cross-platform launcher for an existing Chrome/Chromium-family\n// browser with the Chrome DevTools Protocol enabled. We drive the session\n// over CDP rather than relying on Playwright so `bun build --compile` keeps\n// producing a ~10 MB standalone binary with no bundled Chromium.\n//\n// We deliberately use an ephemeral `--user-data-dir` so the login session\n// is isolated from the user's everyday browser profile. The caller is\n// responsible for disposing the session (we expose a `dispose()` helper\n// that kills the process and removes the temp dir).\n\nexport interface ChromePaths {\n readonly candidates: readonly string[];\n}\n\nexport class ChromeNotFoundError extends Error {\n constructor(readonly candidates: readonly string[]) {\n super(\n `Could not find Chrome or a Chromium-family browser. Tried: ${candidates.join(', ')}.\\n` +\n 'Install Chrome, or set AITCC_BROWSER to an executable path.',\n );\n this.name = 'ChromeNotFoundError';\n }\n}\n\nexport class ChromeLaunchError extends Error {\n constructor(\n readonly executable: string,\n cause: Error,\n ) {\n super(`Failed to launch ${executable}: ${cause.message}`);\n this.name = 'ChromeLaunchError';\n this.cause = cause;\n }\n}\n\nexport class ChromeEndpointTimeoutError extends Error {\n constructor(readonly executable: string) {\n super(\n `${executable} did not print a DevTools endpoint within the timeout. ` +\n 'It may have been blocked by the OS or launched a GUI-less variant.',\n );\n this.name = 'ChromeEndpointTimeoutError';\n }\n}\n\n// Probe order: the common install paths, favouring the vendor's own packaging\n// over snap/flatpak (those sometimes restrict --remote-debugging-port writes\n// due to sandboxing). Respect $AITCC_BROWSER as an override.\nexport function chromeCandidates(\n env: NodeJS.ProcessEnv = process.env,\n platform: NodeJS.Platform = process.platform,\n): ChromePaths {\n const override = env.AITCC_BROWSER;\n const out: string[] = [];\n if (override && override.length > 0) out.push(override);\n\n if (platform === 'darwin') {\n out.push(\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Google Chrome Beta.app/Contents/MacOS/Google Chrome Beta',\n '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',\n '/Applications/Chromium.app/Contents/MacOS/Chromium',\n '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',\n '/Applications/Arc.app/Contents/MacOS/Arc',\n );\n } else if (platform === 'win32') {\n // `path.win32.join` produces backslash-separated paths even when the\n // test/build runner is POSIX, so the candidate list matches what\n // Windows actually uses on disk.\n const pf = env.PROGRAMFILES ?? 'C:\\\\Program Files';\n const pf86 = env['PROGRAMFILES(X86)'] ?? 'C:\\\\Program Files (x86)';\n const local = env.LOCALAPPDATA ?? winPath.join(homedir() || 'C:\\\\', 'AppData', 'Local');\n out.push(\n winPath.join(pf, 'Google', 'Chrome', 'Application', 'chrome.exe'),\n winPath.join(pf86, 'Google', 'Chrome', 'Application', 'chrome.exe'),\n winPath.join(local, 'Google', 'Chrome', 'Application', 'chrome.exe'),\n winPath.join(pf, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),\n winPath.join(pf86, 'Microsoft', 'Edge', 'Application', 'msedge.exe'),\n );\n } else {\n // Linux and the rest: rely on PATH lookup via plain command names.\n out.push(\n 'google-chrome-stable',\n 'google-chrome',\n 'chromium-browser',\n 'chromium',\n 'microsoft-edge-stable',\n 'microsoft-edge',\n );\n }\n\n return { candidates: out };\n}\n\nfunction isAbsolutePath(p: string, platform: NodeJS.Platform): boolean {\n if (platform === 'win32') return /^[A-Za-z]:\\\\/.test(p);\n return p.startsWith('/');\n}\n\nasync function resolveOnPath(\n name: string,\n env: NodeJS.ProcessEnv,\n platform: NodeJS.Platform,\n): Promise<string | null> {\n const path = env.PATH ?? env.Path ?? env.path ?? '';\n if (path.length === 0) return null;\n const sep = platform === 'win32' ? ';' : ':';\n const fs = await import('node:fs/promises');\n // Windows picks the matching executable based on PATHEXT — we reproduce\n // the common case so a bare AITCC_BROWSER=chrome still resolves to\n // chrome.exe on disk.\n const extensions =\n platform === 'win32'\n ? ['', ...(env.PATHEXT ?? '.EXE;.CMD;.BAT').split(';').filter((e) => e.length > 0)]\n : [''];\n for (const dir of path.split(sep)) {\n if (dir.length === 0) continue;\n for (const ext of extensions) {\n const candidate = join(dir, name + ext);\n try {\n // Require executable access, not just presence — otherwise a shell\n // alias file or a build artefact sitting on PATH could be picked\n // up as \"Chrome\".\n await fs.access(candidate, fsConstants.X_OK);\n return candidate;\n } catch {\n // try next\n }\n }\n }\n return null;\n}\n\nexport async function findChrome(\n env: NodeJS.ProcessEnv = process.env,\n platform: NodeJS.Platform = process.platform,\n): Promise<string> {\n const { candidates } = chromeCandidates(env, platform);\n const fs = await import('node:fs/promises');\n for (const candidate of candidates) {\n if (isAbsolutePath(candidate, platform)) {\n try {\n await fs.access(candidate, fsConstants.X_OK);\n return candidate;\n } catch {\n // try next\n }\n continue;\n }\n const resolved = await resolveOnPath(candidate, env, platform);\n if (resolved) return resolved;\n }\n throw new ChromeNotFoundError(candidates);\n}\n\nexport interface LaunchedChrome {\n readonly process: ChildProcess;\n readonly webSocketDebuggerUrl: string;\n readonly userDataDir: string;\n dispose(): Promise<void>;\n}\n\nexport interface LaunchChromeOptions {\n readonly initialUrl: string;\n readonly executable?: string;\n readonly endpointTimeoutMs?: number;\n // Hook for tests: if set, skip actually spawning Chrome and feed these\n // bytes to the stderr parser instead. Keeps the parser in the hot path\n // under test without requiring a real Chrome install on CI.\n readonly spawnOverride?: (args: readonly string[]) => ChildProcess;\n}\n\nconst DEVTOOLS_BANNER = /^DevTools listening on (ws:\\/\\/[^\\s]+)\\s*$/m;\n\nfunction consumeDevtoolsEndpoint(buffer: string): string | null {\n const match = DEVTOOLS_BANNER.exec(buffer);\n return match ? (match[1] ?? null) : null;\n}\n\nexport async function launchChrome(options: LaunchChromeOptions): Promise<LaunchedChrome> {\n const executable = options.executable ?? (await findChrome());\n const endpointTimeoutMs = options.endpointTimeoutMs ?? 15_000;\n\n const userDataDir = await mkdtemp(join(tmpdir(), 'aitcc-chrome-'));\n\n // Minimum viable flags:\n // --remote-debugging-port=0 pick an ephemeral port (printed on stderr)\n // --user-data-dir=<tmp> isolate from the user's real profile\n // --no-first-run / --no-default-browser-check skip greeter dialogs\n // --password-store=basic avoid prompting for keyring unlocks on Linux\n // --use-mock-keychain same, but for macOS keychain\n const args: string[] = [\n '--remote-debugging-port=0',\n `--user-data-dir=${userDataDir}`,\n '--no-first-run',\n '--no-default-browser-check',\n '--disable-features=Translate,OptimizationHints',\n '--password-store=basic',\n '--use-mock-keychain',\n options.initialUrl,\n ];\n\n const spawnFn = options.spawnOverride ?? ((a: readonly string[]) => spawn(executable, [...a]));\n let child: ChildProcess;\n try {\n child = spawnFn(args);\n } catch (err) {\n await rm(userDataDir, { recursive: true, force: true }).catch(() => {});\n throw new ChromeLaunchError(executable, err as Error);\n }\n // Don't block Node's exit on the Chrome child — dispose() kills it\n // explicitly on the happy path; on a hard parent exit we'd rather drop\n // Chrome than hang.\n try {\n child.unref();\n } catch {\n // best-effort\n }\n\n const dispose = async (): Promise<void> => {\n try {\n if (!child.killed) child.kill('SIGTERM');\n } catch {\n // best-effort\n }\n await rm(userDataDir, { recursive: true, force: true }).catch(() => {});\n };\n\n let stderrBuf = '';\n const wsUrl = await new Promise<string>((resolve, reject) => {\n const timer = setTimeout(() => {\n cleanup();\n reject(new ChromeEndpointTimeoutError(executable));\n }, endpointTimeoutMs);\n if (typeof timer.unref === 'function') timer.unref();\n\n const onStderr = (chunk: Buffer) => {\n stderrBuf += chunk.toString('utf8');\n const found = consumeDevtoolsEndpoint(stderrBuf);\n if (found) {\n cleanup();\n resolve(found);\n }\n };\n const onExit = (code: number | null) => {\n cleanup();\n reject(\n new ChromeLaunchError(\n executable,\n new Error(`process exited with code ${code ?? 'null'} before printing endpoint`),\n ),\n );\n };\n const onError = (err: Error) => {\n cleanup();\n reject(new ChromeLaunchError(executable, err));\n };\n const cleanup = () => {\n clearTimeout(timer);\n child.stderr?.off('data', onStderr);\n child.off('exit', onExit);\n child.off('error', onError);\n };\n child.stderr?.on('data', onStderr);\n child.on('exit', onExit);\n child.on('error', onError);\n }).catch(async (err) => {\n await dispose();\n throw err;\n });\n\n return {\n process: child,\n webSocketDebuggerUrl: wsUrl,\n userDataDir,\n dispose,\n };\n}\n\n// Exported for unit tests.\nexport const __test = { consumeDevtoolsEndpoint };\n","// Centralized exit codes so every command and the agent-plugin side agree.\n\nexport const ExitCode = {\n Ok: 0,\n Generic: 1,\n Usage: 2,\n NotAuthenticated: 10,\n NetworkError: 11,\n LoginTimeout: 12,\n // Reserved historical slot (was LoginStateMismatch under the OAuth\n // callback flow). Unused by the CDP login path but kept stable so the\n // agent-plugin side doesn't need to renumber.\n LoginStateMismatch: 13,\n LoginBrowserNotFound: 14,\n LoginBrowserFailed: 15,\n LoginCookieCaptureFailed: 16,\n ApiError: 17,\n UpgradeUnavailable: 20,\n UpgradeAlreadyLatest: 21,\n} as const;\n\nexport type ExitCode = (typeof ExitCode)[keyof typeof ExitCode];\n","// Flush-safe exit: drain stdout before calling `process.exit` so a piped\n// consumer never loses the final JSON line. Callers typically write the\n// JSON payload (or plain-text result) to stdout immediately before\n// calling `return exitAfterFlush(code)`.\n\nexport async function exitAfterFlush(code: number): Promise<never> {\n await new Promise<void>((resolve) => process.stdout.write('', () => resolve()));\n process.exit(code);\n}\n","import { homedir } from 'node:os';\nimport { join } from 'node:path';\n\n// Resolve the config directory following the XDG Base Directory spec on\n// POSIX systems and using %APPDATA% on Windows. Falls back gracefully if\n// environment variables are missing (e.g. minimal containers without HOME).\n\nconst APP_NAME = 'aitcc';\n\nexport function configDir(): string {\n if (process.platform === 'win32') {\n const appData = process.env.APPDATA;\n if (appData && appData.length > 0) return join(appData, APP_NAME);\n return join(homedir() || '.', 'AppData', 'Roaming', APP_NAME);\n }\n const xdg = process.env.XDG_CONFIG_HOME;\n if (xdg && xdg.length > 0) return join(xdg, APP_NAME);\n return join(homedir() || '.', '.config', APP_NAME);\n}\n\nexport function sessionFilePath(): string {\n return join(configDir(), 'session.json');\n}\n","import { chmod, mkdir, readFile, unlink, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport type { CdpCookie } from './cdp.js';\nimport { configDir, sessionFilePath } from './paths.js';\n\n// Minimal, forward-compatible session shape. `cookies` mirrors the CDP\n// `Network.getAllCookies` payload so the login command can drop it in\n// directly and the http layer can replay it against the console API.\n//\n// SECURITY: this module is the only place that touches the secret material.\n// - Never log raw cookies / origins.\n// - Treat file IO errors as \"no session\" in user-facing commands.\n\nexport interface SessionUser {\n id: string;\n email: string;\n displayName?: string;\n}\n\nexport interface Session {\n schemaVersion: 1;\n user: SessionUser;\n // CDP-native cookie list from `Network.getAllCookies`. Treat as opaque\n // secret material outside the login/http code paths.\n cookies: readonly CdpCookie[];\n // Reserved for Playwright `storageState`-style `localStorage` snapshots;\n // empty until a feature needs it.\n origins: unknown[];\n capturedAt: string; // ISO-8601\n}\n\n// Public-safe projection for `whoami` and other diagnostics.\nexport interface SessionSummary {\n user: SessionUser;\n capturedAt: string;\n}\n\nfunction summarize(session: Session): SessionSummary {\n return { user: session.user, capturedAt: session.capturedAt };\n}\n\nexport async function readSession(): Promise<Session | null> {\n const path = sessionFilePath();\n let raw: string;\n try {\n raw = await readFile(path, 'utf8');\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return null;\n // Some other IO error — surface one-line diagnostic on stderr so the\n // user can tell \"permission denied\" from \"no session\". The command\n // still falls back to \"not logged in\" behaviour.\n process.stderr.write(`warning: could not read session file at ${path}: ${code ?? 'unknown'}\\n`);\n return null;\n }\n let parsed: Session;\n try {\n parsed = JSON.parse(raw) as Session;\n } catch {\n // Malformed JSON — warn once, then fall back to \"not logged in\". The\n // user can re-run `aitcc login` to replace the broken file.\n process.stderr.write(`warning: session file at ${path} is corrupt and will be ignored\\n`);\n return null;\n }\n const schemaReason = validateSessionShape(parsed);\n if (schemaReason) {\n process.stderr.write(\n `warning: session file at ${path} ignored (${schemaReason}); re-run \\`aitcc login\\`\\n`,\n );\n return null;\n }\n return parsed;\n}\n\nfunction validateSessionShape(parsed: Session): string | null {\n if (parsed.schemaVersion !== 1) return `unknown schemaVersion ${String(parsed.schemaVersion)}`;\n if (!parsed.user || typeof parsed.user.id !== 'string') return 'missing user.id';\n if (typeof parsed.user.email !== 'string') return 'missing user.email';\n if (parsed.user.displayName !== undefined && typeof parsed.user.displayName !== 'string') {\n return 'user.displayName has wrong type';\n }\n if (!Array.isArray(parsed.cookies)) return 'cookies is not an array';\n return null;\n}\n\nexport async function readSessionSummary(): Promise<SessionSummary | null> {\n const s = await readSession();\n return s ? summarize(s) : null;\n}\n\nexport async function writeSession(session: Session): Promise<void> {\n const dir = dirname(sessionFilePath());\n await mkdir(dir, { recursive: true, mode: 0o700 });\n await writeFile(sessionFilePath(), JSON.stringify(session, null, 2), {\n mode: 0o600,\n });\n // writeFile's mode only applies on creation; tighten existing files too.\n try {\n await chmod(sessionFilePath(), 0o600);\n } catch {\n // Windows / exotic FS: best-effort only.\n }\n}\n\nexport async function clearSession(): Promise<{ existed: boolean }> {\n try {\n await unlink(sessionFilePath());\n return { existed: true };\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ENOENT') return { existed: false };\n throw err;\n }\n}\n\nexport function sessionPathForDiagnostics(): string {\n return sessionFilePath();\n}\n\nexport function configDirForDiagnostics(): string {\n return configDir();\n}\n","import { defineCommand } from 'citty';\nimport { type FetchLike, TossApiError } from '../api/http.js';\nimport { fetchConsoleMemberUserInfo } from '../api/me.js';\nimport {\n attachToFirstPage,\n CdpClient,\n type CdpCookie,\n getAllCookies,\n watchMainFrameNavigations,\n} from '../cdp.js';\nimport {\n ChromeEndpointTimeoutError,\n ChromeLaunchError,\n ChromeNotFoundError,\n launchChrome,\n} from '../chrome.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { type Session, writeSession } from '../session.js';\n\n// Login flow (replaces the prior OAuth-callback-server scaffold):\n//\n// 1. Launch a Chrome-family browser with an isolated user-data-dir,\n// pointed at the Toss Business sign-in URL that redirects into the\n// Apps in Toss console after authentication.\n// 2. Watch main-frame navigations over CDP. Once the URL lands on the\n// console's post-login workspace page, we know the auth cookies have\n// been set (HttpOnly, so JS can't see them — CDP can).\n// 3. Dump all cookies via `Network.getAllCookies`, resolve the member\n// user-info from the console API to capture a stable identity, and\n// persist `{ user, cookies, capturedAt }` at `$XDG_CONFIG_HOME/\n// aitcc/session.json` (0600).\n// 4. Dispose the Chrome process and wipe the ephemeral user-data-dir.\n//\n// The CDP-discovered redirect URL (`https://apps-in-toss.toss.im/workspace`\n// with optional `?code=...&state=...` auth-code tail) is the production\n// redirect configured on the client_id. We never need a localhost callback.\n\nconst DEFAULT_AUTHORIZE_URL =\n 'https://business.toss.im/account/sign-in' +\n '?client_id=4uktpjgqd0cp9txybqzuxc2y6w0cuupb' +\n '&redirect_uri=https%3A%2F%2Fapps-in-toss.toss.im%2Fsign-up' +\n '&state=%2Fworkspace';\n\n// The CDP login is complete once the main frame lands on the workspace URL.\nconst LOGIN_LANDING_HOST = 'apps-in-toss.toss.im';\nconst LOGIN_LANDING_PATH_PREFIX = '/workspace';\n\n// Hosts we'll drive a login flow to. `AITCC_OAUTH_URL` is meant as a\n// staging-environment escape hatch, not a way to redirect the CLI to an\n// attacker-controlled URL via a tampered shell rc. A `.toss.im` suffix\n// match is the tightest allowlist that still permits internal hosts.\nconst ALLOWED_AUTHORIZE_HOST_SUFFIXES = ['.toss.im'] as const;\n\nexport function isAllowedAuthorizeHost(host: string): boolean {\n const lower = host.toLowerCase();\n return ALLOWED_AUTHORIZE_HOST_SUFFIXES.some(\n (suffix) => lower === suffix.slice(1) || lower.endsWith(suffix),\n );\n}\n\nexport function isLoginLanding(url: string): boolean {\n try {\n const u = new URL(url);\n // Use hostname (no port) so a same-host landing on a non-default port\n // still matches — the console hasn't shipped a custom port in the\n // wild but we shouldn't trip on one if it appears.\n if (u.hostname !== LOGIN_LANDING_HOST) return false;\n if (\n u.pathname !== LOGIN_LANDING_PATH_PREFIX &&\n !u.pathname.startsWith(`${LOGIN_LANDING_PATH_PREFIX}/`)\n ) {\n return false;\n }\n // Reject things like `/workspacely`: require the prefix to be followed\n // by end-of-path or a '/'.\n return true;\n } catch {\n return false;\n }\n}\n\nexport const loginCommand = defineCommand({\n meta: {\n name: 'login',\n description: 'Open a browser to sign in, then capture the console session cookies.',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n timeout: {\n type: 'string',\n description: 'Abort if login does not complete within N seconds (default 300).',\n default: '300',\n },\n },\n async run({ args }) {\n const emitError = (payload: Record<string, unknown>, human: string) => {\n if (args.json) {\n process.stdout.write(`${JSON.stringify({ ok: false, ...payload })}\\n`);\n }\n process.stderr.write(`${human}\\n`);\n };\n\n const timeoutSec = Number(args.timeout);\n if (!Number.isFinite(timeoutSec) || timeoutSec < 1) {\n emitError(\n { reason: 'invalid-timeout', given: args.timeout },\n `Invalid --timeout value: ${args.timeout}`,\n );\n return exitAfterFlush(ExitCode.Usage);\n }\n const timeoutMs = timeoutSec * 1000;\n\n const rawAuthorizeUrl = process.env.AITCC_OAUTH_URL;\n const authorizeUrl = rawAuthorizeUrl ?? DEFAULT_AUTHORIZE_URL;\n if (rawAuthorizeUrl) {\n let parsed: URL | null = null;\n try {\n parsed = new URL(rawAuthorizeUrl);\n } catch {\n // fall through\n }\n if (!parsed || (parsed.protocol !== 'https:' && parsed.protocol !== 'http:')) {\n emitError(\n { reason: 'invalid-authorize-url' },\n `AITCC_OAUTH_URL is not a valid http(s) URL: ${rawAuthorizeUrl}`,\n );\n return exitAfterFlush(ExitCode.Usage);\n }\n if (!isAllowedAuthorizeHost(parsed.hostname)) {\n emitError(\n { reason: 'authorize-host-not-allowed', host: parsed.hostname },\n `Refusing to open ${parsed.hostname}: only *.toss.im hosts are allowed for sign-in.`,\n );\n return exitAfterFlush(ExitCode.Usage);\n }\n process.stderr.write(`Using custom authorize URL from AITCC_OAUTH_URL: ${authorizeUrl}\\n`);\n }\n\n // Cap Chrome's own startup window at half the overall --timeout, with\n // a 30-second floor and 60-second ceiling. Corporate anti-virus can\n // easily push a cold Chrome launch past the default 15s; short\n // `--timeout` values shouldn't starve the launch itself.\n const endpointTimeoutMs = Math.min(60_000, Math.max(30_000, Math.floor(timeoutMs / 2)));\n\n // Launch Chrome.\n const launched = await launchChrome({\n initialUrl: authorizeUrl,\n endpointTimeoutMs,\n }).catch((err: Error) => err);\n if (launched instanceof ChromeNotFoundError) {\n emitError({ reason: 'chrome-not-found', candidates: launched.candidates }, launched.message);\n return exitAfterFlush(ExitCode.LoginBrowserNotFound);\n }\n if (launched instanceof ChromeLaunchError || launched instanceof ChromeEndpointTimeoutError) {\n emitError(\n { reason: 'chrome-launch-failed', message: launched.message },\n `Failed to launch browser: ${launched.message}`,\n );\n return exitAfterFlush(ExitCode.LoginBrowserFailed);\n }\n if (launched instanceof Error) {\n // An unexpected Error type — keep enough context to diagnose later.\n emitError(\n {\n reason: 'chrome-launch-failed',\n errorName: launched.name,\n message: launched.message,\n },\n `Failed to launch browser (${launched.name}): ${launched.message}`,\n );\n return exitAfterFlush(ExitCode.LoginBrowserFailed);\n }\n\n process.stderr.write(\n 'Opened a browser window — complete the sign-in there. The CLI will capture the session automatically.\\n',\n );\n\n // Resource disposal must happen BEFORE `exitAfterFlush` is called:\n // `exitAfterFlush` terminates the process, and Chrome children on POSIX\n // are not killed automatically when the parent exits. A `try/finally`\n // wrapper is *not* safe here — the enclosing async function's finally\n // races `process.exit` and may skip the SIGTERM + rm -rf.\n let client: CdpClient | null = null;\n const disposeAll = async (): Promise<void> => {\n if (client) {\n await client.close().catch(() => {});\n client = null;\n }\n await launched.dispose().catch(() => {});\n };\n const exitWith = async (code: number): Promise<never> => {\n await disposeAll();\n return exitAfterFlush(code);\n };\n\n try {\n client = await CdpClient.connect({ url: launched.webSocketDebuggerUrl });\n } catch (err) {\n emitError(\n { reason: 'cdp-connect-failed', message: (err as Error).message },\n `Could not connect to the browser over CDP: ${(err as Error).message}`,\n );\n return exitWith(ExitCode.LoginBrowserFailed);\n }\n\n let attached: Awaited<ReturnType<typeof attachToFirstPage>>;\n try {\n attached = await attachToFirstPage(client);\n } catch (err) {\n emitError(\n { reason: 'cdp-attach-failed', message: (err as Error).message },\n `Could not attach to the browser tab: ${(err as Error).message}`,\n );\n return exitWith(ExitCode.LoginBrowserFailed);\n }\n\n const landing = await waitForLanding(client, attached.sessionId, timeoutMs);\n if (landing === 'timeout') {\n emitError({ reason: 'login-timeout', timeoutSec }, `Login timed out after ${timeoutSec}s.`);\n return exitWith(ExitCode.LoginTimeout);\n }\n if (landing === 'aborted') {\n emitError(\n { reason: 'login-aborted' },\n 'Login was aborted (browser closed before reaching the console).',\n );\n return exitWith(ExitCode.LoginBrowserFailed);\n }\n\n // Pull all cookies across all origins the browser has collected.\n // `Network.getAllCookies` requires a target session (it isn't exposed\n // on the browser-level endpoint), so we route through the attached\n // page. The returned set still spans every origin the browser has\n // stored cookies for (business.toss.im, business-accounts.toss.im,\n // apps-in-toss.toss.im), not just the current page.\n const cookies = await getAllCookies(client, attached.sessionId).catch((err: Error) => err);\n if (cookies instanceof Error) {\n emitError(\n { reason: 'cookie-capture-failed', message: cookies.message },\n `Failed to capture cookies: ${cookies.message}`,\n );\n return exitWith(ExitCode.LoginCookieCaptureFailed);\n }\n\n // Resolve identity via the console member info endpoint. This also\n // doubles as a liveness check — a session that can't call /me means\n // we captured cookies too early (before the auth-code exchange\n // completed). If so, we wait briefly and retry once.\n const user = await resolveUserWithRetry(cookies, {\n onRetry: (ms) =>\n process.stderr.write(\n `Cookies not yet accepted by the console API — retrying in ${ms}ms...\\n`,\n ),\n }).catch((err: Error) => err);\n if (user instanceof Error) {\n const authFailed = user instanceof TossApiError && user.isAuthError;\n emitError(\n {\n reason: authFailed ? 'login-auth-not-active' : 'member-info-failed',\n message: user.message,\n },\n authFailed\n ? 'Browser session did not produce valid console cookies. Try again and wait for the workspace page to load.'\n : `Failed to read member info: ${user.message}`,\n );\n return exitWith(authFailed ? ExitCode.LoginCookieCaptureFailed : ExitCode.ApiError);\n }\n\n const session: Session = {\n schemaVersion: 1,\n user: {\n id: String(user.id),\n email: user.email,\n displayName: user.name,\n },\n cookies,\n origins: [],\n capturedAt: new Date().toISOString(),\n };\n try {\n await writeSession(session);\n } catch (err) {\n emitError(\n { reason: 'session-write-failed', message: (err as Error).message },\n `Failed to write session file: ${(err as Error).message}`,\n );\n return exitWith(ExitCode.Generic);\n }\n\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({\n ok: true,\n status: 'logged-in',\n user: session.user,\n capturedAt: session.capturedAt,\n cookieCount: cookies.length,\n })}\\n`,\n );\n } else {\n process.stdout.write(`Logged in as ${user.name} <${user.email}>\\n`);\n }\n return exitWith(ExitCode.Ok);\n },\n});\n\nexport async function waitForLanding(\n client: CdpClient,\n sessionId: string,\n timeoutMs: number,\n): Promise<'ok' | 'timeout' | 'aborted'> {\n // Two signals, run together, first wins:\n // (a) Page.frameNavigated events — responsive, catches the final redirect.\n // (b) Polling Page.getFrameTree — a safety net for the race where Chrome\n // finishes the auth redirects before we finish attaching and\n // subscribing. The navigation event won't re-fire for pages that\n // already landed, so we have to poll the current URL at least once\n // (and continue polling in case CDP events are dropped on slow links).\n return await new Promise<'ok' | 'timeout' | 'aborted'>((resolve) => {\n let settled = false;\n const stops: Array<() => void | Promise<void>> = [];\n const settle = (outcome: 'ok' | 'timeout' | 'aborted') => {\n if (settled) return;\n settled = true;\n clearTimeout(timer);\n clearInterval(pollTimer);\n for (const s of stops) {\n try {\n void s();\n } catch {\n // best effort\n }\n }\n resolve(outcome);\n };\n\n const timer = setTimeout(() => settle('timeout'), timeoutMs);\n if (typeof timer.unref === 'function') timer.unref();\n\n // Target-destroyed → the user closed the tab before landing.\n stops.push(\n client.on((event) => {\n if (event.method === 'Target.targetDestroyed') settle('aborted');\n }),\n );\n\n // (a) Live event subscription. Fires on fresh navigations after we\n // Page.enable — may not trigger if Chrome already finished all\n // redirects before we attached (handled by (b)).\n watchMainFrameNavigations(client, sessionId, (ev) => {\n if (!ev.isMainFrame) return;\n if (isLoginLanding(ev.url)) settle('ok');\n })\n .then((off) => {\n // Polling may have already settled by the time subscribe returns;\n // in that case unregister the listener immediately rather than\n // leaving it dangling on the client.\n if (settled) off();\n else stops.push(off);\n })\n .catch((err: Error) => {\n if (settled) return;\n process.stderr.write(`Could not watch for navigation: ${err.message}\\n`);\n });\n\n // (b) Poll the current main-frame URL every second. Cheap, robust.\n const checkCurrent = async () => {\n if (settled) return;\n const tree = await client\n .send<{ frameTree: { frame: { url?: string; parentId?: string } } }>(\n 'Page.getFrameTree',\n {},\n sessionId,\n )\n .catch(() => null);\n const url = tree?.frameTree.frame?.url;\n if (url && isLoginLanding(url)) settle('ok');\n };\n // Kick off an immediate check — covers the \"already landed\" case.\n void checkCurrent();\n const pollTimer = setInterval(() => {\n void checkCurrent();\n }, 1000);\n if (typeof pollTimer.unref === 'function') pollTimer.unref();\n });\n}\n\n// The console issues auth cookies a beat after the landing navigation\n// fires — if the first /me call 401s, we wait this long and retry once.\n// Larger than the fastest observed exchange (~200 ms), small enough to\n// keep the user from wondering whether the CLI hung.\nexport const AUTH_SETTLE_DELAY_MS = 750;\n\nexport async function resolveUserWithRetry(\n cookies: readonly CdpCookie[],\n opts: {\n onRetry?: (delayMs: number) => void;\n fetchImpl?: FetchLike;\n } = {},\n): Promise<Awaited<ReturnType<typeof fetchConsoleMemberUserInfo>>> {\n const callArgs = opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {};\n try {\n return await fetchConsoleMemberUserInfo(cookies, callArgs);\n } catch (err) {\n if (err instanceof TossApiError && err.isAuthError) {\n opts.onRetry?.(AUTH_SETTLE_DELAY_MS);\n await new Promise((r) => {\n const t = setTimeout(r, AUTH_SETTLE_DELAY_MS);\n if (typeof t.unref === 'function') t.unref();\n });\n return await fetchConsoleMemberUserInfo(cookies, callArgs);\n }\n throw err;\n }\n}\n","import { defineCommand } from 'citty';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { clearSession, sessionPathForDiagnostics } from '../session.js';\n\nexport const logoutCommand = defineCommand({\n meta: {\n name: 'logout',\n description: 'Delete the local session file.',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n },\n async run({ args }) {\n const path = sessionPathForDiagnostics();\n\n let existed: boolean;\n try {\n const result = await clearSession();\n existed = result.existed;\n } catch (err) {\n const message = (err as Error).message;\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({ ok: false, reason: 'unlink-failed', path, message })}\\n`,\n );\n }\n process.stderr.write(`Failed to remove session file at ${path}: ${message}\\n`);\n return exitAfterFlush(ExitCode.Generic);\n }\n\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({ ok: true, status: existed ? 'logged-out' : 'no-session', path })}\\n`,\n );\n } else if (existed) {\n process.stdout.write(`Logged out. Session removed from ${path}\\n`);\n } else {\n process.stdout.write(`No active session at ${path}.\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n },\n});\n","// Thin GitHub Releases API client. Only reads public endpoints, never writes.\n\nconst REPO_OWNER = 'apps-in-toss-community';\nconst REPO_NAME = 'console-cli';\n\nexport interface ReleaseAsset {\n name: string;\n browser_download_url: string;\n size: number;\n}\n\nexport interface Release {\n tag_name: string;\n name: string | null;\n html_url: string;\n assets: ReleaseAsset[];\n}\n\nfunction defaultHeaders(): HeadersInit {\n const headers: Record<string, string> = {\n Accept: 'application/vnd.github+json',\n 'User-Agent': 'aitcc',\n 'X-GitHub-Api-Version': '2022-11-28',\n };\n const token = process.env.GITHUB_TOKEN;\n if (token && token.length > 0) {\n headers.Authorization = `Bearer ${token}`;\n }\n return headers;\n}\n\nexport async function fetchLatestRelease(): Promise<Release> {\n const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;\n const res = await fetch(url, { headers: defaultHeaders() });\n if (!res.ok) {\n throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);\n }\n return (await res.json()) as Release;\n}\n\n// Parse `tag_name` into a comparable semver string. Changesets tags this repo\n// as `@ait-co/console-cli@0.1.2`; older ad-hoc tags may be `v0.1.2`. We\n// accept both.\nexport function versionFromTag(tag: string): string {\n const at = tag.lastIndexOf('@');\n const candidate = at >= 0 ? tag.slice(at + 1) : tag;\n return candidate.startsWith('v') ? candidate.slice(1) : candidate;\n}\n","// Map Node's `process.platform` / `process.arch` to the binary asset names\n// produced by `scripts/build-bin.ts` and attached to GitHub Releases.\n\nexport interface PlatformTarget {\n os: 'linux' | 'darwin' | 'windows';\n arch: 'x64' | 'arm64';\n assetName: string;\n}\n\nexport function detectPlatform(): PlatformTarget | null {\n let os: PlatformTarget['os'];\n switch (process.platform) {\n case 'linux':\n os = 'linux';\n break;\n case 'darwin':\n os = 'darwin';\n break;\n case 'win32':\n os = 'windows';\n break;\n default:\n return null;\n }\n\n let arch: PlatformTarget['arch'];\n switch (process.arch) {\n case 'x64':\n arch = 'x64';\n break;\n case 'arm64':\n arch = 'arm64';\n break;\n default:\n return null;\n }\n\n // We don't ship windows-arm64 yet — Bun's `--compile` target support is still partial.\n if (os === 'windows' && arch === 'arm64') return null;\n\n const suffix = os === 'windows' ? '.exe' : '';\n return { os, arch, assetName: `aitcc-${os}-${arch}${suffix}` };\n}\n","// Minimal semver comparator. We only need \"is A strictly newer than B?\" for\n// the upgrade check. Pulling the full `semver` package would bloat the\n// compiled binary for one function.\n\nexport function parseSemver(\n v: string,\n): { major: number; minor: number; patch: number; pre: string } | null {\n const m = /^(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/.exec(v);\n if (!m) return null;\n return { major: +m[1]!, minor: +m[2]!, patch: +m[3]!, pre: m[4] ?? '' };\n}\n\n// Returns 1 if a > b, -1 if a < b, 0 if equal. Returns 0 if either is\n// unparseable (defensive — upgrade will treat that as \"already latest\").\nexport function compareSemver(a: string, b: string): number {\n const pa = parseSemver(a);\n const pb = parseSemver(b);\n if (!pa || !pb) return 0;\n if (pa.major !== pb.major) return pa.major > pb.major ? 1 : -1;\n if (pa.minor !== pb.minor) return pa.minor > pb.minor ? 1 : -1;\n if (pa.patch !== pb.patch) return pa.patch > pb.patch ? 1 : -1;\n // Treat \"no prerelease\" as greater than \"has prerelease\" (1.0.0 > 1.0.0-rc).\n if (pa.pre === pb.pre) return 0;\n if (pa.pre === '') return 1;\n if (pb.pre === '') return -1;\n return pa.pre > pb.pre ? 1 : -1;\n}\n","// Single source of truth for the embedded CLI version.\n//\n// The value is replaced at build time:\n// - tsdown → via the `define` block in `tsdown.config.ts`\n// - bun → via `--define AITCC_VERSION=...` in `scripts/build-bin.ts`\n//\n// During `pnpm test` / `ts-node` execution the define isn't applied, so we fall\n// back to reading `package.json` at runtime. That path is never hit in the\n// shipped artifacts.\n\ndeclare const AITCC_VERSION: string | undefined;\n\nfunction resolveVersion(): string {\n try {\n // biome-ignore lint/suspicious/noExplicitAny: globalThis lookup for optional build-time define\n const injected = (globalThis as any).AITCC_VERSION as string | undefined;\n if (typeof injected === 'string' && injected.length > 0) return injected;\n } catch {\n // ignore\n }\n try {\n if (typeof AITCC_VERSION === 'string' && AITCC_VERSION.length > 0) {\n return AITCC_VERSION;\n }\n } catch {\n // ignore\n }\n return '0.0.0-dev';\n}\n\nexport const VERSION = resolveVersion();\n","import { chmod, rename, writeFile } from 'node:fs/promises';\nimport { basename, dirname } from 'node:path';\nimport { defineCommand } from 'citty';\nimport { ExitCode } from '../exit.js';\nimport { fetchLatestRelease, versionFromTag } from '../github.js';\nimport { detectPlatform } from '../platform.js';\nimport { compareSemver } from '../semver.js';\nimport { VERSION } from '../version.js';\n\n// Distinguishes a Bun-compiled standalone (where `process.execPath` points at\n// the binary itself) from a Node-hosted install (where it points at `node`).\n// Only the former can atomically replace itself; the latter should upgrade\n// via npm.\nfunction isStandaloneBinary(): boolean {\n const exe = basename(process.execPath).toLowerCase();\n return exe.startsWith('aitcc');\n}\n\nexport const upgradeCommand = defineCommand({\n meta: {\n name: 'upgrade',\n description: 'Download the latest release binary from GitHub and replace the current one.',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n force: {\n type: 'boolean',\n description: 'Re-install even if already on the latest version.',\n default: false,\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Check for updates without downloading or replacing.',\n default: false,\n },\n },\n async run({ args }) {\n const emit = (payload: Record<string, unknown>, human: string) => {\n if (args.json) {\n process.stdout.write(`${JSON.stringify(payload)}\\n`);\n } else {\n process.stdout.write(`${human}\\n`);\n }\n };\n const emitError = (payload: Record<string, unknown>, human: string) => {\n if (args.json) {\n process.stdout.write(`${JSON.stringify({ ok: false, ...payload })}\\n`);\n } else {\n process.stderr.write(`${human}\\n`);\n }\n };\n\n let release: Awaited<ReturnType<typeof fetchLatestRelease>>;\n try {\n release = await fetchLatestRelease();\n } catch (err) {\n emitError(\n { reason: 'network-error', message: (err as Error).message },\n `Failed to query GitHub releases: ${(err as Error).message}`,\n );\n process.exit(ExitCode.NetworkError);\n }\n\n const latest = versionFromTag(release.tag_name);\n const current = VERSION;\n const cmp = compareSemver(latest, current);\n const needsUpdate = cmp > 0 || args.force;\n\n if (!needsUpdate) {\n emit(\n { ok: true, status: 'already-latest', current, latest },\n `Already on the latest version (${current}).`,\n );\n process.exit(ExitCode.UpgradeAlreadyLatest);\n }\n\n if (args['dry-run']) {\n emit(\n { ok: true, status: 'update-available', current, latest, url: release.html_url },\n `Update available: ${current} → ${latest}\\n${release.html_url}`,\n );\n return;\n }\n\n if (!isStandaloneBinary()) {\n emitError(\n {\n reason: 'not-standalone',\n current,\n latest,\n hint: 'npm i -g @ait-co/console-cli@latest',\n },\n [\n 'This install was launched via Node, not the standalone binary.',\n 'Self-upgrade is only supported for the compiled binary.',\n `Run: npm i -g @ait-co/console-cli@latest (currently ${current}, latest ${latest})`,\n ].join('\\n'),\n );\n process.exit(ExitCode.UpgradeUnavailable);\n }\n\n const platform = detectPlatform();\n if (!platform) {\n emitError(\n {\n reason: 'unsupported-platform',\n platform: process.platform,\n arch: process.arch,\n },\n `No prebuilt binary for ${process.platform}/${process.arch}.`,\n );\n process.exit(ExitCode.UpgradeUnavailable);\n }\n\n const asset = release.assets.find((a) => a.name === platform.assetName);\n if (!asset) {\n emitError(\n { reason: 'asset-missing', assetName: platform.assetName, tag: release.tag_name },\n `Release ${release.tag_name} has no asset named ${platform.assetName}. It may still be uploading.`,\n );\n process.exit(ExitCode.UpgradeUnavailable);\n }\n\n const exePath = process.execPath;\n const stagingPath = `${exePath}.new.${Date.now()}`;\n\n if (!args.json) {\n process.stdout.write(`Downloading ${asset.name} (${latest})...\\n`);\n }\n\n try {\n const res = await fetch(asset.browser_download_url);\n if (!res.ok || !res.body) {\n throw new Error(`Download failed: ${res.status} ${res.statusText}`);\n }\n const buf = new Uint8Array(await res.arrayBuffer());\n await writeFile(stagingPath, buf, { mode: 0o755 });\n await chmod(stagingPath, 0o755);\n } catch (err) {\n emitError(\n { reason: 'download-failed', message: (err as Error).message },\n `Failed to download new binary: ${(err as Error).message}`,\n );\n process.exit(ExitCode.NetworkError);\n }\n\n // Atomic replace. POSIX `rename(2)` on the same filesystem is atomic.\n // On Windows a running exe can't be overwritten directly; the staging\n // path is in the same dir, so rename-over works on most shells, and we\n // leave `<exe>.old` handling to a future refinement.\n try {\n if (process.platform === 'win32') {\n await rename(exePath, `${exePath}.old`);\n await rename(stagingPath, exePath);\n } else {\n await rename(stagingPath, exePath);\n }\n } catch (err) {\n emitError(\n { reason: 'replace-failed', message: (err as Error).message, exePath, stagingPath },\n `Failed to replace binary at ${exePath}: ${(err as Error).message}`,\n );\n process.exit(ExitCode.Generic);\n }\n\n emit(\n {\n ok: true,\n status: 'upgraded',\n from: current,\n to: latest,\n installedAt: exePath,\n installedIn: dirname(exePath),\n },\n `Upgraded aitcc: ${current} → ${latest}`,\n );\n },\n});\n","import { defineCommand } from 'citty';\nimport { NetworkError, TossApiError } from '../api/http.js';\nimport { fetchConsoleMemberUserInfo } from '../api/me.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { readSession, sessionPathForDiagnostics } from '../session.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// Success (session present + — for live mode — reachable):\n// { ok: true, authenticated: true, source: 'live'|'cache', user, capturedAt, ... }\n// Session missing:\n// { ok: true, authenticated: false } exit 10\n// Session expired (console rejected our cookies):\n// { ok: true, authenticated: false, reason: 'session-expired' } exit 10\n// Network failure talking to the console:\n// { ok: false, reason: 'network-error', message } exit 11\n// Any other API / unexpected error:\n// { ok: false, reason: 'api-error', message } exit 17\n//\n// The top-level `ok` is always present and indicates whether the command\n// ran cleanly; `authenticated` is only meaningful when `ok: true`.\n\nexport const whoamiCommand = defineCommand({\n meta: {\n name: 'whoami',\n description: 'Show the currently authenticated user (live from the console API by default).',\n },\n args: {\n json: {\n type: 'boolean',\n description: 'Emit machine-readable JSON to stdout.',\n default: false,\n },\n offline: {\n type: 'boolean',\n description: 'Skip the live API call and read only the cached session summary.',\n default: false,\n },\n },\n async run({ args }) {\n const session = await readSession();\n\n if (!session) {\n if (args.json) {\n process.stdout.write(`${JSON.stringify({ ok: true, authenticated: false })}\\n`);\n } else {\n process.stderr.write('Not logged in. Run `aitcc login` to start a session.\\n');\n process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\\n`);\n }\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n\n if (args.offline) {\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({\n ok: true,\n authenticated: true,\n source: 'cache',\n user: session.user,\n capturedAt: session.capturedAt,\n })}\\n`,\n );\n return exitAfterFlush(ExitCode.Ok);\n }\n const label = session.user.displayName\n ? `${session.user.displayName} <${session.user.email}>`\n : session.user.email;\n process.stdout.write(`Logged in as ${label} (cached)\\n`);\n process.stdout.write(`Session captured: ${session.capturedAt}\\n`);\n return exitAfterFlush(ExitCode.Ok);\n }\n\n try {\n const info = await fetchConsoleMemberUserInfo(session.cookies);\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({\n ok: true,\n authenticated: true,\n source: 'live',\n user: {\n id: String(info.id),\n bizUserNo: info.bizUserNo,\n name: info.name,\n email: info.email,\n role: info.role,\n },\n workspaces: info.workspaces.map((w) => ({\n workspaceId: w.workspaceId,\n workspaceName: w.workspaceName,\n role: w.role,\n })),\n capturedAt: session.capturedAt,\n })}\\n`,\n );\n return exitAfterFlush(ExitCode.Ok);\n }\n process.stdout.write(`Logged in as ${info.name} <${info.email}> (${info.role})\\n`);\n if (info.workspaces.length > 0) {\n process.stdout.write('Workspaces:\\n');\n for (const w of info.workspaces) {\n process.stdout.write(` - ${w.workspaceName} (id ${w.workspaceId}, ${w.role})\\n`);\n }\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n if (err instanceof TossApiError && err.isAuthError) {\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({\n ok: true,\n authenticated: false,\n reason: 'session-expired',\n errorCode: err.errorCode,\n })}\\n`,\n );\n } else {\n process.stderr.write('Session is no longer valid. Run `aitcc login` again.\\n');\n }\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n if (err instanceof NetworkError) {\n // Network failures are surfaced as hard errors — we don't silently\n // fall back to the cache because agent-plugin callers branching on\n // exit code would miss the degradation. Users who explicitly want\n // the cached identity have `--offline` for that.\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({ ok: false, reason: 'network-error', message: err.message })}\\n`,\n );\n } else {\n process.stderr.write(\n `Network error reaching the console API: ${err.message}. Use \\`aitcc whoami --offline\\` for the cached identity.\\n`,\n );\n }\n return exitAfterFlush(ExitCode.NetworkError);\n }\n if (args.json) {\n process.stdout.write(\n `${JSON.stringify({ ok: false, reason: 'api-error', message: (err as Error).message })}\\n`,\n );\n } else {\n process.stderr.write(`Unexpected error: ${(err as Error).message}\\n`);\n }\n return exitAfterFlush(ExitCode.ApiError);\n }\n },\n});\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty';\nimport { loginCommand } from './commands/login.js';\nimport { logoutCommand } from './commands/logout.js';\nimport { upgradeCommand } from './commands/upgrade.js';\nimport { whoamiCommand } from './commands/whoami.js';\nimport { VERSION } from './version.js';\n\nconst main = defineCommand({\n meta: {\n name: 'aitcc',\n version: VERSION,\n description:\n 'aitcc — Apps in Toss Community Console CLI. Unofficial, not affiliated with Toss.',\n },\n subCommands: {\n whoami: whoamiCommand,\n login: loginCommand,\n logout: logoutCommand,\n upgrade: upgradeCommand,\n },\n});\n\nrunMain(main);\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ait-co/console-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "CLI and MCP server for the Apps in Toss developer console — log in once in a browser, then drive builds, deploys, and releases headlessly",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
7
7
|
"node": ">=24"
|
|
8
8
|
},
|
|
9
9
|
"bin": {
|
|
10
|
-
"
|
|
10
|
+
"aitcc": "./dist/cli.mjs"
|
|
11
11
|
},
|
|
12
12
|
"main": "./dist/cli.mjs",
|
|
13
13
|
"types": "./dist/cli.d.mts",
|