@ait-co/console-cli 0.1.8 → 0.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.mjs","names":["BASE","parseYaml","lsCommand","BASE","lsCommand","winPath","fsConstants","lsCommand"],"sources":["../src/api/http.ts","../src/api/mini-apps.ts","../src/exit.ts","../src/flush.ts","../src/paths.ts","../src/session.ts","../src/commands/_shared.ts","../src/config/app-manifest.ts","../src/config/image-validator.ts","../src/commands/register-payload.ts","../src/commands/register.ts","../src/commands/app.ts","../src/api/api-keys.ts","../src/commands/keys.ts","../src/api/me.ts","../src/cdp.ts","../src/chrome.ts","../src/commands/login.ts","../src/commands/logout.ts","../src/api/members.ts","../src/commands/members.ts","../src/github.ts","../src/platform.ts","../src/semver.ts","../src/version.ts","../src/commands/upgrade.ts","../src/update-check.ts","../src/commands/whoami.ts","../src/api/workspaces.ts","../src/commands/workspace.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 return executeAndUnwrap<T>(url, init, options.fetchImpl);\n}\n\n/**\n * Send a pre-built `RequestInit` against the console API and unwrap the\n * Toss envelope. Use this when the caller needs to build the body itself\n * (multipart uploads, binary requests, anything that can't live under\n * `requestConsoleApi`'s JSON-body assumption). Cookie header composition\n * and any additional headers remain the caller's responsibility.\n *\n * Exists so `uploadMiniAppResource` doesn't have to re-implement the\n * text→JSON→envelope→error branch in `requestConsoleApi`; drift between\n * the two paths has bitten us once (cf. the refactor in the\n * `app register` review).\n */\nexport async function executeAndUnwrap<T>(\n url: URL,\n init: RequestInit,\n fetchImpl?: FetchLike,\n): Promise<T> {\n const impl: FetchLike = fetchImpl ?? ((input, i) => fetch(input, i));\n let res: Response;\n try {\n res = await impl(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 {\n cookieHeaderFor,\n executeAndUnwrap,\n type FetchLike,\n MalformedResponseError,\n requestConsoleApi,\n} from './http.js';\n\n// Two endpoints cover the \"list my apps\" surface:\n//\n// GET /workspaces/:id/mini-app → array of app summaries\n// GET /workspaces/:id/mini-apps/review-status → { hasPolicyViolation, miniApps: [...] }\n//\n// Note the singular/plural inconsistency (`mini-app` vs `mini-apps`) is\n// how the upstream API actually spells them, not a transcription error —\n// see TODO.md's console feature inventory.\n//\n// The detailed field shape inside each array element is not yet known to\n// us (the confirmed workspaces currently have zero apps). We model each\n// element as a minimal \"id + name + extras\" envelope so the CLI can show\n// something useful today and layer on specific fields as they're observed.\n// `extra` is typed as unknown-valued so we don't pretend to know more.\n\nconst BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport interface MiniAppSummary {\n readonly id: string | number;\n readonly name: string | undefined;\n readonly extra: Readonly<Record<string, unknown>>;\n}\n\nexport interface ReviewStatusSummary {\n readonly hasPolicyViolation: boolean;\n readonly miniApps: readonly Readonly<Record<string, unknown>>[];\n}\n\nexport async function fetchMiniApps(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<MiniAppSummary[]> {\n const url = `${BASE}/workspaces/${workspaceId}/mini-app`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (!Array.isArray(raw)) {\n throw new Error(`Unexpected mini-app list shape for workspace=${workspaceId}`);\n }\n return raw.map((item, index) => normalizeMiniApp(item, workspaceId, index));\n}\n\nfunction normalizeMiniApp(item: unknown, workspaceId: number, index: number): MiniAppSummary {\n if (item === null || typeof item !== 'object') {\n throw new Error(\n `Unexpected mini-app entry at index ${index} for workspace=${workspaceId}: not an object`,\n );\n }\n const rec = item as Record<string, unknown>;\n const rawId = rec.id ?? rec.miniAppId ?? rec.appId;\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n throw new Error(\n `Unexpected mini-app entry at index ${index} for workspace=${workspaceId}: missing id`,\n );\n }\n const rawName = rec.name ?? rec.miniAppName ?? rec.appName;\n const name = typeof rawName === 'string' ? rawName : undefined;\n const {\n id: _id,\n miniAppId: _mid,\n appId: _aid,\n name: _n,\n miniAppName: _mn,\n appName: _an,\n ...extra\n } = rec;\n return { id: rawId, name, extra };\n}\n\nexport async function fetchReviewStatus(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<ReviewStatusSummary> {\n const url = `${BASE}/workspaces/${workspaceId}/mini-apps/review-status`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (raw === null || typeof raw !== 'object') {\n throw new Error(`Unexpected review-status shape for workspace=${workspaceId}`);\n }\n const rec = raw as Record<string, unknown>;\n const hasPolicyViolation = Boolean(rec.hasPolicyViolation);\n const miniAppsRaw = rec.miniApps;\n if (!Array.isArray(miniAppsRaw)) {\n throw new Error(\n `Unexpected review-status shape for workspace=${workspaceId}: miniApps is not an array`,\n );\n }\n const miniApps = miniAppsRaw.map((m) => {\n if (m === null || typeof m !== 'object') return {};\n return m as Record<string, unknown>;\n });\n return { hasPolicyViolation, miniApps };\n}\n\n// --- Register (create) ---\n//\n// `createMiniApp` and `uploadMiniAppResource` back the `app register`\n// command. The submit payload shape below is *inferred* from static\n// bundle analysis (`VALIDATION-RULES.md` in the umbrella `.playwright-\n// mcp/`); the console UI never round-trips intermediate drafts, so the\n// only authoritative record will come from dog-food task #23. Field\n// names here intentionally mirror the `Xc` function from the bundle so\n// when #23 runs, any correction is a direct rename rather than a\n// restructure.\n\n// Exposed as `unknown` per-image so the caller (not this layer) is the\n// place where a cross-field invariant like \"at least 3 PREVIEW/VERTICAL\"\n// is enforced. Keeping this type open also makes it trivial to add the\n// `LOGO` imageType or other orientation values without touching the\n// network module.\nexport type MiniAppImageType = 'LOGO' | 'THUMBNAIL' | 'PREVIEW';\nexport type MiniAppImageOrientation = 'HORIZONTAL' | 'VERTICAL';\n\nexport interface MiniAppImageEntry {\n readonly imageUrl: string;\n readonly imageType: MiniAppImageType;\n readonly orientation: MiniAppImageOrientation;\n}\n\nexport interface MiniAppSubmitPayload {\n readonly miniApp: {\n readonly title: string;\n readonly titleEn: string;\n readonly appName: string;\n readonly iconUri: string;\n readonly status: 'PREPARE';\n readonly darkModeIconUri?: string;\n readonly homePageUri?: string;\n readonly csEmail: string;\n readonly description: string; // subtitle (≤20 chars)\n readonly detailDescription: string;\n readonly images: readonly MiniAppImageEntry[];\n };\n readonly impression: {\n readonly keywordList: readonly string[];\n readonly categoryIds: readonly number[];\n readonly subCategoryIds?: readonly number[];\n };\n}\n\nexport interface CreateMiniAppResult {\n readonly miniAppId: string | number | undefined;\n readonly reviewState: string | undefined;\n readonly extra: Readonly<Record<string, unknown>>;\n}\n\nexport async function createMiniApp(\n workspaceId: number,\n payload: MiniAppSubmitPayload,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<CreateMiniAppResult> {\n const url = `${BASE}/workspaces/${workspaceId}/mini-app/review`;\n const raw = await requestConsoleApi<unknown>({\n url,\n method: 'POST',\n cookies,\n body: payload,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n return normalizeCreateResult(raw);\n}\n\nfunction normalizeCreateResult(raw: unknown): CreateMiniAppResult {\n if (raw === null || typeof raw !== 'object') {\n return { miniAppId: undefined, reviewState: undefined, extra: {} };\n }\n const rec = raw as Record<string, unknown>;\n const rawId = rec.miniAppId ?? rec.id ?? rec.appId;\n const miniAppId = typeof rawId === 'string' || typeof rawId === 'number' ? rawId : undefined;\n const rawState = rec.reviewState ?? rec.status;\n const reviewState = typeof rawState === 'string' ? rawState : undefined;\n return { miniAppId, reviewState, extra: rec };\n}\n\nexport interface UploadFile {\n readonly buffer: Buffer;\n readonly fileName: string;\n readonly contentType: string;\n}\n\nexport interface UploadParams {\n readonly workspaceId: number;\n readonly validWidth: number;\n readonly validHeight: number;\n readonly file: UploadFile;\n readonly cookies: readonly CdpCookie[];\n}\n\n/**\n * Upload an image to `/resource/:wid/upload?validWidth=W&validHeight=H`\n * and return the CDN URL the server hands back. The endpoint is a\n * multipart/form-data POST; we build a FormData with a single `resource`\n * field because that matches the bundle analysis for the console's\n * uploader, which pairs a `fileName` string field with a `resource`\n * Blob (see VALIDATION-RULES.md → iconUri). Dog-food #23 may reveal that\n * the field name is actually `file` — if so, swap it in one place here.\n */\nexport async function uploadMiniAppResource(\n params: UploadParams,\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<string> {\n const url = new URL(`${BASE}/resource/${params.workspaceId}/upload`);\n url.searchParams.set('validWidth', String(params.validWidth));\n url.searchParams.set('validHeight', String(params.validHeight));\n\n const form = new FormData();\n // A `Buffer` is already a `Uint8Array`, but its `ArrayBufferLike`\n // backing can be a `SharedArrayBuffer` which `BlobPart` doesn't accept.\n // Wrapping in a `Uint8Array` view over the same bytes (byteOffset +\n // byteLength) keeps the type happy without the extra copy that\n // `new Uint8Array(buffer)` would force.\n const view = new Uint8Array(\n params.file.buffer.buffer as ArrayBuffer,\n params.file.buffer.byteOffset,\n params.file.buffer.byteLength,\n );\n const blob = new Blob([view], { type: params.file.contentType });\n form.append('resource', blob, params.file.fileName);\n form.append('fileName', params.file.fileName);\n\n const cookieHeader = cookieHeaderFor(url, params.cookies);\n const headers: Record<string, string> = {\n Accept: 'application/json, text/plain, */*',\n };\n if (cookieHeader) headers.Cookie = cookieHeader;\n\n const imageUrl = await executeAndUnwrap<unknown>(\n url,\n { method: 'POST', headers, body: form },\n opts.fetchImpl,\n );\n if (typeof imageUrl !== 'string') {\n throw new MalformedResponseError(\n url.toString(),\n 200,\n `expected string imageUrl, got ${typeof imageUrl}`,\n );\n }\n return imageUrl;\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 // 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\n// Cache directory — for non-secret, regenerable state. Distinct from\n// `configDir()` so a `rm -rf ~/.cache/aitcc` cannot take the session with\n// it, and so packagers can mount the two on different volumes.\nexport function cacheDir(): string {\n if (process.platform === 'win32') {\n const localAppData = process.env.LOCALAPPDATA;\n if (localAppData && localAppData.length > 0) return join(localAppData, APP_NAME, 'Cache');\n return join(homedir() || '.', 'AppData', 'Local', APP_NAME, 'Cache');\n }\n const xdg = process.env.XDG_CACHE_HOME;\n if (xdg && xdg.length > 0) return join(xdg, APP_NAME);\n return join(homedir() || '.', '.cache', APP_NAME);\n}\n\nexport function upgradeCheckPath(): string {\n return join(cacheDir(), 'upgrade-check.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: 2;\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 // Workspace context. Unset until the user runs `aitcc workspace use <id>`\n // or provides `--workspace` on first use. Writes are explicit — we never\n // guess a default (e.g. \"first workspace the user has access to\") because\n // a silent guess is exactly the class of bug that causes a deploy to land\n // in the wrong account.\n currentWorkspaceId?: number;\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\n/**\n * Read the persisted session. Returns `null` when no session exists, when\n * the file is corrupt, or when the shape fails validation — each of those\n * emits a one-line warning on stderr for diagnostics.\n *\n * **Side effect**: a v1 session file is transparently rewritten to v2 on\n * the first successful read of this process. This keeps read-only callers\n * (`whoami`, `workspace ls`) from stranding users on an old schema. If the\n * rewrite fails, we warn once per process and continue with the in-memory\n * v2 value so the calling command still succeeds.\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 rawParsed: unknown;\n try {\n rawParsed = JSON.parse(raw);\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(rawParsed);\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 // Post-validation: the shape is trusted. `schemaVersion` is now 1 or 2;\n // v1 files are transparently upgraded to v2 in memory. We best-effort\n // rewrite the file so long-lived v1-on-disk sessions eventually migrate\n // without requiring the user to run a write-shaped command. A failed\n // rewrite is non-fatal (the in-memory shape is correct) but we still\n // emit a one-line stderr warning once per process so a read-only mount\n // or permission issue does not silently persist. We await rather than\n // fire-and-forget so concurrent callers observe consistent on-disk state.\n const validated = rawParsed as { schemaVersion: 1 | 2 } & Omit<Session, 'schemaVersion'>;\n if (validated.schemaVersion === 1) {\n const upgraded: Session = { ...validated, schemaVersion: 2 };\n try {\n await writeSession(upgraded);\n } catch (err) {\n warnMigrationOnce(path, (err as NodeJS.ErrnoException).code);\n }\n return upgraded;\n }\n return validated as Session;\n}\n\n// One-shot latch so a failing migration doesn't spam stderr on every call\n// inside the same process. Users still get the diagnostic the first time.\nlet migrationWarned = false;\nfunction warnMigrationOnce(path: string, code: string | undefined): void {\n if (migrationWarned) return;\n migrationWarned = true;\n process.stderr.write(\n `warning: could not migrate session file at ${path} to schemaVersion 2: ${code ?? 'unknown'}\\n`,\n );\n}\n\n// v1 → v2 migration: v1 files are still valid, we just treat the absent\n// `currentWorkspaceId` as \"no workspace selected yet\". The next write (e.g.\n// from `workspace use`) bumps the stored schemaVersion. The validator input\n// is `unknown` so we can inspect raw JSON without the TS type narrowing\n// away the v1 branch.\nfunction validateSessionShape(input: unknown): string | null {\n if (input === null || typeof input !== 'object') return 'root is not an object';\n const parsed = input as {\n schemaVersion?: unknown;\n user?: { id?: unknown; email?: unknown; displayName?: unknown };\n cookies?: unknown;\n origins?: unknown;\n capturedAt?: unknown;\n currentWorkspaceId?: unknown;\n };\n if (parsed.schemaVersion !== 1 && parsed.schemaVersion !== 2) {\n return `unknown schemaVersion ${String(parsed.schemaVersion)}`;\n }\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 if (parsed.origins !== undefined && !Array.isArray(parsed.origins)) {\n return 'origins is not an array';\n }\n if (parsed.capturedAt !== undefined && typeof parsed.capturedAt !== 'string') {\n return 'capturedAt has wrong type';\n }\n if (parsed.currentWorkspaceId !== undefined) {\n const wid = parsed.currentWorkspaceId;\n if (typeof wid !== 'number' || !Number.isInteger(wid) || wid <= 0) {\n return 'currentWorkspaceId has wrong type';\n }\n }\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\n/**\n * Persist a new `currentWorkspaceId` on an existing session. Returns the\n * updated session, or `null` if there is no session to update (callers\n * should surface \"not logged in\" in that case).\n */\nexport async function setCurrentWorkspaceId(workspaceId: number): Promise<Session | null> {\n const session = await readSession();\n if (!session) return null;\n const updated: Session = { ...session, currentWorkspaceId: workspaceId };\n await writeSession(updated);\n return updated;\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 { NetworkError, TossApiError } from '../api/http.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { readSession, type Session, sessionPathForDiagnostics } from '../session.js';\n\n// Shared output helpers used by every session-scoped subcommand\n// (`workspace`, `app`, `members`, `keys`, and the in-flight `deploy`/`logs`).\n// Kept in one place so all commands agree on the `--json` contract — one\n// line, trailing \\n, stdout for structured output, stderr for diagnostics.\n//\n// Auth / network / API failure shapes are identical across every command:\n// { ok: true, authenticated: false } (exit 10), { ok: false,\n// reason: 'network-error', message } (exit 11), { ok: false,\n// reason: 'api-error', message } (exit 17). See any per-command\n// `--json contract` block (e.g. `commands/workspace.ts`) for the full\n// exit-code legend plus the success-shape specific to that command —\n// those per-command blocks are the source of truth for success payloads.\n\nexport interface NotAuthenticatedPayload {\n readonly ok: true;\n readonly authenticated: false;\n readonly reason?: 'session-expired';\n}\n\nexport function emitJson(payload: unknown): void {\n process.stdout.write(`${JSON.stringify(payload)}\\n`);\n}\n\nexport function emitNotAuthenticated(json: boolean, reason?: 'session-expired'): void {\n if (json) {\n // `exactOptionalPropertyTypes` forbids `reason: undefined`, so we omit\n // the key entirely when we don't have a value — hence the branch\n // rather than a single object literal.\n const payload: NotAuthenticatedPayload = reason\n ? { ok: true, authenticated: false, reason }\n : { ok: true, authenticated: false };\n emitJson(payload);\n } else {\n process.stderr.write(\n reason === 'session-expired'\n ? 'Session is no longer valid. Run `aitcc login` again.\\n'\n : 'Not logged in. Run `aitcc login` to start a session.\\n',\n );\n process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\\n`);\n }\n}\n\nexport function emitNetworkError(json: boolean, message: string): void {\n if (json) {\n emitJson({ ok: false, reason: 'network-error', message });\n } else {\n process.stderr.write(`Network error reaching the console API: ${message}.\\n`);\n }\n}\n\nexport function emitApiError(\n json: boolean,\n message: string,\n details?: { status?: number; errorCode?: string },\n): void {\n if (json) {\n emitJson({\n ok: false,\n reason: 'api-error',\n ...(details?.status !== undefined ? { status: details.status } : {}),\n ...(details?.errorCode !== undefined ? { errorCode: details.errorCode } : {}),\n message,\n });\n } else {\n process.stderr.write(`Unexpected error: ${message}\\n`);\n }\n}\n\n/**\n * Shared auth/network/api dispatch. Every session-scoped command's\n * `catch (err)` block boils down to the same sequence: TossApiError\n * (auth → exit 10, otherwise → exit 17 with status + errorCode),\n * NetworkError (exit 11), fallback (exit 17 with just a message).\n * Exists so we get a single source of truth for the api-error JSON\n * shape — previously each command duplicated the if/else ladder and\n * `register` diverged (it exposed `status`/`errorCode` that the others\n * didn't) until this extraction lined them up.\n *\n * Returns `Promise<void>` but never returns at runtime: every branch\n * awaits `exitAfterFlush` which calls `process.exit`.\n */\nexport async function emitFailureFromError(json: boolean, err: unknown): Promise<void> {\n if (err instanceof TossApiError && err.isAuthError) {\n emitNotAuthenticated(json, 'session-expired');\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n if (err instanceof TossApiError) {\n emitApiError(json, err.message, { status: err.status, errorCode: err.errorCode });\n return exitAfterFlush(ExitCode.ApiError);\n }\n if (err instanceof NetworkError) {\n emitNetworkError(json, err.message);\n return exitAfterFlush(ExitCode.NetworkError);\n }\n emitApiError(json, (err as Error).message);\n return exitAfterFlush(ExitCode.ApiError);\n}\n\n// Parse a CLI-provided workspace id strictly: only the form `^[1-9]\\d*$`\n// is accepted. `Number.parseInt('36577x', 10)` returns 36577, so the CLI\n// would otherwise silently accept `workspace use 36577x` and persist the\n// wrong thing on a typo. Returning `null` triggers the caller's usage-error\n// path. Exported so unit tests can guard against \"just use parseInt\"\n// simplification regressions.\nexport function parsePositiveInt(raw: string): number | null {\n if (!/^[1-9]\\d*$/.test(raw)) return null;\n const n = Number.parseInt(raw, 10);\n return Number.isSafeInteger(n) ? n : null;\n}\n\n/**\n * Boilerplate wrapper for any workspace-scoped command (`app ls`,\n * `members ls`, `keys ls`, ...). Loads the session, resolves the workspace\n * id from `--workspace <id>` or the persisted selection, and handles the\n * three common failure branches (`no session`, `invalid id`, `no workspace\n * selected`). On success, the caller gets the session + resolved id back.\n *\n * The return type is `Promise<... | null>` but the `null` branch is never\n * observed at runtime: every failure path `await`s `exitAfterFlush` which\n * calls `process.exit(...)` and doesn't return. The `| null` is a type-\n * level handshake that forces callers to add `if (!ctx) return;`, keeping\n * the bail-out readable.\n */\nexport async function resolveWorkspaceContext(args: {\n workspace?: string | undefined;\n json: boolean;\n}): Promise<{ session: Session; workspaceId: number } | null> {\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n await exitAfterFlush(ExitCode.NotAuthenticated);\n return null;\n }\n\n let workspaceId: number | undefined;\n if (args.workspace) {\n const raw = String(args.workspace);\n const parsed = parsePositiveInt(raw);\n if (parsed === null) {\n const message = `--workspace must be a positive integer (got ${raw})`;\n if (args.json) emitJson({ ok: false, reason: 'invalid-id', message });\n else process.stderr.write(`${message}\\n`);\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n workspaceId = parsed;\n } else {\n workspaceId = session.currentWorkspaceId;\n }\n\n if (workspaceId === undefined) {\n if (args.json) emitJson({ ok: false, reason: 'no-workspace-selected' });\n else {\n process.stderr.write(\n 'No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\\n',\n );\n }\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n\n return { session, workspaceId };\n}\n","import { access, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\n// The app manifest is the CLI's user-facing contract for `aitcc app\n// register`. It intentionally mirrors the console's form field names only\n// at the top level — the inferred submit payload (which nests fields into\n// step1/step2/miniApp/impression) is built by a separate payload module\n// so the manifest shape is stable even if the bundle analysis turns out\n// to be off. Dog-food task #23 is expected to correct the payload\n// transformation but should not force a manifest rewrite.\n//\n// Validation here is \"config shape only\": presence, type, and cheap\n// numeric/length constraints from `VALIDATION-RULES.md` that we can\n// enforce without reading image files. Image-dimension checks live in a\n// sibling module (`image-validator.ts`) because they need FS reads.\n//\n// ManifestError is a single error class carrying (kind, field?, path?)\n// so the command layer can translate it into the documented `--json`\n// error shapes without re-classifying.\n\nexport type ManifestErrorKind = 'invalid-config' | 'missing-required-field';\n\nexport class ManifestError extends Error {\n readonly kind: ManifestErrorKind;\n readonly field: string | undefined;\n\n constructor(kind: ManifestErrorKind, message: string, field?: string) {\n super(message);\n this.name = 'ManifestError';\n this.kind = kind;\n this.field = field;\n }\n}\n\nexport interface AppManifest {\n readonly titleKo: string;\n readonly titleEn: string;\n readonly appName: string;\n readonly homePageUri: string | undefined;\n readonly csEmail: string;\n readonly logo: string;\n readonly logoDarkMode: string | undefined;\n readonly horizontalThumbnail: string;\n readonly categoryIds: readonly number[];\n readonly subtitle: string;\n readonly description: string;\n readonly keywords: readonly string[];\n readonly verticalScreenshots: readonly string[];\n readonly horizontalScreenshots: readonly string[];\n}\n\nconst DEFAULT_NAMES = ['aitcc.app.yaml', 'aitcc.app.json'] as const;\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the manifest file path. When `explicit` is provided, we use it\n * verbatim (resolved against `cwd`) and require it to exist. Otherwise we\n * auto-detect `aitcc.app.yaml` then `aitcc.app.json` under `cwd`.\n */\nexport async function resolveManifestPath(\n explicit: string | undefined,\n cwd: string,\n): Promise<string> {\n if (explicit) {\n const abs = isAbsolute(explicit) ? explicit : resolve(cwd, explicit);\n if (!(await fileExists(abs))) {\n throw new ManifestError('invalid-config', `manifest file not found at ${abs}`);\n }\n return abs;\n }\n for (const name of DEFAULT_NAMES) {\n const abs = resolve(cwd, name);\n if (await fileExists(abs)) return abs;\n }\n throw new ManifestError(\n 'invalid-config',\n `no app manifest found (looked for ${DEFAULT_NAMES.join(', ')} in ${cwd})`,\n );\n}\n\nexport async function loadAppManifest(path: string): Promise<AppManifest> {\n const raw = await readFile(path, 'utf8');\n const parsed = parseManifestFile(path, raw);\n return validateManifest(parsed, dirname(path));\n}\n\nfunction parseManifestFile(path: string, raw: string): Record<string, unknown> {\n const isJson = path.toLowerCase().endsWith('.json');\n try {\n const out = isJson ? JSON.parse(raw) : parseYaml(raw);\n if (out === null || typeof out !== 'object' || Array.isArray(out)) {\n throw new ManifestError('invalid-config', `manifest at ${path} is not a mapping`);\n }\n return out as Record<string, unknown>;\n } catch (err) {\n if (err instanceof ManifestError) throw err;\n const msg = (err as Error).message;\n throw new ManifestError('invalid-config', `failed to parse manifest at ${path}: ${msg}`);\n }\n}\n\nfunction requireString(input: Record<string, unknown>, key: string): string {\n const v = input[key];\n if (v === undefined || v === null) {\n throw new ManifestError('missing-required-field', `${key} is required`, key);\n }\n if (typeof v !== 'string') {\n throw new ManifestError('invalid-config', `${key} must be a string`, key);\n }\n if (v.trim().length === 0) {\n throw new ManifestError('missing-required-field', `${key} is required`, key);\n }\n return v;\n}\n\nfunction optionalString(input: Record<string, unknown>, key: string): string | undefined {\n const v = input[key];\n if (v === undefined || v === null) return undefined;\n if (typeof v !== 'string') {\n throw new ManifestError('invalid-config', `${key} must be a string when provided`, key);\n }\n return v;\n}\n\nfunction requirePath(input: Record<string, unknown>, key: string, configDir: string): string {\n const rel = requireString(input, key);\n return isAbsolute(rel) ? rel : resolve(configDir, rel);\n}\n\nfunction optionalPath(\n input: Record<string, unknown>,\n key: string,\n configDir: string,\n): string | undefined {\n const rel = optionalString(input, key);\n if (rel === undefined) return undefined;\n return isAbsolute(rel) ? rel : resolve(configDir, rel);\n}\n\nfunction requireNumberArray(\n input: Record<string, unknown>,\n key: string,\n { min }: { min: number },\n): number[] {\n const v = input[key];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of numbers`, key);\n }\n if (v.length < min) {\n throw new ManifestError('invalid-config', `${key} must contain at least ${min} item(s)`, key);\n }\n return v.map((item, idx) => {\n if (typeof item !== 'number' || !Number.isInteger(item)) {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be an integer`, key);\n }\n return item;\n });\n}\n\nfunction optionalStringArray(\n input: Record<string, unknown>,\n key: string,\n { max }: { max?: number } = {},\n): string[] {\n const v = input[key];\n if (v === undefined || v === null) return [];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of strings`, key);\n }\n if (max !== undefined && v.length > max) {\n throw new ManifestError(\n 'invalid-config',\n `${key} accepts at most ${max} entries (got ${v.length})`,\n key,\n );\n }\n return v.map((item, idx) => {\n if (typeof item !== 'string') {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be a string`, key);\n }\n return item;\n });\n}\n\nfunction requirePathArray(\n input: Record<string, unknown>,\n key: string,\n configDir: string,\n { min }: { min: number },\n): string[] {\n const v = input[key];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of paths`, key);\n }\n if (v.length < min) {\n throw new ManifestError('invalid-config', `${key} must contain at least ${min} item(s)`, key);\n }\n return v.map((item, idx) => {\n if (typeof item !== 'string' || item.trim().length === 0) {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be a non-empty string`, key);\n }\n return isAbsolute(item) ? item : resolve(configDir, item);\n });\n}\n\nfunction optionalPathArray(\n input: Record<string, unknown>,\n key: string,\n configDir: string,\n): string[] {\n const v = input[key];\n if (v === undefined || v === null) return [];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of paths`, key);\n }\n return v.map((item, idx) => {\n if (typeof item !== 'string' || item.trim().length === 0) {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be a non-empty string`, key);\n }\n return isAbsolute(item) ? item : resolve(configDir, item);\n });\n}\n\n// Regexes mirror the console's client-side validators (`is-email` CDgIL0c0\n// bundle + VALIDATION-RULES.md). Keeping local validators lets agent-\n// plugin surface `missing-required-field`/`invalid-config` instead of a\n// pass-through `api-error` when the user supplies garbage.\nconst EMAIL_REGEX =\n /^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/;\n\nfunction isValidEmail(v: string): boolean {\n return EMAIL_REGEX.test(v.toLowerCase());\n}\n\n// Dog-food #23 (2026-04-22) HTTP 400 errorCode=4000:\n// \"앱 영문 이름은 영어, 숫자, 공백, 콜론(:)만 사용 가능해요\".\nconst TITLE_EN_REGEX = /^[A-Za-z0-9 :]+$/;\n\n// Dog-food #23 (2026-04-22) HTTP 400 errorCode=4000:\n// \"앱 상세설명은 최대 500자를 넘어갈 수 없어요\". Counted by code points\n// (`[...str].length`) to err on the strict side — the server's internal\n// counting rule is not documented, so we use the count that treats\n// astral characters as single units.\nconst DETAIL_DESCRIPTION_MAX_CODEPOINTS = 500;\n\nfunction isValidHttpUrl(v: string): boolean {\n try {\n const parsed = new URL(v);\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\nfunction validateManifest(raw: Record<string, unknown>, configDir: string): AppManifest {\n const titleKo = requireString(raw, 'titleKo');\n const titleEn = requireString(raw, 'titleEn');\n if (!TITLE_EN_REGEX.test(titleEn)) {\n throw new ManifestError(\n 'invalid-config',\n `titleEn may only contain English letters, digits, spaces, and colons (got \"${titleEn}\")`,\n 'titleEn',\n );\n }\n const appName = requireString(raw, 'appName');\n const csEmail = requireString(raw, 'csEmail');\n if (!isValidEmail(csEmail)) {\n throw new ManifestError(\n 'invalid-config',\n `csEmail is not a valid email address (got ${csEmail})`,\n 'csEmail',\n );\n }\n const subtitle = requireString(raw, 'subtitle');\n // subtitle ≤ 20 chars (F(20) in VALIDATION-RULES).\n if (subtitle.length > 20) {\n throw new ManifestError(\n 'invalid-config',\n `subtitle must be 20 characters or fewer (got ${subtitle.length})`,\n 'subtitle',\n );\n }\n const description = requireString(raw, 'description');\n const descriptionCodepoints = [...description].length;\n if (descriptionCodepoints > DETAIL_DESCRIPTION_MAX_CODEPOINTS) {\n throw new ManifestError(\n 'invalid-config',\n `description must be ${DETAIL_DESCRIPTION_MAX_CODEPOINTS} characters or fewer (got ${descriptionCodepoints})`,\n 'description',\n );\n }\n const homePageUri = optionalString(raw, 'homePageUri');\n if (homePageUri !== undefined && !isValidHttpUrl(homePageUri)) {\n throw new ManifestError(\n 'invalid-config',\n `homePageUri must be a http(s) URL (got ${homePageUri})`,\n 'homePageUri',\n );\n }\n const logo = requirePath(raw, 'logo', configDir);\n const logoDarkMode = optionalPath(raw, 'logoDarkMode', configDir);\n const horizontalThumbnail = requirePath(raw, 'horizontalThumbnail', configDir);\n const categoryIds = requireNumberArray(raw, 'categoryIds', { min: 1 });\n const keywords = optionalStringArray(raw, 'keywords', { max: 10 });\n const verticalScreenshots = requirePathArray(raw, 'verticalScreenshots', configDir, { min: 3 });\n const horizontalScreenshots = optionalPathArray(raw, 'horizontalScreenshots', configDir);\n\n return {\n titleKo,\n titleEn,\n appName,\n homePageUri,\n csEmail,\n logo,\n logoDarkMode,\n horizontalThumbnail,\n categoryIds,\n subtitle,\n description,\n keywords,\n verticalScreenshots,\n horizontalScreenshots,\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { imageSize } from 'image-size';\n\n// Image dimension validation lives here (rather than inline in the\n// command) so a future swap of the underlying library is confined. The\n// console upload endpoint validates validWidth/validHeight server-side\n// with a hard 400, but failing locally gives the agent-plugin consumer\n// a structured error (path + expected + actual) instead of a pass-\n// through api-error reason.\n//\n// image-size reads the PNG/JPEG/etc. header directly; we pass a Buffer\n// so we can distinguish \"file missing\" (ENOENT → unreadable) from\n// \"unknown format\" (library throws → unreadable) without two code paths.\n\nexport type ImageDimensionErrorReason = 'mismatch' | 'unreadable';\n\nexport class ImageDimensionError extends Error {\n readonly path: string;\n readonly expected: string;\n readonly actual: string | undefined;\n readonly reason: ImageDimensionErrorReason;\n\n constructor(args: {\n path: string;\n expected: string;\n actual: string | undefined;\n reason: ImageDimensionErrorReason;\n message: string;\n }) {\n super(args.message);\n this.name = 'ImageDimensionError';\n this.path = args.path;\n this.expected = args.expected;\n this.actual = args.actual;\n this.reason = args.reason;\n }\n}\n\nexport interface Dimension {\n readonly width: number;\n readonly height: number;\n}\n\nfunction format(dim: Dimension): string {\n return `${dim.width}x${dim.height}`;\n}\n\nexport async function validateImageDimensions(path: string, expected: Dimension): Promise<void> {\n let buffer: Buffer;\n try {\n buffer = await readFile(path);\n } catch (err) {\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual: undefined,\n reason: 'unreadable',\n message: `could not read image at ${path}: ${(err as Error).message}`,\n });\n }\n let dims: { width?: number; height?: number };\n try {\n dims = imageSize(buffer);\n } catch (err) {\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual: undefined,\n reason: 'unreadable',\n message: `could not decode image header at ${path}: ${(err as Error).message}`,\n });\n }\n if (typeof dims.width !== 'number' || typeof dims.height !== 'number') {\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual: undefined,\n reason: 'unreadable',\n message: `image header at ${path} did not expose width/height`,\n });\n }\n if (dims.width !== expected.width || dims.height !== expected.height) {\n const actual = `${dims.width}x${dims.height}`;\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual,\n reason: 'mismatch',\n message: `image ${path} has dimensions ${actual}; expected ${format(expected)}`,\n });\n }\n}\n\n// Canonical dimension specs for the register flow. Centralising here keeps\n// the command and the payload builder agreeing on the same source of truth.\nexport const DIMENSIONS = {\n logo: { width: 600, height: 600 },\n horizontalThumbnail: { width: 1932, height: 828 },\n verticalScreenshot: { width: 636, height: 1048 },\n horizontalScreenshot: { width: 1504, height: 741 },\n} as const;\n","import type { MiniAppImageEntry, MiniAppSubmitPayload } from '../api/mini-apps.js';\nimport type { AppManifest } from '../config/app-manifest.js';\n\n// Pure transformation from a loaded AppManifest + the URLs produced by\n// the upload step into the `{miniApp, impression}` submit body. The\n// structure mirrors the `Xc` function from the console bundle (see\n// VALIDATION-RULES.md in the umbrella `.playwright-mcp/`). Dog-food\n// task #23 captures the first real network exchange and will either\n// confirm this shape or correct it here.\n\nexport interface UploadedImageUrls {\n readonly logo: string;\n readonly logoDarkMode: string | undefined;\n readonly horizontalThumbnail: string;\n readonly verticalScreenshots: readonly string[];\n readonly horizontalScreenshots: readonly string[];\n}\n\nexport function buildSubmitPayload(\n manifest: AppManifest,\n urls: UploadedImageUrls,\n): MiniAppSubmitPayload {\n const images: MiniAppImageEntry[] = [\n { imageUrl: urls.horizontalThumbnail, imageType: 'THUMBNAIL', orientation: 'HORIZONTAL' },\n ...urls.verticalScreenshots.map<MiniAppImageEntry>((u) => ({\n imageUrl: u,\n imageType: 'PREVIEW',\n orientation: 'VERTICAL',\n })),\n ...urls.horizontalScreenshots.map<MiniAppImageEntry>((u) => ({\n imageUrl: u,\n imageType: 'PREVIEW',\n orientation: 'HORIZONTAL',\n })),\n ];\n\n const miniApp: MiniAppSubmitPayload['miniApp'] = {\n title: manifest.titleKo,\n titleEn: manifest.titleEn,\n appName: manifest.appName,\n iconUri: urls.logo,\n status: 'PREPARE',\n csEmail: manifest.csEmail,\n description: manifest.subtitle,\n detailDescription: manifest.description,\n images,\n ...(urls.logoDarkMode !== undefined ? { darkModeIconUri: urls.logoDarkMode } : {}),\n ...(manifest.homePageUri !== undefined ? { homePageUri: manifest.homePageUri } : {}),\n };\n\n const impression: MiniAppSubmitPayload['impression'] = {\n keywordList: manifest.keywords,\n categoryIds: manifest.categoryIds,\n };\n\n return { miniApp, impression };\n}\n","import { readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\nimport {\n type CreateMiniAppResult,\n createMiniApp,\n type UploadParams,\n uploadMiniAppResource,\n} from '../api/mini-apps.js';\nimport type { CdpCookie } from '../cdp.js';\nimport {\n type AppManifest,\n loadAppManifest,\n ManifestError,\n resolveManifestPath,\n} from '../config/app-manifest.js';\nimport {\n DIMENSIONS,\n ImageDimensionError,\n validateImageDimensions,\n} from '../config/image-validator.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\nimport { buildSubmitPayload, type UploadedImageUrls } from './register-payload.js';\n\n// `runRegister` is the testable seam for `aitcc app register`. The public\n// command (defined in `app.ts`) is a thin citty wrapper that supplies the\n// real `uploadMiniAppResource` and `createMiniApp` implementations;\n// tests pass stubs so each branch of the documented `--json` contract\n// gets pinned byte-for-byte without spawning a subprocess.\n//\n// --json contract (consumed by agent-plugin):\n//\n// success:\n// { ok: true, workspaceId, appId, reviewState } exit 0\n//\n// failures:\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-config', message } exit 2\n// { ok: false, reason: 'missing-required-field', field, message } exit 2\n// { ok: false, reason: 'image-dimension-mismatch',\n// path, expected, actual, message } exit 2\n// { ok: false, reason: 'image-unreadable', path, message } exit 2\n// { ok: true, authenticated: false } exit 10\n// { ok: false, reason: 'network-error', message } exit 11\n// { ok: false, reason: 'api-error',\n// status?, errorCode?, message } exit 17\n//\n// --dry-run:\n// { ok: true, dryRun: true, workspaceId, payload } exit 0\n// (no uploads, no submit — manifest + image dimensions are still\n// validated so dry-run catches the same local errors as a real run.)\n//\n// --accept-terms required for real submits:\n// The console UI gates \"검토 요청하기\" on several mandatory legal-\n// agreement checkboxes (common + category-dependent — see\n// VALIDATION-RULES.md). We can't see those on the wire yet (payload\n// shape is inferred), so the CLI enforces the gate locally: submit\n// refuses without --accept-terms. The flag is not required for\n// --dry-run.\n// { ok: false, reason: 'terms-not-accepted', message } exit 2\n\nexport interface RegisterArgs {\n readonly workspace?: string | undefined;\n readonly config?: string | undefined;\n readonly json: boolean;\n readonly dryRun?: boolean | undefined;\n readonly acceptTerms?: boolean | undefined;\n}\n\nexport interface RegisterDeps {\n readonly cwd?: string;\n readonly uploadImpl?: (params: UploadParams) => Promise<string>;\n readonly submitImpl?: (\n workspaceId: number,\n payload: ReturnType<typeof buildSubmitPayload>,\n cookies: readonly CdpCookie[],\n ) => Promise<CreateMiniAppResult>;\n}\n\n// `runRegister` returns `Promise<void>` as a type-level handshake — at\n// runtime every code path either awaits `exitAfterFlush` (which calls\n// `process.exit` and never returns) or bubbles a thrown exception. A\n// future maintainer should not try to `catch` the absence of a return\n// value as \"success\"; the success signal is `process.exit(0)` itself.\n//\n// `deps` defaults to `{}` so the citty wrapper (`app.ts`) doesn't need\n// to pass a literal every call; tests override specific fields.\nexport async function runRegister(args: RegisterArgs, deps: RegisterDeps = {}): Promise<void> {\n const ctx = await resolveWorkspaceContext({\n json: args.json,\n ...(args.workspace !== undefined ? { workspace: args.workspace } : {}),\n });\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n const manifest = await loadAndValidateManifest(args, deps);\n if (!manifest) return;\n\n // --accept-terms gate: required for real submits only. --dry-run\n // skips it so users can iterate on their manifest without being\n // forced to attest to the legal-agreement checkboxes each time.\n if (!args.dryRun && !args.acceptTerms) {\n emitTermsNotAccepted(args.json);\n await exitAfterFlush(ExitCode.Usage);\n return;\n }\n\n try {\n if (args.dryRun) {\n // Emit the payload with placeholder URLs so the user can\n // inspect exactly what would be sent without spending a round\n // of uploads. Useful during dog-food verification when the\n // inferred payload shape is in flight.\n const placeholderUrls: UploadedImageUrls = {\n logo: '<dry-run:logo>',\n logoDarkMode: manifest.logoDarkMode !== undefined ? '<dry-run:logoDarkMode>' : undefined,\n horizontalThumbnail: '<dry-run:horizontalThumbnail>',\n verticalScreenshots: manifest.verticalScreenshots.map(\n (_, i) => `<dry-run:verticalScreenshots[${i}]>`,\n ),\n horizontalScreenshots: manifest.horizontalScreenshots.map(\n (_, i) => `<dry-run:horizontalScreenshots[${i}]>`,\n ),\n };\n const payload = buildSubmitPayload(manifest, placeholderUrls);\n emitDryRun(args.json, workspaceId, payload);\n return exitAfterFlush(ExitCode.Ok);\n }\n\n const urls = await uploadAllImages(workspaceId, manifest, session.cookies, deps);\n const payload = buildSubmitPayload(manifest, urls);\n const submitImpl = deps.submitImpl ?? ((wid, p, c) => createMiniApp(wid, p, c));\n const result = await submitImpl(workspaceId, payload, session.cookies);\n emitSuccess(args.json, workspaceId, result);\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureAndExit(args.json, err);\n }\n}\n\nasync function loadAndValidateManifest(\n args: RegisterArgs,\n deps: RegisterDeps,\n): Promise<AppManifest | null> {\n const cwd = deps.cwd ?? process.cwd();\n let manifest: AppManifest;\n try {\n const manifestPath = await resolveManifestPath(args.config, cwd);\n manifest = await loadAppManifest(manifestPath);\n } catch (err) {\n if (err instanceof ManifestError) {\n emitManifestError(args.json, err);\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n throw err;\n }\n\n // Image dimension checks run after manifest validation because they\n // need paths that the manifest already resolved to absolute.\n try {\n await validateImageDimensions(manifest.logo, DIMENSIONS.logo);\n if (manifest.logoDarkMode !== undefined) {\n await validateImageDimensions(manifest.logoDarkMode, DIMENSIONS.logo);\n }\n await validateImageDimensions(manifest.horizontalThumbnail, DIMENSIONS.horizontalThumbnail);\n for (const p of manifest.verticalScreenshots) {\n await validateImageDimensions(p, DIMENSIONS.verticalScreenshot);\n }\n for (const p of manifest.horizontalScreenshots) {\n await validateImageDimensions(p, DIMENSIONS.horizontalScreenshot);\n }\n } catch (err) {\n if (err instanceof ImageDimensionError) {\n emitImageDimensionError(args.json, err);\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n throw err;\n }\n\n return manifest;\n}\n\nasync function uploadAllImages(\n workspaceId: number,\n manifest: AppManifest,\n cookies: readonly CdpCookie[],\n deps: RegisterDeps,\n): Promise<UploadedImageUrls> {\n // Serial on purpose: dog-food task #23 has not yet confirmed that the\n // console's `/resource/:wid/upload` endpoint tolerates concurrent\n // POSTs from the same session. `Promise.all` would shave a few seconds\n // off a 5-image upload, but until we've observed the server under\n // parallel load, the failure mode (\"429? 503? silent drop?\") is\n // unknown and a first-registration flake is much more expensive to\n // debug than a slower linear run.\n const uploadImpl = deps.uploadImpl ?? ((p) => uploadMiniAppResource(p));\n\n const logo = await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.logo.width,\n validHeight: DIMENSIONS.logo.height,\n cookies,\n path: manifest.logo,\n });\n const logoDarkMode =\n manifest.logoDarkMode !== undefined\n ? await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.logo.width,\n validHeight: DIMENSIONS.logo.height,\n cookies,\n path: manifest.logoDarkMode,\n })\n : undefined;\n const horizontalThumbnail = await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.horizontalThumbnail.width,\n validHeight: DIMENSIONS.horizontalThumbnail.height,\n cookies,\n path: manifest.horizontalThumbnail,\n });\n const verticalScreenshots: string[] = [];\n for (const p of manifest.verticalScreenshots) {\n verticalScreenshots.push(\n await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.verticalScreenshot.width,\n validHeight: DIMENSIONS.verticalScreenshot.height,\n cookies,\n path: p,\n }),\n );\n }\n const horizontalScreenshots: string[] = [];\n for (const p of manifest.horizontalScreenshots) {\n horizontalScreenshots.push(\n await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.horizontalScreenshot.width,\n validHeight: DIMENSIONS.horizontalScreenshot.height,\n cookies,\n path: p,\n }),\n );\n }\n return { logo, logoDarkMode, horizontalThumbnail, verticalScreenshots, horizontalScreenshots };\n}\n\nasync function uploadOne(\n uploadImpl: (params: UploadParams) => Promise<string>,\n input: {\n workspaceId: number;\n validWidth: number;\n validHeight: number;\n cookies: readonly CdpCookie[];\n path: string;\n },\n): Promise<string> {\n const buffer = await readFile(input.path);\n return uploadImpl({\n workspaceId: input.workspaceId,\n validWidth: input.validWidth,\n validHeight: input.validHeight,\n cookies: input.cookies,\n file: {\n buffer,\n fileName: basename(input.path),\n contentType: 'image/png',\n },\n });\n}\n\nfunction emitManifestError(json: boolean, err: ManifestError): void {\n if (json) {\n if (err.kind === 'missing-required-field') {\n emitJson({\n ok: false,\n reason: 'missing-required-field',\n field: err.field ?? null,\n message: err.message,\n });\n } else {\n emitJson({ ok: false, reason: 'invalid-config', message: err.message });\n }\n } else {\n process.stderr.write(`${err.message}\\n`);\n }\n}\n\nfunction emitImageDimensionError(json: boolean, err: ImageDimensionError): void {\n if (json) {\n if (err.reason === 'mismatch') {\n emitJson({\n ok: false,\n reason: 'image-dimension-mismatch',\n path: err.path,\n expected: err.expected,\n actual: err.actual ?? null,\n message: err.message,\n });\n } else {\n // 'unreadable' covers missing files, permission errors, and\n // decode failures — distinct from a genuine dimension mismatch\n // so agent-plugin can branch (e.g., \"path typo\" vs \"resize me\").\n emitJson({\n ok: false,\n reason: 'image-unreadable',\n path: err.path,\n message: err.message,\n });\n }\n } else {\n process.stderr.write(`${err.message}\\n`);\n }\n}\n\nfunction emitTermsNotAccepted(json: boolean): void {\n const message =\n 'The console requires several legal-agreement checkboxes before submitting a mini-app for review. ' +\n 'Re-run with --accept-terms to attest that you have read and agree to each of them ' +\n '(see VALIDATION-RULES.md or the console UI), or use --dry-run to preview the payload without submitting.';\n if (json) {\n emitJson({ ok: false, reason: 'terms-not-accepted', message });\n } else {\n process.stderr.write(`${message}\\n`);\n }\n}\n\nfunction emitDryRun(\n json: boolean,\n workspaceId: number,\n payload: ReturnType<typeof buildSubmitPayload>,\n): void {\n if (json) {\n emitJson({ ok: true, dryRun: true, workspaceId, payload });\n } else {\n process.stdout.write('[dry-run] Would POST to ');\n process.stdout.write(\n `https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole/workspaces/${workspaceId}/mini-app/review\\n`,\n );\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n }\n}\n\nfunction emitSuccess(json: boolean, workspaceId: number, result: CreateMiniAppResult): void {\n if (json) {\n emitJson({\n ok: true,\n workspaceId,\n appId: result.miniAppId ?? null,\n reviewState: result.reviewState ?? null,\n });\n } else {\n process.stdout.write(\n `Registered mini-app ${result.miniAppId ?? '(id unknown)'} in workspace ${workspaceId}` +\n ` (reviewState=${result.reviewState ?? 'unknown'}).\\n`,\n );\n }\n}\n\n// Thin wrapper kept for local readability — the real dispatch lives in\n// `_shared.ts::emitFailureFromError` and is shared with app/keys/\n// members/workspace so every command's auth/network/api-error JSON\n// shape agrees.\nasync function emitFailureAndExit(json: boolean, err: unknown): Promise<void> {\n return emitFailureFromError(json, err);\n}\n","import { defineCommand } from 'citty';\nimport { fetchMiniApps, fetchReviewStatus } from '../api/mini-apps.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\nimport { runRegister } from './register.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// app ls [--workspace <id>]:\n// { ok: true, workspaceId, hasPolicyViolation, apps: [{id, name, reviewState?, extra}] } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// Auth/network/api failures follow the shared contract from workspace/whoami\n// (ok: true authenticated: false exit 10, network-error exit 11, api-error exit 17).\n\n// Best-effort match of review-status entries against mini-app summaries.\n// The list endpoint and the review-status endpoint key off the same id,\n// but we don't assume the field name is uniform — we compare by `.id` on\n// each record, falling back to `miniAppId` / `appId` (same order as the\n// list normaliser). Exported so the join semantics are unit-testable.\n// Returns `null` if no plausible match; callers render that as \"no review\n// status\" in the output rather than a failure.\nexport function findReviewEntry(\n reviewEntries: readonly Readonly<Record<string, unknown>>[],\n appId: string | number,\n): Readonly<Record<string, unknown>> | null {\n const target = String(appId);\n for (const entry of reviewEntries) {\n const candidate = entry.id ?? entry.miniAppId ?? entry.appId;\n if (candidate !== undefined && String(candidate) === target) return entry;\n }\n return null;\n}\n\nexport function reviewStateFor(\n entry: Readonly<Record<string, unknown>> | null,\n): string | undefined {\n if (!entry) return undefined;\n const raw = entry.reviewState ?? entry.status;\n return typeof raw === 'string' ? raw : undefined;\n}\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List mini-apps in the selected workspace.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n try {\n // List + review-status are independent read endpoints. Fire in parallel\n // so a slow endpoint doesn't serialise the wait. Review-status failures\n // currently propagate (rather than being downgraded to \"unknown\n // review\") because they almost always indicate a shared auth/network\n // problem — if that ever stops being true we can degrade gracefully.\n const [apps, review] = await Promise.all([\n fetchMiniApps(workspaceId, session.cookies),\n fetchReviewStatus(workspaceId, session.cookies),\n ]);\n\n if (args.json) {\n const joined = apps.map((app) => {\n const entry = findReviewEntry(review.miniApps, app.id);\n const reviewState = reviewStateFor(entry);\n return {\n id: app.id,\n name: app.name ?? null,\n ...(reviewState !== undefined ? { reviewState } : {}),\n extra: app.extra,\n };\n });\n emitJson({\n ok: true,\n workspaceId,\n hasPolicyViolation: review.hasPolicyViolation,\n apps: joined,\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n\n if (apps.length === 0) {\n process.stdout.write(`No apps in workspace ${workspaceId}.\\n`);\n if (review.hasPolicyViolation) {\n process.stderr.write('Note: workspace-wide policy violation flag is set.\\n');\n }\n return exitAfterFlush(ExitCode.Ok);\n }\n for (const app of apps) {\n const entry = findReviewEntry(review.miniApps, app.id);\n const reviewState = reviewStateFor(entry) ?? '-';\n // Defensive: the upstream mini-app payload shape is not yet fully\n // observed (no registered apps in our workspaces). Tighten this\n // once sdk-example is registered and `name` is confirmed required.\n const name = app.name ?? '(unnamed)';\n process.stdout.write(`${app.id}\\t${name}\\t${reviewState}\\n`);\n }\n if (review.hasPolicyViolation) {\n process.stderr.write('Note: workspace-wide policy violation flag is set.\\n');\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\n// TODO(#23): after the first real submission we may want a follow-up\n// `aitcc app review-request <id>` command. The console UI has a separate\n// \"검토 요청하기\" step after create; whether it is a distinct endpoint\n// or folded into /mini-app/review is not yet captured.\nconst registerCommand = defineCommand({\n meta: {\n name: 'register',\n description:\n 'Register a mini-app in the selected workspace from a YAML/JSON manifest. ' +\n 'Uploads logo/thumbnail/screenshots, then submits the create payload.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n config: {\n type: 'string',\n description:\n 'Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`.',\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Validate manifest + images and print the inferred submit payload; no uploads.',\n default: false,\n },\n 'accept-terms': {\n type: 'boolean',\n description:\n 'Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.',\n default: false,\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n await runRegister({\n json: args.json,\n dryRun: args['dry-run'],\n acceptTerms: args['accept-terms'],\n ...(args.workspace !== undefined ? { workspace: args.workspace } : {}),\n ...(args.config !== undefined ? { config: args.config } : {}),\n });\n },\n});\n\nexport const appCommand = defineCommand({\n meta: {\n name: 'app',\n description: 'Inspect mini-apps in a workspace.',\n },\n subCommands: {\n ls: lsCommand,\n register: registerCommand,\n },\n});\n","import type { CdpCookie } from '../cdp.js';\nimport { type FetchLike, requestConsoleApi } from './http.js';\n\n// GET /workspaces/:id/api-keys — returns an array of console API keys used\n// for deploy automation. Our confirmed workspaces have zero keys (the UI\n// shows a \"발급받기\" CTA when the list is empty), so the entry shape is\n// unconfirmed. We normalise `id`/`name` across a few plausible spellings\n// and stash everything else under `extra`, matching the mini-app pattern.\n//\n// `keys create` is a deliberate follow-up — once an issued key lands we can\n// tighten this client against the real shape. See TODO.md's Medium list.\n\nconst BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport interface ApiKeySummary {\n readonly id: string | number;\n readonly name: string | undefined;\n readonly extra: Readonly<Record<string, unknown>>;\n}\n\nexport async function fetchApiKeys(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<ApiKeySummary[]> {\n const url = `${BASE}/workspaces/${workspaceId}/api-keys`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (!Array.isArray(raw)) {\n throw new Error(`Unexpected api-keys shape for workspace=${workspaceId}: not an array`);\n }\n return raw.map((entry, index) => normalizeKey(entry, workspaceId, index));\n}\n\nfunction normalizeKey(raw: unknown, workspaceId: number, index: number): ApiKeySummary {\n if (raw === null || typeof raw !== 'object') {\n throw new Error(\n `Unexpected api-key entry at index ${index} for workspace=${workspaceId}: not an object`,\n );\n }\n const rec = raw as Record<string, unknown>;\n const rawId = rec.id ?? rec.apiKeyId ?? rec.keyId;\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n throw new Error(\n `Unexpected api-key entry at index ${index} for workspace=${workspaceId}: missing id`,\n );\n }\n const rawName = rec.name ?? rec.apiKeyName ?? rec.keyName ?? rec.description;\n const name = typeof rawName === 'string' ? rawName : undefined;\n const {\n id: _id,\n apiKeyId: _aid,\n keyId: _kid,\n name: _n,\n apiKeyName: _an,\n keyName: _kn,\n description: _d,\n ...extra\n } = rec;\n return { id: rawId, name, extra };\n}\n","import { defineCommand } from 'citty';\nimport { fetchApiKeys } from '../api/api-keys.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// keys ls [--workspace <id>]:\n// { ok: true, workspaceId, keys: [{id, name, extra}], needsKey? } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// `needsKey: true` is emitted when the key list is empty. The flag is\n// there so `/ait deploy` (and similar agent-plugin skills) can bail\n// with a friendly \"issue a key first\" message instead of attempting a\n// deploy that will 401 server-side. We keep the UI-specific Korean\n// wording out of JSON (it lives on stderr plain output only).\n//\n// Auth/network/api failures follow the shared contract (exit 10/11/17).\n//\n// \"Console API key\" in upstream terminology — used to authenticate\n// automated deploys. We only list here; `keys create` is a follow-up\n// (the management UI 404s until an initial key is issued, so we don't\n// know the creation/rotation endpoint yet).\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List console API keys in the selected workspace.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n try {\n const keys = await fetchApiKeys(workspaceId, session.cookies);\n if (args.json) {\n emitJson({\n ok: true,\n workspaceId,\n keys: keys.map((k) => ({ id: k.id, name: k.name ?? null, extra: k.extra })),\n ...(keys.length === 0 ? { needsKey: true } : {}),\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n if (keys.length === 0) {\n process.stdout.write(`No API keys in workspace ${workspaceId}.\\n`);\n process.stderr.write(\n 'Hint: issue a key from the console UI (API 키 → 발급받기) to enable deploy automation.\\n',\n );\n return exitAfterFlush(ExitCode.Ok);\n }\n process.stdout.write(`${keys.length} API key(s) in workspace ${workspaceId}:\\n`);\n for (const k of keys) {\n const name = k.name ?? '(unnamed)';\n process.stdout.write(`${k.id}\\t${name}\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nexport const keysCommand = defineCommand({\n meta: {\n name: 'keys',\n description: 'Inspect console API keys used for deploy automation.',\n },\n subCommands: {\n ls: lsCommand,\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","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: 2,\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","import type { CdpCookie } from '../cdp.js';\nimport { type FetchLike, requestConsoleApi } from './http.js';\n\n// GET /workspaces/:id/members — confirmed shape (as of 2026-04):\n// [{ workspaceId, bizUserNo, name, email, status, role,\n// isOwnerDelegationRequested, isAdult }]\n// `bizUserNo` is the stable per-person identifier across workspaces —\n// future `members remove` / role-change commands will key off it.\n\nconst BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport interface WorkspaceMember {\n readonly workspaceId: number;\n readonly bizUserNo: number;\n readonly name: string;\n readonly email: string;\n readonly status: string;\n readonly role: string;\n readonly isOwnerDelegationRequested: boolean;\n readonly isAdult: boolean;\n}\n\nexport async function fetchWorkspaceMembers(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<WorkspaceMember[]> {\n const url = `${BASE}/workspaces/${workspaceId}/members`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (!Array.isArray(raw)) {\n throw new Error(`Unexpected members shape for workspace=${workspaceId}: not an array`);\n }\n return raw.map((entry, index) => normalizeMember(entry, workspaceId, index));\n}\n\nfunction normalizeMember(raw: unknown, workspaceId: number, index: number): WorkspaceMember {\n if (raw === null || typeof raw !== 'object') {\n throw new Error(\n `Unexpected member entry at index ${index} for workspace=${workspaceId}: not an object`,\n );\n }\n const rec = raw as Record<string, unknown>;\n const stringField = (k: string): string => {\n const v = rec[k];\n if (typeof v !== 'string') {\n throw new Error(\n `Unexpected member entry at index ${index} for workspace=${workspaceId}: missing ${k}`,\n );\n }\n return v;\n };\n const numField = (k: string): number => {\n const v = rec[k];\n if (typeof v !== 'number' || !Number.isFinite(v)) {\n throw new Error(\n `Unexpected member entry at index ${index} for workspace=${workspaceId}: missing ${k}`,\n );\n }\n return v;\n };\n return {\n workspaceId: numField('workspaceId'),\n bizUserNo: numField('bizUserNo'),\n name: stringField('name'),\n email: stringField('email'),\n status: stringField('status'),\n role: stringField('role'),\n isOwnerDelegationRequested: Boolean(rec.isOwnerDelegationRequested),\n isAdult: Boolean(rec.isAdult),\n };\n}\n","import { defineCommand } from 'citty';\nimport { fetchWorkspaceMembers } from '../api/members.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// members ls [--workspace <id>]:\n// { ok: true, workspaceId, members: [{bizUserNo, name, email, status, role, ...}] } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// Auth/network/api failures follow the shared contract (exit 10/11/17).\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List members of the selected workspace.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n try {\n const members = await fetchWorkspaceMembers(workspaceId, session.cookies);\n if (args.json) {\n // `workspaceId` is omitted per-member (redundant with top level)\n // and `isAdult` is intentionally dropped — it is a Korean-specific\n // age-verification flag (성인 인증) classed as PII under local\n // compliance. Owners see *all* co-members, not just themselves, so\n // default-emitting it would leak every member's adult-verification\n // bit through `--json`. No CLI automation use case justifies\n // exposing it; if one ever arises, an opt-in flag is safer.\n emitJson({\n ok: true,\n workspaceId,\n members: members.map((m) => ({\n bizUserNo: m.bizUserNo,\n name: m.name,\n email: m.email,\n status: m.status,\n role: m.role,\n isOwnerDelegationRequested: m.isOwnerDelegationRequested,\n })),\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n if (members.length === 0) {\n process.stdout.write(`No members in workspace ${workspaceId}.\\n`);\n return exitAfterFlush(ExitCode.Ok);\n }\n for (const m of members) {\n process.stdout.write(`${m.bizUserNo}\\t${m.name}\\t${m.email}\\t${m.role}\\t${m.status}\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nexport const membersCommand = defineCommand({\n meta: {\n name: 'members',\n description: 'Inspect workspace members.',\n },\n subCommands: {\n ls: lsCommand,\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\nexport type ConditionalReleaseResult =\n | { readonly status: 'not-modified'; readonly etag: string | undefined }\n | { readonly status: 'updated'; readonly release: Release; readonly etag: string | undefined };\n\n/**\n * Conditional GET against `releases/latest`. If the server returns 304 we\n * learn \"no change\" without consuming a core rate-limit slot. Intended for\n * the background update check, which re-runs often; `fetchLatestRelease()`\n * remains the right call when the upgrade command actually needs the body.\n */\nexport async function fetchLatestReleaseConditional(\n previousEtag: string | undefined,\n): Promise<ConditionalReleaseResult> {\n const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;\n const headers = defaultHeaders() as Record<string, string>;\n if (previousEtag && previousEtag.length > 0) {\n headers['If-None-Match'] = previousEtag;\n }\n const res = await fetch(url, { headers });\n const etag = res.headers.get('etag') ?? undefined;\n if (res.status === 304) {\n return { status: 'not-modified', etag };\n }\n if (!res.ok) {\n throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);\n }\n const release = (await res.json()) as Release;\n return { status: 'updated', release, etag };\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 { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { fetchLatestReleaseConditional, versionFromTag } from './github.js';\nimport { upgradeCheckPath } from './paths.js';\nimport { compareSemver } from './semver.js';\nimport { VERSION } from './version.js';\n\n// Background \"is there a newer aitcc?\" probe. Rate-limit friendly by design:\n//\n// * At most one network call every 24 hours, regardless of how often the\n// user runs a command that opts in.\n// * Even a failed probe updates the cache timestamp, so a broken network\n// (or a 403 from GitHub) does not loop us back within minutes.\n// * Cache write is stamped BEFORE the network call and promoted atomically\n// via tempfile+rename, so two concurrent `aitcc whoami` invocations can't\n// both escape the throttle and an `exitAfterFlush` mid-write can't leave\n// a truncated JSON file.\n// * Uses a conditional GET with the previous ETag — a 304 response does\n// not consume the anonymous 60/hr core rate-limit bucket.\n// * Fully opt-out via AITCC_NO_UPDATE_CHECK=1 (and implicitly disabled\n// when stderr is not a TTY, so agent-plugin / script consumers never\n// see a stray notice line).\n//\n// The `upgrade` command does NOT use this path — it's the explicit fetch,\n// runs immediately on demand, and its output is the point.\n\nexport const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;\n\nexport interface UpdateCheckCache {\n readonly lastCheckedAt: string; // ISO 8601\n readonly latestTag?: string;\n readonly etag?: string;\n}\n\nexport async function readCache(): Promise<UpdateCheckCache | null> {\n let raw: string;\n try {\n raw = await readFile(upgradeCheckPath(), 'utf8');\n } catch {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== 'object') return null;\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.lastCheckedAt !== 'string') return null;\n // Reject non-string optional fields — a hand-edited or cross-version cache\n // with wrong types shouldn't corrupt later string operations.\n if (obj.latestTag !== undefined && typeof obj.latestTag !== 'string') return null;\n if (obj.etag !== undefined && typeof obj.etag !== 'string') return null;\n const result: UpdateCheckCache = {\n lastCheckedAt: obj.lastCheckedAt,\n ...(obj.latestTag !== undefined ? { latestTag: obj.latestTag as string } : {}),\n ...(obj.etag !== undefined ? { etag: obj.etag as string } : {}),\n };\n return result;\n}\n\nexport async function writeCache(entry: UpdateCheckCache): Promise<void> {\n const path = upgradeCheckPath();\n await mkdir(dirname(path), { recursive: true });\n // Atomic promote: write to a unique sibling tempfile, then rename. On\n // POSIX rename(2) is atomic within the same filesystem, so a truncated\n // or crash-interrupted write never becomes the canonical cache file.\n // Windows is best-effort (ReplaceFileW is atomic for same-volume targets\n // but not guaranteed cross-process).\n // Unique-per-caller tempfile: pid + wall-clock + random suffix. Prevents\n // concurrent writers in the same process (Date.now() has ms resolution and\n // can collide) from stealing each other's tempfiles between writeFile and\n // rename.\n const tmp = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 10)}.tmp`;\n try {\n // Cache body is non-secret, but mtime + the ETag are a mild leak of\n // \"when this user last ran aitcc\" on a multi-user box. Match session\n // storage's 0600 mode for consistency and defence in depth.\n await writeFile(tmp, JSON.stringify(entry, null, 2), { mode: 0o600 });\n await rename(tmp, path);\n } catch (err) {\n // If rename failed we may have left the tempfile behind; clean up.\n await unlink(tmp).catch(() => {});\n throw err;\n }\n}\n\n/** Has the throttle window elapsed since the last recorded check? */\nexport function isDueForCheck(\n cache: UpdateCheckCache | null,\n now: number = Date.now(),\n intervalMs: number = UPDATE_CHECK_INTERVAL_MS,\n): boolean {\n if (!cache) return true;\n const last = Date.parse(cache.lastCheckedAt);\n if (!Number.isFinite(last)) return true;\n // If the system clock jumps backwards (NTP resync, VM resume), treat the\n // cache as stale and re-check. Better to probe once than to be silently\n // stuck until wall-time catches up to `last + interval`.\n if (now < last) return true;\n return now - last >= intervalMs;\n}\n\nexport interface UpdateCheckOptions {\n readonly env?: NodeJS.ProcessEnv;\n readonly isTTY?: boolean;\n readonly now?: number;\n readonly intervalMs?: number;\n}\n\n/**\n * Perform the throttled update check. Returns the final cache entry (for\n * testing) or null when skipped. Never throws — network errors are\n * intentionally swallowed so they never interrupt the foreground command.\n */\nexport async function maybeCheckForUpdate(\n opts: UpdateCheckOptions = {},\n): Promise<UpdateCheckCache | null> {\n const env = opts.env ?? process.env;\n const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);\n const now = opts.now ?? Date.now();\n const intervalMs = opts.intervalMs ?? UPDATE_CHECK_INTERVAL_MS;\n\n // Opt-out: any non-empty value that isn't explicitly falsey counts. Matches\n // the loose convention used by CI / DEBUG env vars — \"AITCC_NO_UPDATE_CHECK=true\"\n // works alongside \"=1\".\n const optOut = env.AITCC_NO_UPDATE_CHECK;\n if (optOut && optOut !== '0' && optOut.toLowerCase() !== 'false') return null;\n // Notice lines are targeted at interactive users. Checking stderr (where\n // the notice is written) rather than stdout means `aitcc whoami > out.log`\n // still shows the notice on the terminal, while a fully piped invocation\n // (stderr redirected or captured) is silent.\n if (!isTTY) return null;\n\n const cache = await readCache();\n if (!isDueForCheck(cache, now, intervalMs)) return null;\n\n // Stamp the cache BEFORE the network call so a concurrent `aitcc whoami`\n // that reads AFTER this write sees \"not due\" and skips its probe. (Two\n // racers that both read before either writes will each fire once — the\n // window is a handful of µs and the anonymous GitHub limit plus the\n // ETag-based 304 path keep the damage bounded.) If the probe crashes\n // the process, this placeholder also naturally satisfies the \"failed\n // probes still update the window\" invariant — the next run is bounded\n // by the interval.\n const nowIso = new Date(now).toISOString();\n const placeholder: UpdateCheckCache = {\n lastCheckedAt: nowIso,\n ...(cache?.latestTag !== undefined ? { latestTag: cache.latestTag } : {}),\n ...(cache?.etag !== undefined ? { etag: cache.etag } : {}),\n };\n await writeCache(placeholder).catch(() => {\n // Non-fatal: proceed with the probe, we'll try to write again on\n // completion. The throttle guarantee weakens here but bounded by\n // whatever caused the write to fail in the first place.\n });\n\n const previousEtag = cache?.etag;\n let entry: UpdateCheckCache = placeholder;\n try {\n const result = await fetchLatestReleaseConditional(previousEtag);\n if (result.status === 'not-modified') {\n // 304: server had no new body. Keep the latestTag we already know\n // about, and refresh the ETag only if the server happened to include\n // one on the 304.\n entry = {\n lastCheckedAt: nowIso,\n ...(cache?.latestTag !== undefined ? { latestTag: cache.latestTag } : {}),\n ...(result.etag !== undefined\n ? { etag: result.etag }\n : cache?.etag !== undefined\n ? { etag: cache.etag }\n : {}),\n };\n } else {\n entry = {\n lastCheckedAt: nowIso,\n latestTag: result.release.tag_name,\n ...(result.etag !== undefined ? { etag: result.etag } : {}),\n };\n }\n await writeCache(entry).catch(() => {\n // Placeholder already wrote above; ignore secondary write failure.\n });\n } catch {\n // Network / parse failure. Placeholder is already on disk, so the\n // throttle invariant holds — just skip the second write.\n }\n\n // If the probe failed, `entry` is still the placeholder — so the notice\n // uses the *previous* known latestTag from cache. That's fine: the\n // throttle window bounds staleness to 24h and semver doesn't move\n // backwards, so an out-of-date tag can only under-report a new release,\n // never falsely suggest a newer one than really exists.\n maybeEmitNotice(entry, env);\n return entry;\n}\n\nfunction maybeEmitNotice(entry: UpdateCheckCache, env: NodeJS.ProcessEnv): void {\n if (!entry.latestTag) return;\n // In the dev fallback VERSION (`0.0.0-dev`, from `src/version.ts` when no\n // build-time define is injected) every released tag looks \"newer\" and the\n // notice would fire on every `pnpm dev` run. Skip it instead — developers\n // running from source don't need an upgrade nag.\n if (VERSION.startsWith('0.0.0-dev')) return;\n const latest = versionFromTag(entry.latestTag);\n if (!latest) return;\n if (compareSemver(latest, VERSION) <= 0) return;\n // Respect NO_COLOR — CLAUDE.md documents it as honored across the CLI.\n const dim = env.NO_COLOR ? '' : '\\x1b[2m';\n const reset = env.NO_COLOR ? '' : '\\x1b[0m';\n // Notice goes to stderr so it never pollutes `--json` stdout.\n process.stderr.write(\n `\\n${dim}(aitcc ${latest} is available — run \\`aitcc upgrade\\` to install)${reset}\\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';\nimport { maybeCheckForUpdate } from '../update-check.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\n// Run the throttled background update check — but bound the wall-clock cost\n// so a slow network never delays the user's whoami output. 500 ms is enough\n// for a 304 (fast path after the first check) and for most 200s; a cold\n// probe that goes long just gets cancelled, and the next whoami within 24h\n// will not retry anyway (cache was written when the probe started).\n//\n// Skipped entirely when `--json` is set — machine consumers (agent-plugin)\n// should never see a \"new version available\" notice line interleaved with\n// their parsed output. The notice in update-check.ts already targets stderr\n// and checks `isTTY`, but belt-and-suspenders costs nothing here.\nasync function runBackgroundUpdateCheck(json: boolean): Promise<void> {\n if (json) return;\n const timeoutMs = 500;\n await Promise.race([\n maybeCheckForUpdate().catch(() => null),\n new Promise<null>((resolve) => {\n const t = setTimeout(() => resolve(null), timeoutMs);\n if (typeof t.unref === 'function') t.unref();\n }),\n ]);\n}\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 await runBackgroundUpdateCheck(args.json);\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 await runBackgroundUpdateCheck(args.json);\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","import type { CdpCookie } from '../cdp.js';\nimport { type FetchLike, requestConsoleApi } from './http.js';\n\n// The list of workspaces a user can see is already baked into the\n// `members/me/user-info` response (see `./me.ts`), so we don't expose a\n// separate `GET /workspaces` wrapper — every caller that needs the list\n// goes through `fetchConsoleMemberUserInfo` and keys off `workspaces`.\n// This module only covers per-workspace detail and future write endpoints.\n\n// Note: the list endpoint (members/me/user-info) and the detail endpoint\n// disagree on field names — list uses workspaceId/workspaceName while\n// detail uses id/name. We normalise detail into the same vocabulary so\n// callers don't have to track which endpoint they came from.\nexport interface WorkspaceDetail {\n readonly workspaceId: number;\n readonly workspaceName: string;\n // The full shape of `/workspaces/:id` has many secondary fields (business\n // registration, verification, licence type, review state, etc) that may\n // grow over time. Stash everything beyond the normalised keys under\n // `extra` so commands like `workspace show --json` can dump the payload\n // without us having to type every field up-front.\n readonly extra?: Readonly<Record<string, unknown>>;\n}\n\nconst WORKSPACES_BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport async function fetchWorkspaceDetail(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<WorkspaceDetail> {\n // workspaceId is a number at compile time — `encodeURIComponent` on the\n // stringified form would be a no-op, so we inline the interpolation.\n const url = `${WORKSPACES_BASE}/workspaces/${workspaceId}`;\n const raw = await requestConsoleApi<Record<string, unknown>>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n const id = raw.id;\n const name = raw.name;\n if (typeof id !== 'number' || !Number.isInteger(id) || id <= 0 || typeof name !== 'string') {\n throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);\n }\n const { id: _id, name: _name, ...extra } = raw;\n return { workspaceId: id, workspaceName: name, extra };\n}\n","import { defineCommand } from 'citty';\nimport { fetchConsoleMemberUserInfo } from '../api/me.js';\nimport { fetchWorkspaceDetail } from '../api/workspaces.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { readSession, setCurrentWorkspaceId } from '../session.js';\nimport {\n emitFailureFromError,\n emitJson,\n emitNotAuthenticated,\n parsePositiveInt,\n} from './_shared.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// workspace ls:\n// { ok: true, workspaces: [{workspaceId, workspaceName, role, current}] }\n// ^--- matches currentWorkspaceId\n// workspace use <id>:\n// { ok: true, workspaceId, workspaceName } exit 0\n// { ok: false, reason: 'not-found', workspaceId } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n// workspace show [--workspace <id>]:\n// { ok: true, workspaceId, workspaceName, extra } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// Every workspace subcommand inherits the standard auth failure modes from\n// whoami: { ok: true, authenticated: false } exit 10, network-error exit 11,\n// api-error exit 17. All JSON writes go through the shared `emitJson` so the\n// single-line-with-trailing-newline invariant is enforced in one place.\n\n// Formatting helper for the plain-text `show` output. `--json` is the\n// structured consumption path; this is a crude fallback so a human can\n// skim the response at a glance. Objects/arrays collapse to a single\n// JSON line on purpose — nested structures are rare in the detail\n// response and unreadable in any form without real tabular formatting.\nfunction formatScalar(v: unknown): string {\n if (v === null) return 'null';\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return String(v);\n return JSON.stringify(v);\n}\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List workspaces the current user has access to.',\n },\n args: {\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n try {\n const info = await fetchConsoleMemberUserInfo(session.cookies);\n const current = session.currentWorkspaceId;\n if (args.json) {\n const workspaces = info.workspaces.map((w) => ({\n workspaceId: w.workspaceId,\n workspaceName: w.workspaceName,\n role: w.role,\n current: w.workspaceId === current,\n }));\n emitJson({ ok: true, workspaces });\n return exitAfterFlush(ExitCode.Ok);\n }\n if (info.workspaces.length === 0) {\n process.stdout.write('No workspaces.\\n');\n return exitAfterFlush(ExitCode.Ok);\n }\n for (const w of info.workspaces) {\n const marker = w.workspaceId === current ? '* ' : ' ';\n process.stdout.write(`${marker}${w.workspaceId} ${w.workspaceName} (${w.role})\\n`);\n }\n if (current === undefined) {\n process.stderr.write('No workspace selected. Run `aitcc workspace use <id>`.\\n');\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nconst useCommand = defineCommand({\n meta: {\n name: 'use',\n description: 'Select the current workspace by ID. Subsequent commands use this.',\n },\n args: {\n id: { type: 'positional', description: 'Workspace ID', required: true },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const raw = String(args.id);\n const parsed = parsePositiveInt(raw);\n if (parsed === null) {\n const message = `workspace id must be a positive integer (got ${raw})`;\n if (args.json) {\n emitJson({ ok: false, reason: 'invalid-id', message });\n } else {\n process.stderr.write(`${message}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n\n // Validate against the user's actual workspace list before writing the\n // selection. `members/me/user-info` is the live list, not the stored\n // one, so a workspace added after login is visible here. Only the\n // detail endpoint (not called here) could still 403 after this check.\n try {\n const info = await fetchConsoleMemberUserInfo(session.cookies);\n const match = info.workspaces.find((w) => w.workspaceId === parsed);\n if (!match) {\n if (args.json) {\n emitJson({ ok: false, reason: 'not-found', workspaceId: parsed });\n } else {\n process.stderr.write(\n `Workspace ${parsed} is not accessible from this account. Run \\`aitcc workspace ls\\` to see available workspaces.\\n`,\n );\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n // `setCurrentWorkspaceId` returns null only if the session disappeared\n // between our `readSession` above and here (e.g. concurrent logout).\n // Surface that as \"not logged in\" for consistency with other commands\n // instead of silently pretending the write landed. For v1 sessions\n // this is a double-read (readSession migrates, then this helper reads\n // again before writing) — benign, and preferable to threading the\n // already-loaded session through a new parameter just to save one IO.\n const updated = await setCurrentWorkspaceId(parsed);\n if (updated === null) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n if (args.json) {\n emitJson({\n ok: true,\n workspaceId: match.workspaceId,\n workspaceName: match.workspaceName,\n });\n } else {\n process.stdout.write(`Using workspace ${match.workspaceId} (${match.workspaceName}).\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nconst showCommand = defineCommand({\n meta: {\n name: 'show',\n description: 'Show details of the selected workspace (or the one passed with --workspace).',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID to inspect. Defaults to the selected workspace.',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n\n let workspaceId: number | undefined;\n if (args.workspace) {\n const raw = String(args.workspace);\n const parsed = parsePositiveInt(raw);\n if (parsed === null) {\n const message = `--workspace must be a positive integer (got ${raw})`;\n if (args.json) {\n emitJson({ ok: false, reason: 'invalid-id', message });\n } else {\n process.stderr.write(`${message}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n workspaceId = parsed;\n } else {\n workspaceId = session.currentWorkspaceId;\n }\n\n if (workspaceId === undefined) {\n if (args.json) {\n emitJson({ ok: false, reason: 'no-workspace-selected' });\n } else {\n process.stderr.write(\n 'No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\\n',\n );\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n\n try {\n const detail = await fetchWorkspaceDetail(workspaceId, session.cookies);\n if (args.json) {\n emitJson({\n ok: true,\n workspaceId: detail.workspaceId,\n workspaceName: detail.workspaceName,\n extra: detail.extra ?? {},\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n process.stdout.write(`Workspace ${detail.workspaceId}: ${detail.workspaceName}\\n`);\n if (detail.extra) {\n for (const [k, v] of Object.entries(detail.extra)) {\n process.stdout.write(` ${k}: ${formatScalar(v)}\\n`);\n }\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nexport const workspaceCommand = defineCommand({\n meta: {\n name: 'workspace',\n description: 'Inspect and switch between the workspaces this account can access.',\n },\n subCommands: {\n ls: lsCommand,\n use: useCommand,\n show: showCommand,\n },\n});\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty';\nimport { appCommand } from './commands/app.js';\nimport { keysCommand } from './commands/keys.js';\nimport { loginCommand } from './commands/login.js';\nimport { logoutCommand } from './commands/logout.js';\nimport { membersCommand } from './commands/members.js';\nimport { upgradeCommand } from './commands/upgrade.js';\nimport { whoamiCommand } from './commands/whoami.js';\nimport { workspaceCommand } from './commands/workspace.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 workspace: workspaceCommand,\n app: appCommand,\n members: membersCommand,\n keys: keysCommand,\n },\n});\n\nrunMain(main);\n"],"mappings":"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"}
1
+ {"version":3,"file":"cli.mjs","names":["BASE","parseYaml","lsCommand","showCommand","BASE","lsCommand","winPath","fsConstants","lsCommand"],"sources":["../src/api/http.ts","../src/api/mini-apps.ts","../src/exit.ts","../src/flush.ts","../src/paths.ts","../src/session.ts","../src/commands/_shared.ts","../src/config/app-manifest.ts","../src/config/image-validator.ts","../src/commands/register-payload.ts","../src/commands/register.ts","../src/commands/app.ts","../src/api/api-keys.ts","../src/commands/keys.ts","../src/api/me.ts","../src/cdp.ts","../src/chrome.ts","../src/commands/login.ts","../src/commands/logout.ts","../src/api/members.ts","../src/commands/members.ts","../src/github.ts","../src/platform.ts","../src/semver.ts","../src/version.ts","../src/commands/upgrade.ts","../src/update-check.ts","../src/commands/whoami.ts","../src/api/workspaces.ts","../src/commands/workspace.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 return executeAndUnwrap<T>(url, init, options.fetchImpl);\n}\n\n/**\n * Send a pre-built `RequestInit` against the console API and unwrap the\n * Toss envelope. Use this when the caller needs to build the body itself\n * (multipart uploads, binary requests, anything that can't live under\n * `requestConsoleApi`'s JSON-body assumption). Cookie header composition\n * and any additional headers remain the caller's responsibility.\n *\n * Exists so `uploadMiniAppResource` doesn't have to re-implement the\n * text→JSON→envelope→error branch in `requestConsoleApi`; drift between\n * the two paths has bitten us once (cf. the refactor in the\n * `app register` review).\n */\nexport async function executeAndUnwrap<T>(\n url: URL,\n init: RequestInit,\n fetchImpl?: FetchLike,\n): Promise<T> {\n const impl: FetchLike = fetchImpl ?? ((input, i) => fetch(input, i));\n let res: Response;\n try {\n res = await impl(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 {\n cookieHeaderFor,\n executeAndUnwrap,\n type FetchLike,\n MalformedResponseError,\n requestConsoleApi,\n} from './http.js';\n\n// Two endpoints cover the \"list my apps\" surface:\n//\n// GET /workspaces/:id/mini-app → array of app summaries\n// GET /workspaces/:id/mini-apps/review-status → { hasPolicyViolation, miniApps: [...] }\n//\n// Note the singular/plural inconsistency (`mini-app` vs `mini-apps`) is\n// how the upstream API actually spells them, not a transcription error —\n// see TODO.md's console feature inventory.\n//\n// The detailed field shape inside each array element is not yet known to\n// us (the confirmed workspaces currently have zero apps). We model each\n// element as a minimal \"id + name + extras\" envelope so the CLI can show\n// something useful today and layer on specific fields as they're observed.\n// `extra` is typed as unknown-valued so we don't pretend to know more.\n\nconst BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport interface MiniAppSummary {\n readonly id: string | number;\n readonly name: string | undefined;\n readonly extra: Readonly<Record<string, unknown>>;\n}\n\nexport interface ReviewStatusSummary {\n readonly hasPolicyViolation: boolean;\n readonly miniApps: readonly Readonly<Record<string, unknown>>[];\n}\n\nexport async function fetchMiniApps(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<MiniAppSummary[]> {\n const url = `${BASE}/workspaces/${workspaceId}/mini-app`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (!Array.isArray(raw)) {\n throw new Error(`Unexpected mini-app list shape for workspace=${workspaceId}`);\n }\n return raw.map((item, index) => normalizeMiniApp(item, workspaceId, index));\n}\n\nfunction normalizeMiniApp(item: unknown, workspaceId: number, index: number): MiniAppSummary {\n if (item === null || typeof item !== 'object') {\n throw new Error(\n `Unexpected mini-app entry at index ${index} for workspace=${workspaceId}: not an object`,\n );\n }\n const rec = item as Record<string, unknown>;\n const rawId = rec.id ?? rec.miniAppId ?? rec.appId;\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n throw new Error(\n `Unexpected mini-app entry at index ${index} for workspace=${workspaceId}: missing id`,\n );\n }\n const rawName = rec.name ?? rec.miniAppName ?? rec.appName;\n const name = typeof rawName === 'string' ? rawName : undefined;\n const {\n id: _id,\n miniAppId: _mid,\n appId: _aid,\n name: _n,\n miniAppName: _mn,\n appName: _an,\n ...extra\n } = rec;\n return { id: rawId, name, extra };\n}\n\nexport async function fetchReviewStatus(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<ReviewStatusSummary> {\n const url = `${BASE}/workspaces/${workspaceId}/mini-apps/review-status`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (raw === null || typeof raw !== 'object') {\n throw new Error(`Unexpected review-status shape for workspace=${workspaceId}`);\n }\n const rec = raw as Record<string, unknown>;\n const hasPolicyViolation = Boolean(rec.hasPolicyViolation);\n const miniAppsRaw = rec.miniApps;\n if (!Array.isArray(miniAppsRaw)) {\n throw new Error(\n `Unexpected review-status shape for workspace=${workspaceId}: miniApps is not an array`,\n );\n }\n const miniApps = miniAppsRaw.map((m) => {\n if (m === null || typeof m !== 'object') return {};\n return m as Record<string, unknown>;\n });\n return { hasPolicyViolation, miniApps };\n}\n\nexport interface MiniAppWithDraft {\n readonly current: Record<string, unknown> | null;\n readonly draft: Record<string, unknown> | null;\n // Top-level envelope fields (not inside current/draft). Present on every\n // with-draft response. `approvalType` distinguishes REVIEW-submitted apps\n // from drafts that haven't been sent for review; `rejectedMessage` is\n // non-null iff the review came back rejected. Together with `current`\n // (null until an approved record exists) they derive the UI banner state.\n readonly approvalType: string | null;\n readonly rejectedMessage: string | null;\n}\n\nexport async function fetchMiniAppWithDraft(\n workspaceId: number,\n miniAppId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<MiniAppWithDraft> {\n const url = `${BASE}/workspaces/${workspaceId}/mini-app/${miniAppId}/with-draft`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (raw === null || typeof raw !== 'object') {\n throw new Error(`Unexpected with-draft shape for mini-app=${miniAppId}`);\n }\n const rec = raw as Record<string, unknown>;\n const current = isRecordOrNull(rec.current)\n ? (rec.current as Record<string, unknown> | null)\n : null;\n const draft = isRecordOrNull(rec.draft) ? (rec.draft as Record<string, unknown> | null) : null;\n const approvalType = typeof rec.approvalType === 'string' ? rec.approvalType : null;\n const rejectedMessage = typeof rec.rejectedMessage === 'string' ? rec.rejectedMessage : null;\n return { current, draft, approvalType, rejectedMessage };\n}\n\nfunction isRecordOrNull(v: unknown): v is Record<string, unknown> | null {\n return v === null || (typeof v === 'object' && !Array.isArray(v));\n}\n\n// --- Register (create) ---\n//\n// `createMiniApp` and `uploadMiniAppResource` back the `app register`\n// command. The submit payload shape below is *inferred* from static\n// bundle analysis (`VALIDATION-RULES.md` in the umbrella `.playwright-\n// mcp/`); the console UI never round-trips intermediate drafts, so the\n// only authoritative record will come from dog-food task #23. Field\n// names here intentionally mirror the `Xc` function from the bundle so\n// when #23 runs, any correction is a direct rename rather than a\n// restructure.\n\n// Exposed as `unknown` per-image so the caller (not this layer) is the\n// place where a cross-field invariant like \"at least 3 PREVIEW/VERTICAL\"\n// is enforced. Keeping this type open also makes it trivial to add the\n// `LOGO` imageType or other orientation values without touching the\n// network module.\nexport type MiniAppImageType = 'LOGO' | 'THUMBNAIL' | 'PREVIEW';\nexport type MiniAppImageOrientation = 'HORIZONTAL' | 'VERTICAL';\n\nexport interface MiniAppImageEntry {\n readonly imageUrl: string;\n readonly imageType: MiniAppImageType;\n readonly orientation: MiniAppImageOrientation;\n}\n\nexport interface MiniAppSubmitPayload {\n readonly miniApp: {\n readonly title: string;\n readonly titleEn: string;\n readonly appName: string;\n readonly iconUri: string;\n readonly status: 'PREPARE';\n readonly darkModeIconUri?: string;\n readonly homePageUri?: string;\n readonly csEmail: string;\n readonly description: string; // subtitle (≤20 chars)\n readonly detailDescription: string;\n readonly images: readonly MiniAppImageEntry[];\n };\n readonly impression: {\n readonly keywordList: readonly string[];\n readonly categoryIds: readonly number[];\n readonly subCategoryIds?: readonly number[];\n };\n}\n\nexport interface CreateMiniAppResult {\n readonly miniAppId: string | number | undefined;\n readonly reviewState: string | undefined;\n readonly extra: Readonly<Record<string, unknown>>;\n}\n\nexport async function createMiniApp(\n workspaceId: number,\n payload: MiniAppSubmitPayload,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<CreateMiniAppResult> {\n const url = `${BASE}/workspaces/${workspaceId}/mini-app/review`;\n const raw = await requestConsoleApi<unknown>({\n url,\n method: 'POST',\n cookies,\n body: payload,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n return normalizeCreateResult(raw);\n}\n\nfunction normalizeCreateResult(raw: unknown): CreateMiniAppResult {\n if (raw === null || typeof raw !== 'object') {\n return { miniAppId: undefined, reviewState: undefined, extra: {} };\n }\n const rec = raw as Record<string, unknown>;\n const rawId = rec.miniAppId ?? rec.id ?? rec.appId;\n const miniAppId = typeof rawId === 'string' || typeof rawId === 'number' ? rawId : undefined;\n const rawState = rec.reviewState ?? rec.status;\n const reviewState = typeof rawState === 'string' ? rawState : undefined;\n return { miniAppId, reviewState, extra: rec };\n}\n\nexport interface UploadFile {\n readonly buffer: Buffer;\n readonly fileName: string;\n readonly contentType: string;\n}\n\nexport interface UploadParams {\n readonly workspaceId: number;\n readonly validWidth: number;\n readonly validHeight: number;\n readonly file: UploadFile;\n readonly cookies: readonly CdpCookie[];\n}\n\n/**\n * Upload an image to `/resource/:wid/upload?validWidth=W&validHeight=H`\n * and return the CDN URL the server hands back. The endpoint is a\n * multipart/form-data POST; we build a FormData with a single `resource`\n * field because that matches the bundle analysis for the console's\n * uploader, which pairs a `fileName` string field with a `resource`\n * Blob (see VALIDATION-RULES.md → iconUri). Dog-food #23 may reveal that\n * the field name is actually `file` — if so, swap it in one place here.\n */\nexport async function uploadMiniAppResource(\n params: UploadParams,\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<string> {\n const url = new URL(`${BASE}/resource/${params.workspaceId}/upload`);\n url.searchParams.set('validWidth', String(params.validWidth));\n url.searchParams.set('validHeight', String(params.validHeight));\n\n const form = new FormData();\n // A `Buffer` is already a `Uint8Array`, but its `ArrayBufferLike`\n // backing can be a `SharedArrayBuffer` which `BlobPart` doesn't accept.\n // Wrapping in a `Uint8Array` view over the same bytes (byteOffset +\n // byteLength) keeps the type happy without the extra copy that\n // `new Uint8Array(buffer)` would force.\n const view = new Uint8Array(\n params.file.buffer.buffer as ArrayBuffer,\n params.file.buffer.byteOffset,\n params.file.buffer.byteLength,\n );\n const blob = new Blob([view], { type: params.file.contentType });\n form.append('resource', blob, params.file.fileName);\n form.append('fileName', params.file.fileName);\n\n const cookieHeader = cookieHeaderFor(url, params.cookies);\n const headers: Record<string, string> = {\n Accept: 'application/json, text/plain, */*',\n };\n if (cookieHeader) headers.Cookie = cookieHeader;\n\n const imageUrl = await executeAndUnwrap<unknown>(\n url,\n { method: 'POST', headers, body: form },\n opts.fetchImpl,\n );\n if (typeof imageUrl !== 'string') {\n throw new MalformedResponseError(\n url.toString(),\n 200,\n `expected string imageUrl, got ${typeof imageUrl}`,\n );\n }\n return imageUrl;\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 // 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\n// Cache directory — for non-secret, regenerable state. Distinct from\n// `configDir()` so a `rm -rf ~/.cache/aitcc` cannot take the session with\n// it, and so packagers can mount the two on different volumes.\nexport function cacheDir(): string {\n if (process.platform === 'win32') {\n const localAppData = process.env.LOCALAPPDATA;\n if (localAppData && localAppData.length > 0) return join(localAppData, APP_NAME, 'Cache');\n return join(homedir() || '.', 'AppData', 'Local', APP_NAME, 'Cache');\n }\n const xdg = process.env.XDG_CACHE_HOME;\n if (xdg && xdg.length > 0) return join(xdg, APP_NAME);\n return join(homedir() || '.', '.cache', APP_NAME);\n}\n\nexport function upgradeCheckPath(): string {\n return join(cacheDir(), 'upgrade-check.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: 2;\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 // Workspace context. Unset until the user runs `aitcc workspace use <id>`\n // or provides `--workspace` on first use. Writes are explicit — we never\n // guess a default (e.g. \"first workspace the user has access to\") because\n // a silent guess is exactly the class of bug that causes a deploy to land\n // in the wrong account.\n currentWorkspaceId?: number;\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\n/**\n * Read the persisted session. Returns `null` when no session exists, when\n * the file is corrupt, or when the shape fails validation — each of those\n * emits a one-line warning on stderr for diagnostics.\n *\n * **Side effect**: a v1 session file is transparently rewritten to v2 on\n * the first successful read of this process. This keeps read-only callers\n * (`whoami`, `workspace ls`) from stranding users on an old schema. If the\n * rewrite fails, we warn once per process and continue with the in-memory\n * v2 value so the calling command still succeeds.\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 rawParsed: unknown;\n try {\n rawParsed = JSON.parse(raw);\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(rawParsed);\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 // Post-validation: the shape is trusted. `schemaVersion` is now 1 or 2;\n // v1 files are transparently upgraded to v2 in memory. We best-effort\n // rewrite the file so long-lived v1-on-disk sessions eventually migrate\n // without requiring the user to run a write-shaped command. A failed\n // rewrite is non-fatal (the in-memory shape is correct) but we still\n // emit a one-line stderr warning once per process so a read-only mount\n // or permission issue does not silently persist. We await rather than\n // fire-and-forget so concurrent callers observe consistent on-disk state.\n const validated = rawParsed as { schemaVersion: 1 | 2 } & Omit<Session, 'schemaVersion'>;\n if (validated.schemaVersion === 1) {\n const upgraded: Session = { ...validated, schemaVersion: 2 };\n try {\n await writeSession(upgraded);\n } catch (err) {\n warnMigrationOnce(path, (err as NodeJS.ErrnoException).code);\n }\n return upgraded;\n }\n return validated as Session;\n}\n\n// One-shot latch so a failing migration doesn't spam stderr on every call\n// inside the same process. Users still get the diagnostic the first time.\nlet migrationWarned = false;\nfunction warnMigrationOnce(path: string, code: string | undefined): void {\n if (migrationWarned) return;\n migrationWarned = true;\n process.stderr.write(\n `warning: could not migrate session file at ${path} to schemaVersion 2: ${code ?? 'unknown'}\\n`,\n );\n}\n\n// v1 → v2 migration: v1 files are still valid, we just treat the absent\n// `currentWorkspaceId` as \"no workspace selected yet\". The next write (e.g.\n// from `workspace use`) bumps the stored schemaVersion. The validator input\n// is `unknown` so we can inspect raw JSON without the TS type narrowing\n// away the v1 branch.\nfunction validateSessionShape(input: unknown): string | null {\n if (input === null || typeof input !== 'object') return 'root is not an object';\n const parsed = input as {\n schemaVersion?: unknown;\n user?: { id?: unknown; email?: unknown; displayName?: unknown };\n cookies?: unknown;\n origins?: unknown;\n capturedAt?: unknown;\n currentWorkspaceId?: unknown;\n };\n if (parsed.schemaVersion !== 1 && parsed.schemaVersion !== 2) {\n return `unknown schemaVersion ${String(parsed.schemaVersion)}`;\n }\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 if (parsed.origins !== undefined && !Array.isArray(parsed.origins)) {\n return 'origins is not an array';\n }\n if (parsed.capturedAt !== undefined && typeof parsed.capturedAt !== 'string') {\n return 'capturedAt has wrong type';\n }\n if (parsed.currentWorkspaceId !== undefined) {\n const wid = parsed.currentWorkspaceId;\n if (typeof wid !== 'number' || !Number.isInteger(wid) || wid <= 0) {\n return 'currentWorkspaceId has wrong type';\n }\n }\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\n/**\n * Persist a new `currentWorkspaceId` on an existing session. Returns the\n * updated session, or `null` if there is no session to update (callers\n * should surface \"not logged in\" in that case).\n */\nexport async function setCurrentWorkspaceId(workspaceId: number): Promise<Session | null> {\n const session = await readSession();\n if (!session) return null;\n const updated: Session = { ...session, currentWorkspaceId: workspaceId };\n await writeSession(updated);\n return updated;\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 { NetworkError, TossApiError } from '../api/http.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { readSession, type Session, sessionPathForDiagnostics } from '../session.js';\n\n// Shared output helpers used by every session-scoped subcommand\n// (`workspace`, `app`, `members`, `keys`, and the in-flight `deploy`/`logs`).\n// Kept in one place so all commands agree on the `--json` contract — one\n// line, trailing \\n, stdout for structured output, stderr for diagnostics.\n//\n// Auth / network / API failure shapes are identical across every command:\n// { ok: true, authenticated: false } (exit 10), { ok: false,\n// reason: 'network-error', message } (exit 11), { ok: false,\n// reason: 'api-error', message } (exit 17). See any per-command\n// `--json contract` block (e.g. `commands/workspace.ts`) for the full\n// exit-code legend plus the success-shape specific to that command —\n// those per-command blocks are the source of truth for success payloads.\n\nexport interface NotAuthenticatedPayload {\n readonly ok: true;\n readonly authenticated: false;\n readonly reason?: 'session-expired';\n}\n\nexport function emitJson(payload: unknown): void {\n process.stdout.write(`${JSON.stringify(payload)}\\n`);\n}\n\nexport function emitNotAuthenticated(json: boolean, reason?: 'session-expired'): void {\n if (json) {\n // `exactOptionalPropertyTypes` forbids `reason: undefined`, so we omit\n // the key entirely when we don't have a value — hence the branch\n // rather than a single object literal.\n const payload: NotAuthenticatedPayload = reason\n ? { ok: true, authenticated: false, reason }\n : { ok: true, authenticated: false };\n emitJson(payload);\n } else {\n process.stderr.write(\n reason === 'session-expired'\n ? 'Session is no longer valid. Run `aitcc login` again.\\n'\n : 'Not logged in. Run `aitcc login` to start a session.\\n',\n );\n process.stderr.write(`Session file checked: ${sessionPathForDiagnostics()}\\n`);\n }\n}\n\nexport function emitNetworkError(json: boolean, message: string): void {\n if (json) {\n emitJson({ ok: false, reason: 'network-error', message });\n } else {\n process.stderr.write(`Network error reaching the console API: ${message}.\\n`);\n }\n}\n\nexport function emitApiError(\n json: boolean,\n message: string,\n details?: { status?: number; errorCode?: string },\n): void {\n if (json) {\n emitJson({\n ok: false,\n reason: 'api-error',\n ...(details?.status !== undefined ? { status: details.status } : {}),\n ...(details?.errorCode !== undefined ? { errorCode: details.errorCode } : {}),\n message,\n });\n } else {\n process.stderr.write(`Unexpected error: ${message}\\n`);\n }\n}\n\n/**\n * Shared auth/network/api dispatch. Every session-scoped command's\n * `catch (err)` block boils down to the same sequence: TossApiError\n * (auth → exit 10, otherwise → exit 17 with status + errorCode),\n * NetworkError (exit 11), fallback (exit 17 with just a message).\n * Exists so we get a single source of truth for the api-error JSON\n * shape — previously each command duplicated the if/else ladder and\n * `register` diverged (it exposed `status`/`errorCode` that the others\n * didn't) until this extraction lined them up.\n *\n * Returns `Promise<void>` but never returns at runtime: every branch\n * awaits `exitAfterFlush` which calls `process.exit`.\n */\nexport async function emitFailureFromError(json: boolean, err: unknown): Promise<void> {\n if (err instanceof TossApiError && err.isAuthError) {\n emitNotAuthenticated(json, 'session-expired');\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n if (err instanceof TossApiError) {\n emitApiError(json, err.message, { status: err.status, errorCode: err.errorCode });\n return exitAfterFlush(ExitCode.ApiError);\n }\n if (err instanceof NetworkError) {\n emitNetworkError(json, err.message);\n return exitAfterFlush(ExitCode.NetworkError);\n }\n emitApiError(json, (err as Error).message);\n return exitAfterFlush(ExitCode.ApiError);\n}\n\n// Parse a CLI-provided workspace id strictly: only the form `^[1-9]\\d*$`\n// is accepted. `Number.parseInt('36577x', 10)` returns 36577, so the CLI\n// would otherwise silently accept `workspace use 36577x` and persist the\n// wrong thing on a typo. Returning `null` triggers the caller's usage-error\n// path. Exported so unit tests can guard against \"just use parseInt\"\n// simplification regressions.\nexport function parsePositiveInt(raw: string): number | null {\n if (!/^[1-9]\\d*$/.test(raw)) return null;\n const n = Number.parseInt(raw, 10);\n return Number.isSafeInteger(n) ? n : null;\n}\n\n/**\n * Boilerplate wrapper for any workspace-scoped command (`app ls`,\n * `members ls`, `keys ls`, ...). Loads the session, resolves the workspace\n * id from `--workspace <id>` or the persisted selection, and handles the\n * three common failure branches (`no session`, `invalid id`, `no workspace\n * selected`). On success, the caller gets the session + resolved id back.\n *\n * The return type is `Promise<... | null>` but the `null` branch is never\n * observed at runtime: every failure path `await`s `exitAfterFlush` which\n * calls `process.exit(...)` and doesn't return. The `| null` is a type-\n * level handshake that forces callers to add `if (!ctx) return;`, keeping\n * the bail-out readable.\n */\nexport async function resolveWorkspaceContext(args: {\n workspace?: string | undefined;\n json: boolean;\n}): Promise<{ session: Session; workspaceId: number } | null> {\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n await exitAfterFlush(ExitCode.NotAuthenticated);\n return null;\n }\n\n let workspaceId: number | undefined;\n if (args.workspace) {\n const raw = String(args.workspace);\n const parsed = parsePositiveInt(raw);\n if (parsed === null) {\n const message = `--workspace must be a positive integer (got ${raw})`;\n if (args.json) emitJson({ ok: false, reason: 'invalid-id', message });\n else process.stderr.write(`${message}\\n`);\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n workspaceId = parsed;\n } else {\n workspaceId = session.currentWorkspaceId;\n }\n\n if (workspaceId === undefined) {\n if (args.json) emitJson({ ok: false, reason: 'no-workspace-selected' });\n else {\n process.stderr.write(\n 'No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\\n',\n );\n }\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n\n return { session, workspaceId };\n}\n","import { access, readFile } from 'node:fs/promises';\nimport { dirname, isAbsolute, resolve } from 'node:path';\nimport { parse as parseYaml } from 'yaml';\n\n// The app manifest is the CLI's user-facing contract for `aitcc app\n// register`. It intentionally mirrors the console's form field names only\n// at the top level — the inferred submit payload (which nests fields into\n// step1/step2/miniApp/impression) is built by a separate payload module\n// so the manifest shape is stable even if the bundle analysis turns out\n// to be off. Dog-food task #23 is expected to correct the payload\n// transformation but should not force a manifest rewrite.\n//\n// Validation here is \"config shape only\": presence, type, and cheap\n// numeric/length constraints from `VALIDATION-RULES.md` that we can\n// enforce without reading image files. Image-dimension checks live in a\n// sibling module (`image-validator.ts`) because they need FS reads.\n//\n// ManifestError is a single error class carrying (kind, field?, path?)\n// so the command layer can translate it into the documented `--json`\n// error shapes without re-classifying.\n\nexport type ManifestErrorKind = 'invalid-config' | 'missing-required-field';\n\nexport class ManifestError extends Error {\n readonly kind: ManifestErrorKind;\n readonly field: string | undefined;\n\n constructor(kind: ManifestErrorKind, message: string, field?: string) {\n super(message);\n this.name = 'ManifestError';\n this.kind = kind;\n this.field = field;\n }\n}\n\nexport interface AppManifest {\n readonly titleKo: string;\n readonly titleEn: string;\n readonly appName: string;\n readonly homePageUri: string | undefined;\n readonly csEmail: string;\n readonly logo: string;\n readonly logoDarkMode: string | undefined;\n readonly horizontalThumbnail: string;\n readonly categoryIds: readonly number[];\n readonly subtitle: string;\n readonly description: string;\n readonly keywords: readonly string[];\n readonly verticalScreenshots: readonly string[];\n readonly horizontalScreenshots: readonly string[];\n}\n\nconst DEFAULT_NAMES = ['aitcc.app.yaml', 'aitcc.app.json'] as const;\n\nasync function fileExists(path: string): Promise<boolean> {\n try {\n await access(path);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve the manifest file path. When `explicit` is provided, we use it\n * verbatim (resolved against `cwd`) and require it to exist. Otherwise we\n * auto-detect `aitcc.app.yaml` then `aitcc.app.json` under `cwd`.\n */\nexport async function resolveManifestPath(\n explicit: string | undefined,\n cwd: string,\n): Promise<string> {\n if (explicit) {\n const abs = isAbsolute(explicit) ? explicit : resolve(cwd, explicit);\n if (!(await fileExists(abs))) {\n throw new ManifestError('invalid-config', `manifest file not found at ${abs}`);\n }\n return abs;\n }\n for (const name of DEFAULT_NAMES) {\n const abs = resolve(cwd, name);\n if (await fileExists(abs)) return abs;\n }\n throw new ManifestError(\n 'invalid-config',\n `no app manifest found (looked for ${DEFAULT_NAMES.join(', ')} in ${cwd})`,\n );\n}\n\nexport async function loadAppManifest(path: string): Promise<AppManifest> {\n const raw = await readFile(path, 'utf8');\n const parsed = parseManifestFile(path, raw);\n return validateManifest(parsed, dirname(path));\n}\n\nfunction parseManifestFile(path: string, raw: string): Record<string, unknown> {\n const isJson = path.toLowerCase().endsWith('.json');\n try {\n const out = isJson ? JSON.parse(raw) : parseYaml(raw);\n if (out === null || typeof out !== 'object' || Array.isArray(out)) {\n throw new ManifestError('invalid-config', `manifest at ${path} is not a mapping`);\n }\n return out as Record<string, unknown>;\n } catch (err) {\n if (err instanceof ManifestError) throw err;\n const msg = (err as Error).message;\n throw new ManifestError('invalid-config', `failed to parse manifest at ${path}: ${msg}`);\n }\n}\n\nfunction requireString(input: Record<string, unknown>, key: string): string {\n const v = input[key];\n if (v === undefined || v === null) {\n throw new ManifestError('missing-required-field', `${key} is required`, key);\n }\n if (typeof v !== 'string') {\n throw new ManifestError('invalid-config', `${key} must be a string`, key);\n }\n if (v.trim().length === 0) {\n throw new ManifestError('missing-required-field', `${key} is required`, key);\n }\n return v;\n}\n\nfunction optionalString(input: Record<string, unknown>, key: string): string | undefined {\n const v = input[key];\n if (v === undefined || v === null) return undefined;\n if (typeof v !== 'string') {\n throw new ManifestError('invalid-config', `${key} must be a string when provided`, key);\n }\n return v;\n}\n\nfunction requirePath(input: Record<string, unknown>, key: string, configDir: string): string {\n const rel = requireString(input, key);\n return isAbsolute(rel) ? rel : resolve(configDir, rel);\n}\n\nfunction optionalPath(\n input: Record<string, unknown>,\n key: string,\n configDir: string,\n): string | undefined {\n const rel = optionalString(input, key);\n if (rel === undefined) return undefined;\n return isAbsolute(rel) ? rel : resolve(configDir, rel);\n}\n\nfunction requireNumberArray(\n input: Record<string, unknown>,\n key: string,\n { min }: { min: number },\n): number[] {\n const v = input[key];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of numbers`, key);\n }\n if (v.length < min) {\n throw new ManifestError('invalid-config', `${key} must contain at least ${min} item(s)`, key);\n }\n return v.map((item, idx) => {\n if (typeof item !== 'number' || !Number.isInteger(item)) {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be an integer`, key);\n }\n return item;\n });\n}\n\nfunction optionalStringArray(\n input: Record<string, unknown>,\n key: string,\n { max }: { max?: number } = {},\n): string[] {\n const v = input[key];\n if (v === undefined || v === null) return [];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of strings`, key);\n }\n if (max !== undefined && v.length > max) {\n throw new ManifestError(\n 'invalid-config',\n `${key} accepts at most ${max} entries (got ${v.length})`,\n key,\n );\n }\n return v.map((item, idx) => {\n if (typeof item !== 'string') {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be a string`, key);\n }\n return item;\n });\n}\n\nfunction requirePathArray(\n input: Record<string, unknown>,\n key: string,\n configDir: string,\n { min }: { min: number },\n): string[] {\n const v = input[key];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of paths`, key);\n }\n if (v.length < min) {\n throw new ManifestError('invalid-config', `${key} must contain at least ${min} item(s)`, key);\n }\n return v.map((item, idx) => {\n if (typeof item !== 'string' || item.trim().length === 0) {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be a non-empty string`, key);\n }\n return isAbsolute(item) ? item : resolve(configDir, item);\n });\n}\n\nfunction optionalPathArray(\n input: Record<string, unknown>,\n key: string,\n configDir: string,\n): string[] {\n const v = input[key];\n if (v === undefined || v === null) return [];\n if (!Array.isArray(v)) {\n throw new ManifestError('invalid-config', `${key} must be an array of paths`, key);\n }\n return v.map((item, idx) => {\n if (typeof item !== 'string' || item.trim().length === 0) {\n throw new ManifestError('invalid-config', `${key}[${idx}] must be a non-empty string`, key);\n }\n return isAbsolute(item) ? item : resolve(configDir, item);\n });\n}\n\n// Regexes mirror the console's client-side validators (`is-email` CDgIL0c0\n// bundle + VALIDATION-RULES.md). Keeping local validators lets agent-\n// plugin surface `missing-required-field`/`invalid-config` instead of a\n// pass-through `api-error` when the user supplies garbage.\nconst EMAIL_REGEX =\n /^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/;\n\nfunction isValidEmail(v: string): boolean {\n return EMAIL_REGEX.test(v.toLowerCase());\n}\n\n// Dog-food #23 (2026-04-22) HTTP 400 errorCode=4000:\n// \"앱 영문 이름은 영어, 숫자, 공백, 콜론(:)만 사용 가능해요\".\nconst TITLE_EN_REGEX = /^[A-Za-z0-9 :]+$/;\n\n// Dog-food #23 (2026-04-22) HTTP 400 errorCode=4000:\n// \"앱 상세설명은 최대 500자를 넘어갈 수 없어요\". Counted by code points\n// (`[...str].length`) to err on the strict side — the server's internal\n// counting rule is not documented, so we use the count that treats\n// astral characters as single units.\nconst DETAIL_DESCRIPTION_MAX_CODEPOINTS = 500;\n\nfunction isValidHttpUrl(v: string): boolean {\n try {\n const parsed = new URL(v);\n return parsed.protocol === 'http:' || parsed.protocol === 'https:';\n } catch {\n return false;\n }\n}\n\nfunction validateManifest(raw: Record<string, unknown>, configDir: string): AppManifest {\n const titleKo = requireString(raw, 'titleKo');\n const titleEn = requireString(raw, 'titleEn');\n if (!TITLE_EN_REGEX.test(titleEn)) {\n throw new ManifestError(\n 'invalid-config',\n `titleEn may only contain English letters, digits, spaces, and colons (got \"${titleEn}\")`,\n 'titleEn',\n );\n }\n const appName = requireString(raw, 'appName');\n const csEmail = requireString(raw, 'csEmail');\n if (!isValidEmail(csEmail)) {\n throw new ManifestError(\n 'invalid-config',\n `csEmail is not a valid email address (got ${csEmail})`,\n 'csEmail',\n );\n }\n const subtitle = requireString(raw, 'subtitle');\n // subtitle ≤ 20 chars (F(20) in VALIDATION-RULES).\n if (subtitle.length > 20) {\n throw new ManifestError(\n 'invalid-config',\n `subtitle must be 20 characters or fewer (got ${subtitle.length})`,\n 'subtitle',\n );\n }\n const description = requireString(raw, 'description');\n const descriptionCodepoints = [...description].length;\n if (descriptionCodepoints > DETAIL_DESCRIPTION_MAX_CODEPOINTS) {\n throw new ManifestError(\n 'invalid-config',\n `description must be ${DETAIL_DESCRIPTION_MAX_CODEPOINTS} characters or fewer (got ${descriptionCodepoints})`,\n 'description',\n );\n }\n const homePageUri = optionalString(raw, 'homePageUri');\n if (homePageUri !== undefined && !isValidHttpUrl(homePageUri)) {\n throw new ManifestError(\n 'invalid-config',\n `homePageUri must be a http(s) URL (got ${homePageUri})`,\n 'homePageUri',\n );\n }\n const logo = requirePath(raw, 'logo', configDir);\n const logoDarkMode = optionalPath(raw, 'logoDarkMode', configDir);\n const horizontalThumbnail = requirePath(raw, 'horizontalThumbnail', configDir);\n const categoryIds = requireNumberArray(raw, 'categoryIds', { min: 1 });\n const keywords = optionalStringArray(raw, 'keywords', { max: 10 });\n const verticalScreenshots = requirePathArray(raw, 'verticalScreenshots', configDir, { min: 3 });\n const horizontalScreenshots = optionalPathArray(raw, 'horizontalScreenshots', configDir);\n\n return {\n titleKo,\n titleEn,\n appName,\n homePageUri,\n csEmail,\n logo,\n logoDarkMode,\n horizontalThumbnail,\n categoryIds,\n subtitle,\n description,\n keywords,\n verticalScreenshots,\n horizontalScreenshots,\n };\n}\n","import { readFile } from 'node:fs/promises';\nimport { imageSize } from 'image-size';\n\n// Image dimension validation lives here (rather than inline in the\n// command) so a future swap of the underlying library is confined. The\n// console upload endpoint validates validWidth/validHeight server-side\n// with a hard 400, but failing locally gives the agent-plugin consumer\n// a structured error (path + expected + actual) instead of a pass-\n// through api-error reason.\n//\n// image-size reads the PNG/JPEG/etc. header directly; we pass a Buffer\n// so we can distinguish \"file missing\" (ENOENT → unreadable) from\n// \"unknown format\" (library throws → unreadable) without two code paths.\n\nexport type ImageDimensionErrorReason = 'mismatch' | 'unreadable';\n\nexport class ImageDimensionError extends Error {\n readonly path: string;\n readonly expected: string;\n readonly actual: string | undefined;\n readonly reason: ImageDimensionErrorReason;\n\n constructor(args: {\n path: string;\n expected: string;\n actual: string | undefined;\n reason: ImageDimensionErrorReason;\n message: string;\n }) {\n super(args.message);\n this.name = 'ImageDimensionError';\n this.path = args.path;\n this.expected = args.expected;\n this.actual = args.actual;\n this.reason = args.reason;\n }\n}\n\nexport interface Dimension {\n readonly width: number;\n readonly height: number;\n}\n\nfunction format(dim: Dimension): string {\n return `${dim.width}x${dim.height}`;\n}\n\nexport async function validateImageDimensions(path: string, expected: Dimension): Promise<void> {\n let buffer: Buffer;\n try {\n buffer = await readFile(path);\n } catch (err) {\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual: undefined,\n reason: 'unreadable',\n message: `could not read image at ${path}: ${(err as Error).message}`,\n });\n }\n let dims: { width?: number; height?: number };\n try {\n dims = imageSize(buffer);\n } catch (err) {\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual: undefined,\n reason: 'unreadable',\n message: `could not decode image header at ${path}: ${(err as Error).message}`,\n });\n }\n if (typeof dims.width !== 'number' || typeof dims.height !== 'number') {\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual: undefined,\n reason: 'unreadable',\n message: `image header at ${path} did not expose width/height`,\n });\n }\n if (dims.width !== expected.width || dims.height !== expected.height) {\n const actual = `${dims.width}x${dims.height}`;\n throw new ImageDimensionError({\n path,\n expected: format(expected),\n actual,\n reason: 'mismatch',\n message: `image ${path} has dimensions ${actual}; expected ${format(expected)}`,\n });\n }\n}\n\n// Canonical dimension specs for the register flow. Centralising here keeps\n// the command and the payload builder agreeing on the same source of truth.\nexport const DIMENSIONS = {\n logo: { width: 600, height: 600 },\n horizontalThumbnail: { width: 1932, height: 828 },\n verticalScreenshot: { width: 636, height: 1048 },\n horizontalScreenshot: { width: 1504, height: 741 },\n} as const;\n","import type { MiniAppImageEntry, MiniAppSubmitPayload } from '../api/mini-apps.js';\nimport type { AppManifest } from '../config/app-manifest.js';\n\n// Pure transformation from a loaded AppManifest + the URLs produced by\n// the upload step into the `{miniApp, impression}` submit body. The\n// structure mirrors the `Xc` function from the console bundle (see\n// VALIDATION-RULES.md in the umbrella `.playwright-mcp/`). Dog-food\n// task #23 captures the first real network exchange and will either\n// confirm this shape or correct it here.\n\nexport interface UploadedImageUrls {\n readonly logo: string;\n readonly logoDarkMode: string | undefined;\n readonly horizontalThumbnail: string;\n readonly verticalScreenshots: readonly string[];\n readonly horizontalScreenshots: readonly string[];\n}\n\nexport function buildSubmitPayload(\n manifest: AppManifest,\n urls: UploadedImageUrls,\n): MiniAppSubmitPayload {\n const images: MiniAppImageEntry[] = [\n { imageUrl: urls.horizontalThumbnail, imageType: 'THUMBNAIL', orientation: 'HORIZONTAL' },\n ...urls.verticalScreenshots.map<MiniAppImageEntry>((u) => ({\n imageUrl: u,\n imageType: 'PREVIEW',\n orientation: 'VERTICAL',\n })),\n ...urls.horizontalScreenshots.map<MiniAppImageEntry>((u) => ({\n imageUrl: u,\n imageType: 'PREVIEW',\n orientation: 'HORIZONTAL',\n })),\n ];\n\n const miniApp: MiniAppSubmitPayload['miniApp'] = {\n title: manifest.titleKo,\n titleEn: manifest.titleEn,\n appName: manifest.appName,\n iconUri: urls.logo,\n status: 'PREPARE',\n csEmail: manifest.csEmail,\n description: manifest.subtitle,\n detailDescription: manifest.description,\n images,\n ...(urls.logoDarkMode !== undefined ? { darkModeIconUri: urls.logoDarkMode } : {}),\n ...(manifest.homePageUri !== undefined ? { homePageUri: manifest.homePageUri } : {}),\n };\n\n const impression: MiniAppSubmitPayload['impression'] = {\n keywordList: manifest.keywords,\n categoryIds: manifest.categoryIds,\n };\n\n return { miniApp, impression };\n}\n","import { readFile } from 'node:fs/promises';\nimport { basename } from 'node:path';\nimport {\n type CreateMiniAppResult,\n createMiniApp,\n type UploadParams,\n uploadMiniAppResource,\n} from '../api/mini-apps.js';\nimport type { CdpCookie } from '../cdp.js';\nimport {\n type AppManifest,\n loadAppManifest,\n ManifestError,\n resolveManifestPath,\n} from '../config/app-manifest.js';\nimport {\n DIMENSIONS,\n ImageDimensionError,\n validateImageDimensions,\n} from '../config/image-validator.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\nimport { buildSubmitPayload, type UploadedImageUrls } from './register-payload.js';\n\n// `runRegister` is the testable seam for `aitcc app register`. The public\n// command (defined in `app.ts`) is a thin citty wrapper that supplies the\n// real `uploadMiniAppResource` and `createMiniApp` implementations;\n// tests pass stubs so each branch of the documented `--json` contract\n// gets pinned byte-for-byte without spawning a subprocess.\n//\n// --json contract (consumed by agent-plugin):\n//\n// success:\n// { ok: true, workspaceId, appId, reviewState } exit 0\n//\n// failures:\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-config', message } exit 2\n// { ok: false, reason: 'missing-required-field', field, message } exit 2\n// { ok: false, reason: 'image-dimension-mismatch',\n// path, expected, actual, message } exit 2\n// { ok: false, reason: 'image-unreadable', path, message } exit 2\n// { ok: true, authenticated: false } exit 10\n// { ok: false, reason: 'network-error', message } exit 11\n// { ok: false, reason: 'api-error',\n// status?, errorCode?, message } exit 17\n//\n// --dry-run:\n// { ok: true, dryRun: true, workspaceId, payload } exit 0\n// (no uploads, no submit — manifest + image dimensions are still\n// validated so dry-run catches the same local errors as a real run.)\n//\n// --accept-terms required for real submits:\n// The console UI gates \"검토 요청하기\" on several mandatory legal-\n// agreement checkboxes (common + category-dependent — see\n// VALIDATION-RULES.md). We can't see those on the wire yet (payload\n// shape is inferred), so the CLI enforces the gate locally: submit\n// refuses without --accept-terms. The flag is not required for\n// --dry-run.\n// { ok: false, reason: 'terms-not-accepted', message } exit 2\n\nexport interface RegisterArgs {\n readonly workspace?: string | undefined;\n readonly config?: string | undefined;\n readonly json: boolean;\n readonly dryRun?: boolean | undefined;\n readonly acceptTerms?: boolean | undefined;\n}\n\nexport interface RegisterDeps {\n readonly cwd?: string;\n readonly uploadImpl?: (params: UploadParams) => Promise<string>;\n readonly submitImpl?: (\n workspaceId: number,\n payload: ReturnType<typeof buildSubmitPayload>,\n cookies: readonly CdpCookie[],\n ) => Promise<CreateMiniAppResult>;\n}\n\n// `runRegister` returns `Promise<void>` as a type-level handshake — at\n// runtime every code path either awaits `exitAfterFlush` (which calls\n// `process.exit` and never returns) or bubbles a thrown exception. A\n// future maintainer should not try to `catch` the absence of a return\n// value as \"success\"; the success signal is `process.exit(0)` itself.\n//\n// `deps` defaults to `{}` so the citty wrapper (`app.ts`) doesn't need\n// to pass a literal every call; tests override specific fields.\nexport async function runRegister(args: RegisterArgs, deps: RegisterDeps = {}): Promise<void> {\n const ctx = await resolveWorkspaceContext({\n json: args.json,\n ...(args.workspace !== undefined ? { workspace: args.workspace } : {}),\n });\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n const manifest = await loadAndValidateManifest(args, deps);\n if (!manifest) return;\n\n // --accept-terms gate: required for real submits only. --dry-run\n // skips it so users can iterate on their manifest without being\n // forced to attest to the legal-agreement checkboxes each time.\n if (!args.dryRun && !args.acceptTerms) {\n emitTermsNotAccepted(args.json);\n await exitAfterFlush(ExitCode.Usage);\n return;\n }\n\n try {\n if (args.dryRun) {\n // Emit the payload with placeholder URLs so the user can\n // inspect exactly what would be sent without spending a round\n // of uploads. Useful during dog-food verification when the\n // inferred payload shape is in flight.\n const placeholderUrls: UploadedImageUrls = {\n logo: '<dry-run:logo>',\n logoDarkMode: manifest.logoDarkMode !== undefined ? '<dry-run:logoDarkMode>' : undefined,\n horizontalThumbnail: '<dry-run:horizontalThumbnail>',\n verticalScreenshots: manifest.verticalScreenshots.map(\n (_, i) => `<dry-run:verticalScreenshots[${i}]>`,\n ),\n horizontalScreenshots: manifest.horizontalScreenshots.map(\n (_, i) => `<dry-run:horizontalScreenshots[${i}]>`,\n ),\n };\n const payload = buildSubmitPayload(manifest, placeholderUrls);\n emitDryRun(args.json, workspaceId, payload);\n return exitAfterFlush(ExitCode.Ok);\n }\n\n const urls = await uploadAllImages(workspaceId, manifest, session.cookies, deps);\n const payload = buildSubmitPayload(manifest, urls);\n const submitImpl = deps.submitImpl ?? ((wid, p, c) => createMiniApp(wid, p, c));\n const result = await submitImpl(workspaceId, payload, session.cookies);\n emitSuccess(args.json, workspaceId, result);\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureAndExit(args.json, err);\n }\n}\n\nasync function loadAndValidateManifest(\n args: RegisterArgs,\n deps: RegisterDeps,\n): Promise<AppManifest | null> {\n const cwd = deps.cwd ?? process.cwd();\n let manifest: AppManifest;\n try {\n const manifestPath = await resolveManifestPath(args.config, cwd);\n manifest = await loadAppManifest(manifestPath);\n } catch (err) {\n if (err instanceof ManifestError) {\n emitManifestError(args.json, err);\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n throw err;\n }\n\n // Image dimension checks run after manifest validation because they\n // need paths that the manifest already resolved to absolute.\n try {\n await validateImageDimensions(manifest.logo, DIMENSIONS.logo);\n if (manifest.logoDarkMode !== undefined) {\n await validateImageDimensions(manifest.logoDarkMode, DIMENSIONS.logo);\n }\n await validateImageDimensions(manifest.horizontalThumbnail, DIMENSIONS.horizontalThumbnail);\n for (const p of manifest.verticalScreenshots) {\n await validateImageDimensions(p, DIMENSIONS.verticalScreenshot);\n }\n for (const p of manifest.horizontalScreenshots) {\n await validateImageDimensions(p, DIMENSIONS.horizontalScreenshot);\n }\n } catch (err) {\n if (err instanceof ImageDimensionError) {\n emitImageDimensionError(args.json, err);\n await exitAfterFlush(ExitCode.Usage);\n return null;\n }\n throw err;\n }\n\n return manifest;\n}\n\nasync function uploadAllImages(\n workspaceId: number,\n manifest: AppManifest,\n cookies: readonly CdpCookie[],\n deps: RegisterDeps,\n): Promise<UploadedImageUrls> {\n // Serial on purpose: dog-food task #23 has not yet confirmed that the\n // console's `/resource/:wid/upload` endpoint tolerates concurrent\n // POSTs from the same session. `Promise.all` would shave a few seconds\n // off a 5-image upload, but until we've observed the server under\n // parallel load, the failure mode (\"429? 503? silent drop?\") is\n // unknown and a first-registration flake is much more expensive to\n // debug than a slower linear run.\n const uploadImpl = deps.uploadImpl ?? ((p) => uploadMiniAppResource(p));\n\n const logo = await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.logo.width,\n validHeight: DIMENSIONS.logo.height,\n cookies,\n path: manifest.logo,\n });\n const logoDarkMode =\n manifest.logoDarkMode !== undefined\n ? await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.logo.width,\n validHeight: DIMENSIONS.logo.height,\n cookies,\n path: manifest.logoDarkMode,\n })\n : undefined;\n const horizontalThumbnail = await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.horizontalThumbnail.width,\n validHeight: DIMENSIONS.horizontalThumbnail.height,\n cookies,\n path: manifest.horizontalThumbnail,\n });\n const verticalScreenshots: string[] = [];\n for (const p of manifest.verticalScreenshots) {\n verticalScreenshots.push(\n await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.verticalScreenshot.width,\n validHeight: DIMENSIONS.verticalScreenshot.height,\n cookies,\n path: p,\n }),\n );\n }\n const horizontalScreenshots: string[] = [];\n for (const p of manifest.horizontalScreenshots) {\n horizontalScreenshots.push(\n await uploadOne(uploadImpl, {\n workspaceId,\n validWidth: DIMENSIONS.horizontalScreenshot.width,\n validHeight: DIMENSIONS.horizontalScreenshot.height,\n cookies,\n path: p,\n }),\n );\n }\n return { logo, logoDarkMode, horizontalThumbnail, verticalScreenshots, horizontalScreenshots };\n}\n\nasync function uploadOne(\n uploadImpl: (params: UploadParams) => Promise<string>,\n input: {\n workspaceId: number;\n validWidth: number;\n validHeight: number;\n cookies: readonly CdpCookie[];\n path: string;\n },\n): Promise<string> {\n const buffer = await readFile(input.path);\n return uploadImpl({\n workspaceId: input.workspaceId,\n validWidth: input.validWidth,\n validHeight: input.validHeight,\n cookies: input.cookies,\n file: {\n buffer,\n fileName: basename(input.path),\n contentType: 'image/png',\n },\n });\n}\n\nfunction emitManifestError(json: boolean, err: ManifestError): void {\n if (json) {\n if (err.kind === 'missing-required-field') {\n emitJson({\n ok: false,\n reason: 'missing-required-field',\n field: err.field ?? null,\n message: err.message,\n });\n } else {\n emitJson({ ok: false, reason: 'invalid-config', message: err.message });\n }\n } else {\n process.stderr.write(`${err.message}\\n`);\n }\n}\n\nfunction emitImageDimensionError(json: boolean, err: ImageDimensionError): void {\n if (json) {\n if (err.reason === 'mismatch') {\n emitJson({\n ok: false,\n reason: 'image-dimension-mismatch',\n path: err.path,\n expected: err.expected,\n actual: err.actual ?? null,\n message: err.message,\n });\n } else {\n // 'unreadable' covers missing files, permission errors, and\n // decode failures — distinct from a genuine dimension mismatch\n // so agent-plugin can branch (e.g., \"path typo\" vs \"resize me\").\n emitJson({\n ok: false,\n reason: 'image-unreadable',\n path: err.path,\n message: err.message,\n });\n }\n } else {\n process.stderr.write(`${err.message}\\n`);\n }\n}\n\nfunction emitTermsNotAccepted(json: boolean): void {\n const message =\n 'The console requires several legal-agreement checkboxes before submitting a mini-app for review. ' +\n 'Re-run with --accept-terms to attest that you have read and agree to each of them ' +\n '(see VALIDATION-RULES.md or the console UI), or use --dry-run to preview the payload without submitting.';\n if (json) {\n emitJson({ ok: false, reason: 'terms-not-accepted', message });\n } else {\n process.stderr.write(`${message}\\n`);\n }\n}\n\nfunction emitDryRun(\n json: boolean,\n workspaceId: number,\n payload: ReturnType<typeof buildSubmitPayload>,\n): void {\n if (json) {\n emitJson({ ok: true, dryRun: true, workspaceId, payload });\n } else {\n process.stdout.write('[dry-run] Would POST to ');\n process.stdout.write(\n `https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole/workspaces/${workspaceId}/mini-app/review\\n`,\n );\n process.stdout.write(`${JSON.stringify(payload, null, 2)}\\n`);\n }\n}\n\nfunction emitSuccess(json: boolean, workspaceId: number, result: CreateMiniAppResult): void {\n if (json) {\n emitJson({\n ok: true,\n workspaceId,\n appId: result.miniAppId ?? null,\n reviewState: result.reviewState ?? null,\n });\n } else {\n process.stdout.write(\n `Registered mini-app ${result.miniAppId ?? '(id unknown)'} in workspace ${workspaceId}` +\n ` (reviewState=${result.reviewState ?? 'unknown'}).\\n`,\n );\n }\n}\n\n// Thin wrapper kept for local readability — the real dispatch lives in\n// `_shared.ts::emitFailureFromError` and is shared with app/keys/\n// members/workspace so every command's auth/network/api-error JSON\n// shape agrees.\nasync function emitFailureAndExit(json: boolean, err: unknown): Promise<void> {\n return emitFailureFromError(json, err);\n}\n","import { defineCommand } from 'citty';\nimport { fetchMiniApps, fetchMiniAppWithDraft, fetchReviewStatus } from '../api/mini-apps.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\nimport { runRegister } from './register.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// app ls [--workspace <id>]:\n// { ok: true, workspaceId, hasPolicyViolation, apps: [{id, name, reviewState?, extra}] } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// Auth/network/api failures follow the shared contract from workspace/whoami\n// (ok: true authenticated: false exit 10, network-error exit 11, api-error exit 17).\n\n// Best-effort match of review-status entries against mini-app summaries.\n// The list endpoint and the review-status endpoint key off the same id,\n// but we don't assume the field name is uniform — we compare by `.id` on\n// each record, falling back to `miniAppId` / `appId` (same order as the\n// list normaliser). Exported so the join semantics are unit-testable.\n// Returns `null` if no plausible match; callers render that as \"no review\n// status\" in the output rather than a failure.\nexport function findReviewEntry(\n reviewEntries: readonly Readonly<Record<string, unknown>>[],\n appId: string | number,\n): Readonly<Record<string, unknown>> | null {\n const target = String(appId);\n for (const entry of reviewEntries) {\n const candidate = entry.id ?? entry.miniAppId ?? entry.appId;\n if (candidate !== undefined && String(candidate) === target) return entry;\n }\n return null;\n}\n\nexport function reviewStateFor(\n entry: Readonly<Record<string, unknown>> | null,\n): string | undefined {\n if (!entry) return undefined;\n const raw = entry.reviewState ?? entry.status;\n return typeof raw === 'string' ? raw : undefined;\n}\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List mini-apps in the selected workspace.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n try {\n // List + review-status are independent read endpoints. Fire in parallel\n // so a slow endpoint doesn't serialise the wait. Review-status failures\n // currently propagate (rather than being downgraded to \"unknown\n // review\") because they almost always indicate a shared auth/network\n // problem — if that ever stops being true we can degrade gracefully.\n const [apps, review] = await Promise.all([\n fetchMiniApps(workspaceId, session.cookies),\n fetchReviewStatus(workspaceId, session.cookies),\n ]);\n\n if (args.json) {\n const joined = apps.map((app) => {\n const entry = findReviewEntry(review.miniApps, app.id);\n const reviewState = reviewStateFor(entry);\n return {\n id: app.id,\n name: app.name ?? null,\n ...(reviewState !== undefined ? { reviewState } : {}),\n extra: app.extra,\n };\n });\n emitJson({\n ok: true,\n workspaceId,\n hasPolicyViolation: review.hasPolicyViolation,\n apps: joined,\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n\n if (apps.length === 0) {\n process.stdout.write(`No apps in workspace ${workspaceId}.\\n`);\n if (review.hasPolicyViolation) {\n process.stderr.write('Note: workspace-wide policy violation flag is set.\\n');\n }\n return exitAfterFlush(ExitCode.Ok);\n }\n for (const app of apps) {\n const entry = findReviewEntry(review.miniApps, app.id);\n const reviewState = reviewStateFor(entry) ?? '-';\n // Defensive: the upstream mini-app payload shape is not yet fully\n // observed (no registered apps in our workspaces). Tighten this\n // once sdk-example is registered and `name` is confirmed required.\n const name = app.name ?? '(unnamed)';\n process.stdout.write(`${app.id}\\t${name}\\t${reviewState}\\n`);\n }\n if (review.hasPolicyViolation) {\n process.stderr.write('Note: workspace-wide policy violation flag is set.\\n');\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\n// --json contract (consumed by agent-plugin):\n//\n// app show <id> [--workspace <id>] [--view draft|current|merged]:\n// { ok: true, workspaceId, appId, view, miniApp: {...} } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n// { ok: false, reason: 'app-not-found', appId } exit 2\n//\n// `view` picks which part of the with-draft envelope to surface:\n// - `draft` (default) — the editor's latest state, populated as soon as\n// the app is created. This is what `app register` just wrote; it's the\n// only reliable view until the app is approved and published.\n// - `current` — the published/reviewed record end users see. Empty until\n// the app's first approval, so defaulting here would hide almost every\n// field we care about — hence the default is `draft`.\n// - `merged` — current with draft overlaid on top (draft wins per field).\n// Useful once both exist and the user wants the \"authoritative\" snapshot.\n//\n// The `--view` flag intentionally never falls back on its own. If the\n// caller asks for `current` on an unreviewed app they get `miniApp: null`\n// with `view: 'current'` so agent-plugin can tell the two cases apart.\nexport function pickMiniAppView(\n envelope: { current: Record<string, unknown> | null; draft: Record<string, unknown> | null },\n view: 'draft' | 'current' | 'merged',\n): Record<string, unknown> | null {\n const extract = (side: Record<string, unknown> | null): Record<string, unknown> | null => {\n if (side === null) return null;\n const ma = side.miniApp;\n if (ma !== null && typeof ma === 'object' && !Array.isArray(ma)) {\n return ma as Record<string, unknown>;\n }\n return null;\n };\n const draft = extract(envelope.draft);\n const current = extract(envelope.current);\n if (view === 'draft') return draft;\n if (view === 'current') return current;\n if (current !== null && draft !== null) return { ...current, ...draft };\n return draft ?? current;\n}\n\nfunction parseAppId(raw: string | undefined): number | null {\n if (raw === undefined || raw === '') return null;\n const n = Number(raw);\n if (!Number.isFinite(n) || !Number.isInteger(n) || n <= 0) return null;\n return n;\n}\n\nconst showCommand = defineCommand({\n meta: {\n name: 'show',\n description:\n 'Show full details of a mini-app, including fields only visible in the draft view.',\n },\n args: {\n id: {\n type: 'positional',\n description: 'Mini-app ID (the numeric `appId` from `app ls` or `app register`).',\n required: true,\n },\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace.',\n },\n view: {\n type: 'string',\n description: 'Which view to render: `draft` (default), `current`, or `merged`.',\n default: 'draft',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const appId = parseAppId(args.id);\n if (appId === null) {\n if (args.json) {\n emitJson({\n ok: false,\n reason: 'invalid-id',\n message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`,\n });\n } else {\n process.stderr.write(`app show: invalid id ${JSON.stringify(args.id)}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n const view = args.view;\n if (view !== 'draft' && view !== 'current' && view !== 'merged') {\n if (args.json) {\n emitJson({\n ok: false,\n reason: 'invalid-config',\n field: 'view',\n message: `--view must be one of draft|current|merged (got ${JSON.stringify(view)})`,\n });\n } else {\n process.stderr.write(`app show: invalid --view ${JSON.stringify(view)}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n try {\n const envelope = await fetchMiniAppWithDraft(workspaceId, appId, session.cookies);\n const miniApp = pickMiniAppView(envelope, view);\n\n if (args.json) {\n emitJson({\n ok: true,\n workspaceId,\n appId,\n view,\n miniApp,\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n\n if (miniApp === null) {\n if (view === 'current' && envelope.draft !== null) {\n process.stdout.write(\n `App ${appId} has no \\`current\\` view yet (not reviewed). Try --view draft.\\n`,\n );\n } else {\n process.stdout.write(`App ${appId} has no data for view=${view}.\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n }\n\n const pick = (k: string): string => {\n const v = miniApp[k];\n return v === null || v === undefined ? '-' : String(v);\n };\n const images = Array.isArray(miniApp.images) ? miniApp.images : [];\n const impression =\n miniApp.impression !== null && typeof miniApp.impression === 'object'\n ? (miniApp.impression as Record<string, unknown>)\n : {};\n const keywords = Array.isArray(impression.keywordList) ? impression.keywordList : [];\n const categoryPaths = Array.isArray(impression.categoryPaths) ? impression.categoryPaths : [];\n\n process.stdout.write(`# App ${appId} (view=${view})\\n\\n`);\n process.stdout.write(`Name (ko) ${pick('title')}\\n`);\n process.stdout.write(`Name (en) ${pick('titleEn')}\\n`);\n process.stdout.write(`App slug ${pick('appName')}\\n`);\n process.stdout.write(`Status ${pick('status')}\\n`);\n process.stdout.write(`Home page ${pick('homePageUri')}\\n`);\n process.stdout.write(`CS email ${pick('csEmail')}\\n`);\n process.stdout.write(`Logo ${pick('iconUri')}\\n`);\n process.stdout.write(`Subtitle ${pick('description')}\\n`);\n const detail =\n typeof miniApp.detailDescription === 'string'\n ? `${[...miniApp.detailDescription].length} chars`\n : '-';\n process.stdout.write(`Detail desc ${detail}\\n`);\n process.stdout.write(`Images ${images.length}\\n`);\n process.stdout.write(`Keywords ${keywords.length} (${keywords.join(', ')})\\n`);\n const firstPath = categoryPaths[0];\n if (firstPath && typeof firstPath === 'object') {\n const fp = firstPath as Record<string, unknown>;\n const parts: string[] = [];\n for (const key of ['group', 'category', 'subCategory']) {\n const node = fp[key];\n if (node !== null && typeof node === 'object') {\n const nm = (node as Record<string, unknown>).name;\n if (typeof nm === 'string') parts.push(nm);\n }\n }\n process.stdout.write(`Category ${parts.join(' > ') || '-'}\\n`);\n } else {\n process.stdout.write(`Category -\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\n// Derived review state. The console UI's \"검토 중\" banner is not a single\n// API field — it's composed from the /with-draft envelope. We surface the\n// derivation so `aitcc app status` is the one place the rule lives.\n//\n// Observed combinations (2026-04-23 on workspace 3095, apps 29349/29356/\n// 29397/29405 all under review):\n//\n// approvalType=REVIEW current=null rejectedMessage=null → under-review\n// approvalType=REVIEW current=null rejectedMessage=STR → rejected\n// approvalType=REVIEW current=ROW (draft is edits-in-flight) → approved (with pending edits)\n// approvalType=REVIEW current=ROW draft=null or equal → approved\n// approvalType=null → not-submitted (edit draft only)\n//\n// Unknown combinations fall through to `unknown` so callers can log and\n// we can extend the ladder as new signals come in.\nexport type ReviewState =\n | 'not-submitted'\n | 'under-review'\n | 'rejected'\n | 'approved'\n | 'approved-with-edits'\n | 'unknown';\n\nexport interface DerivedStatus {\n readonly state: ReviewState;\n readonly approvalType: string | null;\n readonly rejectedMessage: string | null;\n readonly hasCurrent: boolean;\n readonly hasDraft: boolean;\n}\n\nexport function deriveReviewState(env: {\n current: Record<string, unknown> | null;\n draft: Record<string, unknown> | null;\n approvalType: string | null;\n rejectedMessage: string | null;\n}): DerivedStatus {\n const hasCurrent = env.current !== null;\n const hasDraft = env.draft !== null;\n const approvalType = env.approvalType;\n const rejectedMessage = env.rejectedMessage;\n\n let state: ReviewState;\n if (approvalType === null) {\n state = 'not-submitted';\n } else if (rejectedMessage !== null) {\n state = 'rejected';\n } else if (!hasCurrent) {\n state = 'under-review';\n } else if (hasDraft) {\n state = 'approved-with-edits';\n } else {\n state = 'approved';\n }\n // approvalType values other than REVIEW (we haven't observed any yet)\n // or unexpected combinations get flagged as unknown rather than misreported.\n if (approvalType !== null && approvalType !== 'REVIEW' && state === 'under-review') {\n state = 'unknown';\n }\n return { state, approvalType, rejectedMessage, hasCurrent, hasDraft };\n}\n\nconst POLL_MIN_INTERVAL_SEC = 30;\nconst POLL_MAX_INTERVAL_SEC = 3600;\n\nconst statusCommand = defineCommand({\n meta: {\n name: 'status',\n description:\n 'Show the derived review state of a mini-app (under-review / rejected / approved).',\n },\n args: {\n id: {\n type: 'positional',\n description: 'Mini-app ID.',\n required: true,\n },\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace.',\n },\n watch: {\n type: 'boolean',\n description:\n 'Poll until the review state flips off `under-review` (rejected or approved). ' +\n 'Combine with `--interval <seconds>`.',\n default: false,\n },\n interval: {\n type: 'string',\n description: 'Polling interval in seconds when --watch is set. Clamped to [30, 3600].',\n default: '60',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON.', default: false },\n },\n async run({ args }) {\n const appId = parseAppId(args.id);\n if (appId === null) {\n if (args.json) {\n emitJson({\n ok: false,\n reason: 'invalid-id',\n message: `app id must be a positive integer (got ${JSON.stringify(args.id)})`,\n });\n } else {\n process.stderr.write(`app status: invalid id ${JSON.stringify(args.id)}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n const intervalRaw = Number(args.interval);\n if (!Number.isFinite(intervalRaw) || intervalRaw <= 0) {\n if (args.json) {\n emitJson({\n ok: false,\n reason: 'invalid-config',\n field: 'interval',\n message: `--interval must be a positive number (got ${JSON.stringify(args.interval)})`,\n });\n } else {\n process.stderr.write(`app status: invalid --interval ${JSON.stringify(args.interval)}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n const intervalSec = Math.max(\n POLL_MIN_INTERVAL_SEC,\n Math.min(POLL_MAX_INTERVAL_SEC, Math.floor(intervalRaw)),\n );\n\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n const emit = (status: DerivedStatus) => {\n if (args.json) {\n emitJson({ ok: true, workspaceId, appId, ...status });\n } else {\n process.stdout.write(\n `App ${appId} (ws ${workspaceId}): ${status.state}` +\n (status.rejectedMessage ? `\\n reason: ${status.rejectedMessage}` : '') +\n '\\n',\n );\n }\n };\n\n try {\n const once = async (): Promise<DerivedStatus> => {\n const env = await fetchMiniAppWithDraft(workspaceId, appId, session.cookies);\n return deriveReviewState(env);\n };\n\n if (!args.watch) {\n emit(await once());\n return exitAfterFlush(ExitCode.Ok);\n }\n\n // --watch: poll with clear line-per-tick JSON emission. Each JSON line\n // is a self-contained object, NDJSON-style, so agents/shells can pipe\n // it into `jq -c` without waiting for a terminal. Stop when the state\n // is no longer `under-review` (reviewed) or when the process is\n // interrupted — we don't synthesise a \"watch-ended\" record.\n // Human mode prints a one-line update only when the state changes.\n let lastState: ReviewState | null = null;\n while (true) {\n const status = await once();\n if (args.json) {\n emit(status);\n } else if (status.state !== lastState) {\n emit(status);\n }\n lastState = status.state;\n if (status.state !== 'under-review') {\n return exitAfterFlush(ExitCode.Ok);\n }\n await new Promise((resolve) => setTimeout(resolve, intervalSec * 1000));\n }\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nconst registerCommand = defineCommand({\n meta: {\n name: 'register',\n description:\n 'Register a mini-app in the selected workspace from a YAML/JSON manifest. ' +\n 'Uploads logo/thumbnail/screenshots, then submits the create payload.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n config: {\n type: 'string',\n description:\n 'Path to the app manifest. Defaults to `./aitcc.app.yaml`, then `./aitcc.app.json`.',\n },\n 'dry-run': {\n type: 'boolean',\n description: 'Validate manifest + images and print the inferred submit payload; no uploads.',\n default: false,\n },\n 'accept-terms': {\n type: 'boolean',\n description:\n 'Attest to the required console legal-agreement checkboxes (see VALIDATION-RULES.md). Required for real submits.',\n default: false,\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n await runRegister({\n json: args.json,\n dryRun: args['dry-run'],\n acceptTerms: args['accept-terms'],\n ...(args.workspace !== undefined ? { workspace: args.workspace } : {}),\n ...(args.config !== undefined ? { config: args.config } : {}),\n });\n },\n});\n\nexport const appCommand = defineCommand({\n meta: {\n name: 'app',\n description: 'Inspect mini-apps in a workspace.',\n },\n subCommands: {\n ls: lsCommand,\n show: showCommand,\n status: statusCommand,\n register: registerCommand,\n },\n});\n","import type { CdpCookie } from '../cdp.js';\nimport { type FetchLike, requestConsoleApi } from './http.js';\n\n// GET /workspaces/:id/api-keys — returns an array of console API keys used\n// for deploy automation. Our confirmed workspaces have zero keys (the UI\n// shows a \"발급받기\" CTA when the list is empty), so the entry shape is\n// unconfirmed. We normalise `id`/`name` across a few plausible spellings\n// and stash everything else under `extra`, matching the mini-app pattern.\n//\n// `keys create` is a deliberate follow-up — once an issued key lands we can\n// tighten this client against the real shape. See TODO.md's Medium list.\n\nconst BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport interface ApiKeySummary {\n readonly id: string | number;\n readonly name: string | undefined;\n readonly extra: Readonly<Record<string, unknown>>;\n}\n\nexport async function fetchApiKeys(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<ApiKeySummary[]> {\n const url = `${BASE}/workspaces/${workspaceId}/api-keys`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (!Array.isArray(raw)) {\n throw new Error(`Unexpected api-keys shape for workspace=${workspaceId}: not an array`);\n }\n return raw.map((entry, index) => normalizeKey(entry, workspaceId, index));\n}\n\nfunction normalizeKey(raw: unknown, workspaceId: number, index: number): ApiKeySummary {\n if (raw === null || typeof raw !== 'object') {\n throw new Error(\n `Unexpected api-key entry at index ${index} for workspace=${workspaceId}: not an object`,\n );\n }\n const rec = raw as Record<string, unknown>;\n const rawId = rec.id ?? rec.apiKeyId ?? rec.keyId;\n if (typeof rawId !== 'string' && typeof rawId !== 'number') {\n throw new Error(\n `Unexpected api-key entry at index ${index} for workspace=${workspaceId}: missing id`,\n );\n }\n const rawName = rec.name ?? rec.apiKeyName ?? rec.keyName ?? rec.description;\n const name = typeof rawName === 'string' ? rawName : undefined;\n const {\n id: _id,\n apiKeyId: _aid,\n keyId: _kid,\n name: _n,\n apiKeyName: _an,\n keyName: _kn,\n description: _d,\n ...extra\n } = rec;\n return { id: rawId, name, extra };\n}\n","import { defineCommand } from 'citty';\nimport { fetchApiKeys } from '../api/api-keys.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// keys ls [--workspace <id>]:\n// { ok: true, workspaceId, keys: [{id, name, extra}], needsKey? } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// `needsKey: true` is emitted when the key list is empty. The flag is\n// there so `/ait deploy` (and similar agent-plugin skills) can bail\n// with a friendly \"issue a key first\" message instead of attempting a\n// deploy that will 401 server-side. We keep the UI-specific Korean\n// wording out of JSON (it lives on stderr plain output only).\n//\n// Auth/network/api failures follow the shared contract (exit 10/11/17).\n//\n// \"Console API key\" in upstream terminology — used to authenticate\n// automated deploys. We only list here; `keys create` is a follow-up\n// (the management UI 404s until an initial key is issued, so we don't\n// know the creation/rotation endpoint yet).\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List console API keys in the selected workspace.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n try {\n const keys = await fetchApiKeys(workspaceId, session.cookies);\n if (args.json) {\n emitJson({\n ok: true,\n workspaceId,\n keys: keys.map((k) => ({ id: k.id, name: k.name ?? null, extra: k.extra })),\n ...(keys.length === 0 ? { needsKey: true } : {}),\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n if (keys.length === 0) {\n process.stdout.write(`No API keys in workspace ${workspaceId}.\\n`);\n process.stderr.write(\n 'Hint: issue a key from the console UI (API 키 → 발급받기) to enable deploy automation.\\n',\n );\n return exitAfterFlush(ExitCode.Ok);\n }\n process.stdout.write(`${keys.length} API key(s) in workspace ${workspaceId}:\\n`);\n for (const k of keys) {\n const name = k.name ?? '(unnamed)';\n process.stdout.write(`${k.id}\\t${name}\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nexport const keysCommand = defineCommand({\n meta: {\n name: 'keys',\n description: 'Inspect console API keys used for deploy automation.',\n },\n subCommands: {\n ls: lsCommand,\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","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: 2,\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","import type { CdpCookie } from '../cdp.js';\nimport { type FetchLike, requestConsoleApi } from './http.js';\n\n// GET /workspaces/:id/members — confirmed shape (as of 2026-04):\n// [{ workspaceId, bizUserNo, name, email, status, role,\n// isOwnerDelegationRequested, isAdult }]\n// `bizUserNo` is the stable per-person identifier across workspaces —\n// future `members remove` / role-change commands will key off it.\n\nconst BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport interface WorkspaceMember {\n readonly workspaceId: number;\n readonly bizUserNo: number;\n readonly name: string;\n readonly email: string;\n readonly status: string;\n readonly role: string;\n readonly isOwnerDelegationRequested: boolean;\n readonly isAdult: boolean;\n}\n\nexport async function fetchWorkspaceMembers(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<WorkspaceMember[]> {\n const url = `${BASE}/workspaces/${workspaceId}/members`;\n const raw = await requestConsoleApi<unknown>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n if (!Array.isArray(raw)) {\n throw new Error(`Unexpected members shape for workspace=${workspaceId}: not an array`);\n }\n return raw.map((entry, index) => normalizeMember(entry, workspaceId, index));\n}\n\nfunction normalizeMember(raw: unknown, workspaceId: number, index: number): WorkspaceMember {\n if (raw === null || typeof raw !== 'object') {\n throw new Error(\n `Unexpected member entry at index ${index} for workspace=${workspaceId}: not an object`,\n );\n }\n const rec = raw as Record<string, unknown>;\n const stringField = (k: string): string => {\n const v = rec[k];\n if (typeof v !== 'string') {\n throw new Error(\n `Unexpected member entry at index ${index} for workspace=${workspaceId}: missing ${k}`,\n );\n }\n return v;\n };\n const numField = (k: string): number => {\n const v = rec[k];\n if (typeof v !== 'number' || !Number.isFinite(v)) {\n throw new Error(\n `Unexpected member entry at index ${index} for workspace=${workspaceId}: missing ${k}`,\n );\n }\n return v;\n };\n return {\n workspaceId: numField('workspaceId'),\n bizUserNo: numField('bizUserNo'),\n name: stringField('name'),\n email: stringField('email'),\n status: stringField('status'),\n role: stringField('role'),\n isOwnerDelegationRequested: Boolean(rec.isOwnerDelegationRequested),\n isAdult: Boolean(rec.isAdult),\n };\n}\n","import { defineCommand } from 'citty';\nimport { fetchWorkspaceMembers } from '../api/members.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { emitFailureFromError, emitJson, resolveWorkspaceContext } from './_shared.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// members ls [--workspace <id>]:\n// { ok: true, workspaceId, members: [{bizUserNo, name, email, status, role, ...}] } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// Auth/network/api failures follow the shared contract (exit 10/11/17).\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List members of the selected workspace.',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID. Defaults to the selected workspace (`aitcc workspace use`).',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const ctx = await resolveWorkspaceContext(args);\n if (!ctx) return;\n const { session, workspaceId } = ctx;\n\n try {\n const members = await fetchWorkspaceMembers(workspaceId, session.cookies);\n if (args.json) {\n // `workspaceId` is omitted per-member (redundant with top level)\n // and `isAdult` is intentionally dropped — it is a Korean-specific\n // age-verification flag (성인 인증) classed as PII under local\n // compliance. Owners see *all* co-members, not just themselves, so\n // default-emitting it would leak every member's adult-verification\n // bit through `--json`. No CLI automation use case justifies\n // exposing it; if one ever arises, an opt-in flag is safer.\n emitJson({\n ok: true,\n workspaceId,\n members: members.map((m) => ({\n bizUserNo: m.bizUserNo,\n name: m.name,\n email: m.email,\n status: m.status,\n role: m.role,\n isOwnerDelegationRequested: m.isOwnerDelegationRequested,\n })),\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n if (members.length === 0) {\n process.stdout.write(`No members in workspace ${workspaceId}.\\n`);\n return exitAfterFlush(ExitCode.Ok);\n }\n for (const m of members) {\n process.stdout.write(`${m.bizUserNo}\\t${m.name}\\t${m.email}\\t${m.role}\\t${m.status}\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nexport const membersCommand = defineCommand({\n meta: {\n name: 'members',\n description: 'Inspect workspace members.',\n },\n subCommands: {\n ls: lsCommand,\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\nexport type ConditionalReleaseResult =\n | { readonly status: 'not-modified'; readonly etag: string | undefined }\n | { readonly status: 'updated'; readonly release: Release; readonly etag: string | undefined };\n\n/**\n * Conditional GET against `releases/latest`. If the server returns 304 we\n * learn \"no change\" without consuming a core rate-limit slot. Intended for\n * the background update check, which re-runs often; `fetchLatestRelease()`\n * remains the right call when the upgrade command actually needs the body.\n */\nexport async function fetchLatestReleaseConditional(\n previousEtag: string | undefined,\n): Promise<ConditionalReleaseResult> {\n const url = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}/releases/latest`;\n const headers = defaultHeaders() as Record<string, string>;\n if (previousEtag && previousEtag.length > 0) {\n headers['If-None-Match'] = previousEtag;\n }\n const res = await fetch(url, { headers });\n const etag = res.headers.get('etag') ?? undefined;\n if (res.status === 304) {\n return { status: 'not-modified', etag };\n }\n if (!res.ok) {\n throw new Error(`GitHub releases/latest returned ${res.status} ${res.statusText}`);\n }\n const release = (await res.json()) as Release;\n return { status: 'updated', release, etag };\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 { mkdir, readFile, rename, unlink, writeFile } from 'node:fs/promises';\nimport { dirname } from 'node:path';\nimport { fetchLatestReleaseConditional, versionFromTag } from './github.js';\nimport { upgradeCheckPath } from './paths.js';\nimport { compareSemver } from './semver.js';\nimport { VERSION } from './version.js';\n\n// Background \"is there a newer aitcc?\" probe. Rate-limit friendly by design:\n//\n// * At most one network call every 24 hours, regardless of how often the\n// user runs a command that opts in.\n// * Even a failed probe updates the cache timestamp, so a broken network\n// (or a 403 from GitHub) does not loop us back within minutes.\n// * Cache write is stamped BEFORE the network call and promoted atomically\n// via tempfile+rename, so two concurrent `aitcc whoami` invocations can't\n// both escape the throttle and an `exitAfterFlush` mid-write can't leave\n// a truncated JSON file.\n// * Uses a conditional GET with the previous ETag — a 304 response does\n// not consume the anonymous 60/hr core rate-limit bucket.\n// * Fully opt-out via AITCC_NO_UPDATE_CHECK=1 (and implicitly disabled\n// when stderr is not a TTY, so agent-plugin / script consumers never\n// see a stray notice line).\n//\n// The `upgrade` command does NOT use this path — it's the explicit fetch,\n// runs immediately on demand, and its output is the point.\n\nexport const UPDATE_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;\n\nexport interface UpdateCheckCache {\n readonly lastCheckedAt: string; // ISO 8601\n readonly latestTag?: string;\n readonly etag?: string;\n}\n\nexport async function readCache(): Promise<UpdateCheckCache | null> {\n let raw: string;\n try {\n raw = await readFile(upgradeCheckPath(), 'utf8');\n } catch {\n return null;\n }\n let parsed: unknown;\n try {\n parsed = JSON.parse(raw);\n } catch {\n return null;\n }\n if (!parsed || typeof parsed !== 'object') return null;\n const obj = parsed as Record<string, unknown>;\n if (typeof obj.lastCheckedAt !== 'string') return null;\n // Reject non-string optional fields — a hand-edited or cross-version cache\n // with wrong types shouldn't corrupt later string operations.\n if (obj.latestTag !== undefined && typeof obj.latestTag !== 'string') return null;\n if (obj.etag !== undefined && typeof obj.etag !== 'string') return null;\n const result: UpdateCheckCache = {\n lastCheckedAt: obj.lastCheckedAt,\n ...(obj.latestTag !== undefined ? { latestTag: obj.latestTag as string } : {}),\n ...(obj.etag !== undefined ? { etag: obj.etag as string } : {}),\n };\n return result;\n}\n\nexport async function writeCache(entry: UpdateCheckCache): Promise<void> {\n const path = upgradeCheckPath();\n await mkdir(dirname(path), { recursive: true });\n // Atomic promote: write to a unique sibling tempfile, then rename. On\n // POSIX rename(2) is atomic within the same filesystem, so a truncated\n // or crash-interrupted write never becomes the canonical cache file.\n // Windows is best-effort (ReplaceFileW is atomic for same-volume targets\n // but not guaranteed cross-process).\n // Unique-per-caller tempfile: pid + wall-clock + random suffix. Prevents\n // concurrent writers in the same process (Date.now() has ms resolution and\n // can collide) from stealing each other's tempfiles between writeFile and\n // rename.\n const tmp = `${path}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 10)}.tmp`;\n try {\n // Cache body is non-secret, but mtime + the ETag are a mild leak of\n // \"when this user last ran aitcc\" on a multi-user box. Match session\n // storage's 0600 mode for consistency and defence in depth.\n await writeFile(tmp, JSON.stringify(entry, null, 2), { mode: 0o600 });\n await rename(tmp, path);\n } catch (err) {\n // If rename failed we may have left the tempfile behind; clean up.\n await unlink(tmp).catch(() => {});\n throw err;\n }\n}\n\n/** Has the throttle window elapsed since the last recorded check? */\nexport function isDueForCheck(\n cache: UpdateCheckCache | null,\n now: number = Date.now(),\n intervalMs: number = UPDATE_CHECK_INTERVAL_MS,\n): boolean {\n if (!cache) return true;\n const last = Date.parse(cache.lastCheckedAt);\n if (!Number.isFinite(last)) return true;\n // If the system clock jumps backwards (NTP resync, VM resume), treat the\n // cache as stale and re-check. Better to probe once than to be silently\n // stuck until wall-time catches up to `last + interval`.\n if (now < last) return true;\n return now - last >= intervalMs;\n}\n\nexport interface UpdateCheckOptions {\n readonly env?: NodeJS.ProcessEnv;\n readonly isTTY?: boolean;\n readonly now?: number;\n readonly intervalMs?: number;\n}\n\n/**\n * Perform the throttled update check. Returns the final cache entry (for\n * testing) or null when skipped. Never throws — network errors are\n * intentionally swallowed so they never interrupt the foreground command.\n */\nexport async function maybeCheckForUpdate(\n opts: UpdateCheckOptions = {},\n): Promise<UpdateCheckCache | null> {\n const env = opts.env ?? process.env;\n const isTTY = opts.isTTY ?? Boolean(process.stderr.isTTY);\n const now = opts.now ?? Date.now();\n const intervalMs = opts.intervalMs ?? UPDATE_CHECK_INTERVAL_MS;\n\n // Opt-out: any non-empty value that isn't explicitly falsey counts. Matches\n // the loose convention used by CI / DEBUG env vars — \"AITCC_NO_UPDATE_CHECK=true\"\n // works alongside \"=1\".\n const optOut = env.AITCC_NO_UPDATE_CHECK;\n if (optOut && optOut !== '0' && optOut.toLowerCase() !== 'false') return null;\n // Notice lines are targeted at interactive users. Checking stderr (where\n // the notice is written) rather than stdout means `aitcc whoami > out.log`\n // still shows the notice on the terminal, while a fully piped invocation\n // (stderr redirected or captured) is silent.\n if (!isTTY) return null;\n\n const cache = await readCache();\n if (!isDueForCheck(cache, now, intervalMs)) return null;\n\n // Stamp the cache BEFORE the network call so a concurrent `aitcc whoami`\n // that reads AFTER this write sees \"not due\" and skips its probe. (Two\n // racers that both read before either writes will each fire once — the\n // window is a handful of µs and the anonymous GitHub limit plus the\n // ETag-based 304 path keep the damage bounded.) If the probe crashes\n // the process, this placeholder also naturally satisfies the \"failed\n // probes still update the window\" invariant — the next run is bounded\n // by the interval.\n const nowIso = new Date(now).toISOString();\n const placeholder: UpdateCheckCache = {\n lastCheckedAt: nowIso,\n ...(cache?.latestTag !== undefined ? { latestTag: cache.latestTag } : {}),\n ...(cache?.etag !== undefined ? { etag: cache.etag } : {}),\n };\n await writeCache(placeholder).catch(() => {\n // Non-fatal: proceed with the probe, we'll try to write again on\n // completion. The throttle guarantee weakens here but bounded by\n // whatever caused the write to fail in the first place.\n });\n\n const previousEtag = cache?.etag;\n let entry: UpdateCheckCache = placeholder;\n try {\n const result = await fetchLatestReleaseConditional(previousEtag);\n if (result.status === 'not-modified') {\n // 304: server had no new body. Keep the latestTag we already know\n // about, and refresh the ETag only if the server happened to include\n // one on the 304.\n entry = {\n lastCheckedAt: nowIso,\n ...(cache?.latestTag !== undefined ? { latestTag: cache.latestTag } : {}),\n ...(result.etag !== undefined\n ? { etag: result.etag }\n : cache?.etag !== undefined\n ? { etag: cache.etag }\n : {}),\n };\n } else {\n entry = {\n lastCheckedAt: nowIso,\n latestTag: result.release.tag_name,\n ...(result.etag !== undefined ? { etag: result.etag } : {}),\n };\n }\n await writeCache(entry).catch(() => {\n // Placeholder already wrote above; ignore secondary write failure.\n });\n } catch {\n // Network / parse failure. Placeholder is already on disk, so the\n // throttle invariant holds — just skip the second write.\n }\n\n // If the probe failed, `entry` is still the placeholder — so the notice\n // uses the *previous* known latestTag from cache. That's fine: the\n // throttle window bounds staleness to 24h and semver doesn't move\n // backwards, so an out-of-date tag can only under-report a new release,\n // never falsely suggest a newer one than really exists.\n maybeEmitNotice(entry, env);\n return entry;\n}\n\nfunction maybeEmitNotice(entry: UpdateCheckCache, env: NodeJS.ProcessEnv): void {\n if (!entry.latestTag) return;\n // In the dev fallback VERSION (`0.0.0-dev`, from `src/version.ts` when no\n // build-time define is injected) every released tag looks \"newer\" and the\n // notice would fire on every `pnpm dev` run. Skip it instead — developers\n // running from source don't need an upgrade nag.\n if (VERSION.startsWith('0.0.0-dev')) return;\n const latest = versionFromTag(entry.latestTag);\n if (!latest) return;\n if (compareSemver(latest, VERSION) <= 0) return;\n // Respect NO_COLOR — CLAUDE.md documents it as honored across the CLI.\n const dim = env.NO_COLOR ? '' : '\\x1b[2m';\n const reset = env.NO_COLOR ? '' : '\\x1b[0m';\n // Notice goes to stderr so it never pollutes `--json` stdout.\n process.stderr.write(\n `\\n${dim}(aitcc ${latest} is available — run \\`aitcc upgrade\\` to install)${reset}\\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';\nimport { maybeCheckForUpdate } from '../update-check.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\n// Run the throttled background update check — but bound the wall-clock cost\n// so a slow network never delays the user's whoami output. 500 ms is enough\n// for a 304 (fast path after the first check) and for most 200s; a cold\n// probe that goes long just gets cancelled, and the next whoami within 24h\n// will not retry anyway (cache was written when the probe started).\n//\n// Skipped entirely when `--json` is set — machine consumers (agent-plugin)\n// should never see a \"new version available\" notice line interleaved with\n// their parsed output. The notice in update-check.ts already targets stderr\n// and checks `isTTY`, but belt-and-suspenders costs nothing here.\nasync function runBackgroundUpdateCheck(json: boolean): Promise<void> {\n if (json) return;\n const timeoutMs = 500;\n await Promise.race([\n maybeCheckForUpdate().catch(() => null),\n new Promise<null>((resolve) => {\n const t = setTimeout(() => resolve(null), timeoutMs);\n if (typeof t.unref === 'function') t.unref();\n }),\n ]);\n}\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 await runBackgroundUpdateCheck(args.json);\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 await runBackgroundUpdateCheck(args.json);\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","import type { CdpCookie } from '../cdp.js';\nimport { type FetchLike, requestConsoleApi } from './http.js';\n\n// The list of workspaces a user can see is already baked into the\n// `members/me/user-info` response (see `./me.ts`), so we don't expose a\n// separate `GET /workspaces` wrapper — every caller that needs the list\n// goes through `fetchConsoleMemberUserInfo` and keys off `workspaces`.\n// This module only covers per-workspace detail and future write endpoints.\n\n// Note: the list endpoint (members/me/user-info) and the detail endpoint\n// disagree on field names — list uses workspaceId/workspaceName while\n// detail uses id/name. We normalise detail into the same vocabulary so\n// callers don't have to track which endpoint they came from.\nexport interface WorkspaceDetail {\n readonly workspaceId: number;\n readonly workspaceName: string;\n // The full shape of `/workspaces/:id` has many secondary fields (business\n // registration, verification, licence type, review state, etc) that may\n // grow over time. Stash everything beyond the normalised keys under\n // `extra` so commands like `workspace show --json` can dump the payload\n // without us having to type every field up-front.\n readonly extra?: Readonly<Record<string, unknown>>;\n}\n\nconst WORKSPACES_BASE = 'https://apps-in-toss.toss.im/console/api-public/v3/appsintossconsole';\n\nexport async function fetchWorkspaceDetail(\n workspaceId: number,\n cookies: readonly CdpCookie[],\n opts: { fetchImpl?: FetchLike } = {},\n): Promise<WorkspaceDetail> {\n // workspaceId is a number at compile time — `encodeURIComponent` on the\n // stringified form would be a no-op, so we inline the interpolation.\n const url = `${WORKSPACES_BASE}/workspaces/${workspaceId}`;\n const raw = await requestConsoleApi<Record<string, unknown>>({\n url,\n cookies,\n ...(opts.fetchImpl ? { fetchImpl: opts.fetchImpl } : {}),\n });\n const id = raw.id;\n const name = raw.name;\n if (typeof id !== 'number' || !Number.isInteger(id) || id <= 0 || typeof name !== 'string') {\n throw new Error(`Unexpected workspace detail shape for id=${workspaceId}`);\n }\n const { id: _id, name: _name, ...extra } = raw;\n return { workspaceId: id, workspaceName: name, extra };\n}\n","import { defineCommand } from 'citty';\nimport { fetchConsoleMemberUserInfo } from '../api/me.js';\nimport { fetchWorkspaceDetail } from '../api/workspaces.js';\nimport { ExitCode } from '../exit.js';\nimport { exitAfterFlush } from '../flush.js';\nimport { readSession, setCurrentWorkspaceId } from '../session.js';\nimport {\n emitFailureFromError,\n emitJson,\n emitNotAuthenticated,\n parsePositiveInt,\n} from './_shared.js';\n\n// --json contract (consumed by agent-plugin):\n//\n// workspace ls:\n// { ok: true, workspaces: [{workspaceId, workspaceName, role, current}] }\n// ^--- matches currentWorkspaceId\n// workspace use <id>:\n// { ok: true, workspaceId, workspaceName } exit 0\n// { ok: false, reason: 'not-found', workspaceId } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n// workspace show [--workspace <id>]:\n// { ok: true, workspaceId, workspaceName, extra } exit 0\n// { ok: false, reason: 'no-workspace-selected' } exit 2\n// { ok: false, reason: 'invalid-id', message } exit 2\n//\n// Every workspace subcommand inherits the standard auth failure modes from\n// whoami: { ok: true, authenticated: false } exit 10, network-error exit 11,\n// api-error exit 17. All JSON writes go through the shared `emitJson` so the\n// single-line-with-trailing-newline invariant is enforced in one place.\n\n// Formatting helper for the plain-text `show` output. `--json` is the\n// structured consumption path; this is a crude fallback so a human can\n// skim the response at a glance. Objects/arrays collapse to a single\n// JSON line on purpose — nested structures are rare in the detail\n// response and unreadable in any form without real tabular formatting.\nfunction formatScalar(v: unknown): string {\n if (v === null) return 'null';\n if (typeof v === 'string' || typeof v === 'number' || typeof v === 'boolean') return String(v);\n return JSON.stringify(v);\n}\n\nconst lsCommand = defineCommand({\n meta: {\n name: 'ls',\n description: 'List workspaces the current user has access to.',\n },\n args: {\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n try {\n const info = await fetchConsoleMemberUserInfo(session.cookies);\n const current = session.currentWorkspaceId;\n if (args.json) {\n const workspaces = info.workspaces.map((w) => ({\n workspaceId: w.workspaceId,\n workspaceName: w.workspaceName,\n role: w.role,\n current: w.workspaceId === current,\n }));\n emitJson({ ok: true, workspaces });\n return exitAfterFlush(ExitCode.Ok);\n }\n if (info.workspaces.length === 0) {\n process.stdout.write('No workspaces.\\n');\n return exitAfterFlush(ExitCode.Ok);\n }\n for (const w of info.workspaces) {\n const marker = w.workspaceId === current ? '* ' : ' ';\n process.stdout.write(`${marker}${w.workspaceId} ${w.workspaceName} (${w.role})\\n`);\n }\n if (current === undefined) {\n process.stderr.write('No workspace selected. Run `aitcc workspace use <id>`.\\n');\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nconst useCommand = defineCommand({\n meta: {\n name: 'use',\n description: 'Select the current workspace by ID. Subsequent commands use this.',\n },\n args: {\n id: { type: 'positional', description: 'Workspace ID', required: true },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const raw = String(args.id);\n const parsed = parsePositiveInt(raw);\n if (parsed === null) {\n const message = `workspace id must be a positive integer (got ${raw})`;\n if (args.json) {\n emitJson({ ok: false, reason: 'invalid-id', message });\n } else {\n process.stderr.write(`${message}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n\n // Validate against the user's actual workspace list before writing the\n // selection. `members/me/user-info` is the live list, not the stored\n // one, so a workspace added after login is visible here. Only the\n // detail endpoint (not called here) could still 403 after this check.\n try {\n const info = await fetchConsoleMemberUserInfo(session.cookies);\n const match = info.workspaces.find((w) => w.workspaceId === parsed);\n if (!match) {\n if (args.json) {\n emitJson({ ok: false, reason: 'not-found', workspaceId: parsed });\n } else {\n process.stderr.write(\n `Workspace ${parsed} is not accessible from this account. Run \\`aitcc workspace ls\\` to see available workspaces.\\n`,\n );\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n // `setCurrentWorkspaceId` returns null only if the session disappeared\n // between our `readSession` above and here (e.g. concurrent logout).\n // Surface that as \"not logged in\" for consistency with other commands\n // instead of silently pretending the write landed. For v1 sessions\n // this is a double-read (readSession migrates, then this helper reads\n // again before writing) — benign, and preferable to threading the\n // already-loaded session through a new parameter just to save one IO.\n const updated = await setCurrentWorkspaceId(parsed);\n if (updated === null) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n if (args.json) {\n emitJson({\n ok: true,\n workspaceId: match.workspaceId,\n workspaceName: match.workspaceName,\n });\n } else {\n process.stdout.write(`Using workspace ${match.workspaceId} (${match.workspaceName}).\\n`);\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nconst showCommand = defineCommand({\n meta: {\n name: 'show',\n description: 'Show details of the selected workspace (or the one passed with --workspace).',\n },\n args: {\n workspace: {\n type: 'string',\n description: 'Workspace ID to inspect. Defaults to the selected workspace.',\n },\n json: { type: 'boolean', description: 'Emit machine-readable JSON to stdout.', default: false },\n },\n async run({ args }) {\n const session = await readSession();\n if (!session) {\n emitNotAuthenticated(args.json);\n return exitAfterFlush(ExitCode.NotAuthenticated);\n }\n\n let workspaceId: number | undefined;\n if (args.workspace) {\n const raw = String(args.workspace);\n const parsed = parsePositiveInt(raw);\n if (parsed === null) {\n const message = `--workspace must be a positive integer (got ${raw})`;\n if (args.json) {\n emitJson({ ok: false, reason: 'invalid-id', message });\n } else {\n process.stderr.write(`${message}\\n`);\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n workspaceId = parsed;\n } else {\n workspaceId = session.currentWorkspaceId;\n }\n\n if (workspaceId === undefined) {\n if (args.json) {\n emitJson({ ok: false, reason: 'no-workspace-selected' });\n } else {\n process.stderr.write(\n 'No workspace selected. Pass `--workspace <id>` or run `aitcc workspace use <id>`.\\n',\n );\n }\n return exitAfterFlush(ExitCode.Usage);\n }\n\n try {\n const detail = await fetchWorkspaceDetail(workspaceId, session.cookies);\n if (args.json) {\n emitJson({\n ok: true,\n workspaceId: detail.workspaceId,\n workspaceName: detail.workspaceName,\n extra: detail.extra ?? {},\n });\n return exitAfterFlush(ExitCode.Ok);\n }\n process.stdout.write(`Workspace ${detail.workspaceId}: ${detail.workspaceName}\\n`);\n if (detail.extra) {\n for (const [k, v] of Object.entries(detail.extra)) {\n process.stdout.write(` ${k}: ${formatScalar(v)}\\n`);\n }\n }\n return exitAfterFlush(ExitCode.Ok);\n } catch (err) {\n return emitFailureFromError(args.json, err);\n }\n },\n});\n\nexport const workspaceCommand = defineCommand({\n meta: {\n name: 'workspace',\n description: 'Inspect and switch between the workspaces this account can access.',\n },\n subCommands: {\n ls: lsCommand,\n use: useCommand,\n show: showCommand,\n },\n});\n","#!/usr/bin/env node\nimport { defineCommand, runMain } from 'citty';\nimport { appCommand } from './commands/app.js';\nimport { keysCommand } from './commands/keys.js';\nimport { loginCommand } from './commands/login.js';\nimport { logoutCommand } from './commands/logout.js';\nimport { membersCommand } from './commands/members.js';\nimport { upgradeCommand } from './commands/upgrade.js';\nimport { whoamiCommand } from './commands/whoami.js';\nimport { workspaceCommand } from './commands/workspace.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 workspace: workspaceCommand,\n app: appCommand,\n members: membersCommand,\n keys: keysCommand,\n },\n});\n\nrunMain(main);\n"],"mappings":"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"}