@nalvietnam/avatar-cli 3.0.0-beta.4 → 3.0.0-beta.5

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/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/filesystem-helpers.ts","../src/lib/git-operations.ts","../src/lib/mustache-template-engine.ts","../src/lib/template-bundle-loader.ts","../src/lib/project-tree-scaffolder.ts","../src/lib/workspace-repos-manifest-store.ts","../src/lib/clone-code-repo-into-src.ts","../src/lib/terminal-logger.ts","../src/lib/resolve-github-username-default.ts","../src/lib/create-empty-github-repo.ts","../src/lib/google-oauth-device-flow.ts","../src/index.ts","../src/commands/add-repo.ts","../src/lib/clone-repo-with-recovery-menu.ts","../src/lib/verify-git-remote-accessible.ts","../src/lib/detect-gitnexus-installation.ts","../src/lib/detect-host-platform.ts","../src/lib/check-gh-cli-auth-status.ts","../src/lib/detect-package-manager.ts","../src/lib/handle-remote-access-failure-with-account-switch.ts","../src/lib/reset-folder-git-and-create-new-remote-under-current-user.ts","../src/lib/execute-gh-repo-create.ts","../src/lib/create-github-remote-from-folder.ts","../src/lib/validate-repo-name-and-visibility.ts","../src/lib/install-gh-cli-via-package-manager.ts","../src/lib/prompt-recovery-action-on-failure.ts","../src/lib/setup-git-credential-via-gh.ts","../src/lib/git-auth-and-install-orchestrator.ts","../src/lib/trigger-gh-cli-auth-login.ts","../src/lib/resolve-avatar-workspace-root-from-cwd.ts","../src/lib/run-gitnexus-setup-and-analyze.ts","../src/lib/run-gitnexus-wiki-conditional.ts","../src/lib/detect-reasoning-model-from-name.ts","../src/lib/write-gitnexus-config-to-avoid-cli-arg-key-leak.ts","../src/commands/ai.ts","../src/lib/audit-log-appender.ts","../src/lib/user-config-store.ts","../src/types/config-schema.ts","../src/lib/check-claude-code-subscription-and-quota.ts","../src/lib/detect-claude-code-installation.ts","../src/lib/install-claude-code-via-npm.ts","../src/lib/prompt-ai-provider-choice.ts","../src/lib/setup-anthropic-api-key-and-model.ts","../src/lib/setup-llmlite-api-key-and-model.ts","../src/lib/run-ai-setup-phase.ts","../src/lib/write-claude-settings-json-per-project.ts","../src/lib/test-ai-provider-by-detected-mode.ts","../src/commands/doctor.ts","../src/lib/check-enabled-tools-health.ts","../src/lib/apply-tool-manifest-to-settings.ts","../src/lib/merge-pack-settings-into-project-settings.ts","../src/lib/tool-state-store.ts","../src/commands/gitnexus.ts","../src/lib/run-gitnexus-setup-phase.ts","../src/lib/install-gitnexus-via-npm.ts","../src/lib/register-gitnexus-mcp-server.ts","../src/commands/init.ts","../src/lib/avatar-ascii-banner.ts","../src/lib/safe-bootstrap-for-dirty-folder.ts","../src/lib/check-folder-has-git.ts","../src/lib/create-initial-git-commit.ts","../src/lib/detect-folder-tech-stack.ts","../src/lib/gitignore-template-loader.ts","../src/lib/write-or-merge-gitignore.ts","../src/lib/team-pack-submodule-manager.ts","../src/lib/pack-manifest-store.ts","../src/lib/supabase-pack-downloader.ts","../src/commands/init-success-rendering-and-helpers.ts","../src/lib/format-pack-commands-cheatsheet-box.ts","../src/commands/init-conflict-detection-helpers.ts","../src/commands/login.ts","../src/commands/workspace-scaffold-and-finalize-orchestrator.ts","../src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts","../src/lib/setup-default-tools-on-init.ts","../src/lib/discover-pack-tools-and-defaults.ts","../src/lib/tool-enable-disable-orchestrator.ts","../src/lib/symlink-farm-for-team-pack-mount-dirs.ts","../src/lib/backup-existing-dir-before-symlink-override.ts","../src/lib/read-cli-version-from-package-json.ts","../src/commands/init-scaffold-variable-builders.ts","../src/lib/not-implemented-stub.ts","../src/commands/mcp-run.ts","../src/commands/pack-status-and-version-check.ts","../src/commands/restore.ts","../src/commands/review.ts","../src/commands/scan.ts","../src/commands/status.ts","../src/lib/pack-backup-manager.ts","../src/commands/sync.ts","../src/lib/preview-team-pack-sync-changes-for-dry-run.ts","../src/lib/reapply-enabled-tools-after-sync.ts","../src/commands/tools.ts","../src/lib/tool-add-remove-selection.ts","../src/commands/uninstall.ts","../src/lib/create-uninstall-backup-snapshot.ts","../src/lib/detect-avatar-project-artifacts.ts","../src/lib/execute-uninstall-deletion.ts"],"sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\n// Thin promise-based wrappers around node:fs/promises with the patterns Avatar\n// uses most: ensure-dir, read-text, write-text-atomic, copy-dir-recursive.\n// Atomic write writes to a temp file then renames to avoid corruption if Avatar\n// crashes mid-write.\nimport { createHash } from \"node:crypto\";\nimport { constants, promises as fs, createReadStream } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\nimport { Readable } from \"node:stream\";\n\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await fs.access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDir(path: string): Promise<void> {\n await fs.mkdir(path, { recursive: true });\n}\n\nexport async function readText(path: string): Promise<string> {\n return await fs.readFile(path, \"utf8\");\n}\n\nexport async function readJson<T>(path: string): Promise<T> {\n return JSON.parse(await readText(path)) as T;\n}\n\n// Atomic write: write to temp file then rename. Prevents corruption if process\n// is killed between open() and close(). chmod is applied AFTER rename.\nexport async function writeTextAtomic(path: string, content: string, mode?: number): Promise<void> {\n await ensureDir(dirname(path));\n const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;\n await fs.writeFile(tmp, content, \"utf8\");\n if (mode !== undefined) {\n await fs.chmod(tmp, mode);\n }\n await fs.rename(tmp, path);\n}\n\nexport async function writeJsonAtomic(path: string, data: unknown, mode?: number): Promise<void> {\n await writeTextAtomic(path, `${JSON.stringify(data, null, 2)}\\n`, mode);\n}\n\n// Recursive copy. excludeNames filters by basename at any depth (e.g. \".git\").\nexport async function copyDirRecursive(\n source: string,\n destination: string,\n excludeNames: string[] = [],\n): Promise<void> {\n await ensureDir(destination);\n const entries = await fs.readdir(source, { withFileTypes: true });\n for (const entry of entries) {\n if (excludeNames.includes(entry.name)) continue;\n const src = join(source, entry.name);\n const dst = join(destination, entry.name);\n if (entry.isDirectory()) {\n await copyDirRecursive(src, dst, excludeNames);\n } else if (entry.isSymbolicLink()) {\n const target = await fs.readlink(src);\n await fs.symlink(target, dst);\n } else {\n await fs.copyFile(src, dst);\n }\n }\n}\n\nexport async function removeRecursive(path: string): Promise<void> {\n await fs.rm(path, { recursive: true, force: true });\n}\n\nexport function relativeFromCwd(path: string): string {\n return relative(process.cwd(), path);\n}\n\n// ─── Pack download/extract helpers (v3: tải tarball pack từ Supabase) ────────\n\n// Tải file từ URL về `dest`. Stream body ra file (không nạp hết vào memory).\n// S3: AbortController timeout (default 300s = thời hạn signed URL Supabase) +\n// dọn partial file nếu fail giữa chừng. Tránh treo vô hạn / để lại file rác.\nconst DOWNLOAD_TIMEOUT_MS = 300_000;\n\nexport async function downloadFile(\n url: string,\n dest: string,\n timeoutMs = DOWNLOAD_TIMEOUT_MS,\n): Promise<void> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n await ensureDir(dirname(dest));\n const { createWriteStream } = await import(\"node:fs\");\n\n try {\n const res = await fetch(url, { signal: controller.signal });\n if (!res.ok) {\n throw new Error(`Download fail (${res.status}) từ ${url.slice(0, 80)}...`);\n }\n if (!res.body) {\n throw new Error(\"Response không có body để tải.\");\n }\n const fileStream = createWriteStream(dest);\n await new Promise<void>((resolve, reject) => {\n const src = Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]);\n src.on(\"error\", reject); // S-fix: bắt lỗi mid-stream của source.\n fileStream.on(\"error\", reject);\n fileStream.on(\"finish\", () => resolve());\n src.pipe(fileStream);\n });\n } catch (err) {\n // Dọn partial file để không để lại tarball hỏng.\n await fs.rm(dest, { force: true }).catch(() => {});\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Tải file quá ${timeoutMs / 1000}s (signed URL có thể đã hết hạn).`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Giải nén tarball .tar.gz vào `destDir` bằng system `tar`. macOS/Linux có sẵn.\n// S4: Windows mặc định không có `tar` ở mọi phiên bản → probe trước, throw rõ ràng.\nlet tarAvailableCache: boolean | null = null;\nfunction tarAvailable(): boolean {\n if (tarAvailableCache !== null) return tarAvailableCache;\n const r = spawnSync(\"tar\", [\"--version\"], { stdio: \"ignore\" });\n tarAvailableCache = r.status === 0;\n return tarAvailableCache;\n}\n\nexport async function extractTarballToDir(tarball: string, destDir: string): Promise<void> {\n if (!tarAvailable()) {\n throw new Error(\n \"Không tìm thấy lệnh `tar` trên hệ thống. Cần để giải nén pack.\\n\" +\n \" Windows: cài qua WSL2, Git Bash, hoặc MSYS2.\\n\" +\n \" macOS/Linux: tar thường có sẵn — kiểm tra PATH.\",\n );\n }\n await ensureDir(destDir);\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"tar\", [\"-xzf\", tarball, \"-C\", destDir], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n let stderr = \"\";\n child.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`tar extract exit ${code}: ${stderr.trim()}`));\n });\n });\n}\n\n// Tính SHA-256 của file (hex). Để verify integrity tarball trước khi giải nén.\n// HIỆN CHƯA DÙNG: get-pack endpoint Supabase chưa trả sha256 trong response.\n// Giữ lại để bật verify khi endpoint bổ sung field sha (roadmap S5). Khi đó:\n// downloadAndExtractPack nhận expectedSha → so với computeFileSha256(tarball) trước extract.\nexport async function computeFileSha256(path: string): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n const hash = createHash(\"sha256\");\n const stream = createReadStream(path);\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => resolve(hash.digest(\"hex\")));\n stream.on(\"error\", reject);\n });\n}\n","import { join } from \"node:path\";\n// Wrapper around simple-git for the operations Avatar uses repeatedly:\n// repo detection, submodule add/update, status, log, tag listing.\n// Centralised so error formatting and cwd handling stays consistent.\nimport { type SimpleGit, simpleGit } from \"simple-git\";\nimport { pathExists } from \"./filesystem-helpers.js\";\n\nexport function git(cwd: string = process.cwd()): SimpleGit {\n return simpleGit({ baseDir: cwd, binary: \"git\" });\n}\n\nexport async function isGitRepo(cwd: string = process.cwd()): Promise<boolean> {\n return await pathExists(join(cwd, \".git\"));\n}\n\nexport async function currentBranch(cwd: string = process.cwd()): Promise<string> {\n const result = await git(cwd).revparse([\"--abbrev-ref\", \"HEAD\"]);\n return result.trim();\n}\n\nexport async function addSubmodule(\n repoUrl: string,\n destPath: string,\n cwd: string = process.cwd(),\n): Promise<void> {\n await git(cwd).subModule([\"add\", repoUrl, destPath]);\n}\n\n// Checkout a tag inside a submodule. Used after `addSubmodule` to pin to a\n// specific team-ai-pack release.\nexport async function checkoutTagInSubmodule(\n submodulePath: string,\n tag: string,\n cwd: string = process.cwd(),\n): Promise<void> {\n const submoduleCwd = join(cwd, submodulePath);\n await git(submoduleCwd).fetch([\"--tags\"]);\n await git(submoduleCwd).checkout(tag);\n}\n\n// v1.8.0: Checkout HEAD của branch (default main) thay vì tag.\n// Dùng cho `avatar sync --latest` — pull commits mới ngay khi pack team push,\n// không cần đợi tag SemVer mới. Bleeding-edge mode cho member dev.\n//\n// Flow:\n// 1. fetch origin (lấy commits mới + tags)\n// 2. checkout <branch> (di chuyển HEAD tới branch local, tạo nếu thiếu)\n// 3. reset --hard origin/<branch> (force align với remote, xóa local divergence)\n//\n// reset --hard cần thiết vì submodule có thể đang detached HEAD (sau checkout tag).\n// Pack thường KHÔNG có local commits → reset --hard safe.\nexport async function checkoutBranchHeadInSubmodule(\n submodulePath: string,\n branch: string,\n cwd: string = process.cwd(),\n): Promise<void> {\n const submoduleCwd = join(cwd, submodulePath);\n await git(submoduleCwd).fetch([\"origin\"]);\n // Tạo / switch local branch tracking remote.\n await git(submoduleCwd).checkout([\"-B\", branch, `origin/${branch}`]);\n}\n\nexport async function listTags(cwd: string = process.cwd()): Promise<string[]> {\n const result = await git(cwd).tags();\n return result.all;\n}\n\n// v1.7.1: Return tag tại HEAD nếu có (semantics khác latestTag).\n// Dùng cho avatar status / readPinnedPackVersion — báo user đang ở version nào.\nexport async function tagAtHead(cwd: string = process.cwd()): Promise<string | null> {\n try {\n const result = await git(cwd).raw([\"describe\", \"--tags\", \"--exact-match\", \"HEAD\"]);\n return result.trim() || null;\n } catch {\n // HEAD không trùng tag nào → return null, caller fallback dùng SHA.\n return null;\n }\n}\n\nexport async function currentCommitSha(cwd: string = process.cwd()): Promise<string> {\n const result = await git(cwd).revparse([\"HEAD\"]);\n return result.trim();\n}\n\nexport async function workingTreeIsDirty(cwd: string = process.cwd()): Promise<boolean> {\n const status = await git(cwd).status();\n return !status.isClean();\n}\n","// Minimal mustache-style {{variable}} replacement engine. Avoids pulling in\n// full mustache dependency — Avatar templates only need flat variable\n// substitution, no loops or conditionals.\n//\n// Unknown variables left as-is, NOT replaced with empty string. This makes\n// missing variables visible in output for easier debugging.\n\nconst TEMPLATE_PATTERN = /\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\}\\}/g;\n\nexport function renderTemplate(\n source: string,\n variables: Record<string, string | number | undefined>,\n): string {\n return source.replace(TEMPLATE_PATTERN, (match, key: string) => {\n const value = variables[key];\n if (value === undefined) return match;\n return String(value);\n });\n}\n\n// Extract the variable names referenced by a template — useful for\n// scaffolding tests and validating that callers pass every required key.\nexport function extractVariables(source: string): string[] {\n const found = new Set<string>();\n for (const match of source.matchAll(TEMPLATE_PATTERN)) {\n if (match[1]) found.add(match[1]);\n }\n return Array.from(found).sort();\n}\n","// Resolve template files bundled inside the Avatar CLI package. tsup bundles\n// src/ into dist/index.js but does NOT bundle template files — they ship as\n// loose files via package.json `files` field (src/templates, src/hooks).\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { readText } from \"./filesystem-helpers.js\";\nimport { renderTemplate } from \"./mustache-template-engine.js\";\n\n// Templates ship in src/templates/ and src/hooks/ at runtime (see\n// package.json `files` field — both src/templates and src/hooks are included\n// in the npm tarball). Locate them by walking up from this file to the\n// package root (folder containing package.json), then descending to src/.\n//\n// This works identically from dist/index.js (npm-installed) and from\n// src/lib/*.ts (vitest/tsx) without path string sniffing.\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst PACKAGE_ROOT = findPackageRoot(HERE);\nconst TEMPLATES_ROOT = join(PACKAGE_ROOT, \"src\", \"templates\");\nconst HOOKS_ROOT = join(PACKAGE_ROOT, \"src\", \"hooks\");\n\nfunction findPackageRoot(startDir: string): string {\n let dir = startDir;\n while (true) {\n if (existsSync(join(dir, \"package.json\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) {\n throw new Error(`Cannot locate package root from ${startDir}`);\n }\n dir = parent;\n }\n}\n\nexport type TemplateName =\n | \"CLAUDE.md\"\n | \"settings.json\"\n | \"project/tech-stack.md\"\n | \"project/conventions.md\"\n | \"project/architecture.md\"\n | \"project/domain.md\"\n | \"project/gotchas.md\";\n\nexport type HookName = \"post-merge\" | \"pre-push\";\n\nexport async function loadTemplate(name: TemplateName): Promise<string> {\n return await readText(join(TEMPLATES_ROOT, `${name}.tpl`));\n}\n\nexport async function renderTemplateByName(\n name: TemplateName,\n variables: Record<string, string | number | undefined>,\n): Promise<string> {\n const source = await loadTemplate(name);\n return renderTemplate(source, variables);\n}\n\nexport async function loadHook(name: HookName): Promise<string> {\n return await readText(join(HOOKS_ROOT, `${name}.sh.tpl`));\n}\n","import { promises as fs } from \"node:fs\";\n// Scaffold the .claude/ directory tree inside a project.\n// v3: workspace KHÔNG git, 1 flow init duy nhất (bỏ internal/client/library modes).\nimport { join } from \"node:path\";\nimport { ensureDir, pathExists, writeTextAtomic } from \"./filesystem-helpers.js\";\nimport { type TemplateName, loadHook, renderTemplateByName } from \"./template-bundle-loader.js\";\n\n// Top-level paths Avatar will create or overwrite during init.\n// init.ts imports this for conflict detection so the list stays in sync.\n// v3: workspace KHÔNG git → bỏ .gitmodules (không còn submodule).\nexport const AVATAR_MANAGED_PATHS = [\".claude\", \"CLAUDE.md\"] as const;\n\n// Rename `path` to `{path}.avatar-backup-{ISO timestamp}` if it exists.\n// Returns the backup path if created, null if source didn't exist.\n// Caller logs the backup so the user knows where their original file went.\n//\n// Collision handling: if the timestamped backup name already exists (two\n// backups within the same millisecond, or pre-existing leftover from prior\n// run), appends `-{counter}` until a free name is found. Prevents silent\n// overwrite of a previous backup.\nexport async function backupIfExists(path: string): Promise<string | null> {\n if (!(await pathExists(path))) return null;\n const ts = new Date().toISOString().replace(/[:.]/g, \"-\");\n const basePath = `${path}.avatar-backup-${ts}`;\n let backupPath = basePath;\n let counter = 1;\n while (await pathExists(backupPath)) {\n backupPath = `${basePath}-${counter}`;\n counter++;\n if (counter > 5) {\n throw new Error(`Could not find free backup name for ${path}`);\n }\n }\n await fs.rename(path, backupPath);\n return backupPath;\n}\n\n// Backup-aware atomic write. Used by all scaffolder write functions so user's\n// pre-existing files in `.claude/` are preserved when Avatar re-scaffolds.\n// Returns the backup path if a backup was created, null otherwise.\nasync function writeWithBackup(\n path: string,\n content: string,\n mode?: number,\n): Promise<string | null> {\n const backup = await backupIfExists(path);\n await writeTextAtomic(path, content, mode);\n return backup;\n}\n\n// Subdirectories Avatar creates inside .claude/ on init. `pack/` is added\n// separately (tải tarball qua addTeamPackSubmodule) — không liệt kê ở đây.\n// v1.3.3: Bỏ \"project\" subdir vì CLAUDE.md template không còn reference các knowledge file\n// trong đó (team-ai-pack/knowledge auto-scan chưa implement). Khi implement xong sẽ\n// add lại + writeProjectKnowledgeFiles.\nconst CLAUDE_SUBDIRS = [\"state\", \"_pending\", \"_backup\"] as const;\n\nexport interface ScaffoldVariables {\n projectName: string;\n projectDescription: string;\n teamOwner: string;\n avatarVersion: string;\n packVersion: string;\n lastScan: string;\n // v1.4.0 — GitNexus section content (empty string nếu không cài).\n // Mustache engine không support conditional → render pre-built string.\n gitnexusSection: string;\n // Index signature — `renderTemplateByName` consume Record<string, string | number | undefined>.\n // Cho phép template engine truy cập field bằng string key động.\n [key: string]: string | number | undefined;\n}\n\n// Create .claude/{project,state,_pending,_backup}/ and a .gitkeep in each\n// so they survive a fresh checkout.\nexport async function createClaudeDirTree(projectRoot: string): Promise<void> {\n const claudeRoot = join(projectRoot, \".claude\");\n await ensureDir(claudeRoot);\n for (const sub of CLAUDE_SUBDIRS) {\n const dir = join(claudeRoot, sub);\n await ensureDir(dir);\n await writeTextAtomic(join(dir, \".gitkeep\"), \"\");\n }\n}\n\n// v1.3.3-v1.11.1: STUB. Project knowledge files (tech-stack/conventions/architecture/\n// domain/gotchas) chờ `avatar scan` (M06) + team-ai-pack content ready.\n// Caller `init.ts` vẫn gọi để giữ API stable cho khi re-enable, hiện trả [].\n//\n// Re-enable checklist:\n// 1. team-ai-pack có content thật (chương trình knowledge curation chạy)\n// 2. avatar scan implement xong + output structured knowledge JSON\n// 3. CLAUDE.md template re-add @-imports cho 5 file knowledge\n// 4. Update test/lib/project-tree-scaffolder.test.ts ngừng expect []\n//\n// Nếu sau v1.15 vẫn chưa wire → đánh giá lại có cần stub không, có thể xóa và\n// inline empty array tại caller để giảm noise.\nexport async function writeProjectKnowledgeFiles(\n _projectRoot: string,\n _vars: ScaffoldVariables,\n): Promise<string[]> {\n return [];\n}\n\n// Write root CLAUDE.md from template — this is the entry point Claude Code reads.\n// Returns backup path if a pre-existing CLAUDE.md was renamed.\nexport async function writeRootClaudeMd(\n projectRoot: string,\n vars: ScaffoldVariables,\n): Promise<string | null> {\n const content = await renderTemplateByName(\"CLAUDE.md\", vars);\n return await writeWithBackup(join(projectRoot, \"CLAUDE.md\"), content);\n}\n\n// Write .claude/settings.json from template. Returns backup path if existed.\nexport async function writeProjectSettings(\n projectRoot: string,\n vars: ScaffoldVariables,\n): Promise<string | null> {\n const content = await renderTemplateByName(\"settings.json\", vars);\n return await writeWithBackup(join(projectRoot, \".claude\", \"settings.json\"), content);\n}\n\n// v3: workspace root KHÔNG git → KHÔNG tạo .gitignore ở root (gây hiểu nhầm).\n// Thay bằng .claude/.gitignore nhỏ — bảo vệ settings.json (chứa API key) + state,\n// PHÒNG khi user/Claude lỡ git init bên trong .claude/ hoặc workspace.\n// Nội dung path tương đối từ .claude/ (không phải workspace root).\nconst CLAUDE_GITIGNORE_CONTENT = `# Avatar — bảo vệ file nhạy cảm trong .claude/.\n# Workspace root KHÔNG git; file này phòng trường hợp .claude/ bị track nhầm.\nsettings.json\nsettings.json.backup-*\n.env\n.env.*\n_pending/\n_backup/\nstate/session-*.json\n`;\n\nexport async function writeClaudeGitignore(projectRoot: string): Promise<void> {\n const path = join(projectRoot, \".claude\", \".gitignore\");\n await writeTextAtomic(path, CLAUDE_GITIGNORE_CONTENT);\n}\n\n// Install git hooks into <gitDir>/hooks/. `gitDir` lets caller point at\n// src/.git/hooks (client mode pre-push) or workspace .git/hooks (post-merge).\nexport async function installGitHook(\n gitDir: string,\n hookName: \"post-merge\" | \"pre-push\",\n): Promise<void> {\n const content = await loadHook(hookName);\n const hooksDir = join(gitDir, \"hooks\");\n await ensureDir(hooksDir);\n const dest = join(hooksDir, hookName);\n await writeTextAtomic(dest, content, 0o755);\n}\n","// workspace-repos-manifest-store.ts\n//\n// v3: workspace KHÔNG git → không có .gitmodules để biết \"workspace có repo nào\".\n// Thay bằng .claude/repos.json do Avatar quản — nguồn sự thật DUY NHẤT về các repo\n// code trong src/. Mỗi repo là git clone độc lập.\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const REPOS_MANIFEST_RELATIVE = \".claude/repos.json\";\n\nexport interface RepoEntry {\n name: string; // tên thư mục trong src/ (vd \"frontend\")\n path: string; // relative từ workspace root (vd \"src/frontend\")\n url: string; // git remote URL\n addedAt: string; // ISO timestamp\n}\n\ninterface ReposManifest {\n repos: RepoEntry[];\n}\n\nfunction manifestPath(workspaceRoot: string): string {\n return join(workspaceRoot, REPOS_MANIFEST_RELATIVE);\n}\n\nexport async function readReposManifest(workspaceRoot: string): Promise<RepoEntry[]> {\n const p = manifestPath(workspaceRoot);\n if (!(await pathExists(p))) return [];\n try {\n const data = await readJson<ReposManifest>(p);\n return Array.isArray(data.repos) ? data.repos : [];\n } catch {\n return [];\n }\n}\n\nexport async function addRepoToManifest(workspaceRoot: string, entry: RepoEntry): Promise<void> {\n const repos = await readReposManifest(workspaceRoot);\n // Replace nếu cùng name (idempotent), else append.\n const filtered = repos.filter((r) => r.name !== entry.name);\n filtered.push(entry);\n await writeJsonAtomic(manifestPath(workspaceRoot), { repos: filtered });\n}\n\nexport async function removeRepoFromManifest(workspaceRoot: string, name: string): Promise<void> {\n const repos = await readReposManifest(workspaceRoot);\n await writeJsonAtomic(manifestPath(workspaceRoot), {\n repos: repos.filter((r) => r.name !== name),\n });\n}\n\n// Check tên repo đã tồn tại trong manifest chưa (tránh trùng).\nexport async function repoNameExists(workspaceRoot: string, name: string): Promise<boolean> {\n const repos = await readReposManifest(workspaceRoot);\n return repos.some((r) => r.name === name);\n}\n","// clone-code-repo-into-src.ts\n//\n// v3: Clone 1 repo code vào src/<name> (KHÔNG submodule — workspace không git).\n// Mỗi repo là git clone độc lập, có remote riêng, push/pull bình thường.\n//\n// Cài pre-push hook cho repo sau clone (P6: hook cài Ở ĐÂY, không ở init).\nimport { join } from \"node:path\";\nimport { ensureDir, pathExists } from \"./filesystem-helpers.js\";\nimport { git } from \"./git-operations.js\";\nimport { installGitHook } from \"./project-tree-scaffolder.js\";\nimport { addRepoToManifest, repoNameExists } from \"./workspace-repos-manifest-store.js\";\n\n// Tên repo: kebab/alphanumeric, không path separator, không trùng \"pack\".\nexport function validateRepoName(name: string): string | null {\n if (!name || name.trim().length === 0) return \"Tên repo không được rỗng.\";\n if (/[/\\\\]/.test(name) || name.includes(\"..\")) return \"Tên repo không được chứa '/', '\\\\', '..'.\";\n if (name === \"pack\") return \"Tên 'pack' bị giữ cho team-ai-pack.\";\n return null;\n}\n\n// Suy tên repo từ git URL. \"git@github.com:org/frontend.git\" → \"frontend\".\nexport function inferRepoNameFromUrl(url: string): string {\n const trimmed = url.trim().replace(/\\/+$/, \"\");\n const last = trimmed.split(/[/:]/).pop() ?? \"\";\n return last.replace(/\\.git$/, \"\") || \"repo\";\n}\n\nexport interface CloneRepoResult {\n name: string;\n path: string; // absolute\n url: string;\n}\n\n// Clone repo từ remote URL vào src/<name> của workspace.\n// Validate tên + chống trùng. Cài pre-push hook. Ghi manifest.\nexport async function cloneCodeRepoIntoSrc(args: {\n workspaceRoot: string;\n url: string;\n name: string;\n}): Promise<CloneRepoResult> {\n const nameError = validateRepoName(args.name);\n if (nameError) throw new Error(nameError);\n\n if (await repoNameExists(args.workspaceRoot, args.name)) {\n throw new Error(`Repo \"${args.name}\" đã có trong workspace. Chọn tên khác hoặc gỡ trước.`);\n }\n\n const srcDir = join(args.workspaceRoot, \"src\");\n await ensureDir(srcDir);\n const destPath = join(srcDir, args.name);\n if (await pathExists(destPath)) {\n throw new Error(`Thư mục src/${args.name} đã tồn tại. Chọn tên khác.`);\n }\n\n // git clone <url> src/<name>\n await git(srcDir).clone(args.url, args.name);\n\n // Cài pre-push hook cho repo vừa clone (P6).\n const gitDir = join(destPath, \".git\");\n await installGitHook(gitDir, \"pre-push\").catch(() => {\n // Hook là tiện ích, fail không block add repo.\n });\n\n // Ghi manifest.\n await addRepoToManifest(args.workspaceRoot, {\n name: args.name,\n path: `src/${args.name}`,\n url: args.url,\n addedAt: new Date().toISOString(),\n });\n\n return { name: args.name, path: destPath, url: args.url };\n}\n\n// Check folder có uncommitted changes không (P9: tránh clone-from-remote mất data).\n// Trả porcelain output (rỗng = clean).\nexport async function folderDirtyStatus(folderPath: string): Promise<string> {\n try {\n const status = await git(folderPath).status();\n return status.isClean() ? \"\" : status.files.map((f) => `${f.path}`).join(\", \");\n } catch {\n return \"\";\n }\n}\n\n// Đọc remote origin URL của 1 folder git (cho flow \"thư mục đã có\").\nexport async function readFolderRemoteUrl(folderPath: string): Promise<string | null> {\n try {\n const remotes = await git(folderPath).getRemotes(true);\n const origin = remotes.find((r) => r.name === \"origin\");\n return origin?.refs.fetch ?? origin?.refs.push ?? null;\n } catch {\n return null;\n }\n}\n","// Terminal logging helpers — chalk for color, ora for spinners.\n// All Avatar CLI output should funnel through here for consistent UX.\nimport chalk from \"chalk\";\nimport ora, { type Ora } from \"ora\";\n\ntype LogFn = (message: string) => void;\n\nexport const log: {\n info: LogFn;\n success: LogFn;\n warn: LogFn;\n error: LogFn;\n dim: LogFn;\n plain: LogFn;\n} = {\n info: (m) => process.stdout.write(`${chalk.blue(\"ℹ\")} ${m}\\n`),\n success: (m) => process.stdout.write(`${chalk.green(\"✓\")} ${m}\\n`),\n warn: (m) => process.stdout.write(`${chalk.yellow(\"⚠\")} ${m}\\n`),\n error: (m) => process.stderr.write(`${chalk.red(\"✗\")} ${m}\\n`),\n dim: (m) => process.stdout.write(`${chalk.dim(m)}\\n`),\n plain: (m) => process.stdout.write(`${m}\\n`),\n};\n\n// Spinner wrapper — handles non-TTY by degrading to plain output.\nexport function spinner(text: string): Ora {\n return ora({\n text,\n spinner: \"dots\",\n isEnabled: process.stdout.isTTY ?? false,\n }).start();\n}\n\n// Long-running spinner với elapsed time — update text mỗi giây.\n// Trả về { stop } để caller clear interval + stop spinner đúng cách.\n// Format: \"⠋ <prefix> (1:23)\" — user biết tool alive + chạy bao lâu.\nexport function spinnerWithElapsed(prefix: string): {\n succeed: (text: string) => void;\n fail: (text: string) => void;\n stop: () => void;\n} {\n const startMs = Date.now();\n const sp = spinner(`${prefix} (0:00)`);\n const formatElapsed = (): string => {\n const sec = Math.floor((Date.now() - startMs) / 1000);\n const m = Math.floor(sec / 60);\n const s = sec % 60;\n return `${m}:${String(s).padStart(2, \"0\")}`;\n };\n const interval = setInterval(() => {\n sp.text = `${prefix} (${formatElapsed()})`;\n }, 1000);\n return {\n succeed: (text: string) => {\n clearInterval(interval);\n sp.succeed(`${text} (${formatElapsed()})`);\n },\n fail: (text: string) => {\n clearInterval(interval);\n sp.fail(`${text} (${formatElapsed()})`);\n },\n stop: () => {\n clearInterval(interval);\n sp.stop();\n },\n };\n}\n\n// Chalk re-export so commands don't all import chalk separately.\nexport { chalk };\n","// Lấy GitHub login mặc định của user qua `gh api user --jq .login`.\n// Dùng làm default org khi user không truyền --repo-org.\nimport { spawnSync } from \"node:child_process\";\n\nexport function resolveGithubUsernameDefault(): string {\n const r = spawnSync(\"gh\", [\"api\", \"user\", \"--jq\", \".login\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.status !== 0) {\n throw new Error(`Không lấy được GitHub username: ${r.stderr?.trim()}`);\n }\n return r.stdout.trim();\n}\n","// create-empty-github-repo.ts\n//\n// v3: Tạo repo GitHub RỖNG (không cần folder source) cho flow \"avatar add repo →\n// dự án mới\". Khác executeGhRepoCreate (cần --source folder để push). Ở đây ta\n// tạo repo trống rồi caller clone về src/<name>.\nimport { spawnSync } from \"node:child_process\";\nimport { resolveGithubUsernameDefault } from \"./resolve-github-username-default.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Tạo repo rỗng trên GitHub, trả SSH clone URL.\nexport async function createEmptyGithubRepoAndGetUrl(\n name: string,\n visibility: \"private\" | \"public\",\n org?: string,\n): Promise<string> {\n const owner = org ?? resolveGithubUsernameDefault();\n const fullName = `${owner}/${name}`;\n log.info(`Tạo GitHub repo rỗng ${fullName} (${visibility})...`);\n\n // --add-readme để repo không rỗng hoàn toàn (clone được ngay, có HEAD).\n const r = spawnSync(\"gh\", [\"repo\", \"create\", fullName, `--${visibility}`, \"--add-readme\"], {\n stdio: \"inherit\",\n });\n if (r.status !== 0) {\n throw new Error(`gh repo create thất bại (exit ${r.status}). Repo có thể đã tồn tại.`);\n }\n\n log.success(`Đã tạo: git@github.com:${fullName}.git`);\n return `git@github.com:${fullName}.git`;\n}\n","// Google OAuth 2.0 Device Authorization Grant (RFC 8628) implementation.\n//\n// Why Device Flow: Avatar is a terminal CLI with no browser redirect URL.\n// The user logs in via google.com/device on a browser and the CLI polls\n// Google for the resulting token.\n//\n// Why the client secret is bundled in source: Device Flow does not treat the\n// secret as a security boundary — the human Allow click in the browser is.\n// Google's TV/Limited Input docs explicitly permit this.\n//\n// To rotate: Google Cloud Console → APIs & Services → Credentials → click the\n// OAuth client → Reset Secret. Replace GOOGLE_CLIENT_SECRET below.\n\nimport type { UserConfig } from \"../types/config-schema.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuth client config (hardcoded — see file header for rationale).\n// To regenerate: Google Cloud Console → project \"avatar-cli\" → Clients.\n// Application type must be \"TV and Limited Input devices\".\n//\n// v1.13.0 SECURITY: Module-private (không `export`). Giảm attack surface bằng\n// cách khóa secret chỉ accessible từ chính file này.\n// Action item ngoài code (NAL ops):\n// 1. ROTATE secret hiện tại (đã ship trong dist/ npm v1.10.0+ → public)\n// 2. Set Cloud Console quota limits cho client này tránh abuse\n// 3. Monitor unusual auth traffic qua audit log Google Cloud\n// ─────────────────────────────────────────────────────────────────────────────\nconst GOOGLE_CLIENT_ID =\n \"1014766441755-i4jimivh5rd7vt8phuhmepmt45sovtph.apps.googleusercontent.com\";\nconst GOOGLE_CLIENT_SECRET = \"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\";\n\n// Restrict to the NAL Workspace domain. Enforced at TWO layers:\n// 1. ?hd=nal.vn appended to the verification URL — Google filters the picker\n// 2. Verify id_token.hd === HOSTED_DOMAIN after token exchange (defense-in-depth)\nexport const HOSTED_DOMAIN = \"nal.vn\";\n\nexport const SCOPES = [\"openid\", \"email\", \"profile\"];\n\nconst DEVICE_CODE_URL = \"https://oauth2.googleapis.com/device/code\";\nconst TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst REVOKE_URL = \"https://oauth2.googleapis.com/revoke\";\n\nexport interface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_url: string;\n expires_in: number;\n interval: number;\n}\n\nexport interface TokenResponse {\n access_token: string;\n refresh_token: string;\n id_token: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\nexport interface IdTokenClaims {\n email: string;\n email_verified: boolean;\n name?: string;\n hd?: string;\n exp: number;\n iss: string;\n aud: string;\n}\n\n// ── Step 2 of Command 01 spec: request device + user codes.\nexport async function requestDeviceCode(): Promise<DeviceCodeResponse> {\n const body = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n scope: SCOPES.join(\" \"),\n });\n const res = await fetch(DEVICE_CODE_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Device code request failed (${res.status}): ${text}`);\n }\n return (await res.json()) as DeviceCodeResponse;\n}\n\n// ── Step 5: poll the token endpoint until user authorises or expiry.\n// Returns the token response on success, null while still pending.\n// Throws on hard errors (access_denied, expired_token).\nexport async function pollForToken(deviceCode: string): Promise<TokenResponse | null> {\n const body = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n device_code: deviceCode,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n });\n const res = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (res.ok) {\n return (await res.json()) as TokenResponse;\n }\n\n // Google returns 4xx with a JSON {error} field for both pending and fatal states.\n let errorCode = \"\";\n try {\n const data = (await res.json()) as { error?: string };\n errorCode = data.error ?? \"\";\n } catch {\n errorCode = \"\";\n }\n\n if (errorCode === \"authorization_pending\" || errorCode === \"slow_down\") {\n return null;\n }\n if (errorCode === \"access_denied\") {\n throw new Error(\"User từ chối quyền truy cập\");\n }\n if (errorCode === \"expired_token\") {\n throw new Error(\"Hết hạn chờ (5 phút). Chạy lại 'avatar login'.\");\n }\n throw new Error(`OAuth token endpoint trả lỗi: ${errorCode || res.status}`);\n}\n\n// Decode JWT payload WITHOUT verifying signature. Safe here because we receive\n// the token directly from Google over HTTPS — no MITM surface.\nexport function decodeIdToken(idToken: string): IdTokenClaims {\n const parts = idToken.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"id_token format không hợp lệ\");\n }\n const payload = parts[1];\n if (!payload) throw new Error(\"id_token thiếu payload\");\n // Convert base64url to base64.\n const base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const json = Buffer.from(base64, \"base64\").toString(\"utf8\");\n return JSON.parse(json) as IdTokenClaims;\n}\n\n// ── Step 6: enforce hosted domain. Reject any non-@nal.vn account.\n// v1.14.0 SECURITY: Full JWT claims verify (iss + aud + exp + hd + email_verified).\n// Trước fix: chỉ check `hd` + `email_verified` → token forged với matching `hd`\n// nhưng issuer khác / audience khác / đã expired vẫn pass.\n// Sau fix: defense-in-depth — 5 invariants. Token Google sign nhưng signature\n// verify để skip (NAL không có private key Google). Claims-only check vẫn block\n// most attack vectors khi token đến trực tiếp từ Google endpoint qua HTTPS.\n\n// Issuer canonical Google. Accept cả 2 form: với https:// và không (theo OIDC spec).\nconst VALID_ISSUERS = new Set([\"https://accounts.google.com\", \"accounts.google.com\"]);\n// Clock skew tolerance — Google docs khuyến nghị 30-60s, dùng 60s.\nconst CLOCK_SKEW_SECONDS = 60;\n\nexport function verifyIdTokenClaims(claims: IdTokenClaims): void {\n // 1. Issuer phải là Google.\n if (!VALID_ISSUERS.has(claims.iss)) {\n throw new Error(`id_token issuer không hợp lệ: ${claims.iss} (expect: accounts.google.com)`);\n }\n // 2. Audience phải khớp client ID của Avatar.\n if (claims.aud !== GOOGLE_CLIENT_ID) {\n throw new Error(\n \"id_token audience không khớp client ID Avatar. Token có thể được sign cho app khác.\",\n );\n }\n // 3. Token chưa expire (với clock skew tolerance).\n const nowSec = Math.floor(Date.now() / 1000);\n if (claims.exp + CLOCK_SKEW_SECONDS < nowSec) {\n const ageSec = nowSec - claims.exp;\n throw new Error(`id_token đã hết hạn ${ageSec}s trước. Login lại để lấy token mới.`);\n }\n // 4. Hosted domain restriction NAL.\n if (claims.hd !== HOSTED_DOMAIN) {\n throw new Error(\n `Email không thuộc workspace NAL (yêu cầu @${HOSTED_DOMAIN}). Nhận: ${claims.email}`,\n );\n }\n // 5. Email Google verified.\n if (!claims.email_verified) {\n throw new Error(\"Email chưa được Google verify\");\n }\n}\n\n// Backward-compat alias — existing callers giữ tên cũ. Có thể deprecate sau.\nexport const verifyHostedDomain = verifyIdTokenClaims;\n\n// Convert OAuth token response + decoded claims into the on-disk UserConfig shape.\nexport function buildUserConfig(token: TokenResponse, claims: IdTokenClaims): UserConfig {\n const expiresAt = new Date(Date.now() + token.expires_in * 1000).toISOString();\n return {\n email: claims.email,\n name: claims.name ?? claims.email,\n access_token: token.access_token,\n refresh_token: token.refresh_token,\n expires_at: expiresAt,\n id_token: token.id_token,\n };\n}\n\n// Refresh flow: exchange refresh_token for a new access_token. Used by other\n// commands when they detect an expired token (see isTokenExpired).\nexport async function refreshAccessToken(refreshToken: string): Promise<{\n access_token: string;\n expires_in: number;\n id_token?: string;\n}> {\n const body = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n refresh_token: refreshToken,\n grant_type: \"refresh_token\",\n });\n const res = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Refresh token failed (${res.status}): ${text}`);\n }\n return (await res.json()) as { access_token: string; expires_in: number; id_token?: string };\n}\n\n// Revoke a token (used when login is rejected post-hoc, e.g. wrong domain).\nexport async function revokeToken(token: string): Promise<void> {\n const body = new URLSearchParams({ token });\n await fetch(REVOKE_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n }).catch(() => {\n // Best-effort revoke — don't fail the caller if revoke itself errors.\n });\n}\n\n// Build the verification URL with hd hint so Google pre-filters the account picker.\nexport function buildVerificationUrl(response: DeviceCodeResponse): string {\n const url = new URL(response.verification_url);\n url.searchParams.set(\"user_code\", response.user_code);\n url.searchParams.set(\"hd\", HOSTED_DOMAIN);\n return url.toString();\n}\n","// Bootstrap: load commander, register all 15 subcommands, parse argv.\n// Each command lives in src/commands/<name>.ts as a function that accepts the\n// commander Command instance and registers itself.\nimport { Command } from \"commander\";\nimport { registerAddCommand } from \"./commands/add-repo.js\";\nimport { registerAiCommand } from \"./commands/ai.js\";\nimport { registerDoctorCommand } from \"./commands/doctor.js\";\nimport { registerGitnexusCommand } from \"./commands/gitnexus.js\";\nimport { registerInitCommand } from \"./commands/init.js\";\nimport { registerLoginCommand } from \"./commands/login.js\";\nimport { registerMcpRunCommand } from \"./commands/mcp-run.js\";\nimport { registerPackCommand } from \"./commands/pack-status-and-version-check.js\";\nimport { registerRestoreCommand } from \"./commands/restore.js\";\nimport { registerReviewCommand } from \"./commands/review.js\";\nimport { registerScanCommand } from \"./commands/scan.js\";\nimport { registerStatusCommand } from \"./commands/status.js\";\nimport { registerSyncCommand } from \"./commands/sync.js\";\nimport { registerToolsCommand } from \"./commands/tools.js\";\nimport { registerUninstallCommand } from \"./commands/uninstall.js\";\nimport { printAvatarBanner, renderAvatarBanner } from \"./lib/avatar-ascii-banner.js\";\nimport { readCliVersion } from \"./lib/read-cli-version-from-package-json.js\";\n\nconst CLI_VERSION = readCliVersion();\n\nconst program = new Command();\n\nprogram\n .name(\"avatar\")\n .description(\"AI harness CLI for NAL Vietnam engineering\")\n .version(CLI_VERSION, \"-v, --version\", \"Hiển thị phiên bản Avatar CLI\")\n // Override mặc định của commander để in banner kèm version. Commander cho phép\n // custom output qua configureOutput nhưng cách đơn giản nhất là intercept ở argv.\n .addHelpText(\n \"beforeAll\",\n () =>\n `\\n${renderAvatarBanner({ tagline: `v${CLI_VERSION} · AI harness CLI for NAL Vietnam` })}\\n\\n`,\n );\n\n// In banner khi user gọi `avatar -v` / `avatar --version`. Banner đã chứa\n// version trong tagline nên ta short-circuit luôn, không để commander in thêm\n// dòng \"1.0.1\" dư thừa.\nconst isVersionCall = process.argv.includes(\"-v\") || process.argv.includes(\"--version\");\nif (isVersionCall) {\n printAvatarBanner({ tagline: `v${CLI_VERSION} · AI harness CLI for NAL Vietnam` });\n process.exit(0);\n}\n\n// Register all commands. Order matches Chapter 09 spec in implementation doc.\nregisterLoginCommand(program);\nregisterInitCommand(program);\nregisterSyncCommand(program);\nregisterScanCommand(program);\nregisterReviewCommand(program);\nregisterStatusCommand(program);\nregisterDoctorCommand(program);\nregisterRestoreCommand(program);\nregisterToolsCommand(program);\nregisterMcpRunCommand(program);\nregisterAiCommand(program);\nregisterGitnexusCommand(program);\nregisterPackCommand(program);\nregisterAddCommand(program);\nregisterUninstallCommand(program);\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n // Top-level error sink. Individual commands should handle their own errors;\n // anything reaching here is unexpected.\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`✗ Lỗi không xử lý được: ${msg}\\n`);\n process.exit(1);\n});\n","import { resolve } from \"node:path\";\n// `avatar add repo` — v3. Cài repo code vào src/<name> bằng git clone.\n//\n// 3 nguồn: ① repo có sẵn (URL), ② thư mục đã có (clone từ remote của nó),\n// ③ dự án mới (tạo repo GitHub rồi clone). Sau clone: ghi repos.json, hỏi\n// GitNexus index per-repo + wiki. Loop add tiếp.\nimport { confirm, input, select } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport {\n folderDirtyStatus,\n inferRepoNameFromUrl,\n readFolderRemoteUrl,\n} from \"../lib/clone-code-repo-into-src.js\";\nimport { cloneRepoWithRecovery } from \"../lib/clone-repo-with-recovery-menu.js\";\nimport { detectGitnexusInstallation } from \"../lib/detect-gitnexus-installation.js\";\nimport { ensureGitHubReady } from \"../lib/git-auth-and-install-orchestrator.js\";\nimport { resolveAvatarWorkspaceRootFromCwd } from \"../lib/resolve-avatar-workspace-root-from-cwd.js\";\nimport { runGitnexusAnalyze } from \"../lib/run-gitnexus-setup-and-analyze.js\";\nimport { runGitnexusWikiConditional } from \"../lib/run-gitnexus-wiki-conditional.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\nexport function registerAddCommand(program: Command): void {\n const add = program.command(\"add\").description(\"Thêm tài nguyên vào workspace\");\n add\n .command(\"repo\")\n .description(\"Clone 1 repo code vào src/ (repo có sẵn / thư mục / dự án mới)\")\n .option(\"--url <url>\", \"URL git repo có sẵn (bỏ qua prompt nguồn)\")\n .option(\"--name <name>\", \"Tên thư mục trong src/ (mặc định suy từ URL)\")\n .option(\"--yes\", \"Auto-confirm prompt\")\n .action(async (opts: { url?: string; name?: string; yes?: boolean }) => {\n try {\n await runAddRepo(opts);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nfunction ensureWorkspace(): string {\n const root = resolveAvatarWorkspaceRootFromCwd(process.cwd());\n if (!root) {\n log.error(\n \"Không tìm thấy Avatar workspace từ thư mục hiện tại.\\n Cần .claude/ + CLAUDE.md + src/. Chạy 'avatar init' trước.\",\n );\n process.exit(1);\n }\n return root;\n}\n\nasync function runAddRepo(opts: { url?: string; name?: string; yes?: boolean }): Promise<void> {\n const workspaceRoot = ensureWorkspace();\n\n let addMore = true;\n let firstUrl = opts.url;\n let firstName = opts.name;\n\n while (addMore) {\n const url = await resolveRepoUrl(firstUrl);\n const name = firstName ?? (await promptRepoName(url));\n\n log.info(`Clone ${url} → src/${name} ...`);\n // Clone với recovery menu: repo not found / permission / network → menu\n // retry / nhập URL khác / switch account / bỏ qua (thay vì git văng lỗi thô).\n const { cloned, skipped } = await cloneRepoWithRecovery({ workspaceRoot, url, name });\n\n if (skipped || !cloned) {\n log.dim(`Bỏ qua repo ${name}.`);\n } else {\n log.success(`✓ Đã clone src/${cloned.name}`);\n await maybeIndexRepo(workspaceRoot, cloned.path, opts.yes);\n log.success(`✓ Done repo: ${cloned.name}`);\n }\n\n // Reset cho vòng sau + hỏi add tiếp.\n firstUrl = undefined;\n firstName = undefined;\n if (opts.yes) break;\n addMore = await confirm({ message: \"Add repo khác nữa?\", default: false });\n }\n}\n\n// Resolve URL: nếu có --url thì dùng; else hỏi nguồn (3 lựa chọn).\nasync function resolveRepoUrl(presetUrl?: string): Promise<string> {\n if (presetUrl) return presetUrl;\n\n const source = await select({\n message: \"Nguồn repo?\",\n choices: [\n { name: \"1. Repo có sẵn (điền URL git)\", value: \"url\" as const },\n { name: \"2. Thư mục đã có trên máy (clone từ remote của nó)\", value: \"folder\" as const },\n { name: \"3. Dự án mới hoàn toàn (tạo repo GitHub)\", value: \"new\" as const },\n ],\n });\n\n if (source === \"url\") {\n return await input({\n message: \"URL git repo:\",\n validate: (v) => (v.trim().length > 0 ? true : \"URL bắt buộc\"),\n });\n }\n\n if (source === \"folder\") {\n const folderPath = resolve(\n await input({\n message: \"Đường dẫn folder:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Path bắt buộc\"),\n }),\n );\n // P9: cảnh báo nếu folder có uncommitted changes (clone-from-remote sẽ KHÔNG có chúng).\n const dirty = await folderDirtyStatus(folderPath);\n if (dirty) {\n log.warn(`⚠ Folder có thay đổi chưa commit: ${dirty.slice(0, 120)}`);\n log.warn(\" Clone lấy bản REMOTE → các thay đổi local này sẽ KHÔNG có trong src/<name>.\");\n const proceed = await confirm({\n message: \"Tiếp tục clone từ remote (bỏ thay đổi local chưa push)?\",\n default: false,\n });\n if (!proceed) {\n throw new Error(\n \"Hủy. Commit + push thay đổi trong folder gốc rồi chạy lại 'avatar add repo'.\",\n );\n }\n }\n const remote = await readFolderRemoteUrl(folderPath);\n if (!remote) {\n throw new Error(\n `Folder ${folderPath} không có remote origin → không clone được. Tạo remote (gh repo create) rồi thử lại.`,\n );\n }\n return remote;\n }\n\n // source === \"new\": tạo repo GitHub mới rồi clone.\n await ensureGitHubReady();\n const projectName = await input({\n message: \"Tên repo mới:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Tên bắt buộc\"),\n });\n const visibility = (await select({\n message: \"Visibility?\",\n choices: [\n { name: \"private (mặc định)\", value: \"private\" as const },\n { name: \"public\", value: \"public\" as const },\n ],\n })) as \"private\" | \"public\";\n\n // Tạo repo rỗng trên GitHub (không cần folder source — dùng gh repo create + clone).\n const { createEmptyGithubRepoAndGetUrl } = await import(\"../lib/create-empty-github-repo.js\");\n return await createEmptyGithubRepoAndGetUrl(projectName.trim(), visibility);\n}\n\nasync function promptRepoName(url: string): Promise<string> {\n const inferred = inferRepoNameFromUrl(url);\n return await input({ message: \"Tên thư mục trong src/:\", default: inferred });\n}\n\n// Hỏi GitNexus index repo này (per-repo, P7). Sau index, nếu có key AI thì hỏi\n// tạo wiki cho repo. Chỉ khi gitnexus đã cài.\n// workspaceRoot: đọc .claude/settings.json (credentials AI cho wiki)\n// repoPath: src/<name> — nơi analyze + chạy wiki\nasync function maybeIndexRepo(\n workspaceRoot: string,\n repoPath: string,\n autoYes?: boolean,\n): Promise<void> {\n const info = detectGitnexusInstallation();\n if (!info.installed) {\n log.dim(\"GitNexus chưa cài — skip index. Cài qua 'avatar gitnexus install'.\");\n return;\n }\n const shouldIndex = autoYes\n ? true\n : await confirm({ message: \"Index repo này bằng GitNexus?\", default: true });\n if (!shouldIndex) return;\n\n try {\n runGitnexusAnalyze(repoPath);\n log.success(` ✓ GitNexus indexed src/${repoPath.split(\"/\").pop()}`);\n } catch (err) {\n log.warn(\n ` ! GitNexus index fail (repo vẫn dùng được): ${err instanceof Error ? err.message : err}`,\n );\n return; // index fail → không tạo wiki được (wiki cần index).\n }\n\n // Sau index OK: nếu có key AI (settings.json) → hỏi tạo wiki cho repo.\n // runGitnexusWikiConditional tự detect provider + key, tự confirm (cost prompt),\n // tự skip nếu subscription mode (không key). Wiki tạo trong <repo>/.gitnexus/wiki/.\n try {\n // settings ở workspaceRoot, wiki chạy trong repoPath (src/<name>).\n const wiki = await runGitnexusWikiConditional(workspaceRoot, repoPath);\n if (wiki.ran) {\n log.success(` ✓ GitNexus wiki tạo cho src/${repoPath.split(\"/\").pop()}`);\n } else if (wiki.reason === \"subscription-mode\") {\n log.dim(\" (Subscription mode — không có API key cho wiki, skip.)\");\n }\n } catch (err) {\n log.warn(` ! Wiki fail (repo vẫn dùng được): ${err instanceof Error ? err.message : err}`);\n }\n}\n","import { spawnSync } from \"node:child_process\";\n// clone-repo-with-recovery-menu.ts\n//\n// v3: Wrap clone repo vào src/ với recovery menu khi gặp vấn đề (repo not found,\n// permission denied, network). Thay vì để git văng lỗi thô + crash, verify remote\n// TRƯỚC clone → classify lỗi → menu: retry / nhập URL khác / switch account / bỏ.\n//\n// Cũng dọn thư mục src/<name> dở nếu clone fail giữa chừng (tránh \"đã tồn tại\"\n// khi user retry).\nimport { join } from \"node:path\";\nimport { confirm, input, select } from \"@inquirer/prompts\";\nimport { type CloneRepoResult, cloneCodeRepoIntoSrc } from \"./clone-code-repo-into-src.js\";\nimport { removeRecursive } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { tryVerifyGitRemoteAccessible } from \"./verify-git-remote-accessible.js\";\n\n// Hướng dẫn theo từng loại lỗi remote.\nfunction explainRemoteFailure(url: string, reason: string, detail?: string): void {\n log.warn(`⛔ Không clone được ${url}`);\n switch (reason) {\n case \"not-found\":\n log.dim(\" → Repo không tồn tại (sai URL, hoặc repo chưa được tạo trên GitHub).\");\n break;\n case \"no-access\":\n log.dim(\n \" → Không có quyền truy cập (repo private + sai account), HOẶC repo không tồn tại.\\n\" +\n \" Kiểm tra: đang login đúng GitHub account chưa? (gh auth status)\",\n );\n break;\n case \"network\":\n log.dim(\" → Lỗi mạng (mất kết nối / DNS / timeout).\");\n break;\n default:\n log.dim(` → Lỗi không xác định${detail ? `: ${detail}` : \"\"}.`);\n }\n}\n\n// Trigger gh auth login interactive để switch account.\nfunction triggerGhAuthLogin(): void {\n log.info(\"Mở browser để switch GitHub account...\");\n const r = spawnSync(\"gh\", [\"auth\", \"login\", \"--web\"], { stdio: \"inherit\" });\n if (r.status !== 0) {\n log.warn(`gh auth login exit ${r.status}. Có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Menu recovery khi clone fail. Trả action user chọn.\nasync function promptCloneRecovery(\n reason: string,\n): Promise<\"retry\" | \"new-url\" | \"switch-account\" | \"skip\"> {\n const choices: Array<{ name: string; value: string }> = [\n { name: \"Thử lại (retry)\", value: \"retry\" },\n { name: \"Nhập URL repo khác\", value: \"new-url\" },\n ];\n // Chỉ offer switch account khi lỗi liên quan quyền truy cập.\n if (reason === \"no-access\") {\n choices.push({\n name: \"Switch GitHub account (gh auth login — mở browser)\",\n value: \"switch-account\",\n });\n }\n choices.push({ name: \"Bỏ qua repo này\", value: \"skip\" });\n\n return (await select({ message: \"Cách xử lý?\", choices })) as\n | \"retry\"\n | \"new-url\"\n | \"switch-account\"\n | \"skip\";\n}\n\nexport interface CloneWithRecoveryResult {\n cloned: CloneRepoResult | null; // null nếu user skip.\n skipped: boolean;\n}\n\n// Clone repo vào src/<name> với recovery menu. url/name có thể đổi qua menu.\n// Trả { cloned, skipped } — skipped=true nếu user chọn \"Bỏ qua repo này\".\nexport async function cloneRepoWithRecovery(args: {\n workspaceRoot: string;\n url: string;\n name: string;\n}): Promise<CloneWithRecoveryResult> {\n let url = args.url;\n let name = args.name;\n\n while (true) {\n // 1. Verify remote accessible TRƯỚC clone (tránh git văng lỗi thô + thư mục dở).\n const verify = tryVerifyGitRemoteAccessible(url);\n if (!verify.ok) {\n explainRemoteFailure(url, verify.reason ?? \"unknown\", verify.detail);\n const action = await promptCloneRecovery(verify.reason ?? \"unknown\");\n\n if (action === \"skip\") {\n log.dim(`Bỏ qua repo ${name}.`);\n return { cloned: null, skipped: true };\n }\n if (action === \"switch-account\") {\n triggerGhAuthLogin();\n continue; // re-verify với account mới.\n }\n if (action === \"new-url\") {\n url = await input({\n message: \"URL repo khác:\",\n validate: (v) => (v.trim().length > 0 ? true : \"URL bắt buộc\"),\n });\n // Hỏi đổi tên không (URL mới có thể tên khác).\n const renameToo = await confirm({\n message: `Đổi tên thư mục trong src/ theo URL mới? (hiện: ${name})`,\n default: false,\n });\n if (renameToo) {\n const { inferRepoNameFromUrl } = await import(\"./clone-code-repo-into-src.js\");\n name = await input({ message: \"Tên thư mục:\", default: inferRepoNameFromUrl(url) });\n }\n continue;\n }\n // action === \"retry\" → loop lại verify.\n continue;\n }\n\n // 2. Remote OK → clone. Vẫn try/catch phòng clone fail sau verify (race).\n try {\n const cloned = await cloneCodeRepoIntoSrc({ workspaceRoot: args.workspaceRoot, url, name });\n return { cloned, skipped: false };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`Clone fail: ${message}`);\n // Dọn thư mục src/<name> dở (nếu git clone tạo ra rồi fail).\n await removeRecursive(join(args.workspaceRoot, \"src\", name)).catch(() => {});\n\n const action = await promptCloneRecovery(\"unknown\");\n if (action === \"skip\") return { cloned: null, skipped: true };\n if (action === \"switch-account\") {\n triggerGhAuthLogin();\n continue;\n }\n if (action === \"new-url\") {\n url = await input({\n message: \"URL repo khác:\",\n validate: (v) => (v.trim().length > 0 ? true : \"URL bắt buộc\"),\n });\n continue;\n }\n // retry → loop.\n }\n }\n}\n","// Verify một remote URL có accessible không bằng `git ls-remote <url> HEAD`\n// với timeout 5 giây. Tách ra để init flow check sớm — fail-fast nếu URL sai.\n//\n// v1.2.4: Capture stderr để caller diagnose root cause (private no access\n// vs URL wrong vs network). Thêm `tryVerifyGitRemoteAccessible` non-throwing\n// variant trả structured result cho UX recovery flow.\nimport { spawnSync } from \"node:child_process\";\n\nconst TIMEOUT_MS = 5_000;\n\nexport type RemoteFailReason =\n | \"timeout\"\n | \"no-access\" // 403/auth required (private repo + wrong user)\n | \"not-found\" // 404 URL wrong hoặc repo bị xóa\n | \"network\" // DNS / unreachable\n | \"unknown\";\n\nexport interface RemoteAccessResult {\n ok: boolean;\n reason?: RemoteFailReason;\n detail?: string; // stderr sample để log debug\n}\n\nexport class RemoteNotAccessibleError extends Error {\n reason: RemoteFailReason;\n detail?: string;\n url: string;\n constructor(url: string, reason: RemoteFailReason, detail?: string) {\n super(`Không truy cập được remote ${url}: ${reason}${detail ? ` (${detail})` : \"\"}`);\n this.name = \"RemoteNotAccessibleError\";\n this.reason = reason;\n this.detail = detail;\n this.url = url;\n }\n}\n\n// Classify git ls-remote stderr thành reason code chuẩn hóa.\n// Patterns dựa theo git output thực tế trên GitHub HTTPS auth-required scenarios.\nfunction classifyRemoteError(stderr: string): RemoteFailReason {\n const text = stderr.toLowerCase();\n if (\n text.includes(\"authentication\") ||\n text.includes(\"could not read username\") ||\n text.includes(\"permission denied\") ||\n text.includes(\"403\") ||\n text.includes(\"access denied\") ||\n text.includes(\"repository not found\") // GitHub trả \"not found\" cho cả private no-access (bảo mật)\n ) {\n return \"no-access\";\n }\n if (text.includes(\"404\") || text.includes(\"does not exist\")) {\n return \"not-found\";\n }\n if (\n text.includes(\"could not resolve host\") ||\n text.includes(\"network\") ||\n text.includes(\"connection refused\") ||\n text.includes(\"connection timed out\")\n ) {\n return \"network\";\n }\n return \"unknown\";\n}\n\n// Non-throwing variant — trả structured result cho caller decide flow.\n// Recommend dùng cho flow cần fallback recovery (vd account switch).\n//\n// v1.13.0 fix: Check `r.error` TRƯỚC khi classify. spawnSync trả error khi:\n// - Binary not found (ENOENT) — git chưa cài\n// - Permission denied (EACCES)\n// - Killed by signal khác SIGTERM\n// Trước fix: r.status=null + stderr=\"\" → classify(\"\") → \"unknown\" → user thấy\n// \"Lỗi không xác định\" thay vì \"git chưa cài\". Pattern này đã chuẩn ở\n// check-claude-code-subscription-and-quota.ts:38, sync hóa cho consistency.\nexport function tryVerifyGitRemoteAccessible(url: string): RemoteAccessResult {\n const r = spawnSync(\"git\", [\"ls-remote\", \"--exit-code\", url, \"HEAD\"], {\n encoding: \"utf8\",\n timeout: TIMEOUT_MS,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.error) {\n const err = r.error as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\") {\n return {\n ok: false,\n reason: \"network\",\n detail: \"git binary không tìm thấy — cài git rồi retry\",\n };\n }\n if (err.code === \"ETIMEDOUT\") {\n return { ok: false, reason: \"timeout\", detail: `git ls-remote > ${TIMEOUT_MS / 1000}s` };\n }\n return { ok: false, reason: \"unknown\", detail: err.message.slice(0, 300) };\n }\n if (r.status === 0) return { ok: true };\n if (r.signal === \"SIGTERM\") {\n return { ok: false, reason: \"timeout\", detail: `git ls-remote > ${TIMEOUT_MS / 1000}s` };\n }\n const stderr = (r.stderr || \"\").trim();\n const reason = classifyRemoteError(stderr);\n return { ok: false, reason, detail: stderr.slice(0, 300) };\n}\n\n// Throwing variant — backward compat cho existing callers.\n// Caller catch RemoteNotAccessibleError nếu cần handle.\nexport function verifyGitRemoteAccessible(url: string): void {\n const result = tryVerifyGitRemoteAccessible(url);\n if (result.ok) return;\n throw new RemoteNotAccessibleError(url, result.reason ?? \"unknown\", result.detail);\n}\n","// Detect GitNexus CLI (\"gitnexus\" binary) trên PATH + parse SemVer version.\n// Trả structured info để các module sau (install / setup / analyze) decide flow.\n// Cross-platform: Unix dùng `which`, Windows dùng `where`.\n//\n// Pattern identical với detect-claude-code-installation.ts — reuse design.\nimport { spawnSync } from \"node:child_process\";\nimport { detectHostPlatform } from \"./detect-host-platform.js\";\n\nexport interface GitnexusInstallationInfo {\n installed: boolean;\n version: string | null; // SemVer string khi detect được, vd \"1.6.5\"\n path: string | null; // Absolute path đến binary nếu tìm thấy\n}\n\n// Timeout cho `gitnexus --version` — nếu binary hang thì abort tránh block init.\nconst VERSION_PROBE_TIMEOUT_MS = 5000;\n\n// Regex SemVer chấp nhận extra suffix vd \"1.6.5 (GitNexus)\" hoặc \"gitnexus 1.6.5\".\nconst SEMVER_REGEX = /(\\d+\\.\\d+\\.\\d+)/;\n\n// Probe binary location qua `which` (POSIX) hoặc `where` (Windows).\n// Cả 2 đều là external executable → KHÔNG cần shell:true → tránh DEP0190 Node 23+.\nfunction probeGitnexusBinaryPath(): string | null {\n const isWindows = detectHostPlatform() === \"win32\";\n const probeCmd = isWindows ? \"where\" : \"which\";\n const result = spawnSync(probeCmd, [\"gitnexus\"], { encoding: \"utf8\" });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n if (!out) return null;\n // `where` có thể list nhiều dòng — lấy dòng đầu.\n // out đã verified non-empty (line 30) → split[0] luôn tồn tại.\n return out.split(/\\r?\\n/)[0]!.trim();\n}\n\n// Parse `gitnexus --version` output → SemVer. Null nếu không match.\nfunction probeGitnexusVersion(): string | null {\n const result = spawnSync(\"gitnexus\", [\"--version\"], {\n encoding: \"utf8\",\n timeout: VERSION_PROBE_TIMEOUT_MS,\n });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n const match = SEMVER_REGEX.exec(out);\n return match?.[1] ?? null;\n}\n\n// Main API: detect GitNexus installation state.\n// Không throw — luôn return structured info để caller branch logic.\n//\n// v1.13.0 perf: Module-level cache. Init flow gọi 2-3 lần → save 500ms-1.5s.\n// Caller invalidate qua `invalidateGitnexusInstallationCache()` sau install.\nlet cachedInfo: GitnexusInstallationInfo | null = null;\n\nexport function detectGitnexusInstallation(): GitnexusInstallationInfo {\n if (cachedInfo !== null) return cachedInfo;\n const path = probeGitnexusBinaryPath();\n if (!path) {\n cachedInfo = { installed: false, version: null, path: null };\n return cachedInfo;\n }\n const version = probeGitnexusVersion();\n cachedInfo = { installed: true, version, path };\n return cachedInfo;\n}\n\nexport function invalidateGitnexusInstallationCache(): void {\n cachedInfo = null;\n}\n","// Wrapper mỏng quanh os.platform() để các module khác switch theo OS dễ hơn\n// và test dễ mock.\nimport { platform } from \"node:os\";\n\nexport type HostPlatform = \"darwin\" | \"linux\" | \"win32\" | \"unsupported\";\n\n// Trả về tên platform chuẩn hoá. Mọi giá trị khác 3 OS chính → \"unsupported\".\nexport function detectHostPlatform(): HostPlatform {\n const p = platform();\n if (p === \"darwin\" || p === \"linux\" || p === \"win32\") return p;\n return \"unsupported\";\n}\n","// Kiểm tra gh CLI đã login chưa. `gh auth status` exit 0 = OK.\n// Không parse stdout vì format thay đổi giữa các bản gh.\nimport { spawnSync } from \"node:child_process\";\n\nexport type GhAuthState = \"not-installed\" | \"not-authenticated\" | \"authenticated\";\n\nexport function checkGhCliAuthStatus(): GhAuthState {\n // Probe binary trước. spawnSync với gh không tồn tại trả ENOENT.\n const r = spawnSync(\"gh\", [\"auth\", \"status\"], { stdio: \"ignore\" });\n if (r.error && (r.error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return \"not-installed\";\n }\n return r.status === 0 ? \"authenticated\" : \"not-authenticated\";\n}\n","// Detect package manager để cài binary hệ thống (gh CLI). Order matters:\n// brew (macOS preferred) → winget (Windows) → apt/dnf/pacman (Linux distros).\n// Returns null nếu không tìm được PM nào — caller phải fail-fast.\nimport { spawnSync } from \"node:child_process\";\nimport { detectHostPlatform } from \"./detect-host-platform.js\";\n\nexport type PackageManager = \"brew\" | \"apt\" | \"dnf\" | \"pacman\" | \"winget\";\n\n// Probe binary có trong PATH không. Dùng `command -v` (POSIX) hoặc `where` (Win).\nfunction hasBinary(name: string): boolean {\n const platform = detectHostPlatform();\n const probe = platform === \"win32\" ? \"where\" : \"command\";\n const args = platform === \"win32\" ? [name] : [\"-v\", name];\n // spawnSync với shell=true để `command -v` (builtin) hoạt động.\n const r = spawnSync(probe, args, {\n shell: platform !== \"win32\",\n stdio: \"ignore\",\n });\n return r.status === 0;\n}\n\n// Trả về PM đầu tiên có sẵn theo thứ tự ưu tiên phù hợp với OS.\nexport function detectPackageManager(): PackageManager | null {\n const platform = detectHostPlatform();\n const candidates: PackageManager[] =\n platform === \"darwin\"\n ? [\"brew\"]\n : platform === \"win32\"\n ? [\"winget\"]\n : platform === \"linux\"\n ? [\"apt\", \"dnf\", \"pacman\"]\n : [];\n for (const pm of candidates) {\n if (hasBinary(pm)) return pm;\n }\n return null;\n}\n","// Recovery handler khi verify remote fail — thay vì throw văng init, prompt user\n// xử lý phù hợp với reason code:\n// - no-access: có thể wrong gh account → offer switch (gh auth login)\n// - not-found: URL sai → user re-nhập URL đúng (v1.2.7)\n// - network: user check mạng/VPN, retry\n// - timeout: tương tự network\n//\n// Loop retry cho phép user thử nhiều lần (switch account, re-nhập URL, accept invite, ...).\n// Return { resolvedUrl } khi access OK (URL có thể khác input nếu user re-nhập).\n// Throw RemoteAccessAbortedError khi user abort.\n//\n// v1.2.7: Thêm option \"Nhập lại URL\" để user fix URL sai mà không phải abort init.\nimport { spawnSync } from \"node:child_process\";\nimport { input, select } from \"@inquirer/prompts\";\nimport { resetFolderGitAndCreateNewRemoteUnderCurrentUser } from \"./reset-folder-git-and-create-new-remote-under-current-user.js\";\nimport { log } from \"./terminal-logger.js\";\nimport {\n type RemoteFailReason,\n tryVerifyGitRemoteAccessible,\n} from \"./verify-git-remote-accessible.js\";\n\n// Custom error báo user abort recovery — caller catch để exit 0 graceful\n// (giống pattern TeamPackAccessAbortedError v1.2.3).\nexport class RemoteAccessAbortedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"RemoteAccessAbortedError\";\n }\n}\n\n// Resolve gh user hiện tại (nếu có) để show context cho user khi prompt.\nfunction getCurrentGhUser(): string | null {\n const r = spawnSync(\"gh\", [\"api\", \"user\", \"--jq\", \".login\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.status !== 0) return null;\n return r.stdout.trim() || null;\n}\n\n// Trigger `gh auth login` interactive — block đến khi user complete browser.\n// Sau khi switch, gh CLI dùng account mới cho mọi command sau.\nfunction triggerGhAuthLoginInteractive(): void {\n log.info(\"Mở browser để switch GitHub account...\");\n const r = spawnSync(\"gh\", [\"auth\", \"login\", \"--web\"], { stdio: \"inherit\" });\n if (r.status !== 0) {\n log.warn(`gh auth login exit ${r.status}. Bạn có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Hint theo reason code — giúp user biết phải làm gì cụ thể.\nfunction getReasonHint(reason: RemoteFailReason, url: string, ghUser: string | null): string {\n switch (reason) {\n case \"no-access\":\n return ghUser\n ? `Repo có thể private và account '${ghUser}' không có quyền access. Switch sang account có quyền, hoặc xin invite từ owner repo.`\n : \"Repo có thể private và gh CLI chưa login. Login trước rồi retry.\";\n case \"not-found\":\n return `URL có thể sai chính tả, hoặc repo đã bị xóa/rename. Nhập lại URL đúng. Check: ${url}`;\n case \"network\":\n return \"Không kết nối được GitHub. Check mạng / VPN / firewall.\";\n case \"timeout\":\n return \"Network chậm > 5s. Check mạng rồi retry.\";\n default:\n return \"Lỗi không xác định. URL có thể sai — nhập lại, hoặc check gh auth status.\";\n }\n}\n\n// Validate URL format git remote (http/https/ssh github).\nfunction isValidGitUrl(url: string): boolean {\n const trimmed = url.trim();\n if (!trimmed) return false;\n return (\n /^https?:\\/\\/[\\w.@/-]+$/.test(trimmed) ||\n /^git@[\\w.-]+:[\\w./-]+\\.git$/.test(trimmed) ||\n /^[\\w.-]+\\/[\\w.-]+$/.test(trimmed) // owner/repo shorthand\n );\n}\n\n// Show menu recovery options theo reason. Loop cho đến khi user pick abort\n// hoặc remote accessible. Return resolved URL (có thể khác input nếu user re-nhập).\n// Throw RemoteAccessAbortedError nếu user abort.\n//\n// v1.2.7: Thêm option \"Nhập lại URL\" để user fix URL sai mà không phải abort init.\n// v1.12.0: Thêm option \"Reset & make folder mine\" — chỉ enable khi caller pass\n// folderPath (tức flow existing-folder). Cho phép user clone folder team khác\n// xuống làm khởi đầu project mới của mình mà không bị block bởi remote không access.\nexport async function handleRemoteAccessFailureWithAccountSwitch(args: {\n url: string;\n initialReason: RemoteFailReason;\n initialDetail?: string;\n // v1.12.0: Khi caller pass folderPath (existing-folder flow), enable option\n // \"Reset folder & tạo repo mới\". Khi không có (existing-remote flow), folder\n // chưa tồn tại nên option này không áp dụng.\n folderPath?: string;\n // v1.12.0: Skip prompt khi caller đã có visibility từ --repo-visibility.\n defaultVisibility?: \"private\" | \"public\";\n}): Promise<{ resolvedUrl: string }> {\n let currentUrl = args.url;\n let reason = args.initialReason;\n let detail = args.initialDetail;\n\n while (true) {\n const ghUser = getCurrentGhUser();\n log.warn(`Không truy cập được ${currentUrl}`);\n log.dim(` Lý do: ${reason}${detail ? ` — ${detail.slice(0, 150)}` : \"\"}`);\n log.info(getReasonHint(reason, currentUrl, ghUser));\n if (ghUser) log.dim(` gh CLI hiện đang login: ${ghUser}`);\n\n // Choices base — 4 option có sẵn từ v1.2.7.\n const choices: Array<{\n name: string;\n value: \"re-input-url\" | \"switch\" | \"retry\" | \"reset-folder\" | \"abort\";\n }> = [\n {\n name: \"Nhập lại URL đúng (recommended khi URL sai chính tả)\",\n value: \"re-input-url\",\n },\n {\n name: \"Switch GitHub account (gh auth login — mở browser)\",\n value: \"switch\",\n },\n {\n name: \"Tôi vừa fix (accept invite / sửa permission) — retry verify\",\n value: \"retry\",\n },\n ];\n\n // v1.12.0: Conditional option — chỉ show khi caller pass folderPath.\n // Common case: clone folder team khác xuống, không có quyền push lên repo gốc,\n // muốn dùng làm khởi đầu project riêng.\n if (args.folderPath) {\n choices.push({\n name: \"Reset folder & tạo repo MỚI dưới account của tôi (backup .git cũ)\",\n value: \"reset-folder\",\n });\n }\n\n choices.push({\n name: \"Tạm ngưng init — chạy lại 'avatar init' sau\",\n value: \"abort\",\n });\n\n const action = await select({ message: \"Cách xử lý?\", choices });\n\n if (action === \"abort\") {\n throw new RemoteAccessAbortedError(\n `User chọn tạm ngưng. Fix access ${currentUrl} rồi chạy lại 'avatar init'.`,\n );\n }\n\n if (action === \"reset-folder\") {\n // Defensive: folderPath required cho branch này — đã guard ở choices push,\n // nhưng TS không narrow qua select() callback.\n if (!args.folderPath) {\n log.warn(\"Reset folder cần folderPath — internal error.\");\n continue;\n }\n try {\n const reset = await resetFolderGitAndCreateNewRemoteUnderCurrentUser({\n folderPath: args.folderPath,\n visibility: args.defaultVisibility,\n });\n log.success(`Folder đã reset. Backup tại: ${reset.backupPath}`);\n log.success(`Remote mới: ${reset.newRemoteUrl}`);\n return { resolvedUrl: reset.newRemoteUrl };\n } catch (err) {\n log.warn(`Reset folder thất bại: ${(err as Error).message}`);\n // Loop tiếp — user có thể chọn option khác.\n continue;\n }\n }\n\n if (action === \"re-input-url\") {\n const newUrl = await input({\n message:\n \"URL git remote (https://github.com/owner/repo.git hoặc git@github.com:owner/repo.git):\",\n default: currentUrl,\n validate: (v) => isValidGitUrl(v) || \"URL không đúng format git remote\",\n });\n currentUrl = newUrl.trim();\n }\n\n if (action === \"switch\") {\n triggerGhAuthLoginInteractive();\n }\n\n // Re-verify (cả 3 case re-input-url + switch + retry).\n log.info(`Verify remote lại: ${currentUrl}...`);\n const result = tryVerifyGitRemoteAccessible(currentUrl);\n if (result.ok) {\n log.success(`Remote accessible: ${currentUrl}`);\n return { resolvedUrl: currentUrl };\n }\n // Fail tiếp — update reason + loop.\n reason = result.reason ?? \"unknown\";\n detail = result.detail;\n }\n}\n","// Recovery action cho scenario \"folder code clone từ team khác, user không có\n// quyền push lên repo gốc nhưng muốn dùng folder làm khởi đầu cho project mới\n// của mình\".\n//\n// Flow:\n// 1. Backup .git → .git.backup-{YYMMDD-HHMM} (safe undo nếu nhầm)\n// 2. git init mới trên folder\n// 3. Optional: gh repo create dưới owner mới (current gh user hoặc org)\n// 4. Trả URL mới để caller dùng cho submodule add\n//\n// User trigger qua menu trong handleRemoteAccessFailureWithAccountSwitch khi\n// detect \"no-access\" trên existing-folder flow.\n\nimport { spawnSync } from \"node:child_process\";\nimport { promises as fs } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport { createGithubRemoteFromFolder } from \"./create-github-remote-from-folder.js\";\nimport { pathExists } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\nimport type { RepoVisibility } from \"./validate-repo-name-and-visibility.js\";\n\n// Timestamp YYMMDD-HHMM cho backup folder — readable + sortable.\nfunction backupTimestamp(): string {\n const d = new Date();\n return (\n `${d.getFullYear().toString().slice(-2)}` +\n `${String(d.getMonth() + 1).padStart(2, \"0\")}` +\n `${String(d.getDate()).padStart(2, \"0\")}-` +\n `${String(d.getHours()).padStart(2, \"0\")}` +\n `${String(d.getMinutes()).padStart(2, \"0\")}`\n );\n}\n\nexport interface ResetFolderResult {\n newRemoteUrl: string; // HTTPS URL của repo mới\n backupPath: string; // Path tới .git.backup-{stamp}\n}\n\n// Backup .git → .git.backup-{stamp} thay vì xóa hẳn.\n// User restore được nếu nhầm: `mv .git.backup-XXX .git`.\nasync function backupExistingDotGit(folderPath: string): Promise<string> {\n const gitDir = join(folderPath, \".git\");\n if (!(await pathExists(gitDir))) {\n throw new Error(`.git không tồn tại ở ${folderPath} — không cần reset.`);\n }\n const backupName = `.git.backup-${backupTimestamp()}`;\n const backupPath = join(folderPath, backupName);\n await fs.rename(gitDir, backupPath);\n log.success(`Backup .git → ${backupName}`);\n return backupPath;\n}\n\n// git init + add all + initial commit để folder ready cho gh repo create.\nfunction reinitGitInFolder(folderPath: string): void {\n const r1 = spawnSync(\"git\", [\"-C\", folderPath, \"init\", \"-b\", \"main\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r1.status !== 0) {\n throw new Error(`git init thất bại: ${r1.stderr || r1.stdout}`);\n }\n log.success(\"Git init mới (branch main)\");\n\n // Stage all + commit để gh repo create có thể push.\n spawnSync(\"git\", [\"-C\", folderPath, \"add\", \"-A\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const r3 = spawnSync(\n \"git\",\n [\"-C\", folderPath, \"commit\", \"-m\", \"chore: initial commit from existing folder\"],\n { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n );\n if (r3.status !== 0) {\n // Commit fail OK nếu folder rỗng — log warn không throw.\n log.warn(`git commit thất bại (folder có thể rỗng): ${(r3.stderr || \"\").slice(0, 200)}`);\n } else {\n log.success(\"Initial commit\");\n }\n}\n\n// Main entry: backup .git + reinit + tạo remote mới + return URL.\n//\n// repoName: default = basename(folderPath). Caller có thể override qua opts.\n// autoYes=true: skip mọi confirm (CI mode) — visibility default \"private\".\nexport async function resetFolderGitAndCreateNewRemoteUnderCurrentUser(opts: {\n folderPath: string;\n repoName?: string;\n visibility?: RepoVisibility;\n org?: string; // default = current gh user\n autoYes?: boolean;\n}): Promise<ResetFolderResult> {\n const folderName = basename(opts.folderPath);\n const repoName = opts.repoName ?? folderName;\n\n // Confirm trước khi destructive op — clear UX về backup behavior.\n if (!opts.autoYes) {\n const confirmed = await confirm({\n message: `Folder '${folderName}' sẽ được reset:\\n 1. Backup .git → .git.backup-{timestamp}\\n 2. Git init mới (branch main, initial commit)\\n 3. Tạo GitHub repo mới '${repoName}' dưới account của bạn\\nTiếp tục?`,\n default: true,\n });\n if (!confirmed) {\n throw new Error(\"User abort reset folder.\");\n }\n }\n\n const visibility =\n opts.visibility ??\n (opts.autoYes\n ? \"private\"\n : ((await select({\n message: \"Visibility cho repo mới?\",\n choices: [\n { name: \"private (mặc định)\", value: \"private\" as const },\n { name: \"public\", value: \"public\" as const },\n ],\n })) as RepoVisibility));\n\n // Step 1: Backup .git.\n const backupPath = await backupExistingDotGit(opts.folderPath);\n\n // Step 2: Reinit + commit.\n reinitGitInFolder(opts.folderPath);\n\n // Step 3: Tạo remote qua gh + push initial commit.\n const urls = createGithubRemoteFromFolder({\n folder: opts.folderPath,\n name: repoName,\n visibility,\n org: opts.org,\n });\n\n return {\n newRemoteUrl: urls.httpsUrl,\n backupPath,\n };\n}\n","// Wrapper around `gh repo create <org>/<name> --<vis> --source=<folder>\n// --remote=origin --push`. Stream stdio để user thấy gh prompt nếu có.\nimport { spawnSync } from \"node:child_process\";\nimport type { RepoVisibility } from \"./validate-repo-name-and-visibility.js\";\n\nexport class RepoAlreadyExistsError extends Error {\n constructor(fullName: string) {\n super(`Repo \"${fullName}\" đã tồn tại trên GitHub. Đổi tên hoặc xóa repo cũ.`);\n this.name = \"RepoAlreadyExistsError\";\n }\n}\n\nexport interface ExecuteGhRepoCreateInput {\n folder: string;\n org: string;\n name: string;\n visibility: RepoVisibility;\n}\n\nexport interface ExecuteGhRepoCreateOutput {\n sshUrl: string;\n httpsUrl: string;\n}\n\nexport function executeGhRepoCreate(input: ExecuteGhRepoCreateInput): ExecuteGhRepoCreateOutput {\n const fullName = `${input.org}/${input.name}`;\n const args = [\n \"repo\",\n \"create\",\n fullName,\n `--${input.visibility}`,\n \"--source\",\n input.folder,\n \"--remote\",\n \"origin\",\n \"--push\",\n ];\n const r = spawnSync(\"gh\", args, { stdio: \"inherit\" });\n if (r.status !== 0) {\n // gh thường in \"GraphQL: Name already exists\" cho duplicate. Không parse\n // stdout (vì inherit) — surface generic error, user sẽ thấy stderr của gh.\n if (r.status === 1) {\n throw new RepoAlreadyExistsError(fullName);\n }\n throw new Error(`gh repo create thất bại (exit ${r.status})`);\n }\n return {\n sshUrl: `git@github.com:${fullName}.git`,\n httpsUrl: `https://github.com/${fullName}.git`,\n };\n}\n","// Orchestrator phase 4: validate input → gọi gh repo create → return URLs.\n// Caller chịu trách nhiệm đảm bảo gh đã auth (gọi ensureGitHubReady trước).\nimport { type ExecuteGhRepoCreateOutput, executeGhRepoCreate } from \"./execute-gh-repo-create.js\";\nimport { resolveGithubUsernameDefault } from \"./resolve-github-username-default.js\";\nimport { log } from \"./terminal-logger.js\";\nimport {\n type RepoVisibility,\n validateRepoName,\n validateRepoVisibility,\n} from \"./validate-repo-name-and-visibility.js\";\n\nexport interface CreateGithubRemoteInput {\n folder: string;\n name: string;\n visibility: RepoVisibility;\n org?: string; // mặc định = GitHub login của user\n}\n\nexport function createGithubRemoteFromFolder(\n input: CreateGithubRemoteInput,\n): ExecuteGhRepoCreateOutput {\n validateRepoName(input.name);\n validateRepoVisibility(input.visibility);\n\n const org = input.org ?? resolveGithubUsernameDefault();\n log.info(`Tạo GitHub repo ${org}/${input.name} (${input.visibility})...`);\n\n const urls = executeGhRepoCreate({\n folder: input.folder,\n org,\n name: input.name,\n visibility: input.visibility,\n });\n\n log.success(`Đã tạo: ${urls.sshUrl}`);\n return urls;\n}\n","// Validate repo name + visibility trước khi gọi `gh repo create`. Fail-fast với\n// thông báo Vietnamese rõ ràng. GitHub repo name spec: alphanumeric, dash,\n// underscore, dot. 1-100 chars.\nconst REPO_NAME_REGEX = /^[a-zA-Z0-9._-]{1,100}$/;\n\nexport type RepoVisibility = \"private\" | \"public\";\n\nexport class InvalidRepoNameError extends Error {\n constructor(name: string) {\n super(\n `Tên repo \"${name}\" không hợp lệ. Chỉ dùng chữ/số/dấu chấm/gạch/underscore, dài 1-100 ký tự.`,\n );\n this.name = \"InvalidRepoNameError\";\n }\n}\n\nexport function validateRepoName(name: string): void {\n if (!REPO_NAME_REGEX.test(name)) {\n throw new InvalidRepoNameError(name);\n }\n}\n\nexport function validateRepoVisibility(v: string): asserts v is RepoVisibility {\n if (v !== \"private\" && v !== \"public\") {\n throw new Error(`Visibility phải là \"private\" hoặc \"public\", nhận: \"${v}\"`);\n }\n}\n","// Cài `gh` CLI qua package manager đã detect. Stream stdio để user thấy progress.\n// Throws nếu cài fail hoặc PM trả non-zero.\nimport { spawnSync } from \"node:child_process\";\nimport type { PackageManager } from \"./detect-package-manager.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Map PM → command + args để cài gh. apt/dnf cần sudo; brew/pacman/winget thì không.\nconst INSTALL_COMMANDS: Record<PackageManager, { cmd: string; args: string[] }> = {\n brew: { cmd: \"brew\", args: [\"install\", \"gh\"] },\n apt: { cmd: \"sudo\", args: [\"apt-get\", \"install\", \"-y\", \"gh\"] },\n dnf: { cmd: \"sudo\", args: [\"dnf\", \"install\", \"-y\", \"gh\"] },\n pacman: { cmd: \"sudo\", args: [\"pacman\", \"-S\", \"--noconfirm\", \"github-cli\"] },\n winget: { cmd: \"winget\", args: [\"install\", \"--id\", \"GitHub.cli\", \"-e\", \"--silent\"] },\n};\n\nexport function installGhCliViaPackageManager(pm: PackageManager): void {\n const spec = INSTALL_COMMANDS[pm];\n log.info(`Đang cài gh CLI qua ${pm}...`);\n const r = spawnSync(spec.cmd, spec.args, { stdio: \"inherit\" });\n if (r.status !== 0) {\n throw new Error(`Cài gh CLI thất bại qua ${pm} (exit ${r.status}). Cài tay rồi chạy lại.`);\n }\n log.success(\"Đã cài gh CLI\");\n}\n","// Generic recovery prompt cho các bước có thể recover trong init flow.\n// Dùng pattern này thay vì throw thẳng để user luôn có choice (retry/skip/abort).\n//\n// 3 mode tùy task:\n// - retry-only: task không thể skip (vd login fail) → \"Try again\" / \"Abort\"\n// - retry-skip: task có thể bỏ qua (vd workspace remote fail) → \"Retry\" / \"Skip & continue\" / \"Abort\"\n// - manual-fix: task cần user tự fix tay (vd workspace path conflict) → \"Provide value\" / \"Abort\"\n//\n// Loop retry — caller gọi trong while loop, user chỉ exit khi pick abort hoặc success.\nimport { input, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\n// User chọn abort recovery — caller catch để exit 0 graceful.\nexport class UserAbortedRecoveryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"UserAbortedRecoveryError\";\n }\n}\n\nexport type RecoveryAction = \"retry\" | \"skip\" | \"abort\";\n\ninterface PromptRetryOrSkipArgs {\n // Tiêu đề task fail (vd \"Cài đặt gh CLI\").\n taskName: string;\n // Lý do fail (extracted từ error message hoặc custom).\n reason: string;\n // Có cho phép skip task này không? false = chỉ Retry / Abort.\n allowSkip?: boolean;\n // Custom hint hiển thị trước menu (vd \"Check mạng, VPN\").\n hint?: string;\n}\n\n// Show menu Retry/Skip/Abort. Trả về action user chọn.\n// Caller responsibility: implement retry logic + handle skip semantic theo task.\nexport async function promptRetryOrSkip(args: PromptRetryOrSkipArgs): Promise<RecoveryAction> {\n log.warn(`${args.taskName} thất bại: ${args.reason}`);\n if (args.hint) log.info(args.hint);\n\n const choices: Array<{ name: string; value: RecoveryAction }> = [\n { name: \"Thử lại (Retry)\", value: \"retry\" },\n ];\n if (args.allowSkip) {\n choices.push({ name: \"Bỏ qua bước này và tiếp tục (Skip)\", value: \"skip\" });\n }\n choices.push({ name: \"Tạm ngưng init — chạy lại sau (Abort)\", value: \"abort\" });\n\n return (await select({\n message: \"Cách xử lý?\",\n choices,\n })) as RecoveryAction;\n}\n\n// Prompt user input manual value khi automated detection fail.\n// Vd: workspace name conflict → user tự nhập tên khác.\n// Trả về giá trị user nhập, hoặc throw UserAbortedRecoveryError nếu abort.\nexport async function promptManualValueOrAbort(args: {\n message: string;\n validate?: (v: string) => boolean | string;\n abortMessage?: string;\n}): Promise<string> {\n // Allow user nhập empty để abort (Ctrl+C vẫn work).\n // Hoặc nhập value hợp lệ.\n const value = await input({\n message: args.message,\n validate: args.validate ?? ((v) => v.trim().length > 0 || \"Giá trị không được rỗng\"),\n });\n if (!value.trim()) {\n throw new UserAbortedRecoveryError(\n args.abortMessage ?? \"User để rỗng giá trị — abort recovery.\",\n );\n }\n return value.trim();\n}\n","// Config git credential helper dùng gh token. Cần thiết để `git ls-remote`,\n// `git clone`, `git push` qua HTTPS hoạt động khi gh đã auth nhưng git chưa\n// biết về token đó. Idempotent — chạy nhiều lần OK.\nimport { spawnSync } from \"node:child_process\";\nimport { log } from \"./terminal-logger.js\";\n\nexport function setupGitCredentialViaGh(): void {\n const r = spawnSync(\"gh\", [\"auth\", \"setup-git\"], { stdio: \"ignore\" });\n if (r.status !== 0) {\n // Không throw — nếu setup-git fail, git operation sau có thể vẫn work\n // (vd user đã có credential helper khác). Chỉ warn.\n log.warn(\"gh auth setup-git fail (non-fatal). Nếu git clone lỗi 128 → chạy thủ công.\");\n return;\n }\n log.dim(\"Git credential helper đã link với gh token.\");\n}\n","// Orchestrator phase 2: đảm bảo gh CLI có và đã auth. Tự cài + tự login nếu cần.\n//\n// v1.2.4: Khi verify remote fail → KHÔNG throw văng. Call recovery handler\n// prompt user switch account / retry / abort.\n//\n// v1.2.5: gh CLI install fail + gh auth login fail giờ cũng có recovery prompt\n// (loop retry / skip / abort) thay vì throw thẳng.\nimport { checkGhCliAuthStatus } from \"./check-gh-cli-auth-status.js\";\nimport { detectPackageManager } from \"./detect-package-manager.js\";\nimport { handleRemoteAccessFailureWithAccountSwitch } from \"./handle-remote-access-failure-with-account-switch.js\";\nimport { installGhCliViaPackageManager } from \"./install-gh-cli-via-package-manager.js\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.js\";\nimport { setupGitCredentialViaGh } from \"./setup-git-credential-via-gh.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { triggerGhCliAuthLogin } from \"./trigger-gh-cli-auth-login.js\";\nimport { tryVerifyGitRemoteAccessible } from \"./verify-git-remote-accessible.js\";\n\n// Gọi trước mọi flow cần GitHub (init nhánh 1, nhánh 2-create-remote, nhánh 3).\n// remoteUrl optional — nếu truyền sẽ verify access cụ thể.\n// v1.2.5: Loop retry cho install + auth steps — user luôn có choice.\n// v1.2.7: Return resolvedRemoteUrl — user có thể đã re-nhập URL khác trong recovery menu.\n// Caller dùng URL này thay vì URL gốc cho các bước tiếp theo.\nexport async function ensureGitHubReady(\n remoteUrl?: string,\n): Promise<{ resolvedRemoteUrl?: string }> {\n // === Step 1: Ensure gh CLI installed (loop retry) ===\n while (checkGhCliAuthStatus() === \"not-installed\") {\n log.warn(\"gh CLI chưa cài. Avatar sẽ tự cài.\");\n const pm = detectPackageManager();\n if (!pm) {\n // Không có PM → user phải cài tay. Loop prompt cho retry sau khi cài.\n const action = await promptRetryOrSkip({\n taskName: \"Phát hiện package manager\",\n reason: \"Không tìm thấy brew/apt/dnf/pacman/winget trên máy.\",\n allowSkip: false,\n hint: \"Cài gh CLI tay (https://cli.github.com) rồi chọn Retry.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước cài gh CLI.\");\n }\n continue; // Retry → re-check status loop\n }\n\n try {\n installGhCliViaPackageManager(pm);\n } catch (err) {\n const action = await promptRetryOrSkip({\n taskName: `Cài gh CLI qua ${pm}`,\n reason: (err as Error).message,\n allowSkip: false,\n hint: \"Cài tay (https://cli.github.com) rồi chọn Retry, hoặc Abort.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước cài gh CLI.\");\n }\n // Retry → loop tiếp re-check installed.\n }\n }\n\n // === Step 2: Ensure gh CLI authenticated (loop retry) ===\n while (checkGhCliAuthStatus() === \"not-authenticated\") {\n log.warn(\"Chưa đăng nhập GitHub.\");\n try {\n triggerGhCliAuthLogin();\n } catch (err) {\n const action = await promptRetryOrSkip({\n taskName: \"Đăng nhập GitHub qua gh\",\n reason: (err as Error).message,\n allowSkip: false,\n hint: \"Thử lại — chọn cách login khác (browser vs token) khi gh prompt.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước gh auth login.\");\n }\n continue;\n }\n // Re-check sau attempt — nếu vẫn fail, prompt retry.\n if (checkGhCliAuthStatus() !== \"authenticated\") {\n const action = await promptRetryOrSkip({\n taskName: \"Verify gh auth\",\n reason: \"Sau gh auth login vẫn báo not-authenticated.\",\n allowSkip: false,\n hint: \"Có thể user cancel browser. Thử lại hoặc abort.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước verify gh auth.\");\n }\n }\n }\n\n log.success(\"gh CLI sẵn sàng\");\n\n // Đảm bảo git CLI dùng gh token cho HTTPS operations. Idempotent — chạy\n // mỗi lần init OK. Fix lỗi `git ls-remote exit 128` khi gh đã auth nhưng\n // git chưa biết.\n setupGitCredentialViaGh();\n\n if (remoteUrl) {\n const result = tryVerifyGitRemoteAccessible(remoteUrl);\n if (result.ok) {\n log.success(`Remote accessible: ${remoteUrl}`);\n return { resolvedRemoteUrl: remoteUrl };\n }\n // Recovery handler — loop menu cho user re-input URL / switch account / retry / abort.\n // Throws RemoteAccessAbortedError nếu user abort (init.ts catch exit 0).\n const recovered = await handleRemoteAccessFailureWithAccountSwitch({\n url: remoteUrl,\n initialReason: result.reason ?? \"unknown\",\n initialDetail: result.detail,\n });\n return { resolvedRemoteUrl: recovered.resolvedUrl };\n }\n return {};\n}\n","// Spawn `gh auth login --hostname github.com --web` interactive.\n// User sẽ thấy device-code prompt và browser tự mở. Block đến khi xong.\nimport { spawnSync } from \"node:child_process\";\nimport { log } from \"./terminal-logger.js\";\n\nexport function triggerGhCliAuthLogin(): void {\n log.info(\"Khởi động đăng nhập GitHub qua gh CLI (browser sẽ mở)...\");\n const r = spawnSync(\n \"gh\",\n [\"auth\", \"login\", \"--hostname\", \"github.com\", \"--web\", \"--git-protocol\", \"ssh\"],\n { stdio: \"inherit\" },\n );\n if (r.status !== 0) {\n throw new Error(`gh auth login thất bại (exit ${r.status}). Thử 'gh auth login' tay.`);\n }\n log.success(\"Đã đăng nhập GitHub\");\n}\n","// Resolve Avatar workspace root từ current cwd qua walk-up search.\n// Avatar workspace marker = có CẢ 3:\n// - .claude/ directory (Claude Code config)\n// - CLAUDE.md file (Avatar-scaffolded marker)\n// - .gitmodules với entry src/ HOẶC src/ directory\n//\n// Phân biệt với folder random có `.claude/` (vd Claude Code config generic\n// ở random dir hoặc parent của project). Tránh user chạy `avatar ai` ở\n// parent dir vô tình ghi config sai chỗ — bug user gặp v1.3.0.\n//\n// Walk up max 5 levels từ cwd (đủ cho mọi case practical).\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nconst MAX_WALKUP_LEVELS = 5;\n\n// Check thư mục có phải Avatar workspace không.\n// v3: workspace KHÔNG git → marker = .claude/ + CLAUDE.md + src/ folder.\n// (Bỏ check .gitmodules — workspace không còn dùng submodule.)\nfunction isAvatarWorkspace(dir: string): boolean {\n const hasClaudeDir = existsSync(join(dir, \".claude\"));\n const hasClaudeMd = existsSync(join(dir, \"CLAUDE.md\"));\n const hasSrcDir = existsSync(join(dir, \"src\"));\n return hasClaudeDir && hasClaudeMd && hasSrcDir;\n}\n\n// Walk-up từ startDir tìm Avatar workspace root.\n// Trả về path workspace nếu tìm thấy, null nếu không.\nexport function resolveAvatarWorkspaceRootFromCwd(startDir: string): string | null {\n let current = startDir;\n for (let i = 0; i < MAX_WALKUP_LEVELS; i++) {\n if (isAvatarWorkspace(current)) return current;\n const parent = dirname(current);\n if (parent === current) return null; // Reached filesystem root.\n current = parent;\n }\n return null;\n}\n","// Step 2 + 3 của Phase 10 GitNexus integration:\n// - runGitnexusSetup() : `gitnexus setup` → install 4-6 core skills vào ~/.claude/skills/gitnexus-*\n// - runGitnexusAnalyze() : `gitnexus analyze .` ở workspace root → tạo .gitnexus/ với index + meta.json\n//\n// Cả 2 stream stdio để user thấy progress (long-running commands).\n// Throws GitnexusOperationError với operation + reason để caller (orchestrator) recovery menu.\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spinnerWithElapsed } from \"./terminal-logger.js\";\n\n// Timeouts theo doc GitNexus performance estimate.\nconst SETUP_TIMEOUT_MS = 2 * 60 * 1000; // 2 phút — gitnexus setup nhanh (chỉ copy skill files)\nconst ANALYZE_TIMEOUT_MS = 5 * 60 * 1000; // 5 phút — analyze repo medium\n\nexport type GitnexusOperation = \"setup\" | \"analyze\";\nexport type GitnexusOperationReason =\n | \"binary-not-found\" // gitnexus không trong PATH (sanity check)\n | \"timeout\" // SIGTERM\n | \"permission\" // EACCES khi write file\n | \"non-zero-exit\" // generic failure\n | \"missing-output\"; // analyze xong nhưng .gitnexus/meta.json không tạo\n\nexport class GitnexusOperationError extends Error {\n operation: GitnexusOperation;\n reason: GitnexusOperationReason;\n exitCode: number | null;\n stderr?: string;\n constructor(\n operation: GitnexusOperation,\n reason: GitnexusOperationReason,\n message: string,\n exitCode: number | null = null,\n stderr?: string,\n ) {\n super(message);\n this.name = \"GitnexusOperationError\";\n this.operation = operation;\n this.reason = reason;\n this.exitCode = exitCode;\n this.stderr = stderr;\n }\n}\n\n// Classify exit code + stderr thành reason chuẩn hóa cho recovery menu.\nfunction classifyOperationFailure(\n operation: GitnexusOperation,\n exitCode: number | null,\n signal: string | null,\n stderrSample: string,\n): GitnexusOperationError {\n if (signal === \"SIGTERM\") {\n return new GitnexusOperationError(\n operation,\n \"timeout\",\n `gitnexus ${operation} timeout. Check mạng / repo size.`,\n null,\n stderrSample,\n );\n }\n const stderr = stderrSample.toLowerCase();\n if (stderr.includes(\"eacces\") || stderr.includes(\"permission denied\")) {\n return new GitnexusOperationError(\n operation,\n \"permission\",\n `gitnexus ${operation} fail (permission). Check write access ~/.claude hoặc cwd.`,\n exitCode,\n stderrSample,\n );\n }\n return new GitnexusOperationError(\n operation,\n \"non-zero-exit\",\n `gitnexus ${operation} exit ${exitCode ?? \"null\"}. Xem log phía trên.`,\n exitCode,\n stderrSample,\n );\n}\n\n// Tail N dòng cuối — dùng khi fail để debug mà không spam terminal.\nfunction tailLines(text: string, n: number): string {\n const lines = text.split(\"\\n\");\n return lines.slice(-n).join(\"\\n\");\n}\n\n// Step 2 — Run `gitnexus setup` để install global skills (~/.claude/skills/gitnexus-*).\n// Idempotent: chạy lần 2 OK, GitNexus tự skip nếu skills đã có.\n// Spinner với elapsed time + buffer stdio (không inherit) để spinner mượt.\nexport function runGitnexusSetup(): void {\n const sp = spinnerWithElapsed(\"Setup GitNexus global skills (~/.claude/skills/gitnexus-*)\");\n const result = spawnSync(\"gitnexus\", [\"setup\"], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: SETUP_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || result.signal === \"SIGTERM\") {\n sp.fail(\"GitNexus setup failed\");\n const stderr = (result.stderr || \"\").trim();\n const stdout = (result.stdout || \"\").trim();\n if (stderr) process.stderr.write(`${tailLines(stderr, 30)}\\n`);\n else if (stdout) process.stderr.write(`${tailLines(stdout, 30)}\\n`);\n throw classifyOperationFailure(\"setup\", result.status, result.signal, stderr);\n }\n sp.succeed(\"GitNexus setup OK (global skills installed)\");\n}\n\n// Step 3 — Run `gitnexus analyze .` ở workspace root.\n// KHÔNG pass --skills flag (Q5 confirmed bỏ qua repo-specific generated skills).\n// Verify .gitnexus/meta.json tạo ra sau khi xong.\n// Spinner với elapsed time + buffer stdio.\nexport function runGitnexusAnalyze(workspacePath: string): void {\n const sp = spinnerWithElapsed(`Analyze workspace ${workspacePath} (1-3 phút)`);\n const result = spawnSync(\"gitnexus\", [\"analyze\", \".\"], {\n cwd: workspacePath,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: ANALYZE_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || result.signal === \"SIGTERM\") {\n sp.fail(\"Analyze failed\");\n const stderr = (result.stderr || \"\").trim();\n const stdout = (result.stdout || \"\").trim();\n if (stderr) process.stderr.write(`${tailLines(stderr, 30)}\\n`);\n else if (stdout) process.stderr.write(`${tailLines(stdout, 30)}\\n`);\n throw classifyOperationFailure(\"analyze\", result.status, result.signal, stderr);\n }\n\n // Verify output tồn tại — analyze có thể exit 0 nhưng không tạo meta.json (edge case).\n const metaPath = join(workspacePath, \".gitnexus\", \"meta.json\");\n if (!existsSync(metaPath)) {\n sp.fail(\"Analyze exit 0 nhưng không thấy meta.json\");\n throw new GitnexusOperationError(\n \"analyze\",\n \"missing-output\",\n `gitnexus analyze xong nhưng không thấy ${metaPath}. Repo có thể empty hoặc gitnexus fail silent.`,\n );\n }\n sp.succeed(`Analyze OK (index tại ${join(workspacePath, \".gitnexus\")})`);\n}\n","// Step 4 của Phase 10 GitNexus integration — chạy `gitnexus wiki` conditional theo AI provider:\n// - LLMLite mode (env.ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL) → run wiki via NAL gateway\n// - Anthropic Direct (env.ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL) → run wiki via Anthropic OpenAI-compat layer (v1.6.1)\n// - Subscription mode (no API key env) → SKIP wiki, log warning (OAuth không expose key cho GitNexus)\n//\n// GitNexus dùng OpenAI-compatible HTTP client (POST /v1/chat/completions).\n// Anthropic API có compatibility layer tại https://api.anthropic.com/v1/ (Bearer auth) — work với\n// GitNexus default --provider openai. Cần normalize base URL có /v1/ suffix cho Anthropic.\n//\n// Confirm prompt cost trước khi chạy (~$0.50 cho repo 100 modules, 2-5 phút).\n// Verify .gitnexus/wiki/index.html tạo ra sau khi xong.\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { isReasoningModel } from \"./detect-reasoning-model-from-name.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\nimport { log, spinnerWithElapsed } from \"./terminal-logger.js\";\nimport { writeGitnexusConfigForWikiRun } from \"./write-gitnexus-config-to-avoid-cli-arg-key-leak.js\";\n\n// Wiki gen full lần đầu có thể lâu — 15 phút timeout an toàn cho repo lớn.\nconst WIKI_TIMEOUT_MS = 15 * 60 * 1000;\n\n// Fallback model cho LLMLite (last-resort nếu settings.json không có top-level \"model\").\n// nal-claude là default an toàn cho LLMLite NAL. Thường KHÔNG dùng đến vì avatar ai setup\n// luôn ghi model user đã chọn vào settings.json.model — fallback chỉ chạy khi user manual edit.\n// GitNexus default = minimax/minimax-m2.5 → LLMLite key reject → MUST override.\nconst FALLBACK_LLMLITE_MODEL = \"nal-claude\";\n\n// Fallback model cho Anthropic Direct (last-resort).\nconst FALLBACK_ANTHROPIC_MODEL = \"claude-sonnet-4-5\";\n\nexport type WikiProvider = \"llmlite\" | \"anthropic\";\nexport type WikiSkipReason = \"subscription-mode\" | \"user-declined\" | \"fail\";\n\nexport interface GitnexusWikiResult {\n ran: boolean;\n skipped: boolean;\n reason?: WikiSkipReason;\n wikiPath?: string;\n detail?: string;\n}\n\ninterface WikiCredentials {\n provider: WikiProvider;\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\n// Anthropic OpenAI-compat layer REQUIRE `/v1/` suffix trong base URL — nếu user lưu\n// \"https://api.anthropic.com\" (no slash) → append \"/v1/\" để GitNexus call đúng path.\n// LLMLite endpoint base URL không cần /v1/ vì GitNexus tự append.\nfunction normalizeAnthropicBaseUrl(rawBaseUrl: string): string {\n const cleaned = rawBaseUrl.replace(/\\/+$/, \"\"); // strip trailing slash\n if (cleaned.endsWith(\"/v1\")) return `${cleaned}/`;\n if (cleaned.endsWith(\"/v1/\")) return cleaned;\n return `${cleaned}/v1/`;\n}\n\n// Đọc settings.json detect provider + credentials + model.\n// Order check: Anthropic Direct (ANTHROPIC_API_KEY) > LLMLite (ANTHROPIC_AUTH_TOKEN) > null (Subscription).\n//\n// v1.20.0 fix: Keys giờ sống ở .envrc (direnv) thay vì settings.json. Đọc 2 nguồn:\n// 1. settings.json.env (legacy, hoặc subscription-clear path)\n// 2. process.env (direnv đã load .envrc vào shell env)\n// Order: process.env wins if both exist (latest source of truth).\n//\n// v1.6.3: Model đọc từ TOP-LEVEL `settings.json.model` (Claude Code spec).\n// Trả null nếu thật sự không tìm thấy key nào (Subscription mode hoặc direnv\n// chưa load — caller skip wiki).\nasync function readSettingsForWikiCredentials(\n workspacePath: string,\n): Promise<WikiCredentials | null> {\n const settingsPath = join(workspacePath, \".claude\", \"settings.json\");\n if (!(await pathExists(settingsPath))) return null;\n\n try {\n const settings = await readJson<{\n env?: Record<string, unknown>;\n model?: unknown;\n avatarProvider?: unknown;\n }>(settingsPath);\n const env = settings.env || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : null;\n if (!baseUrl) return null;\n\n // Model do `avatar ai setup` lưu ở top-level (Claude Code schema).\n const topLevelModel = typeof settings.model === \"string\" ? settings.model : \"\";\n const envModel = typeof env.ANTHROPIC_MODEL === \"string\" ? env.ANTHROPIC_MODEL : \"\";\n const userModel = topLevelModel.length > 0 ? topLevelModel : envModel;\n\n // v3: key sống trong settings.json.env (bỏ direnv/.envrc). Đọc 2 nguồn:\n // 1. process.env — nếu user/CI export thủ công.\n // 2. settings.json.env — nơi `avatar ai setup` ghi key (chmod 600, gitignored).\n const resolveKey = (envVarName: string): string | null => {\n const fromProcess = process.env[envVarName];\n if (typeof fromProcess === \"string\" && fromProcess.length > 0) {\n return fromProcess;\n }\n const fromSettings = env[envVarName];\n if (typeof fromSettings === \"string\" && fromSettings.length > 0) {\n return fromSettings;\n }\n return null;\n };\n\n // v1.20.1: Provider detection — 2-step:\n // Step 1: avatarProvider field (v1.20.0 informational marker) — explicit.\n // Step 2: baseUrl heuristic — Anthropic official endpoint vs NAL gateway.\n //\n // Tránh process.env.ANTHROPIC_API_KEY leak (vd user export key cho project A,\n // chạy `avatar` trong project B với LLMLite config — không nên dùng key project A).\n const providerHint =\n typeof settings.avatarProvider === \"string\" ? settings.avatarProvider : null;\n // Match hostname `api.anthropic.com` trong URL — preceded bởi protocol slash\n // hoặc domain dot. Sử dụng URL parse cho accuracy.\n let baseUrlSuggestsAnthropic = false;\n try {\n const parsed = new URL(baseUrl);\n baseUrlSuggestsAnthropic = parsed.hostname === \"api.anthropic.com\";\n } catch {\n // Invalid URL — treat as non-Anthropic (will fall to LLMLite path)\n baseUrlSuggestsAnthropic = false;\n }\n const effectiveProvider: \"anthropic\" | \"llmlite\" =\n providerHint === \"anthropic\"\n ? \"anthropic\"\n : providerHint === \"llmlite\"\n ? \"llmlite\"\n : baseUrlSuggestsAnthropic\n ? \"anthropic\"\n : \"llmlite\";\n\n if (effectiveProvider === \"anthropic\") {\n const anthropicKey = resolveKey(\"ANTHROPIC_API_KEY\");\n if (anthropicKey) {\n return {\n provider: \"anthropic\",\n apiKey: anthropicKey,\n baseUrl: normalizeAnthropicBaseUrl(baseUrl),\n model: userModel.length > 0 ? userModel : FALLBACK_ANTHROPIC_MODEL,\n };\n }\n } else {\n const llmliteToken = resolveKey(\"ANTHROPIC_AUTH_TOKEN\");\n if (llmliteToken) {\n return {\n provider: \"llmlite\",\n apiKey: llmliteToken,\n baseUrl,\n model: userModel.length > 0 ? userModel : FALLBACK_LLMLITE_MODEL,\n };\n }\n }\n\n // No key anywhere → Subscription mode hoặc direnv chưa load.\n return null;\n } catch {\n return null;\n }\n}\n\n// Confirm prompt UX — user biết cost + thời gian + model trước khi commit.\nasync function confirmWikiGeneration(baseUrl: string, model: string): Promise<boolean> {\n return await confirm({\n message: `Generate wiki cho workspace? (~$0.50 qua ${baseUrl} model=${model}, 2-5 phút). Continue?`,\n default: true,\n });\n}\n\n// Tail N dòng cuối — dùng khi fail để debug mà không spam terminal.\nfunction tailLines(text: string, n: number): string {\n const lines = text.split(\"\\n\");\n return lines.slice(-n).join(\"\\n\");\n}\n\n// Main entry — branch theo provider mode + run wiki nếu LLMLite/Anthropic + user confirm.\n// workspacePath: nơi đọc .claude/settings.json (credentials).\n// runDir: nơi chạy `gitnexus wiki .` (mặc định = workspacePath). v3 multi-repo:\n// settings ở workspace root, wiki chạy trong src/<repo> → 2 path khác nhau.\nexport async function runGitnexusWikiConditional(\n workspacePath: string,\n runDir: string = workspacePath,\n): Promise<GitnexusWikiResult> {\n // Branch 1: detect provider mode.\n const creds = await readSettingsForWikiCredentials(workspacePath);\n if (!creds) {\n log.warn(\n \"Không resolve được API key cho wiki gen.\\n\" +\n \" Đã kiểm tra: process.env → .envrc Avatar block → settings.json.env\\n\" +\n \" Nguyên nhân có thể:\\n\" +\n \" • Subscription mode (OAuth, không có key)\\n\" +\n \" • Key chưa setup — chạy 'avatar ai setup' hoặc 'avatar secrets set ANTHROPIC_API_KEY'\\n\" +\n \" • settings.json.env.ANTHROPIC_BASE_URL bị thiếu (provider chưa configured)\",\n );\n log.dim(\n \"Để gen wiki sau khi đã có key, chạy manual:\\n\" +\n \" gitnexus wiki . --api-key <key> --base-url <url> --model <model>\",\n );\n return { ran: false, skipped: true, reason: \"subscription-mode\" };\n }\n\n // Branch 2: confirm prompt cost.\n const proceed = await confirmWikiGeneration(creds.baseUrl, creds.model);\n if (!proceed) {\n log.dim(\n \"User decline wiki gen — workspace OK không có wiki. Chạy `gitnexus wiki` manual sau khi cần.\",\n );\n return { ran: false, skipped: true, reason: \"user-declined\" };\n }\n\n // Branch 3: run wiki với spinner + buffer stdio.\n // CRITICAL: GitNexus default --model = minimax/minimax-m2.5 → key sẽ reject. MUST override --model.\n // Provider naming: Anthropic Direct dùng OpenAI-compat layer của Anthropic API (https://api.anthropic.com/v1/)\n // → GitNexus --provider openai vẫn work với base URL đó (Anthropic ship compat endpoint).\n //\n // v1.6.4: Auto-detect reasoning models (Claude 4+, OpenAI o-series) → add --reasoning-model\n // để strip `temperature` param (API reject \"temperature is deprecated for this model\").\n const reasoningMode = isReasoningModel(creds.model);\n\n // v1.13.0 SECURITY FIX: Pre-write `~/.gitnexus/config.json` để gitnexus binary\n // tự đọc credentials, KHÔNG pass `--api-key` qua CLI args (leak qua `ps aux`).\n // Gitnexus support đọc config từ file khi vắng flag (xác minh `gitnexus wiki --help`).\n await writeGitnexusConfigForWikiRun({\n apiKey: creds.apiKey,\n baseUrl: creds.baseUrl,\n model: creds.model,\n isReasoningModel: reasoningMode,\n });\n\n // Args giờ KHÔNG còn --api-key. Gitnexus đọc từ ~/.gitnexus/config.json (chmod 600).\n const args = [\"wiki\", \".\", \"--base-url\", creds.baseUrl, \"--model\", creds.model];\n if (reasoningMode) {\n args.push(\"--reasoning-model\");\n }\n\n const sp = spinnerWithElapsed(\n `Generating wiki via ${creds.baseUrl} (${creds.provider}) model=${creds.model}${reasoningMode ? \" [reasoning]\" : \"\"}`,\n );\n const result = spawnSync(\"gitnexus\", args, {\n cwd: runDir,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: WIKI_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || result.signal === \"SIGTERM\") {\n const reason = result.signal === \"SIGTERM\" ? \"timeout\" : \"non-zero-exit\";\n sp.fail(`Wiki gen ${reason} (exit ${result.status ?? \"null\"})`);\n // Dump tail 30 dòng output để debug, không spam full.\n const stderr = (result.stderr || \"\").trim();\n const stdout = (result.stdout || \"\").trim();\n if (stderr) process.stderr.write(`${tailLines(stderr, 30)}\\n`);\n else if (stdout) process.stderr.write(`${tailLines(stdout, 30)}\\n`);\n return {\n ran: false,\n skipped: true,\n reason: \"fail\",\n detail: `Wiki gen ${reason} (exit ${result.status ?? \"null\"})`,\n };\n }\n\n // Verify output tồn tại (wiki tạo trong runDir, không phải workspacePath).\n const wikiPath = join(runDir, \".gitnexus\", \"wiki\", \"index.html\");\n if (!existsSync(wikiPath)) {\n sp.fail(\"Wiki exit 0 nhưng không thấy index.html\");\n return {\n ran: false,\n skipped: true,\n reason: \"fail\",\n detail: `Wiki exit 0 nhưng không thấy ${wikiPath}`,\n };\n }\n\n sp.succeed(`Wiki ready: ${wikiPath}`);\n return { ran: true, skipped: false, wikiPath };\n}\n","// Detect xem model name có phải reasoning model không (cần strip temperature, dùng\n// max_completion_tokens thay max_tokens).\n//\n// Background:\n// - Reasoning models (Anthropic Claude 4+ extended thinking, OpenAI o-series)\n// KHÔNG accept `temperature` param → API 400 invalid_request_error.\n// - GitNexus có flag --reasoning-model strip param này. Avatar cần auto-pass khi\n// detect model thuộc nhóm reasoning.\n// - Code khác Avatar call LLM (vd avatar ai test /v1/messages) cũng cần biết để\n// skip temperature param.\n//\n// Pattern match (theo Anthropic + OpenAI docs):\n// - Anthropic: claude-opus-4-*, claude-sonnet-4-* (thinking từ Claude 4)\n// LLMLite alias mapping: nal-claude-opus-* nếu user map tới opus-4\n// - OpenAI: o1, o1-mini, o1-preview, o3, o3-mini, o4-mini\n//\n// Maintainable: thêm pattern vào REASONING_PATTERNS array khi Anthropic/OpenAI ship\n// model mới với reasoning.\n\n// Regex patterns — case-insensitive match. Order không quan trọng (any match → true).\nconst REASONING_PATTERNS: RegExp[] = [\n // Anthropic Claude 4+ với extended thinking.\n // Match: claude-opus-4-7, claude-opus-4-8, claude-sonnet-4-5, claude-opus-4-1, ...\n /^claude-(opus|sonnet)-4/i,\n\n // Anthropic Claude 5+ (future-proof — assume reasoning theo trend).\n /^claude-(opus|sonnet|haiku)-[5-9]/i,\n\n // OpenAI o-series (o1, o3, o4).\n /^o1(-|$)/i,\n /^o3(-|$)/i,\n /^o4(-|$)/i,\n\n // LLMLite NAL alias mapping — phổ biến nal-claude-opus-* trỏ tới opus-4+.\n // (Conservative: chỉ match nếu name có chứa opus/sonnet + version số.)\n /nal-claude-(opus|sonnet)-?4/i,\n];\n\n// Main entry. Trả true nếu model name khớp 1 pattern reasoning.\n// Empty string / null → false (an toàn — fallback model thường dùng temperature OK).\nexport function isReasoningModel(modelName: string | null | undefined): boolean {\n if (!modelName) return false;\n return REASONING_PATTERNS.some((pattern) => pattern.test(modelName));\n}\n","// Pre-write `~/.gitnexus/config.json` để chạy `gitnexus wiki` mà KHÔNG phải pass\n// `--api-key` qua CLI argument list. Lý do: argv leak qua `ps aux` / `/proc/<pid>/cmdline`\n// cho mọi process cùng user trong suốt wiki run (timeout 5 phút) — secret exposure cao.\n//\n// Gitnexus binary tự đọc config từ `~/.gitnexus/config.json` khi không có `--api-key`\n// (xác minh qua `gitnexus wiki --help`: \"--api-key ... saved to ~/.gitnexus/config.json\").\n//\n// Approach: idempotent merge — read existing config (preserve user fields khác), update\n// `apiKey/baseUrl/model/isReasoningModel`, write back với chmod 600.\n//\n// v1.13.0 security fix CVE-internal: gitnexus CLI arg API key leak.\n\nimport { promises as fs } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nconst GITNEXUS_CONFIG_DIR = join(homedir(), \".gitnexus\");\nconst GITNEXUS_CONFIG_PATH = join(GITNEXUS_CONFIG_DIR, \"config.json\");\n\nexport interface GitnexusConfigPayload {\n apiKey: string;\n baseUrl: string;\n model: string;\n isReasoningModel?: boolean;\n}\n\n// Read existing config nếu có. Lỗi parse / file missing → trả empty object để merge.\nasync function readExistingConfig(): Promise<Record<string, unknown>> {\n try {\n const raw = await fs.readFile(GITNEXUS_CONFIG_PATH, \"utf8\");\n const parsed = JSON.parse(raw);\n return typeof parsed === \"object\" && parsed !== null ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\n// Atomic write: tmp file + rename để tránh corrupt config nếu process crash giữa chừng.\nasync function writeConfigAtomic(content: string): Promise<void> {\n await fs.mkdir(dirname(GITNEXUS_CONFIG_PATH), { recursive: true });\n const tmpPath = `${GITNEXUS_CONFIG_PATH}.tmp-${process.pid}`;\n await fs.writeFile(tmpPath, content, { mode: 0o600 });\n await fs.rename(tmpPath, GITNEXUS_CONFIG_PATH);\n}\n\n// Main API: merge payload vào existing config, write atomic, chmod 600.\n// Caller (run-gitnexus-wiki-conditional.ts) gọi function này TRƯỚC khi spawn\n// `gitnexus wiki` mà không kèm `--api-key`.\nexport async function writeGitnexusConfigForWikiRun(payload: GitnexusConfigPayload): Promise<void> {\n const existing = await readExistingConfig();\n const merged = {\n ...existing,\n apiKey: payload.apiKey,\n baseUrl: payload.baseUrl,\n model: payload.model,\n isReasoningModel: payload.isReasoningModel ?? false,\n };\n await writeConfigAtomic(JSON.stringify(merged, null, 2));\n // Defensive: ensure 600 dù tmp file đã chmod (some FS lose mode on rename).\n await fs.chmod(GITNEXUS_CONFIG_PATH, 0o600).catch(() => {\n // Best-effort — Windows / WSL ignore chmod, mode đã set ở writeFile.\n });\n}\n","// `avatar ai` — Quản lý AI provider config per-workspace (M12).\n// 4 subcommand:\n// - setup : Wizard re-config (reuse runAiSetupPhase từ phase 07)\n// - status : Show provider/model/baseUrl/token-mask hiện tại\n// - test : Cheap prompt verify provider work\n// - reset : Strip env.ANTHROPIC_* khỏi settings.json (về dùng default subscription)\n//\n// v1.3.1 fix: walk-up resolve Avatar workspace root (strict 3-marker check)\n// thay vì chỉ check `.claude/` ở cwd. Tránh user chạy ở parent dir (vd\n// /Users/Luke/Downloads/Programming/ có .claude/ của Claude Code generic)\n// → ghi nhầm config vào sai chỗ.\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { confirm } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport { pathExists, readJson, writeJsonAtomic } from \"../lib/filesystem-helpers.js\";\nimport { resolveAvatarWorkspaceRootFromCwd } from \"../lib/resolve-avatar-workspace-root-from-cwd.js\";\nimport { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport { maskApiKey } from \"../lib/setup-llmlite-api-key-and-model.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { testAiProviderByDetectedMode } from \"../lib/test-ai-provider-by-detected-mode.js\";\n\n// Resolve Avatar workspace root từ cwd. Walk-up tìm dir có cả .claude/ +\n// CLAUDE.md + (src/ hoặc .gitmodules với src/) — phân biệt với folder random\n// có .claude/. Exit 1 với hint rõ nếu không tìm thấy.\nasync function ensureWorkspaceCwd(): Promise<string> {\n const cwd = process.cwd();\n const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);\n if (!workspaceRoot) {\n log.error(\n `Không tìm thấy Avatar workspace từ thư mục hiện tại.\n Avatar workspace cần có: .claude/ + CLAUDE.md + src/ (hoặc .gitmodules).\n Bạn đang ở: ${cwd}\n Cd vào workspace dir (vd /path/to/<project>-workspace) rồi chạy lại.`,\n );\n process.exit(1);\n }\n if (workspaceRoot !== cwd) {\n log.dim(`Detected workspace root: ${workspaceRoot}`);\n }\n return workspaceRoot;\n}\n\n// Read settings.json hiện có. Trả {} nếu chưa có file (subscription default state).\nasync function readWorkspaceSettings(workspacePath: string): Promise<Record<string, unknown>> {\n const settingsPath = join(workspacePath, \".claude\", \"settings.json\");\n if (!(await pathExists(settingsPath))) return {};\n try {\n return await readJson<Record<string, unknown>>(settingsPath);\n } catch {\n return {};\n }\n}\n\n// === setup ===\nasync function runAiSetup(): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n await runAiSetupPhase({ workspacePath });\n}\n\n// === status ===\n// v1.6.0: Detect 4 provider modes — LLMLite (BASE_URL + AUTH_TOKEN), Anthropic Direct\n// (BASE_URL + API_KEY), Custom (chỉ AUTH_TOKEN), Subscription (no env).\nasync function runAiStatus(): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n const settings = await readWorkspaceSettings(workspacePath);\n const env = (settings.env as Record<string, unknown> | undefined) || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : undefined;\n const token = typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" ? env.ANTHROPIC_AUTH_TOKEN : undefined;\n const apiKey = typeof env.ANTHROPIC_API_KEY === \"string\" ? env.ANTHROPIC_API_KEY : undefined;\n const model = typeof settings.model === \"string\" ? settings.model : undefined;\n\n // Distinguish Anthropic Direct (sk-ant- + api.anthropic.com) vs LLMLite (other gateway).\n let provider: string;\n let credentialDisplay: string;\n if (apiKey) {\n provider =\n baseUrl?.includes(\"api.anthropic.com\") || apiKey.startsWith(\"sk-ant-\")\n ? \"Anthropic Direct\"\n : \"Custom (API key)\";\n credentialDisplay = maskApiKey(apiKey);\n } else if (baseUrl && token) {\n provider = \"LLMLite\";\n credentialDisplay = maskApiKey(token);\n } else if (token) {\n provider = \"Custom\";\n credentialDisplay = maskApiKey(token);\n } else {\n provider = \"Subscription (default)\";\n credentialDisplay = \"(không set — dùng subscription auth)\";\n }\n\n log.info(`Project: ${workspacePath}`);\n log.info(`Provider: ${provider}${baseUrl ? ` (${baseUrl})` : \"\"}`);\n log.info(`Model: ${model ?? \"(default — Claude Code chọn)\"}`);\n log.info(`Token: ${credentialDisplay}`);\n}\n\n// === test ===\n// v1.2.3: Detect provider mode từ settings.json, test đúng path (LLMLite hoặc Subscription).\nasync function runAiTest(): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n const settings = await readWorkspaceSettings(workspacePath);\n try {\n const result = await testAiProviderByDetectedMode(settings);\n log.success(`✓ ${result.message}`);\n } catch (err) {\n log.error(`Test fail: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\n// === reset ===\nasync function runAiReset(opts: { yes?: boolean }): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n const settingsPath = join(workspacePath, \".claude\", \"settings.json\");\n const settings = await readWorkspaceSettings(workspacePath);\n\n if (!opts.yes) {\n const ok = await confirm({\n message: \"Xóa AI config (về dùng Claude Code Subscription default)?\",\n default: false,\n });\n if (!ok) {\n log.dim(\"Đã hủy.\");\n return;\n }\n }\n\n // Rebuild settings strip MỌI provider env keys (LLMLite token + Anthropic direct key + baseUrl).\n // v1.6.0: include ANTHROPIC_API_KEY (Anthropic direct provider key).\n const { env: existingEnv, ...rest } = settings as { env?: Record<string, unknown> };\n const reset: Record<string, unknown> = { ...rest };\n if (existingEnv) {\n const {\n ANTHROPIC_BASE_URL: _b,\n ANTHROPIC_AUTH_TOKEN: _t,\n ANTHROPIC_API_KEY: _k,\n ...envRest\n } = existingEnv;\n if (Object.keys(envRest).length > 0) {\n reset.env = envRest;\n }\n }\n\n // Nếu sau strip object rỗng → xóa file luôn (clean state).\n if (Object.keys(reset).length === 0) {\n await fs.unlink(settingsPath).catch(() => {});\n log.success(\"Đã xóa .claude/settings.json (clean state)\");\n } else {\n await writeJsonAtomic(settingsPath, reset, 0o600);\n log.success(\"Đã reset env block trong .claude/settings.json\");\n }\n}\n\nexport function registerAiCommand(program: Command): void {\n const ai = program.command(\"ai\").description(\"Quản lý AI provider config (M12)\");\n\n ai.command(\"setup\")\n .description(\"Wizard setup/re-config AI provider cho workspace hiện tại\")\n .action(async () => {\n await runAiSetup();\n });\n\n ai.command(\"status\")\n .description(\"Show AI config hiện tại (mask token)\")\n .action(async () => {\n await runAiStatus();\n });\n\n ai.command(\"test\")\n .description(\"Verify AI provider qua cheap prompt\")\n .action(async () => {\n await runAiTest();\n });\n\n ai.command(\"reset\")\n .description(\"Xóa env.ANTHROPIC_* khỏi settings.json (về Subscription default)\")\n .option(\"--yes\", \"Skip confirm\")\n .action(async (opts: { yes?: boolean }) => {\n await runAiReset(opts);\n });\n}\n","// Append-only audit log at ~/.avatar/audit.log. Records sensitive actions\n// (secrets set/rm, tool install/remove) WITHOUT logging secret values.\n// Used by `avatar doctor` to surface recent activity.\nimport { promises as fs } from \"node:fs\";\nimport { AUDIT_LOG_PATH, ensureAvatarHome } from \"./user-config-store.js\";\n\n// Union khai báo theo thực tế caller (verified qua grep `appendAuditEntry(\"...\")`).\n// Thêm action mới: vừa update union vừa update caller — TypeScript sẽ enforce.\n// \"logout\", \"secret_*\", \"tool_*\", \"sync\", \"restore\" reserved cho future commands\n// (đã được spec ở blueprint nhưng caller chưa wire — giữ trong union để command\n// mới chỉ cần thêm caller, không cần đổi type).\nexport type AuditAction =\n | \"login\"\n | \"login_reset\"\n | \"logout\"\n | \"secret_set\"\n | \"secret_rm\"\n | \"tool_install\"\n | \"tool_remove\"\n | \"init\"\n | \"sync\"\n | \"restore\"\n | \"uninstall\"\n | \"ai_setup\"\n | \"gitnexus_setup\"\n | \"bootstrap\";\n\nexport interface AuditEntry {\n timestamp: string;\n action: AuditAction;\n detail?: string;\n}\n\n// v1.14.0 SECURITY: chmod 600 audit.log. Default `fs.appendFile` không set mode\n// → file world-readable (umask thông thường 644). Audit entries có thể chứa email,\n// path tuyệt đối từ error messages → defense-in-depth chmod 600 (owner-only).\n// Best-effort: Windows/WSL ignore chmod nhưng không throw → safe.\nasync function ensureAuditLogPermissions(): Promise<void> {\n try {\n await fs.chmod(AUDIT_LOG_PATH, 0o600);\n } catch {\n // File chưa tồn tại lần đầu (appendFile sẽ tạo) hoặc Windows skip chmod.\n }\n}\n\nexport async function appendAuditEntry(action: AuditAction, detail?: string): Promise<void> {\n await ensureAvatarHome();\n const entry: AuditEntry = {\n timestamp: new Date().toISOString(),\n action,\n ...(detail ? { detail } : {}),\n };\n const line = `${JSON.stringify(entry)}\\n`;\n await fs.appendFile(AUDIT_LOG_PATH, line, { encoding: \"utf8\", mode: 0o600 });\n // Re-enforce chmod 600 vì appendFile mode chỉ apply khi tạo file mới — file\n // pre-exist với mode khác sẽ không bị thay đổi. Idempotent + cheap.\n await ensureAuditLogPermissions();\n}\n","// Read/write ~/.avatar/config.json (OAuth credentials) and ~/.avatar/state.json\n// (non-credential user state). Validates with Zod, enforces chmod 600 on config.\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n type UserConfig,\n type UserState,\n userConfigSchema,\n userStateSchema,\n} from \"../types/config-schema.js\";\nimport { ensureDir, pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const AVATAR_HOME = join(homedir(), \".avatar\");\nexport const USER_CONFIG_PATH = join(AVATAR_HOME, \"config.json\");\nexport const USER_STATE_PATH = join(AVATAR_HOME, \"state.json\");\nexport const AUDIT_LOG_PATH = join(AVATAR_HOME, \"audit.log\");\nexport const BACKUPS_DIR = join(AVATAR_HOME, \"backups\");\n\n// chmod 600 — owner-only read/write. Matches spec for credential files.\nconst SECRET_FILE_MODE = 0o600;\n\nexport async function ensureAvatarHome(): Promise<void> {\n await ensureDir(AVATAR_HOME);\n}\n\nexport async function readUserConfig(): Promise<UserConfig | null> {\n if (!(await pathExists(USER_CONFIG_PATH))) return null;\n const raw = await readJson<unknown>(USER_CONFIG_PATH);\n const parsed = userConfigSchema.safeParse(raw);\n if (!parsed.success) return null;\n return parsed.data;\n}\n\nexport async function writeUserConfig(config: UserConfig): Promise<void> {\n await ensureAvatarHome();\n await writeJsonAtomic(USER_CONFIG_PATH, config, SECRET_FILE_MODE);\n}\n\nexport async function clearUserConfig(): Promise<void> {\n if (await pathExists(USER_CONFIG_PATH)) {\n const { promises: fs } = await import(\"node:fs\");\n await fs.unlink(USER_CONFIG_PATH);\n }\n}\n\nexport async function readUserState(): Promise<UserState> {\n if (!(await pathExists(USER_STATE_PATH))) {\n return userStateSchema.parse({});\n }\n const raw = await readJson<unknown>(USER_STATE_PATH);\n const parsed = userStateSchema.safeParse(raw);\n if (!parsed.success) return userStateSchema.parse({});\n return parsed.data;\n}\n\nexport async function writeUserState(state: UserState): Promise<void> {\n await ensureAvatarHome();\n await writeJsonAtomic(USER_STATE_PATH, state);\n}\n\n// Token is considered expired if it would expire in the next 60 seconds.\n// 60s buffer accounts for slow network round-trip on the upcoming request.\nexport function isTokenExpired(config: UserConfig): boolean {\n const expiresAt = Date.parse(config.expires_at);\n return Number.isNaN(expiresAt) || expiresAt - Date.now() < 60_000;\n}\n\n// Lỗi báo caller cần chạy `avatar login` lại (chưa login HOẶC refresh fail).\nexport class NoValidTokenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NoValidTokenError\";\n }\n}\n\n// Decode JWT exp của id_token (giây). Trả null nếu không parse được.\n// id_token có lifetime RIÊNG, có thể hết hạn dù access_token (expires_at) còn —\n// nên phải check exp của chính id_token, không chỉ dựa expires_at (S2).\nasync function idTokenIsExpired(idToken: string): Promise<boolean> {\n const { decodeIdToken } = await import(\"./google-oauth-device-flow.js\");\n try {\n const claims = decodeIdToken(idToken);\n const nowSec = Math.floor(Date.now() / 1000);\n // 60s buffer cho network round-trip của request sắp gọi.\n return claims.exp - 60 < nowSec;\n } catch {\n return true; // không decode được = coi như không hợp lệ.\n }\n}\n\n// v3: Lấy id_token CÒN HẠN + ĐÃ VERIFY để gọi Supabase get-pack. Tự refresh nếu hết hạn.\n//\n// SECURITY (S1+S2): MỌI nhánh đều decode + verifyIdTokenClaims id_token cuối cùng\n// trước khi return (issuer/audience/hd=nal.vn/email_verified/exp). Không nhánh nào\n// trả token chưa-verify hoặc đã-hết-hạn xuống downloader.\n//\n// - chưa login → NoValidTokenError\n// - id_token còn hạn (theo JWT exp) → verify → trả\n// - hết hạn → refresh → nếu Google KHÔNG trả id_token mới (hợp lệ theo OAuth khi\n// thiếu scope) thì id_token cũ vẫn hết hạn → throw, KHÔNG fallback im lặng\n// - refresh fail (refresh_token revoked) → NoValidTokenError (prompt login)\nexport async function getValidIdToken(): Promise<string> {\n const config = await readUserConfig();\n if (!config) {\n throw new NoValidTokenError(\"Chưa đăng nhập. Chạy 'avatar login' trước.\");\n }\n\n const { refreshAccessToken, decodeIdToken, verifyIdTokenClaims } = await import(\n \"./google-oauth-device-flow.js\"\n );\n\n // Fast path: id_token hiện tại còn hạn (theo chính JWT exp của nó, không phải expires_at).\n if (!(await idTokenIsExpired(config.id_token))) {\n verifyIdTokenClaims(decodeIdToken(config.id_token));\n return config.id_token;\n }\n\n // id_token hết hạn → refresh.\n let refreshed: { access_token: string; expires_in: number; id_token?: string };\n try {\n refreshed = await refreshAccessToken(config.refresh_token);\n } catch (err) {\n throw new NoValidTokenError(\n `Token hết hạn và refresh thất bại (${err instanceof Error ? err.message : err}). Chạy 'avatar login' lại.`,\n );\n }\n\n // S1: Google có thể KHÔNG trả id_token mới (OAuth spec, khi request thiếu scope).\n // KHÔNG fallback id_token cũ (đã hết hạn) — đó là lỗ hổng. Yêu cầu id_token mới.\n if (!refreshed.id_token) {\n throw new NoValidTokenError(\n \"Refresh không trả id_token mới (id_token cũ đã hết hạn). Chạy 'avatar login' lại.\",\n );\n }\n\n // S1: verify claims id_token MỚI trước khi tin (defense-in-depth, gồm exp).\n verifyIdTokenClaims(decodeIdToken(refreshed.id_token));\n\n const updated: UserConfig = {\n ...config,\n access_token: refreshed.access_token,\n id_token: refreshed.id_token,\n expires_at: new Date(Date.now() + refreshed.expires_in * 1000).toISOString(),\n };\n await writeUserConfig(updated);\n return refreshed.id_token;\n}\n","// Zod schemas + inferred TypeScript types for all on-disk Avatar config files.\n// Centralised so commands import from one place and validation matches storage.\nimport { z } from \"zod\";\n\n// ~/.avatar/config.json — OAuth credentials after `avatar login`.\n// chmod 600 enforced at write site.\nexport const userConfigSchema = z.object({\n email: z.string().email(),\n name: z.string(),\n access_token: z.string().min(1),\n refresh_token: z.string().min(1),\n expires_at: z.string().datetime(),\n id_token: z.string().min(1),\n});\nexport type UserConfig = z.infer<typeof userConfigSchema>;\n\n// ~/.avatar/state.json — non-credential user state (tool install records,\n// allowed-paths chosen for filesystem MCP, etc).\nexport const userStateSchema = z.object({\n installed_tools: z\n .record(\n z.string(),\n z.object({\n version: z.string().optional(),\n installed_at: z.string().datetime(),\n install_method: z.string(),\n }),\n )\n .default({}),\n tool_inputs: z.record(z.string(), z.unknown()).default({}),\n});\nexport type UserState = z.infer<typeof userStateSchema>;\n\n// .claude/settings.json — per-project Claude Code settings emitted by `avatar init`.\n// Schema migrated v1.5.0 from legacy `allowedTools` to Claude Code modern schema\n// (permissions.allow/deny + hooks per-event). Pack template merging targets this shape.\nexport const projectSettingsSchema = z.object({\n $schema: z.string().optional(),\n includeCoAuthoredBy: z.boolean().optional(),\n env: z.record(z.string(), z.string()).default({}),\n permissions: z\n .object({\n allow: z.array(z.string()).default([]),\n deny: z.array(z.string()).default([]),\n })\n .partial()\n .optional(),\n // Hooks shape per Claude Code spec: each event key maps to array of matcher entries.\n // We accept unknown body since pack/template control concrete schema; Zod just guards\n // top-level structure to avoid corrupting user file on merge.\n hooks: z.record(z.string(), z.array(z.unknown())).optional(),\n statusLine: z\n .object({\n type: z.string(),\n command: z.string(),\n padding: z.number().optional(),\n })\n .optional(),\n});\nexport type ProjectSettings = z.infer<typeof projectSettingsSchema>;\n\n// Init mode — determines workspace topology (Chapter 02 + Chapter 07 spec).\nexport const initModeSchema = z.enum([\"internal\", \"client\", \"library\"]);\nexport type InitMode = z.infer<typeof initModeSchema>;\n","// Subscription path (Anthropic Pro/Team/Max plan) — 3 step verify:\n// 1) checkClaudeCodeSubscriptionAuth() — `claude auth status` exit code + parse JSON loggedIn\n// 2) triggerClaudeCodeAuthLogin() — `claude auth login` interactive (browser)\n// 3) verifyClaudeCodeQuota() — cheap prompt detect quota/auth issues\n//\n// Mục đích phase 03: đảm bảo claude binary có thể chạy thật trước khi ghi settings.\n// Quan trọng: `auth status loggedIn:true` KHÔNG đảm bảo token còn valid — có thể stale/revoked.\n// verifyClaudeCodeQuota phát hiện token expired qua 401 và trả reason=auth-expired.\nimport { spawnSync } from \"node:child_process\";\nimport { log } from \"./terminal-logger.js\";\n\nexport type ClaudeCodeAuthState = \"authenticated\" | \"not-authenticated\";\n\n// v1.12.2: Rich auth info để skip verify quota khi auth status đã đủ tin cậy.\n// Cost: 0.5s `claude auth status` thay vì 30-60s `claude --print ok`.\nexport interface ClaudeCodeAuthInfo {\n state: ClaudeCodeAuthState;\n email?: string;\n subscriptionType?: string; // \"max\" | \"pro\" | \"team\" | ...\n apiProvider?: string; // \"firstParty\" | ...\n}\n\nexport interface ClaudeCodeQuotaResult {\n ok: boolean;\n // Reason code chuẩn hóa để caller branch:\n // \"credit_balance_too_low\" | \"insufficient_quota\" | \"invalid_api_key\"\n // | \"auth-expired\" | \"rate_limit\" | \"timeout\" | \"unknown\"\n reason?: string;\n // Raw stderr/stdout sample để debug (truncated 500 chars).\n detail?: string;\n}\n\n// Quota verify timeout — claude --print API call thông thường 5-15s, nhưng:\n// - Mạng VN chậm + Anthropic API có thể spike 30s+\n// - First-call sau auth có thể slow vì load model cache\n// 60s threshold safe cho most cases, vẫn không quá lâu để user wait UX.\nconst QUOTA_VERIFY_TIMEOUT_MS = 60_000;\n\n// Cheap prompt: nội dung tối giản. Cost ~$0.0001 trên Sonnet.\nconst QUOTA_VERIFY_PROMPT = \"ok\";\n\n// Step 1: check `claude auth status` exit code + parse JSON output.\n// v2.1.x output JSON `{ loggedIn: bool, ... }`. Trust BOTH exit code AND loggedIn flag.\n// Nếu exit 0 nhưng loggedIn:false → treat as not-authenticated.\nexport function checkClaudeCodeSubscriptionAuth(): ClaudeCodeAuthState {\n return readClaudeCodeAuthInfo().state;\n}\n\n// v1.12.2: Rich variant — trả về cả email, subscriptionType, apiProvider.\n// Dùng để skip verify quota khi auth status đủ tin cậy (subscriptionType set =\n// account active có plan, không cần probe thêm `claude --print` 30-60s).\nexport function readClaudeCodeAuthInfo(): ClaudeCodeAuthInfo {\n const result = spawnSync(\"claude\", [\"auth\", \"status\"], { encoding: \"utf8\" });\n if (result.error || result.status !== 0) return { state: \"not-authenticated\" };\n\n const stdout = (result.stdout || \"\").trim();\n if (!stdout.startsWith(\"{\")) {\n // Older versions plain text output → trust exit code only.\n return { state: \"authenticated\" };\n }\n\n try {\n const parsed = JSON.parse(stdout) as {\n loggedIn?: boolean;\n email?: string;\n subscriptionType?: string;\n apiProvider?: string;\n };\n if (parsed.loggedIn !== true) return { state: \"not-authenticated\" };\n return {\n state: \"authenticated\",\n email: parsed.email,\n subscriptionType: parsed.subscriptionType,\n apiProvider: parsed.apiProvider,\n };\n } catch {\n // JSON parse fail → trust exit code.\n return { state: \"authenticated\" };\n }\n}\n\n// Step 2: trigger `claude auth login` interactive — block đến khi user complete trên browser.\n// Throws nếu spawn fail / user cancel (exit non-zero).\nexport function triggerClaudeCodeAuthLogin(): void {\n log.info(\"Khởi động đăng nhập Claude Code (browser sẽ mở)...\");\n const result = spawnSync(\"claude\", [\"auth\", \"login\"], { stdio: \"inherit\" });\n if (result.status !== 0) {\n throw new Error(\n `claude auth login thất bại (exit ${result.status}). Thử 'claude auth login' tay rồi chạy lại.`,\n );\n }\n log.success(\"Đã đăng nhập Claude Code\");\n}\n\n// Map error keyword → reason code chuẩn hóa.\n// Order quan trọng: check specific trước generic.\n// Expanded coverage cho stderr message dạng mới (vd \"401 Invalid authentication credentials\").\nfunction classifyQuotaError(combinedOutput: string): string {\n const text = combinedOutput.toLowerCase();\n\n // Credit / quota family.\n if (text.includes(\"credit_balance_too_low\") || text.includes(\"credit balance too low\")) {\n return \"credit_balance_too_low\";\n }\n if (text.includes(\"insufficient_quota\") || text.includes(\"insufficient quota\")) {\n return \"insufficient_quota\";\n }\n if (\n text.includes(\"quota_exceeded\") ||\n text.includes(\"quota exceeded\") ||\n text.includes(\"usage limit\") ||\n text.includes(\"you've used all\")\n ) {\n return \"insufficient_quota\";\n }\n\n // Auth family — token expired / 401 / invalid credentials.\n // Common Anthropic messages: \"401 Invalid authentication credentials\", \"authentication failed\",\n // \"failed to authenticate\", \"unauthorized\".\n if (\n text.includes(\"401\") ||\n text.includes(\"invalid authentication\") ||\n text.includes(\"authentication credentials\") ||\n text.includes(\"failed to authenticate\") ||\n text.includes(\"authentication failed\") ||\n text.includes(\"unauthorized\")\n ) {\n return \"auth-expired\";\n }\n if (text.includes(\"invalid_api_key\") || text.includes(\"invalid api key\")) {\n return \"invalid_api_key\";\n }\n\n // Rate limit family.\n if (text.includes(\"rate_limit\") || text.includes(\"rate limit\") || text.includes(\"429\")) {\n return \"rate_limit\";\n }\n\n return \"unknown\";\n}\n\n// User-facing hint string theo reason code — giúp message cuối cùng actionable.\n// Caller (run-ai-setup-phase) sẽ append hint này vào log warn.\nexport function getQuotaErrorHint(reason: string): string {\n switch (reason) {\n case \"auth-expired\":\n return \"Token Claude Code đã hết hạn/bị revoke. Chạy: `claude auth logout && claude auth login`.\";\n case \"credit_balance_too_low\":\n case \"insufficient_quota\":\n return \"Hết quota subscription. Upgrade plan hoặc dùng LLMLite (avatar ai setup → chọn LLMLite).\";\n case \"invalid_api_key\":\n return \"API key invalid. Re-login: `claude auth login`.\";\n case \"rate_limit\":\n return \"Bị rate limit tạm thời. Chờ vài phút rồi chạy `avatar ai setup`.\";\n case \"timeout\":\n return \"Timeout 60s: (1) mạng VN chậm — thử VPN, (2) Anthropic API spike — retry vài phút, (3) token vẫn auth nhưng quota hết âm thầm — check tại claude.ai/settings/usage.\";\n default:\n return \"Lỗi chưa biết. Xem stderr ở trên + chạy `claude --print ok` tay để debug.\";\n }\n}\n\n// Step 3: verify quota qua cheap prompt. Trả structured result.\n// Không throw — caller cần ok/reason để decide branch (warn + suggest LLMLite).\n// Khi reason=\"unknown\" — log full stderr+stdout để user/dev biết Anthropic đổi message gì.\n//\n// v1.12 fix: stdin \"ignore\" thay vì default pipe — tránh race nếu parent đang đợi\n// inquirer prompt (stdin TTY buffer leak gây claude --print block/fail intermittent).\n// v1.12 fix: dump cả stdout (không chỉ stderr) khi unknown — observed case status≠0\n// nhưng stdout có response (Anthropic message qua stdout, exit code lệch).\nexport function verifyClaudeCodeQuota(): ClaudeCodeQuotaResult {\n const result = spawnSync(\"claude\", [\"--print\", QUOTA_VERIFY_PROMPT], {\n encoding: \"utf8\",\n timeout: QUOTA_VERIFY_TIMEOUT_MS,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n // Detect timeout — cross-platform Node.js quirk:\n // - macOS thường: signal=\"SIGTERM\", status=null\n // - Một số env: signal=null, status=143 (128+15 = SIGTERM exit code convention)\n // - Cũng có thể: error.code=\"ETIMEDOUT\"\n // Cover cả 3 case để không classify nhầm thành \"unknown\".\n const isTimeout =\n result.signal === \"SIGTERM\" ||\n result.status === 143 ||\n (result.error as NodeJS.ErrnoException | undefined)?.code === \"ETIMEDOUT\";\n if (isTimeout) {\n return {\n ok: false,\n reason: \"timeout\",\n detail: `claude --print > ${QUOTA_VERIFY_TIMEOUT_MS / 1000}s (mạng chậm / API rate limit / token revoked không return error)`,\n };\n }\n\n const stderr = result.stderr || \"\";\n const stdout = result.stdout || \"\";\n\n if (result.status === 0) {\n // Claude trả response — quota OK.\n return { ok: true };\n }\n\n // Defensive fallback: exit non-zero nhưng stdout có response thật (>20 chars,\n // không có keyword lỗi) → likely false-negative (claude binary quirk).\n // Trust stdout content thay vì exit code.\n const stdoutTrimmed = stdout.trim();\n const stderrLower = stderr.toLowerCase();\n if (\n stdoutTrimmed.length > 20 &&\n !stderrLower.includes(\"error\") &&\n !stderrLower.includes(\"limit\") &&\n !stderrLower.includes(\"quota\") &&\n !stderrLower.includes(\"401\")\n ) {\n log.warn(\n `claude --print exit=${result.status} nhưng có response (${stdoutTrimmed.length} chars). Accept với caution.`,\n );\n return { ok: true };\n }\n\n // Exit non-zero — parse stderr+stdout để classify.\n const reason = classifyQuotaError(`${stderr}\\n${stdout}`);\n\n // Khi unknown → dump cả stderr + stdout (truncated 500) qua log.warn (không phải\n // log.dim) để user dev thấy ngay. log.dim quá mờ trên terminal nhiều màu.\n if (reason === \"unknown\") {\n log.warn(`[debug] claude --print exit=${result.status} signal=${result.signal ?? \"none\"}`);\n if (stderr.trim()) log.warn(`[debug] stderr: ${stderr.slice(0, 500)}`);\n if (stdout.trim()) log.warn(`[debug] stdout: ${stdout.slice(0, 300)}`);\n }\n\n return { ok: false, reason, detail: stderr.slice(0, 500) || stdout.slice(0, 500) };\n}\n","// Detect Claude Code CLI (\"claude\" binary) trên PATH + parse SemVer version.\n// Trả structured info để các module sau (install / auth-check) decide flow.\n// Cross-platform: Unix dùng `which`, Windows dùng `where`.\n// Lưu ý: TRÁNH `command -v` (shell builtin) + `shell:true` — Node ≥23 emit DEP0190.\nimport { spawnSync } from \"node:child_process\";\nimport { detectHostPlatform } from \"./detect-host-platform.js\";\n\nexport interface ClaudeCodeInstallationInfo {\n installed: boolean;\n version: string | null; // SemVer string khi detect được, vd \"1.2.3\"\n path: string | null; // Absolute path đến binary nếu tìm thấy\n}\n\n// Timeout cho `claude --version` — nếu binary hang thì abort tránh block init.\nconst VERSION_PROBE_TIMEOUT_MS = 5000;\n\n// Regex SemVer chấp nhận extra suffix vd \"1.2.3 (Claude Code)\" hoặc \"claude 1.2.3\".\nconst SEMVER_REGEX = /(\\d+\\.\\d+\\.\\d+)/;\n\n// Probe binary location qua `which` (POSIX) hoặc `where` (Windows).\n// Cả 2 đều là external executable → KHÔNG cần shell:true → tránh DEP0190 trên Node 23+.\nfunction probeClaudeBinaryPath(): string | null {\n const isWindows = detectHostPlatform() === \"win32\";\n const probeCmd = isWindows ? \"where\" : \"which\";\n const result = spawnSync(probeCmd, [\"claude\"], { encoding: \"utf8\" });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n if (!out) return null;\n // `where` có thể list nhiều dòng — lấy dòng đầu.\n // out đã verified non-empty (line 29) → split[0] luôn tồn tại.\n return out.split(/\\r?\\n/)[0]!.trim();\n}\n\n// Parse `claude --version` output → SemVer. Null nếu không match.\nfunction probeClaudeVersion(): string | null {\n const result = spawnSync(\"claude\", [\"--version\"], {\n encoding: \"utf8\",\n timeout: VERSION_PROBE_TIMEOUT_MS,\n });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n const match = SEMVER_REGEX.exec(out);\n return match?.[1] ?? null;\n}\n\n// Main API: detect Claude Code installation state.\n// Không throw — luôn return structured info để caller branch logic.\n//\n// v1.13.0 perf: Module-level cache để tránh spawnSync `which claude` + `claude --version`\n// mỗi lần caller gọi. Init flow gọi function này 3+ lần → save 1-3s. Caller có thể\n// invalidate qua `invalidateClaudeCodeInstallationCache()` sau khi install/uninstall.\nlet cachedInfo: ClaudeCodeInstallationInfo | null = null;\n\nexport function detectClaudeCodeInstallation(): ClaudeCodeInstallationInfo {\n if (cachedInfo !== null) return cachedInfo;\n const path = probeClaudeBinaryPath();\n if (!path) {\n cachedInfo = { installed: false, version: null, path: null };\n return cachedInfo;\n }\n const version = probeClaudeVersion();\n cachedInfo = { installed: true, version, path };\n return cachedInfo;\n}\n\n// Caller gọi sau khi install/uninstall để re-probe state.\nexport function invalidateClaudeCodeInstallationCache(): void {\n cachedInfo = null;\n}\n","// Auto-install Claude Code CLI qua `npm install -g @anthropic-ai/claude-code`.\n// Stream stdio để user thấy npm progress. Sau khi cài xong → re-detect verify PATH.\n// Throws InstallClaudeCodeError với hint cụ thể cho EACCES / ENOSPC / generic.\nimport { spawnSync } from \"node:child_process\";\nimport {\n detectClaudeCodeInstallation,\n invalidateClaudeCodeInstallationCache,\n} from \"./detect-claude-code-installation.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// 5 phút — npm global install có thể chậm trên mạng yếu (registry resolve + download).\nconst NPM_INSTALL_TIMEOUT_MS = 5 * 60 * 1000;\n\n// Package name chính thức của Anthropic Claude Code CLI.\nconst CLAUDE_CODE_PACKAGE = \"@anthropic-ai/claude-code\";\n\nexport type InstallClaudeCodeReason =\n | \"permission-denied\" // EACCES → cần sudo hoặc fix npm prefix\n | \"disk-full\" // ENOSPC\n | \"timeout\" // > 5 phút\n | \"binary-not-in-path\" // install OK nhưng PATH chưa refresh\n | \"generic\"; // exit non-zero khác\n\nexport class InstallClaudeCodeError extends Error {\n reason: InstallClaudeCodeReason;\n exitCode: number | null;\n constructor(reason: InstallClaudeCodeReason, message: string, exitCode: number | null = null) {\n super(message);\n this.name = \"InstallClaudeCodeError\";\n this.reason = reason;\n this.exitCode = exitCode;\n }\n}\n\n// Phân loại exit từ npm install thành reason cụ thể để caller branch hint.\n// Hint hiển thị qua message, không log trực tiếp ở đây (separation of concerns).\nfunction classifyNpmFailure(exitCode: number | null, stderrSample: string): InstallClaudeCodeError {\n const stderr = stderrSample.toLowerCase();\n if (stderr.includes(\"eacces\") || stderr.includes(\"permission denied\")) {\n return new InstallClaudeCodeError(\n \"permission-denied\",\n `npm install -g cần quyền. Thử: sudo npm install -g ${CLAUDE_CODE_PACKAGE} hoặc fix npm prefix (npm config set prefix ~/.npm-global).`,\n exitCode,\n );\n }\n if (stderr.includes(\"enospc\") || stderr.includes(\"no space\")) {\n return new InstallClaudeCodeError(\n \"disk-full\",\n \"Đĩa đầy. Free disk space rồi thử lại.\",\n exitCode,\n );\n }\n return new InstallClaudeCodeError(\n \"generic\",\n `npm install thất bại (exit ${exitCode ?? \"null\"}). Xem log npm phía trên.`,\n exitCode,\n );\n}\n\n// Main API: cài Claude Code + verify. Throws InstallClaudeCodeError nếu fail.\n// Return version đã detect (luôn non-null nếu success vì re-detect verify SemVer).\nexport function installClaudeCodeViaNpm(): { version: string | null; path: string } {\n log.info(\"Đang cài Claude Code qua npm (có thể mất 1-2 phút)...\");\n\n // Pipe stderr để classify error sau, nhưng inherit stdout cho user thấy progress.\n // Note: stdio mixed array — [stdin, stdout, stderr].\n const result = spawnSync(\"npm\", [\"install\", \"-g\", CLAUDE_CODE_PACKAGE], {\n stdio: [\"inherit\", \"inherit\", \"pipe\"],\n timeout: NPM_INSTALL_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n // Timeout case — Node set signal SIGTERM khi timeout.\n if (result.signal === \"SIGTERM\") {\n throw new InstallClaudeCodeError(\n \"timeout\",\n `npm install timeout sau ${NPM_INSTALL_TIMEOUT_MS / 1000}s. Check mạng rồi thử lại.`,\n null,\n );\n }\n\n if (result.status !== 0) {\n // In stderr ra để user thấy lỗi gốc trước khi throw.\n if (result.stderr) process.stderr.write(result.stderr);\n throw classifyNpmFailure(result.status, result.stderr || \"\");\n }\n\n // v1.14.1 BUG FIX: Invalidate memoize cache TRƯỚC re-detect.\n // Cùng bug như install-gitnexus-via-npm: cache pre-install giữ {installed: false}\n // → re-detect trả false dù binary đã thực sự install.\n invalidateClaudeCodeInstallationCache();\n\n // Re-detect — npm install OK không đồng nghĩa binary đã trong PATH (PATH cache).\n const probe = detectClaudeCodeInstallation();\n if (!probe.installed || !probe.path) {\n throw new InstallClaudeCodeError(\n \"binary-not-in-path\",\n \"npm cài xong nhưng `claude` không trong PATH. Reload shell (source ~/.zshrc) hoặc thêm npm global bin vào PATH.\",\n null,\n );\n }\n\n log.success(`Đã cài Claude Code${probe.version ? ` v${probe.version}` : \"\"} tại ${probe.path}`);\n return { version: probe.version, path: probe.path };\n}\n","// Wizard chọn AI provider cho workspace — hybrid logic:\n// 1) Nếu detect được ~/.claude/settings.json global có ANTHROPIC_BASE_URL + token\n// → hỏi user \"Dùng global hay setup riêng?\"\n// 2) Nếu user setup riêng (hoặc không có global) → hỏi \"Subscription / LLMLite?\"\n// Output: discriminated union — caller branch theo provider tiếp tục phase 03 (sub) hoặc 05 (llmlite).\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { select } from \"@inquirer/prompts\";\n\n// AI provider options:\n// - subscription : Claude Code Pro/Max OAuth (no API key — Anthropic billing qua subscription)\n// - llmlite : NAL gateway ai.nal.vn (key sk-pvd... bypass billing direct)\n// - anthropic : Anthropic API direct (key sk-ant-... pay-as-you-go credit) (v1.6.0)\n// - use-global : Copy ~/.claude/settings.json (đã có config sẵn từ Claude Code global)\n//\n// v1.20.3: Codex/Gemini providers removed — Claude Code native không support\n// OpenAI/Gemini. Để dùng OPENAI_API_KEY / GEMINI_API_KEY cho skills khác\n// (ai-multimodal, vv) → 'avatar secrets set OPENAI_API_KEY' / 'GEMINI_API_KEY'.\nexport type AiProviderChoice = \"use-global\" | \"subscription\" | \"llmlite\" | \"anthropic\";\n\n// Snapshot info global config — KHÔNG return raw token (security).\n// Token presence chỉ check, value không expose ra UI / log / return.\nexport interface GlobalClaudeSettingsInfo {\n exists: boolean;\n hasBaseUrl: boolean;\n baseUrl?: string; // OK expose vì base URL là endpoint public (vd https://ai.nal.vn)\n hasToken: boolean;\n model?: string;\n rawSettings?: Record<string, unknown>; // Để caller (use-global path) copy\n}\n\n// Path mặc định Claude Code đọc settings — không config khác.\nfunction getGlobalSettingsPath(): string {\n return join(homedir(), \".claude\", \"settings.json\");\n}\n\n// Read + validate ~/.claude/settings.json an toàn. Không throw — graceful null-ish.\nexport function detectGlobalClaudeSettings(): GlobalClaudeSettingsInfo {\n const path = getGlobalSettingsPath();\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return { exists: false, hasBaseUrl: false, hasToken: false };\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n // JSON corrupted — treat as no global config (an toàn hơn dùng config rác).\n return { exists: true, hasBaseUrl: false, hasToken: false };\n }\n\n const env = (parsed.env as Record<string, unknown> | undefined) || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : undefined;\n const hasToken =\n typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" && env.ANTHROPIC_AUTH_TOKEN.length > 0;\n const model = typeof parsed.model === \"string\" ? parsed.model : undefined;\n\n return {\n exists: true,\n hasBaseUrl: !!baseUrl,\n baseUrl,\n hasToken,\n model,\n rawSettings: parsed,\n };\n}\n\n// Main wizard. Trả enum choice — caller branch theo phase 03 / 05 / use-global path.\n// `globalInfo` injectable cho test (default = detect thật).\nexport async function promptAiProviderChoice(\n globalInfo: GlobalClaudeSettingsInfo = detectGlobalClaudeSettings(),\n): Promise<AiProviderChoice> {\n // Hybrid path: chỉ offer use-global khi global có ĐỦ baseUrl + token (config full).\n if (globalInfo.exists && globalInfo.hasBaseUrl && globalInfo.hasToken) {\n const choice = (await select({\n message: `Phát hiện AI config global (base URL: ${globalInfo.baseUrl}). Dùng cho project này?`,\n choices: [\n {\n name: \"a. Yes — copy config global vào .claude/settings.json (per-project)\",\n value: \"use-global\" as const,\n },\n {\n name: \"b. No — setup riêng (chọn provider khác)\",\n value: \"setup-fresh\" as const,\n },\n ],\n })) as \"use-global\" | \"setup-fresh\";\n\n if (choice === \"use-global\") return \"use-global\";\n // Fall-through to provider picker.\n }\n\n // Provider picker — 3 paths (Subscription / LLMLite / Anthropic Direct).\n return (await select({\n message: \"Chọn provider cho AI tools:\",\n choices: [\n {\n name: \"1. Claude Code Subscription (dùng quota cá nhân Anthropic, OAuth login)\",\n value: \"subscription\" as const,\n },\n {\n name: \"2. LLM key NAL (ai.nal.vn — gateway nội bộ, key sk-...)\",\n value: \"llmlite\" as const,\n },\n {\n name: \"3. Anthropic API key trực tiếp (console.anthropic.com, key sk-ant-...)\",\n value: \"anthropic\" as const,\n },\n ],\n })) as \"subscription\" | \"llmlite\" | \"anthropic\";\n}\n","// Anthropic direct API setup wizard — flow:\n// 1) Prompt API key ẩn (password input, format sk-ant-api03-...)\n// 2) Verify GET https://api.anthropic.com/v1/models với x-api-key header\n// 3) Smart picker: auto-pick nếu chỉ 1 claude model, prompt nếu nhiều\n//\n// Output: { apiKey, baseUrl, model } cho phase 06 write settings.json.\n//\n// KHÁC LLMLite:\n// - Header auth: x-api-key (Anthropic standard) thay vì Authorization: Bearer\n// - Bắt buộc anthropic-version: 2023-06-01 (API version header required)\n// - Base URL hardcode https://api.anthropic.com (Anthropic 1 endpoint chính thức)\n// - Settings.json output dùng ANTHROPIC_API_KEY (KHÔNG phải ANTHROPIC_AUTH_TOKEN)\nimport { password, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface AnthropicConfig {\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\n// Anthropic official endpoint. KHÔNG cho user override (decision Q1: keep simple).\n// User cần proxy đặc biệt → edit settings.json tay sau init.\nconst ANTHROPIC_BASE_URL = \"https://api.anthropic.com\";\n\n// API version bắt buộc theo Anthropic spec — endpoint reject nếu thiếu.\n// 2023-06-01 là version stable hiện tại (sẽ phải update nếu Anthropic bump).\nconst ANTHROPIC_API_VERSION = \"2023-06-01\";\n\n// 10s timeout fetch — đủ cho list models (response < 2KB).\nconst FETCH_TIMEOUT_MS = 10_000;\n\n// Trả \"sk-ant...XXXX\" để log không expose full token nhưng vẫn distinguishable.\nexport function maskAnthropicKey(key: string): string {\n if (key.length <= 12) return \"sk-ant-***\";\n return `${key.slice(0, 7)}...${key.slice(-4)}`;\n}\n\n// Anthropic key format: sk-ant-api03-... (108 ký tự). Soft validate prefix\n// để catch typo nhưng không strict length (Anthropic có thể bump format).\nfunction validateAnthropicKeyFormat(key: string): true | string {\n const trimmed = key.trim();\n if (trimmed.length === 0) return \"API key bắt buộc\";\n if (!trimmed.startsWith(\"sk-ant-\")) {\n return \"Anthropic API key thường bắt đầu bằng 'sk-ant-' (lấy từ console.anthropic.com).\";\n }\n return true;\n}\n\n// Prompt ẩn — `password` không echo input, hiển thị `*`.\nasync function promptAnthropicKeyHidden(): Promise<string> {\n return await password({\n message: \"Anthropic API key (sk-ant-..., ẩn input):\",\n mask: \"*\",\n validate: validateAnthropicKeyFormat,\n });\n}\n\n// Shape response /v1/models theo Anthropic spec — chỉ trust field `id`.\ninterface AnthropicModelsResponse {\n data?: Array<{ id?: unknown; type?: unknown }>;\n}\n\n// Fetch danh sách model từ Anthropic /v1/models.\n// Headers: x-api-key + anthropic-version (KHÔNG phải Authorization: Bearer).\nexport async function fetchAnthropicModels(apiKey: string): Promise<string[]> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(`${ANTHROPIC_BASE_URL}/v1/models`, {\n method: \"GET\",\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n Accept: \"application/json\",\n },\n signal: controller.signal,\n });\n\n if (res.status === 401) {\n throw new Error(\"API key invalid (HTTP 401). Check key trên console.anthropic.com.\");\n }\n if (res.status === 403) {\n throw new Error(\n \"API key bị reject (HTTP 403). Key có thể đã bị revoke hoặc thiếu permission.\",\n );\n }\n if (res.status === 429) {\n throw new Error(\"Rate limit (HTTP 429). Chờ vài giây rồi thử lại.\");\n }\n if (!res.ok) {\n throw new Error(`Fetch models thất bại (HTTP ${res.status}).`);\n }\n\n const json = (await res.json()) as AnthropicModelsResponse;\n const models = (json.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n\n if (models.length === 0) {\n throw new Error(\"Anthropic trả về list rỗng. Liên hệ support hoặc check account.\");\n }\n return models;\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new Error(`Connect ${ANTHROPIC_BASE_URL} timeout sau ${FETCH_TIMEOUT_MS / 1000}s.`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Smart picker: auto-pick nếu chỉ 1 model, prompt nếu nhiều.\n// Anthropic /v1/models trả tất cả claude-* models account access được — không cần\n// filter substring như LLMLite (LLMLite list có cả minimax, gpt, gemini...).\nexport async function promptAnthropicModelChoice(models: string[]): Promise<string> {\n if (models.length === 1) {\n // length === 1 đã guard → models[0] non-null.\n const only = models[0]!;\n log.info(`Auto-pick model: ${only} (chỉ 1 model available)`);\n return only;\n }\n\n // Sort: Sonnet preferred, sau đó Opus, Haiku. Default top option.\n const sorted = [...models].sort((a, b) => {\n const score = (m: string) => {\n const lower = m.toLowerCase();\n if (lower.includes(\"sonnet\")) return 0;\n if (lower.includes(\"opus\")) return 1;\n if (lower.includes(\"haiku\")) return 2;\n return 3;\n };\n return score(a) - score(b);\n });\n\n return await select({\n message: \"Chọn model mặc định cho project:\",\n choices: sorted.map((m) => ({ name: m, value: m })),\n });\n}\n\n// Main wizard. Orchestrate 3 step (KHÔNG có baseUrl prompt — hardcode endpoint).\nexport async function setupAnthropicApiKeyAndModel(): Promise<AnthropicConfig> {\n const apiKey = await promptAnthropicKeyHidden();\n\n log.info(`Verify key (${maskAnthropicKey(apiKey)}) qua ${ANTHROPIC_BASE_URL}/v1/models...`);\n const models = await fetchAnthropicModels(apiKey);\n log.success(`Endpoint OK — ${models.length} models available`);\n\n const model = await promptAnthropicModelChoice(models);\n return { apiKey, baseUrl: ANTHROPIC_BASE_URL, model };\n}\n","// LLMLite setup wizard — flow:\n// 1) Prompt API key ẩn (password input, không echo)\n// 2) Prompt base URL (default https://ai.nal.vn)\n// 3) Fetch GET /v1/models verify key + lấy danh sách model\n// 4) Smart picker: auto-pick nếu chỉ 1 claude-alias, prompt nếu nhiều, fallback toàn list\n// Output: { apiKey, baseUrl, model } cho phase 06 write settings.json.\nimport { input, password, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface LLMLiteConfig {\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\n// Endpoint mặc định NAL host. User có thể override (vd self-hosted dev).\nconst DEFAULT_BASE_URL = \"https://ai.nal.vn\";\n\n// 10s timeout fetch — VN mạng có thể chậm nhưng 10s đủ cho list models (response < 1KB).\nconst FETCH_TIMEOUT_MS = 10_000;\n\n// Trả \"sk-...XXXX\" để log không expose full token nhưng vẫn distinguishable.\nexport function maskApiKey(key: string): string {\n if (key.length <= 8) return \"sk-***\";\n return `${key.slice(0, 3)}...${key.slice(-4)}`;\n}\n\n// Prompt ẩn — `password` không echo input, hiển thị `*`.\nasync function promptApiKeyHidden(): Promise<string> {\n return await password({\n message: \"LLMLite API key (ẩn input):\",\n mask: \"*\",\n validate: (v) => (v.trim().length > 0 ? true : \"API key bắt buộc\"),\n });\n}\n\n// Prompt base URL với default cho user enter-to-accept nhanh.\nasync function promptBaseUrl(defaultUrl: string = DEFAULT_BASE_URL): Promise<string> {\n const value = await input({\n message: \"LLMLite base URL:\",\n default: defaultUrl,\n validate: (v) => (/^https?:\\/\\//.test(v) ? true : \"Phải là URL hợp lệ (http/https)\"),\n });\n // Strip trailing slash để concat /v1/... predictable.\n return value.replace(/\\/+$/, \"\");\n}\n\n// Shape response /v1/models theo OpenAI spec — chỉ trust field `id`.\ninterface ModelsResponse {\n data?: Array<{ id?: unknown }>;\n}\n\n// Fetch danh sách model từ endpoint. Throws có message rõ để caller hiển thị.\nexport async function fetchAvailableModels(baseUrl: string, apiKey: string): Promise<string[]> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(`${baseUrl}/v1/models`, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n signal: controller.signal,\n });\n\n if (res.status === 401 || res.status === 403) {\n throw new Error(`API key invalid (HTTP ${res.status}).`);\n }\n if (res.status === 404) {\n throw new Error(`Endpoint /v1/models không tồn tại trên ${baseUrl}.`);\n }\n if (!res.ok) {\n throw new Error(`Fetch models thất bại (HTTP ${res.status}).`);\n }\n\n const json = (await res.json()) as ModelsResponse;\n const models = (json.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n\n if (models.length === 0) {\n throw new Error(\"LLMLite trả về list rỗng. Liên hệ admin NAL.\");\n }\n return models;\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n // v1.14.2 UX: NAL gateway (ai.nal.vn) là internal endpoint — chỉ accessible qua VPN NAL.\n // Common case: user quên bật VPN → timeout. Hint actionable thay vì chỉ \"timeout\".\n const isNalGateway = baseUrl.includes(\"nal.vn\") || baseUrl.includes(\"nal-vn\");\n const vpnHint = isNalGateway\n ? \"\\n Hint: ai.nal.vn là internal endpoint NAL — kiểm tra VPN NAL đã bật chưa, hoặc tcp test bằng `curl -v https://ai.nal.vn`.\"\n : \"\\n Hint: check mạng / firewall / VPN, hoặc base URL có đúng không.\";\n throw new Error(`Connect ${baseUrl} timeout sau ${FETCH_TIMEOUT_MS / 1000}s.${vpnHint}`);\n }\n // Network error khác (DNS fail, refused, ...) — log raw error.\n const errMsg = (err as Error).message || String(err);\n if (errMsg.toLowerCase().includes(\"fetch failed\") || errMsg.includes(\"ENOTFOUND\")) {\n const isNalGateway = baseUrl.includes(\"nal.vn\");\n const vpnHint = isNalGateway ? \" (ai.nal.vn cần VPN NAL — kiểm tra VPN đã bật chưa)\" : \"\";\n throw new Error(`Network error khi connect ${baseUrl}: ${errMsg}${vpnHint}`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Smart picker: auto-pick nếu chỉ 1 claude-alias, prompt nếu nhiều, fallback all.\n// `claude` substring match cover: nal-claude, claude-sonnet-4-5, claude-3-5-sonnet, ...\nexport async function promptModelChoice(models: string[]): Promise<string> {\n const claudeAliases = models.filter((m) => m.toLowerCase().includes(\"claude\"));\n\n if (claudeAliases.length === 1) {\n // length === 1 đã guard → claudeAliases[0] non-null.\n const only = claudeAliases[0]!;\n log.info(`Auto-pick model: ${only} (chỉ 1 claude alias trên endpoint)`);\n return only;\n }\n\n const choiceList = claudeAliases.length > 0 ? claudeAliases : models;\n return await select({\n message: \"Chọn model mặc định cho project:\",\n choices: choiceList.map((m) => ({ name: m, value: m })),\n });\n}\n\n// Main wizard. Orchestrate 4 step.\nexport async function setupLLMLiteApiKeyAndModel(): Promise<LLMLiteConfig> {\n const apiKey = await promptApiKeyHidden();\n const baseUrl = await promptBaseUrl();\n\n log.info(`Verify key (${maskApiKey(apiKey)}) qua ${baseUrl}/v1/models...`);\n const models = await fetchAvailableModels(baseUrl, apiKey);\n log.success(`Endpoint OK — ${models.length} models available`);\n\n const model = await promptModelChoice(models);\n return { apiKey, baseUrl, model };\n}\n","// Orchestrator AI Setup — compose phase 01-06 thành 1 flow fail-soft.\n// Gọi sau `maybeCreateWorkspaceRemote` trong `finalizeWorkspaceScaffold`.\n// Try/catch toàn bộ: success → log + audit ok. Fail → log warn + audit failed, KHÔNG throw —\n// workspace vẫn dùng được, user setup AI lại qua `avatar ai setup`.\nimport { appendAuditEntry } from \"./audit-log-appender.js\";\nimport {\n getQuotaErrorHint,\n readClaudeCodeAuthInfo,\n triggerClaudeCodeAuthLogin,\n verifyClaudeCodeQuota,\n} from \"./check-claude-code-subscription-and-quota.js\";\nimport {\n detectClaudeCodeInstallation,\n invalidateClaudeCodeInstallationCache,\n} from \"./detect-claude-code-installation.js\";\nimport { installClaudeCodeViaNpm } from \"./install-claude-code-via-npm.js\";\nimport {\n type AiProviderChoice,\n detectGlobalClaudeSettings,\n promptAiProviderChoice,\n} from \"./prompt-ai-provider-choice.js\";\nimport { setupAnthropicApiKeyAndModel } from \"./setup-anthropic-api-key-and-model.js\";\nimport { setupLLMLiteApiKeyAndModel } from \"./setup-llmlite-api-key-and-model.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { writeClaudeSettings } from \"./write-claude-settings-json-per-project.js\";\n\n// Default model alias cho Subscription path — Claude Code expect string vd \"sonnet\".\n// User có thể edit settings.json sau nếu muốn dùng model khác.\nconst SUBSCRIPTION_DEFAULT_MODEL = \"sonnet\";\n\n// v1.20.0: warnAboutPlaintextSecret removed — keys giờ lưu vào .envrc qua direnv,\n// không còn plaintext trong settings.json.\n\nexport interface AiSetupArgs {\n workspacePath: string;\n // CI / test mode: skip mọi interactive prompt — caller phải đảm bảo không vào path cần input.\n // Hiện tại không dùng vì AI setup interactive — reserve cho future enhancement.\n autoYes?: boolean;\n}\n\nexport type AiSetupResult =\n | { ok: true; provider: AiProviderChoice; model?: string }\n | { ok: false; reason: string; phase?: string };\n\n// Main orchestrator. Fail-soft — không throw.\nexport async function runAiSetupPhase(args: AiSetupArgs): Promise<AiSetupResult> {\n try {\n log.info(\"Setup AI provider cho workspace...\");\n\n // Phase 01: detect Claude Code. Phase 02: install nếu thiếu.\n let info = detectClaudeCodeInstallation();\n if (!info.installed) {\n log.info(\"Chưa có Claude Code — sẽ tự cài qua npm.\");\n installClaudeCodeViaNpm();\n // v1.13.0: Invalidate cache sau install để re-probe thấy binary mới.\n invalidateClaudeCodeInstallationCache();\n info = detectClaudeCodeInstallation();\n if (!info.installed) {\n throw new Error(\"Cài Claude Code xong nhưng vẫn không detect được binary.\");\n }\n } else {\n log.success(`Claude Code đã có${info.version ? ` v${info.version}` : \"\"}`);\n }\n\n // Phase 04: prompt provider choice (hybrid global detect).\n const globalInfo = detectGlobalClaudeSettings();\n const choice = await promptAiProviderChoice(globalInfo);\n\n // Branch theo choice → call phase 03 hoặc 05 → call phase 06 (write settings).\n switch (choice) {\n case \"subscription\": {\n // Phase 03: auth check + optional quota verify.\n //\n // v1.12.2: Đổi strategy verify quota từ MANDATORY → OPTIONAL.\n // Cũ: luôn chạy `claude --print ok` (30-60s) để verify token còn valid.\n // Vấn đề: trên mạng VN, prompt \"ok\" mất 30s+ → timeout 60s vẫn fail\n // thường xuyên dù subscription thực sự OK. UX cực tệ.\n // Mới: trust `claude auth status` JSON output. Nếu có\n // `subscriptionType` (max/pro/team) → trust ngay. Skip 60s probe.\n // Token revoked sẽ phát hiện lazy khi user run claude command thật,\n // không block setup wizard.\n let authInfo = readClaudeCodeAuthInfo();\n if (authInfo.state !== \"authenticated\") {\n triggerClaudeCodeAuthLogin();\n authInfo = readClaudeCodeAuthInfo();\n }\n\n // Trust path: auth status JSON có subscriptionType → active plan, skip probe.\n if (authInfo.state === \"authenticated\" && authInfo.subscriptionType) {\n await writeClaudeSettings(args.workspacePath, {\n provider: \"subscription\",\n model: SUBSCRIPTION_DEFAULT_MODEL,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=subscription,result=ok,plan=${authInfo.subscriptionType},probe=skipped`,\n );\n log.success(\n `AI ready · Subscription (${authInfo.subscriptionType}) · model=${SUBSCRIPTION_DEFAULT_MODEL}`,\n );\n return { ok: true, provider: \"subscription\", model: SUBSCRIPTION_DEFAULT_MODEL };\n }\n\n // Fallback: auth OK nhưng không có subscriptionType field (older claude version,\n // hoặc edge case JSON shape khác) → fallback verify quota như cũ.\n log.dim(\"Auth status không trả subscriptionType — verify quota (30-60s)...\");\n let quota = verifyClaudeCodeQuota();\n if (!quota.ok && quota.reason === \"auth-expired\") {\n log.warn(\"Token Claude Code đã hết hạn. Tự động re-login...\");\n triggerClaudeCodeAuthLogin();\n quota = verifyClaudeCodeQuota();\n }\n\n // Soft-pass: timeout/unknown KHÔNG block init. Auth status đã pass — trust.\n // Setup vẫn ghi settings.json, user dùng được. Token revoked sẽ fail lazy\n // ở first real call, user re-login manual qua `claude auth login`.\n if (!quota.ok && (quota.reason === \"timeout\" || quota.reason === \"unknown\")) {\n log.warn(`Probe verify ${quota.reason} — accept trust auth status. Tiếp tục.`);\n if (quota.detail?.trim()) log.warn(` Chi tiết: ${quota.detail.slice(0, 200)}`);\n await writeClaudeSettings(args.workspacePath, {\n provider: \"subscription\",\n model: SUBSCRIPTION_DEFAULT_MODEL,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=subscription,result=ok,probe=${quota.reason}-soft-pass`,\n );\n log.success(\n `AI ready · Subscription (probe ${quota.reason}, soft-pass) · model=${SUBSCRIPTION_DEFAULT_MODEL}`,\n );\n return { ok: true, provider: \"subscription\", model: SUBSCRIPTION_DEFAULT_MODEL };\n }\n\n // Hard fail: quota actually exhausted / invalid key / rate limit — block và suggest LLMLite.\n if (!quota.ok) {\n const reason = quota.reason ?? \"unknown\";\n await appendAuditEntry(\n \"ai_setup\",\n `provider=subscription,result=no-quota,reason=${reason}`,\n );\n log.warn(`Subscription verify thất bại (${reason}).`);\n if (quota.detail?.trim()) log.warn(` Chi tiết: ${quota.detail.slice(0, 200)}`);\n log.warn(` → ${getQuotaErrorHint(reason)}`);\n return { ok: false, reason: `subscription-${reason}`, phase: \"quota\" };\n }\n\n await writeClaudeSettings(args.workspacePath, {\n provider: \"subscription\",\n model: SUBSCRIPTION_DEFAULT_MODEL,\n });\n await appendAuditEntry(\"ai_setup\", \"provider=subscription,result=ok\");\n log.success(`AI ready · Subscription · model=${SUBSCRIPTION_DEFAULT_MODEL}`);\n return { ok: true, provider: \"subscription\", model: SUBSCRIPTION_DEFAULT_MODEL };\n }\n\n case \"llmlite\": {\n // Phase 05: API key + model picker + verify.\n const llmConfig = await setupLLMLiteApiKeyAndModel();\n // v3: bỏ direnv — key ghi thẳng vào settings.json (chmod 600 + .claude/.gitignore\n // bảo vệ). Claude Code đọc trực tiếp env field. skipApiKey=false.\n await writeClaudeSettings(args.workspacePath, {\n provider: \"llmlite\",\n apiKey: llmConfig.apiKey,\n baseUrl: llmConfig.baseUrl,\n model: llmConfig.model,\n skipApiKey: false,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=llmlite,result=ok,model=${llmConfig.model},base=${llmConfig.baseUrl},storage=settings.json`,\n );\n log.success(`AI ready · LLMLite (NAL) · model=${llmConfig.model} · ${llmConfig.baseUrl}`);\n return { ok: true, provider: \"llmlite\", model: llmConfig.model };\n }\n\n case \"anthropic\": {\n // v1.6.0: Anthropic API direct (key sk-ant-..., x-api-key auth).\n // v3: key ghi vào settings.json (chmod 600 + gitignored).\n const anthropicConfig = await setupAnthropicApiKeyAndModel();\n await writeClaudeSettings(args.workspacePath, {\n provider: \"anthropic\",\n apiKey: anthropicConfig.apiKey,\n baseUrl: anthropicConfig.baseUrl,\n model: anthropicConfig.model,\n skipApiKey: false,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=anthropic,result=ok,model=${anthropicConfig.model},storage=settings.json`,\n );\n log.success(\n `AI ready · Anthropic Direct · model=${anthropicConfig.model} · ${anthropicConfig.baseUrl}`,\n );\n return { ok: true, provider: \"anthropic\", model: anthropicConfig.model };\n }\n\n case \"use-global\": {\n if (!globalInfo.rawSettings) {\n throw new Error(\"use-global chọn nhưng không đọc được global settings.\");\n }\n await writeClaudeSettings(args.workspacePath, {\n provider: \"use-global\",\n sourceSettings: globalInfo.rawSettings,\n });\n await appendAuditEntry(\"ai_setup\", \"provider=use-global,result=ok\");\n log.success(`AI ready · Copy từ global config (${globalInfo.baseUrl ?? \"subscription\"})`);\n return { ok: true, provider: \"use-global\", model: globalInfo.model };\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`AI setup thất bại: ${message}`);\n log.dim(\"Workspace vẫn sẵn sàng. Setup AI sau qua: avatar ai setup\");\n await appendAuditEntry(\"ai_setup\", `result=failed,error=${message.slice(0, 200)}`);\n return { ok: false, reason: message };\n }\n}\n","// Ghi <workspace>/.claude/settings.json — schema Claude Code expect.\n// 3 branch input: subscription / llmlite / use-global.\n// Merge-safe: preserve key user thêm thủ công (vd \"theme\", \"permissions\"),\n// chỉ override env.ANTHROPIC_BASE_URL / env.ANTHROPIC_AUTH_TOKEN / model.\n// Atomic write qua tmp-rename + chmod 600 (file chứa secret token).\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\n// chmod 600 = owner read/write only. Bắt buộc cho file chứa raw token.\nconst SECRET_FILE_MODE = 0o600;\n\n// 4 branch input — discriminated union để TS narrow chính xác.\n// - subscription : OAuth Pro/Max, không cần key\n// - llmlite : NAL gateway (ai.nal.vn), dùng ANTHROPIC_AUTH_TOKEN (Bearer)\n// - anthropic : Anthropic direct, dùng ANTHROPIC_API_KEY (x-api-key) (v1.6.0)\n// - use-global : copy raw từ ~/.claude/settings.json\n//\n// v1.20.0: skipApiKey=true → keys lưu ở .envrc thay vì settings.json plaintext.\n// settings.json chỉ giữ non-secret config (provider label, baseUrl, model).\n// direnv tự load env var từ .envrc khi cd vào workspace.\n//\n// v1.20.3: Codex/Gemini providers removed — Claude Code native không support.\n// Để lưu OPENAI_API_KEY / GEMINI_API_KEY cho skills khác → 'avatar secrets set'.\nexport type ClaudeSettingsInput =\n | { provider: \"subscription\"; model: string }\n | { provider: \"llmlite\"; apiKey: string; baseUrl: string; model: string; skipApiKey?: boolean }\n | { provider: \"anthropic\"; apiKey: string; baseUrl: string; model: string; skipApiKey?: boolean }\n | { provider: \"use-global\"; sourceSettings: Record<string, unknown> };\n\n// Shape minimal của Claude Code settings.json — chỉ key Avatar đụng tới.\n// Index signature cho phép user thêm key custom (permissions, theme...) mà không lỗi type.\ninterface ClaudeSettings {\n env?: {\n ANTHROPIC_BASE_URL?: string;\n ANTHROPIC_AUTH_TOKEN?: string; // LLMLite gateway (Bearer auth)\n ANTHROPIC_API_KEY?: string; // Anthropic direct (x-api-key auth) — v1.6.0\n [k: string]: unknown;\n };\n model?: string;\n // v1.20.0: provider label (informational only, Claude Code không đọc).\n avatarProvider?: string;\n [k: string]: unknown;\n}\n\n// Đường dẫn settings target trong workspace.\nexport function getClaudeSettingsPath(workspacePath: string): string {\n return join(workspacePath, \".claude\", \"settings.json\");\n}\n\n// Đọc settings hiện có nếu file tồn tại. Throw nếu JSON corrupted để caller báo user\n// (an toàn hơn merge với object rỗng — có thể nuốt config user đã có).\nasync function readExistingSettings(path: string): Promise<ClaudeSettings> {\n if (!(await pathExists(path))) return {};\n try {\n return await readJson<ClaudeSettings>(path);\n } catch (err) {\n throw new Error(\n `Không parse được ${path} (JSON lỗi): ${(err as Error).message}. Backup file rồi xóa để Avatar tạo lại.`,\n );\n }\n}\n\n// Subscription path: clear MỌI provider env keys (LLMLite token + Anthropic direct key\n// nếu có từ config cũ), set model. OAuth dùng quota cá nhân, không cần env var.\nfunction applySubscription(existing: ClaudeSettings, model: string): ClaudeSettings {\n const { env: existingEnv, ...rest } = existing;\n const merged: ClaudeSettings = { ...rest, model };\n if (existingEnv) {\n const {\n ANTHROPIC_BASE_URL: _b,\n ANTHROPIC_AUTH_TOKEN: _t,\n ANTHROPIC_API_KEY: _k,\n ...envRest\n } = existingEnv;\n if (Object.keys(envRest).length > 0) {\n merged.env = envRest as ClaudeSettings[\"env\"];\n }\n }\n return merged;\n}\n\n// Drop all provider-specific keys (env vars that might conflict between providers).\n// Per-provider apply functions add back the ones THIS provider needs.\nfunction stripAllProviderKeys(env: ClaudeSettings[\"env\"]): ClaudeSettings[\"env\"] {\n if (!env) return env;\n const { ANTHROPIC_AUTH_TOKEN: _t, ANTHROPIC_API_KEY: _k, ...rest } = env;\n return rest as ClaudeSettings[\"env\"];\n}\n\n// LLMLite path: ANTHROPIC_AUTH_TOKEN (Bearer auth) + baseUrl gateway + model.\n// v1.20.0: if skipApiKey=true, omit token field (direnv .envrc handles it).\nfunction applyLLMLite(\n existing: ClaudeSettings,\n apiKey: string,\n baseUrl: string,\n model: string,\n skipApiKey: boolean,\n): ClaudeSettings {\n const envRest = stripAllProviderKeys(existing.env);\n const env: ClaudeSettings[\"env\"] = {\n ...envRest,\n ANTHROPIC_BASE_URL: baseUrl,\n };\n if (!skipApiKey) env.ANTHROPIC_AUTH_TOKEN = apiKey;\n return { ...existing, env, model, avatarProvider: \"llmlite\" };\n}\n\nfunction applyAnthropic(\n existing: ClaudeSettings,\n apiKey: string,\n baseUrl: string,\n model: string,\n skipApiKey: boolean,\n): ClaudeSettings {\n const envRest = stripAllProviderKeys(existing.env);\n const env: ClaudeSettings[\"env\"] = {\n ...envRest,\n ANTHROPIC_BASE_URL: baseUrl,\n };\n if (!skipApiKey) env.ANTHROPIC_API_KEY = apiKey;\n return { ...existing, env, model, avatarProvider: \"anthropic\" };\n}\n\n// Use-global path: copy block env + model từ ~/.claude/settings.json, giữ key local khác.\nfunction applyUseGlobal(existing: ClaudeSettings, source: Record<string, unknown>): ClaudeSettings {\n const sourceEnv = (source.env as Record<string, unknown> | undefined) || {};\n const sourceModel = typeof source.model === \"string\" ? source.model : undefined;\n return {\n ...existing,\n env: {\n ...(existing.env || {}),\n ...sourceEnv,\n },\n ...(sourceModel ? { model: sourceModel } : {}),\n };\n}\n\n// Main API. Ghi settings + chmod 600. Throws nếu file corrupted hoặc IO fail.\nexport async function writeClaudeSettings(\n workspacePath: string,\n input: ClaudeSettingsInput,\n): Promise<{ path: string; mode: number }> {\n const path = getClaudeSettingsPath(workspacePath);\n const existing = await readExistingSettings(path);\n\n let merged: ClaudeSettings;\n switch (input.provider) {\n case \"subscription\":\n merged = applySubscription(existing, input.model);\n break;\n case \"llmlite\":\n merged = applyLLMLite(\n existing,\n input.apiKey,\n input.baseUrl,\n input.model,\n input.skipApiKey === true,\n );\n break;\n case \"anthropic\":\n merged = applyAnthropic(\n existing,\n input.apiKey,\n input.baseUrl,\n input.model,\n input.skipApiKey === true,\n );\n break;\n case \"use-global\":\n merged = applyUseGlobal(existing, input.sourceSettings);\n break;\n }\n\n await writeJsonAtomic(path, merged, SECRET_FILE_MODE);\n\n // Re-chmod sau write — writeJsonAtomic đã apply mode trên tmp, rename giữ mode trên POSIX.\n // Trên Windows chmod no-op, không lỗi.\n try {\n await fs.chmod(path, SECRET_FILE_MODE);\n } catch {\n // Windows hoặc filesystem không support — bỏ qua, không fail flow.\n }\n\n return { path, mode: SECRET_FILE_MODE };\n}\n","// Test AI provider hoạt động — detect mode từ settings.json, test đúng path:\n// - LLMLite mode (có env.ANTHROPIC_BASE_URL + token): fetch /v1/models + /v1/chat/completions\n// - Subscription mode (no env): spawn `claude --print \"say ok\"`\n//\n// v1.2.3 fix: trước đây luôn dùng `claude --print` ngay cả khi LLMLite mode →\n// test sai (validate SDK auth chứ không phải LLMLite key). Giờ detect đúng provider.\nimport { spawnSync } from \"node:child_process\";\nimport { maskApiKey } from \"./setup-llmlite-api-key-and-model.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Timeout cho HTTP fetch LLMLite — 10s đủ cho list models + chat completion.\nconst FETCH_TIMEOUT_MS = 10_000;\n\n// Timeout cho subprocess claude --print — 30s an toàn cho cold start.\nconst CLAUDE_PRINT_TIMEOUT_MS = 30_000;\n\n// Cheap chat completion — max_tokens nhỏ giảm cost (~$0.0001 trên Sonnet).\nconst TEST_CHAT_MAX_TOKENS = 5;\nconst TEST_CHAT_PROMPT = \"say ok\";\n\nexport interface TestResult {\n ok: boolean;\n provider: \"llmlite\" | \"subscription\" | \"anthropic\";\n message: string;\n}\n\n// Anthropic API version bắt buộc theo spec (giống setup-anthropic module).\nconst ANTHROPIC_API_VERSION = \"2023-06-01\";\n\ninterface ModelsResponse {\n data?: Array<{ id?: unknown }>;\n}\n\ninterface ChatResponse {\n choices?: Array<{ message?: { content?: unknown } }>;\n usage?: { total_tokens?: number };\n}\n\n// LLMLite test path: /v1/models (verify connectivity + key) + /v1/chat/completions (verify model work).\n// Throws với message rõ nếu fail bất kỳ step nào.\nasync function testLLMLiteProvider(baseUrl: string, token: string, model: string): Promise<void> {\n log.info(`Testing LLMLite provider: ${baseUrl} (key: ${maskApiKey(token)})`);\n\n // Step 1: List models — verify connectivity + key valid.\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const modelsRes = await fetch(`${baseUrl}/v1/models`, {\n headers: { Authorization: `Bearer ${token}` },\n signal: controller.signal,\n });\n\n if (modelsRes.status === 401 || modelsRes.status === 403) {\n throw new Error(`API key invalid (HTTP ${modelsRes.status}). Re-run: avatar ai setup`);\n }\n if (!modelsRes.ok) {\n throw new Error(`Endpoint /v1/models lỗi (HTTP ${modelsRes.status}).`);\n }\n\n const modelsJson = (await modelsRes.json()) as ModelsResponse;\n const models = (modelsJson.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n\n log.success(`Connectivity OK · ${models.length} models available`);\n if (models.length > 0) {\n const preview = models.slice(0, 5).join(\", \");\n const more = models.length > 5 ? ` ...+${models.length - 5} more` : \"\";\n log.dim(` Models: ${preview}${more}`);\n }\n\n // Step 2: Cheap chat completion — verify model thật sự work end-to-end.\n log.info(`Testing chat completion với model \"${model}\"...`);\n const chatRes = await fetch(`${baseUrl}/v1/chat/completions`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model,\n messages: [{ role: \"user\", content: TEST_CHAT_PROMPT }],\n max_tokens: TEST_CHAT_MAX_TOKENS,\n }),\n signal: controller.signal,\n });\n\n if (!chatRes.ok) {\n const errBody = (await chatRes.text()).slice(0, 200);\n throw new Error(`Chat completion fail (HTTP ${chatRes.status}). ${errBody}`);\n }\n\n const chatJson = (await chatRes.json()) as ChatResponse;\n const reply =\n typeof chatJson.choices?.[0]?.message?.content === \"string\"\n ? chatJson.choices[0].message.content\n : \"(empty response)\";\n const tokens = chatJson.usage?.total_tokens ?? \"?\";\n\n log.success(`Response: \"${String(reply).trim().slice(0, 100)}\"`);\n log.dim(` Tokens used: ${tokens}`);\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new Error(`Timeout ${FETCH_TIMEOUT_MS / 1000}s. Check mạng / endpoint ${baseUrl}.`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Subscription test path: chạy `claude --print` — verify SDK auth + quota.\n// Hint cụ thể khi 401 (token stale — pattern từ v1.2.1).\nfunction testSubscriptionProvider(): void {\n log.info(\"Testing Subscription provider qua `claude --print`...\");\n const result = spawnSync(\"claude\", [\"--print\", TEST_CHAT_PROMPT], {\n encoding: \"utf8\",\n timeout: CLAUDE_PRINT_TIMEOUT_MS,\n });\n\n if (result.signal === \"SIGTERM\") {\n throw new Error(`Timeout ${CLAUDE_PRINT_TIMEOUT_MS / 1000}s. Check mạng / endpoint.`);\n }\n if (result.status !== 0) {\n const stderr = (result.stderr || \"\").toLowerCase();\n if (\n stderr.includes(\"401\") ||\n stderr.includes(\"invalid authentication\") ||\n stderr.includes(\"unauthorized\")\n ) {\n throw new Error(\n \"Token Claude Code stale (401). Fix: `claude auth logout && claude auth login`.\",\n );\n }\n throw new Error(\n `Test fail (exit ${result.status}). Stderr: ${(result.stderr || \"\").slice(0, 200)}`,\n );\n }\n log.success(`Response: \"${(result.stdout || \"\").trim().slice(0, 100)}\"`);\n}\n\n// Anthropic Direct test path: /v1/models (verify key) + /v1/messages (verify model work).\n// KHÁC LLMLite: header x-api-key + anthropic-version, body schema khác (system/messages).\nasync function testAnthropicProvider(\n baseUrl: string,\n apiKey: string,\n model: string,\n): Promise<void> {\n log.info(`Testing Anthropic Direct provider: ${baseUrl} (key: ${maskApiKey(apiKey)})`);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n // Step 1: List models — verify connectivity + key valid.\n const modelsRes = await fetch(`${baseUrl}/v1/models`, {\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n },\n signal: controller.signal,\n });\n\n if (modelsRes.status === 401 || modelsRes.status === 403) {\n throw new Error(`API key invalid (HTTP ${modelsRes.status}). Re-run: avatar ai setup`);\n }\n if (!modelsRes.ok) {\n throw new Error(`Endpoint /v1/models lỗi (HTTP ${modelsRes.status}).`);\n }\n\n const modelsJson = (await modelsRes.json()) as ModelsResponse;\n const models = (modelsJson.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n log.success(`Connectivity OK · ${models.length} models available`);\n\n // Step 2: Cheap message — verify model work end-to-end via /v1/messages.\n log.info(`Testing message với model \"${model}\"...`);\n const msgRes = await fetch(`${baseUrl}/v1/messages`, {\n method: \"POST\",\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model,\n max_tokens: TEST_CHAT_MAX_TOKENS,\n messages: [{ role: \"user\", content: TEST_CHAT_PROMPT }],\n }),\n signal: controller.signal,\n });\n\n if (!msgRes.ok) {\n const errText = (await msgRes.text()).slice(0, 200);\n throw new Error(`Message endpoint fail (HTTP ${msgRes.status}): ${errText}`);\n }\n const msgJson = (await msgRes.json()) as {\n content?: Array<{ text?: unknown }>;\n usage?: { input_tokens?: number; output_tokens?: number };\n };\n const text = (msgJson.content || [])\n .map((c) => (typeof c.text === \"string\" ? c.text : \"\"))\n .join(\"\")\n .trim()\n .slice(0, 100);\n log.success(`Response: \"${text}\"`);\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Main entry — detect provider từ settings, run đúng test path.\n// Throws với message actionable nếu fail. Caller (avatar ai test) catch + log.error.\n//\n// v1.6.0: Add Anthropic Direct branch (ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL).\n// Order check: anthropic > llmlite > subscription (more specific provider first).\nexport async function testAiProviderByDetectedMode(\n settings: Record<string, unknown>,\n): Promise<TestResult> {\n const env = (settings.env as Record<string, unknown> | undefined) || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : undefined;\n const token = typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" ? env.ANTHROPIC_AUTH_TOKEN : undefined;\n const apiKey = typeof env.ANTHROPIC_API_KEY === \"string\" ? env.ANTHROPIC_API_KEY : undefined;\n const model = typeof settings.model === \"string\" ? settings.model : \"default\";\n\n if (apiKey && baseUrl) {\n await testAnthropicProvider(baseUrl, apiKey, model);\n return { ok: true, provider: \"anthropic\", message: \"Anthropic Direct provider working\" };\n }\n\n if (baseUrl && token) {\n await testLLMLiteProvider(baseUrl, token, model);\n return { ok: true, provider: \"llmlite\", message: \"LLMLite provider working\" };\n }\n\n testSubscriptionProvider();\n return { ok: true, provider: \"subscription\", message: \"Subscription provider working\" };\n}\n","import { spawnSync } from \"node:child_process\";\n// `avatar doctor [--fix]` — Command 07 spec.\n// Run a series of health checks and surface ✓/✗ for each. --fix attempts\n// auto-fixes for the ones safe to fix automatically.\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { checkEnabledToolsHealth } from \"../lib/check-enabled-tools-health.js\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\nimport { isTokenExpired, readUserConfig } from \"../lib/user-config-store.js\";\n\ninterface CheckResult {\n name: string;\n status: \"ok\" | \"warn\" | \"fail\";\n detail: string;\n fixable: boolean;\n fix?: () => Promise<void>;\n}\n\nexport function registerDoctorCommand(program: Command): void {\n program\n .command(\"doctor\")\n .description(\"Chẩn đoán cài đặt Avatar: hooks, MCP, login, submodule, ...\")\n .option(\"--fix\", \"Tự động fix các issue có thể fix tự động\")\n .action(async (opts: { fix?: boolean }) => {\n try {\n const checks = await runChecks(process.cwd());\n renderChecks(checks);\n if (opts.fix) await applyFixes(checks);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nasync function runChecks(cwd: string): Promise<CheckResult[]> {\n const checks: CheckResult[] = [];\n\n // 1. Node.js version >= 18.17\n const nodeVer = process.versions.node;\n const [major, minor] = nodeVer.split(\".\").map((n) => Number.parseInt(n, 10));\n const nodeOk = (major ?? 0) > 18 || ((major ?? 0) === 18 && (minor ?? 0) >= 17);\n checks.push({\n name: \"Node.js version\",\n status: nodeOk ? \"ok\" : \"fail\",\n detail: `v${nodeVer}${nodeOk ? \"\" : \" (cần >= 18.17)\"}`,\n fixable: false,\n });\n\n // 2. Login.\n const config = await readUserConfig();\n if (!config) {\n checks.push({\n name: \"Login status\",\n status: \"fail\",\n detail: \"Chưa đăng nhập — chạy 'avatar login'\",\n fixable: false,\n });\n } else if (isTokenExpired(config)) {\n checks.push({\n name: \"Login status\",\n status: \"warn\",\n detail: `Token hết hạn (${config.email}) — chạy 'avatar login'`,\n fixable: false,\n });\n } else {\n checks.push({\n name: \"Login status\",\n status: \"ok\",\n detail: `Logged in: ${config.email}`,\n fixable: false,\n });\n }\n\n // v1.14.0 perf: Batch 1 — 4 probes I/O độc lập chạy parallel qua Promise.all.\n // Save ~80-150ms (4 stat calls × ~30ms mỗi cái → ~30ms total thay vì 120ms).\n // v3: workspace KHÔNG git (pack tarball, repos clone độc lập trong src/).\n // Bỏ check \"Git repository\" + post-merge hook. Avatar-init = có .claude/pack/ + CLAUDE.md.\n const packPath = join(cwd, \".claude\", \"pack\");\n const claudeMdPath = join(cwd, \"CLAUDE.md\");\n const [hasPack, hasClaudeMd] = await Promise.all([\n pathExists(packPath),\n pathExists(claudeMdPath),\n ]);\n\n // 4. .claude/pack/ installation (tarball).\n checks.push({\n name: \"team-ai-pack installation\",\n status: hasPack ? \"ok\" : \"warn\",\n detail: hasPack ? packPath : \"Avatar chưa init — chạy 'avatar init'\",\n fixable: false,\n });\n\n // 5. CLAUDE.md present.\n checks.push({\n name: \"CLAUDE.md\",\n status: hasClaudeMd ? \"ok\" : \"warn\",\n detail: hasClaudeMd ? \"tồn tại ở project root\" : \"thiếu — chạy 'avatar init'\",\n fixable: false,\n });\n\n // v3: post-merge hook + workspace .gitignore BỎ (workspace không git).\n // Secrets (API key) sống trong .claude/settings.json, bảo vệ bằng .claude/.gitignore\n // (phòng .claude/ bị git track nhầm). Check file đó có + bảo vệ settings.json.\n if (hasPack) {\n const claudeGitignorePath = join(cwd, \".claude\", \".gitignore\");\n let settingsProtected = false;\n if (await pathExists(claudeGitignorePath)) {\n const content = await fs.readFile(claudeGitignorePath, \"utf8\");\n settingsProtected = content.includes(\"settings.json\");\n }\n checks.push({\n name: \"🔒 settings.json gitignored (.claude/.gitignore)\",\n status: settingsProtected ? \"ok\" : \"fail\",\n detail: settingsProtected\n ? \"an toàn — settings.json không commit nhầm\"\n : \"CRITICAL: settings.json chứa API key — chạy 'avatar doctor --fix' để bảo vệ\",\n fixable: !settingsProtected,\n fix: settingsProtected\n ? undefined\n : async () => {\n const { writeClaudeGitignore } = await import(\"../lib/project-tree-scaffolder.js\");\n await writeClaudeGitignore(cwd);\n },\n });\n }\n\n // v1.7.0 — 7c. python vs python3 (pack scripts thường hardcode python).\n // Modern macOS chỉ có python3 → pack script fail. Check + suggest alias.\n const pythonCheck = spawnSync(\"which\", [\"python\"]);\n const python3Check = spawnSync(\"which\", [\"python3\"]);\n const hasPython = pythonCheck.status === 0;\n const hasPython3 = python3Check.status === 0;\n if (hasPython3 && !hasPython) {\n checks.push({\n name: \"Python binary alias\",\n status: \"warn\",\n detail: `Chỉ có python3 (modern macOS). Pack scripts thường ref 'python' → suggest: ln -s ${python3Check.stdout.toString().trim()} ~/.local/bin/python`,\n fixable: false,\n });\n } else if (hasPython) {\n checks.push({\n name: \"Python binary\",\n status: \"ok\",\n detail: `python: ${pythonCheck.stdout.toString().trim()}`,\n fixable: false,\n });\n } else if (hasPython3) {\n checks.push({\n name: \"Python binary\",\n status: \"ok\",\n detail: `python3: ${python3Check.stdout.toString().trim()}`,\n fixable: false,\n });\n }\n\n // v1.7.0 — 7d. statusLine path in settings.json — verify file ref tồn tại.\n // Pack template thường ref `node .claude/statusline.cjs` nhưng pack KHÔNG ship file.\n // Avatar v1.7.0 merger đã skip merge nếu file missing, nhưng workspace cũ có thể đã commit settings này.\n const settingsPath = join(cwd, \".claude\", \"settings.json\");\n if (await pathExists(settingsPath)) {\n try {\n const settingsRaw = await fs.readFile(settingsPath, \"utf8\");\n const settings = JSON.parse(settingsRaw) as {\n statusLine?: { command?: string };\n };\n if (settings.statusLine?.command) {\n const cmd = settings.statusLine.command.trim();\n const match = cmd.match(/^(node|python|python3|bash|sh)\\s+([^\\s]+)/);\n if (match?.[2]) {\n const refFile = match[2];\n const fullPath = refFile.startsWith(\"/\") ? refFile : join(cwd, refFile);\n const fileExists = await pathExists(fullPath);\n checks.push({\n name: \"statusLine command\",\n status: fileExists ? \"ok\" : \"fail\",\n detail: fileExists\n ? `ref OK: ${refFile}`\n : `BROKEN: settings.json ref '${refFile}' nhưng file không tồn tại. Strip field statusLine hoặc fix path.`,\n fixable: false,\n });\n }\n }\n } catch {\n // settings.json corrupted — đã có check khác handle.\n }\n }\n\n // 8. Claude Code CLI installed (best-effort `which claude`).\n const which = spawnSync(\"which\", [\"claude\"]);\n const hasClaudeCli = which.status === 0;\n checks.push({\n name: \"Claude Code CLI\",\n status: hasClaudeCli ? \"ok\" : \"warn\",\n detail: hasClaudeCli ? which.stdout.toString().trim() : \"không tìm thấy 'claude' trên PATH\",\n fixable: false,\n });\n\n // 9. Tool toggle health (prompt-scoring, ...). Mỗi tool enabled trong state:\n // manifest còn? hook còn trong settings.json? (skew/orphan → warn; mất hook → fail+fix).\n if (hasPack) {\n const toolChecks = await checkEnabledToolsHealth(cwd);\n checks.push(...toolChecks);\n }\n\n return checks;\n}\n\nfunction renderChecks(checks: CheckResult[]): void {\n const lines = [chalk.bold(\"Avatar Doctor\"), \"─\".repeat(48)];\n let passed = 0;\n let issues = 0;\n let fixable = 0;\n for (const c of checks) {\n const icon =\n c.status === \"ok\"\n ? chalk.green(\"✓\")\n : c.status === \"warn\"\n ? chalk.yellow(\"⚠\")\n : chalk.red(\"✗\");\n lines.push(`${icon} ${c.name.padEnd(28)} ${chalk.dim(c.detail)}`);\n if (c.status === \"ok\") passed += 1;\n else {\n issues += 1;\n if (c.fixable) fixable += 1;\n }\n }\n lines.push(\"─\".repeat(48));\n lines.push(\n `${passed} checks passed, ${issues} issue${issues === 1 ? \"\" : \"s\"}${fixable > 0 ? ` (${fixable} fixable — chạy 'avatar doctor --fix')` : \"\"}`,\n );\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n\nasync function applyFixes(checks: CheckResult[]): Promise<void> {\n let count = 0;\n for (const c of checks) {\n if (c.fixable && c.fix) {\n try {\n await c.fix();\n log.success(`Fixed: ${c.name}`);\n count += 1;\n } catch (err) {\n log.error(`Failed to fix ${c.name}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n if (count === 0) log.dim(\"Không có gì để fix tự động.\");\n}\n","// check-enabled-tools-health.ts\n//\n// Doctor check: với mỗi tool enabled=true trong state, xác minh\n// (a) manifest còn trong pack (phát hiện version skew / orphan state)\n// (b) hook của tool THỰC SỰ có trong settings.json (phát hiện \"state nói bật\n// nhưng settings bị ghi đè/mất hook\" — fixable bằng re-enable)\n//\n// Trả về list check shape khớp doctor CheckResult (name/status/detail/fixable/fix).\n\nimport { join } from \"node:path\";\nimport {\n type ToolManifest,\n enableTool,\n readToolManifest,\n} from \"./apply-tool-manifest-to-settings.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\nimport { listEnabledTools } from \"./tool-state-store.js\";\n\nexport interface ToolCheck {\n name: string;\n status: \"ok\" | \"warn\" | \"fail\";\n detail: string;\n fixable: boolean;\n fix?: () => Promise<void>;\n}\n\ninterface SettingsShape {\n hooks?: Record<string, Array<{ matcher?: string; hooks?: Array<{ command?: string }> }>>;\n}\n\n// Mọi command của tool đã có mặt trong settings.json chưa?\nfunction manifestHooksPresent(settings: SettingsShape, manifest: ToolManifest): boolean {\n const mHooks = manifest.settings.hooks ?? {};\n const sHooks = settings.hooks ?? {};\n for (const [event, featEntries] of Object.entries(mHooks)) {\n const present = sHooks[event] ?? [];\n const presentCommands = new Set(\n present.flatMap((e) => (e.hooks ?? []).map((h) => h.command ?? \"\")),\n );\n for (const entry of featEntries) {\n for (const h of entry.hooks ?? []) {\n if (h.command && !presentCommands.has(h.command)) {\n return false;\n }\n }\n }\n }\n return true;\n}\n\nexport async function checkEnabledToolsHealth(cwd: string): Promise<ToolCheck[]> {\n const enabled = await listEnabledTools(cwd);\n if (enabled.length === 0) {\n return [];\n }\n\n const settingsPath = join(cwd, \".claude\", \"settings.json\");\n let settings: SettingsShape = {};\n if (await pathExists(settingsPath)) {\n try {\n settings = await readJson<SettingsShape>(settingsPath);\n } catch {\n settings = {};\n }\n }\n\n const checks: ToolCheck[] = [];\n for (const name of enabled) {\n const manifest = await readToolManifest(cwd, name);\n if (!manifest) {\n checks.push({\n name: `Tool: ${name}`,\n status: \"warn\",\n detail: \"state=enabled nhưng pack thiếu manifest (version skew). Chạy 'avatar sync'.\",\n fixable: false,\n });\n continue;\n }\n if (manifestHooksPresent(settings, manifest)) {\n checks.push({\n name: `Tool: ${name}`,\n status: \"ok\",\n detail: `enabled, hook active (v${manifest.version})`,\n fixable: false,\n });\n } else {\n checks.push({\n name: `Tool: ${name}`,\n status: \"fail\",\n detail: \"state=enabled nhưng hook thiếu trong settings.json — fixable (re-apply)\",\n fixable: true,\n fix: async () => {\n await enableTool(cwd, manifest);\n },\n });\n }\n }\n return checks;\n}\n","// apply-tool-manifest-to-settings.ts\n//\n// Áp / rút manifest của 1 tool toggle (vd prompt-scoring) vào project\n// settings.json. Đối xứng: enable = merge, disable = remove ĐÚNG những gì manifest\n// khai. Nguồn sự thật là tool.json (KHÔNG nhúng marker `_feature` vào settings\n// → settings giữ schema chuẩn 100%).\n//\n// Manifest sống ở: <project>/.claude/pack/tools/<name>/tool.json\n// (submodule mount tại .claude/pack/). Manifest.settings chỉ chứa subset cần cho\n// tool: hooks (per-event) + permissions.deny.\n//\n// Khác mergePackSettingsIntoProjectSettings (chỉ áp template + có statusLine/env/\n// model): file này CHỈ lo hooks + deny của 1 tool, và biết RÚT ngược lại.\n//\n// Safety: backup settings.json trước mọi thay đổi; atomic write; idempotent\n// (enable lại không nhân đôi; disable tool chưa bật → no-op).\n\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\nimport {\n type PackSettingsTemplate,\n backupFilename,\n unionDedupe,\n} from \"./merge-pack-settings-into-project-settings.js\";\n\n// Một hook entry trong settings: { matcher?, hooks: [{type, command}] }.\ninterface HookEntry {\n matcher?: string;\n hooks?: Array<{ type?: string; command?: string }>;\n}\n\nexport interface ToolManifest {\n name: string;\n version: string;\n description?: string;\n requires?: { runtime?: string };\n settings: {\n hooks?: Record<string, HookEntry[]>;\n permissions?: { deny?: string[]; allow?: string[] };\n };\n}\n\nexport interface ApplyToolResult {\n action: \"enabled\" | \"disabled\" | \"no-change\" | \"no-manifest\";\n backupPath?: string;\n changes: string[];\n}\n\nexport function featureManifestPath(workspacePath: string, name: string): string {\n return join(workspacePath, \".claude\", \"pack\", \"tools\", name, \"tool.json\");\n}\n\n// Đọc manifest tool. Thiếu → null (caller fail mềm: \"pack chưa hỗ trợ tool\").\nexport async function readToolManifest(\n workspacePath: string,\n name: string,\n): Promise<ToolManifest | null> {\n const path = featureManifestPath(workspacePath, name);\n if (!(await pathExists(path))) {\n return null;\n }\n return await readJson<ToolManifest>(path);\n}\n\nconst PROJECT_SETTINGS_REL = [\".claude\", \"settings.json\"];\n\nfunction projectSettingsPath(workspacePath: string): string {\n return join(workspacePath, ...PROJECT_SETTINGS_REL);\n}\n\n// So khớp 2 hook entry theo (matcher + danh sách command) — định danh dùng cho\n// cả dedupe (enable) lẫn remove (disable).\nfunction hookEntryKey(entry: HookEntry): string {\n const commands = (entry.hooks ?? [])\n .map((h) => h.command ?? \"\")\n .sort()\n .join(\"\u0000\");\n return `${entry.matcher ?? \"\"}\u0001${commands}`;\n}\n\nasync function loadProjectSettings(\n workspacePath: string,\n): Promise<{ settings: PackSettingsTemplate; existed: boolean }> {\n const path = projectSettingsPath(workspacePath);\n if (!(await pathExists(path))) {\n return { settings: {}, existed: false };\n }\n try {\n return { settings: await readJson<PackSettingsTemplate>(path), existed: true };\n } catch (err) {\n throw new Error(\n `Project settings.json không parse được: ${(err as Error).message}. Manual fix trước khi enable/disable tool.`,\n );\n }\n}\n\nasync function backupAndWrite(\n workspacePath: string,\n settings: PackSettingsTemplate,\n existed: boolean,\n): Promise<string | undefined> {\n const path = projectSettingsPath(workspacePath);\n let backupPath: string | undefined;\n if (existed) {\n backupPath = backupFilename(path);\n await fs.copyFile(path, backupPath);\n }\n await writeJsonAtomic(path, settings);\n return backupPath;\n}\n\n// v1.18.0: Normalize command để detect stale relative-path entries cần migrate.\n// Strip ${CLAUDE_PROJECT_DIR}/ prefix → so sánh logical-equivalent giữa old + new.\nfunction normalizeHookEntryForMigration(entry: HookEntry): string {\n const commands = (entry.hooks ?? [])\n .map((h) => (h.command ?? \"\").replace(/\\$\\{CLAUDE_PROJECT_DIR\\}\\//g, \"\").trim())\n .sort()\n .join(\"|\");\n return `${entry.matcher ?? \"\"}::${commands}`;\n}\n\nfunction entryUsesProjectDirVar(entry: HookEntry): boolean {\n return (entry.hooks ?? []).some((h) => (h.command ?? \"\").includes(\"${CLAUDE_PROJECT_DIR}\"));\n}\n\n// v1.18.0 MIGRATION: Trong cùng event, nếu user settings có entry old (relative\n// path) VÀ tool manifest mang entry new (${CLAUDE_PROJECT_DIR}/) logical-tương-đương,\n// drop entry old để chỉ giữ new sau merge. Fix cho upgrade từ pack pre-v0.7.1.\nfunction migrateStaleEntries(\n userEntries: HookEntry[],\n featureEntries: HookEntry[],\n): { migratedUser: HookEntry[]; droppedCount: number } {\n // Build set of normalized keys mà tool đang ship (đang là new version)\n const featureNormalized = new Set<string>();\n for (const fe of featureEntries) {\n if (entryUsesProjectDirVar(fe)) {\n featureNormalized.add(normalizeHookEntryForMigration(fe));\n }\n }\n if (featureNormalized.size === 0) {\n return { migratedUser: userEntries, droppedCount: 0 };\n }\n let dropped = 0;\n const migratedUser = userEntries.filter((ue) => {\n if (entryUsesProjectDirVar(ue)) return true; // keep new versions\n // Old version — drop nếu tool đang ship new logical-equivalent\n if (featureNormalized.has(normalizeHookEntryForMigration(ue))) {\n dropped++;\n return false;\n }\n return true;\n });\n return { migratedUser, droppedCount: dropped };\n}\n\n// ENABLE: merge hooks (append + dedupe theo matcher+command) + union deny.\n// v1.18.0: Auto-migrate stale relative-path entries của cùng tool trong user\n// settings — drop old keep new. Trigger khi user upgrade pack từ pre-v0.7.1.\nexport async function enableTool(\n workspacePath: string,\n manifest: ToolManifest,\n): Promise<ApplyToolResult> {\n const { settings, existed } = await loadProjectSettings(workspacePath);\n const merged: PackSettingsTemplate = { ...settings };\n const changes: string[] = [];\n\n // Hooks per-event: migrate stale + append entries tool, dedupe theo key.\n const mHooks = manifest.settings.hooks ?? {};\n if (Object.keys(mHooks).length > 0) {\n const userHooks = (settings.hooks ?? {}) as Record<string, HookEntry[]>;\n const outHooks: Record<string, HookEntry[]> = { ...userHooks };\n const touched: string[] = [];\n let totalMigrated = 0;\n for (const [event, featEntries] of Object.entries(mHooks)) {\n const existing = userHooks[event] ?? [];\n // v1.18.0: Drop stale relative-path versions của entries tool đang re-ship.\n const { migratedUser, droppedCount } = migrateStaleEntries(existing, featEntries);\n if (droppedCount > 0) {\n totalMigrated += droppedCount;\n }\n const seen = new Set(migratedUser.map(hookEntryKey));\n const toAdd = featEntries.filter((e) => !seen.has(hookEntryKey(e)));\n if (toAdd.length > 0 || droppedCount > 0) {\n outHooks[event] = [...migratedUser, ...toAdd];\n touched.push(event);\n }\n }\n if (touched.length > 0) {\n merged.hooks = outHooks as Record<string, unknown[]>;\n changes.push(`hooks added: ${touched.join(\", \")}`);\n if (totalMigrated > 0) {\n changes.push(\n `migrated ${totalMigrated} stale relative-path entries → \\\\${\"{\"}CLAUDE_PROJECT_DIR\\\\${\"}\"} version`,\n );\n }\n }\n }\n\n // permissions.deny: union + dedupe.\n const featDeny = manifest.settings.permissions?.deny ?? [];\n if (featDeny.length > 0) {\n const userDeny = settings.permissions?.deny ?? [];\n const unionDeny = unionDedupe(userDeny, featDeny);\n if (unionDeny.length !== userDeny.length) {\n merged.permissions = { ...settings.permissions, deny: unionDeny };\n changes.push(`deny +${unionDeny.length - userDeny.length}`);\n }\n }\n\n if (changes.length === 0) {\n return { action: \"no-change\", changes: [] };\n }\n const backupPath = await backupAndWrite(workspacePath, merged, existed);\n return { action: \"enabled\", backupPath, changes };\n}\n\n// DISABLE: rút ĐÚNG hook entries + deny patterns mà manifest khai. Giữ nguyên mọi\n// thứ khác (scout-block, deny user tự thêm). Dọn mảng rỗng sau khi rút.\nexport async function disableTool(\n workspacePath: string,\n manifest: ToolManifest,\n): Promise<ApplyToolResult> {\n const { settings, existed } = await loadProjectSettings(workspacePath);\n if (!existed) {\n return { action: \"no-change\", changes: [] };\n }\n // Build merged bằng cách OMIT key (không dùng `delete` — theo convention codebase\n // + lint noDelete). Bắt đầu từ bản sao không gồm hooks/permissions; gán lại nếu\n // còn nội dung sau khi rút.\n const { hooks: _h, permissions: _p, ...rest } = settings;\n const merged: PackSettingsTemplate = { ...rest };\n const changes: string[] = [];\n\n // Hooks: xoá entry khớp key của tool, GIỮ entry khác (scout-block...).\n const mHooks = manifest.settings.hooks ?? {};\n const userHooks = (settings.hooks ?? {}) as Record<string, HookEntry[]>;\n const outHooks: Record<string, HookEntry[]> = {};\n const removed: string[] = [];\n for (const [event, entries] of Object.entries(userHooks)) {\n const featEntries = mHooks[event];\n if (!featEntries) {\n outHooks[event] = entries; // event không thuộc tool → giữ nguyên.\n continue;\n }\n const featKeys = new Set(featEntries.map(hookEntryKey));\n const kept = entries.filter((e) => !featKeys.has(hookEntryKey(e)));\n if (kept.length !== entries.length) {\n removed.push(event);\n }\n if (kept.length > 0) {\n outHooks[event] = kept; // còn entry khác → giữ; rỗng → bỏ key.\n }\n }\n if (Object.keys(outHooks).length > 0) {\n merged.hooks = outHooks as Record<string, unknown[]>;\n }\n if (removed.length > 0) {\n changes.push(`hooks removed: ${removed.join(\", \")}`);\n }\n\n // Deny: chỉ xoá đúng patterns manifest đã thêm. KHÔNG xoá deny user tự thêm.\n const featDeny = new Set(manifest.settings.permissions?.deny ?? []);\n const userDeny = settings.permissions?.deny ?? [];\n const keptDeny = featDeny.size > 0 ? userDeny.filter((d) => !featDeny.has(d)) : userDeny;\n // Tái dựng permissions: giữ field khác (allow...), set deny nếu còn, OMIT nếu rỗng.\n if (settings.permissions) {\n const { deny: _d, ...permRest } = settings.permissions;\n const newPerms = { ...permRest, ...(keptDeny.length > 0 ? { deny: keptDeny } : {}) };\n if (Object.keys(newPerms).length > 0) {\n merged.permissions = newPerms;\n }\n }\n if (keptDeny.length !== userDeny.length) {\n changes.push(`deny -${userDeny.length - keptDeny.length}`);\n }\n\n if (changes.length === 0) {\n return { action: \"no-change\", changes: [] };\n }\n const backupPath = await backupAndWrite(workspacePath, merged, existed);\n return { action: \"disabled\", backupPath, changes };\n}\n","// Merge pack template settings.json vào project settings.json.\n//\n// Background:\n// - Pack ship templates/settings.json.tpl với `hooks`, `statusLine`, `permissions`\n// declare workflow team-shared (dev-rules-reminder, scout-block, ...).\n// - Avatar `init` ghi base settings.json với env Avatar vars + permissions baseline.\n// - Sau khi `sync` symlink farm xong, hooks pack KHÔNG active vì project\n// settings.json chưa khai báo hook entries → Claude Code không trigger.\n//\n// Strategy (Q1 confirmed: \"Pack base + user override\"):\n// - Pack template = baseline cho hooks + statusLine + permissions.\n// - User customization (env, model, existing permissions extras, existing hook entries\n// dùng matcher khác) → preserve.\n// - Deep merge field-level:\n// env → user wins (giữ API key)\n// statusLine → pack wins nếu user chưa set\n// permissions → union (pack + user, dedupe)\n// hooks → merge per-event, dedupe theo (matcher + command)\n// model → user wins nếu set; pack default fallback\n// includeCoAuthoredBy → pack value áp dụng nếu user chưa set\n//\n// Safety:\n// - Backup .claude/settings.json → .claude/settings.json.backup-<timestamp> trước merge\n// - Atomic write file mới\n// - Return result để caller log từng action\n\nimport { promises as fs } from \"node:fs\";\nimport path, { join } from \"node:path\";\nimport { pathExists, readJson, readText, writeJsonAtomic } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface PackSettingsTemplate {\n $schema?: string;\n includeCoAuthoredBy?: boolean;\n model?: string;\n env?: Record<string, string>;\n permissions?: { allow?: string[]; deny?: string[] };\n hooks?: Record<string, unknown[]>;\n statusLine?: { type: string; command: string; padding?: number };\n}\n\nexport interface MergeSettingsResult {\n action: \"merged\" | \"no-change\" | \"no-pack-template\";\n backupPath?: string;\n changes: string[]; // human-readable list of fields touched\n}\n\n// v1.7.0: Verify statusLine command file ref có thật trong workspace.\n// Pack template thường ref `node .claude/statusline.cjs` hoặc shell command absolute.\n// Parse simple: nếu là `node <path>` hoặc `python3 <path>` → check path tồn tại.\n// Nếu là shell command khác (vd `echo`, `date`) → assume OK (không Avatar's concern).\nasync function isStatusLineCommandResolvable(\n workspacePath: string,\n command: string,\n): Promise<boolean> {\n const trimmed = command.trim();\n // Pattern: <interpreter> <relative-or-absolute-path> [args...]\n const match = trimmed.match(/^(node|python|python3|bash|sh)\\s+([^\\s]+)/);\n if (!match?.[2]) {\n // Shell command (echo, date, ...) hoặc regex miss capture group — không verify, assume OK.\n return true;\n }\n const filePath = match[2];\n // v1.14.0 SECURITY: Reject absolute paths từ pack template (supply chain attack vector).\n // Pack compromise có thể inject hook trỏ tới `/etc/passwd` / `/root/.ssh/...`\n // (pathExists pass cho mọi file readable trên system → leak hint).\n // Pack hook PHẢI relative tới workspace — không vượt khỏi `.claude/`.\n if (filePath.startsWith(\"/\")) {\n log.warn(\n `Pack hook reject: absolute path \"${filePath}\" — chỉ accept relative path tới workspace.`,\n );\n return false;\n }\n // Resolve relative path từ workspace root (.claude/statusline.cjs → <ws>/.claude/statusline.cjs).\n // Defensive: assert resolved path vẫn nằm trong workspace (block `../etc/...` escape).\n const fullPath = join(workspacePath, filePath);\n const wsResolved = path.resolve(workspacePath);\n const targetResolved = path.resolve(fullPath);\n if (!targetResolved.startsWith(`${wsResolved}/`) && targetResolved !== wsResolved) {\n log.warn(`Pack hook reject: path \"${filePath}\" resolve ra ngoài workspace (path traversal).`);\n return false;\n }\n return await pathExists(fullPath);\n}\n\n// Generate backup filename với timestamp YYMMDD-HHMM để debug + restore.\n// Exported: tái dùng cho apply-tool-manifest (enable/disable cũng backup trước).\nexport function backupFilename(originalPath: string): string {\n const d = new Date();\n const stamp = `${\n d.getFullYear().toString().slice(-2) +\n String(d.getMonth() + 1).padStart(2, \"0\") +\n String(d.getDate()).padStart(2, \"0\")\n }-${String(d.getHours()).padStart(2, \"0\")}${String(d.getMinutes()).padStart(2, \"0\")}`;\n return `${originalPath}.backup-${stamp}`;\n}\n\n// Dedupe + union 2 arrays giữ thứ tự (first occurrence wins).\n// Exported: tái dùng cho apply-tool-manifest (union deny + dedupe hooks).\nexport function unionDedupe<T>(a: T[], b: T[]): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const item of [...a, ...b]) {\n const key = typeof item === \"string\" ? item : JSON.stringify(item);\n if (!seen.has(key)) {\n seen.add(key);\n out.push(item);\n }\n }\n return out;\n}\n\n// Normalize command để so sánh \"logically same hook\" giữa old relative-path\n// và new ${CLAUDE_PROJECT_DIR}-path. Strip prefix để hai version cùng hook\n// có chung normalized form.\n//\n// Examples:\n// \"node .claude/hooks/foo.cjs\" → \"node .claude/hooks/foo.cjs\"\n// \"node ${CLAUDE_PROJECT_DIR}/.claude/hooks/foo.cjs\" → \"node .claude/hooks/foo.cjs\"\n// → cùng normalized form → coi là duplicate.\nfunction normalizeCommandForMigration(command: string): string {\n return command.replace(/\\$\\{CLAUDE_PROJECT_DIR\\}\\//g, \"\").trim();\n}\n\n// Extract command string từ hook entry (best-effort — return all commands joined\n// nếu entry có nhiều hooks; mỗi entry thường chỉ có 1 hook).\nfunction extractCommandsFromEntry(entry: unknown): string[] {\n if (typeof entry !== \"object\" || entry === null) return [];\n const obj = entry as Record<string, unknown>;\n const hooks = Array.isArray(obj.hooks) ? obj.hooks : [];\n const cmds: string[] = [];\n for (const h of hooks) {\n if (typeof h === \"object\" && h !== null) {\n const cmd = (h as Record<string, unknown>).command;\n if (typeof cmd === \"string\") cmds.push(cmd);\n }\n }\n return cmds;\n}\n\n// v1.18.0 MIGRATION: Loại bỏ stale relative-path entries khi pack template/tool\n// manifest đã chuyển sang ${CLAUDE_PROJECT_DIR}/. Background: v0.7.0 pack ship\n// relative path \"node .claude/hooks/foo.cjs\"; Bash tool cd vào subdir làm cwd\n// kẹt → hook fail ENOENT. v0.7.1 pack đổi sang \"node ${CLAUDE_PROJECT_DIR}/...\".\n// Nhưng user settings.json đã có entry cũ — merge mới chỉ APPEND new, không xóa\n// old → cả 2 entries cùng tồn tại, old fail noisy mỗi prompt.\n//\n// Migration logic:\n// - Trong mỗi event, group entries theo normalized command.\n// - Nếu group có cả old (no ${CLAUDE_PROJECT_DIR}) + new (with) → drop old, keep new.\n// - Group chỉ có 1 version → giữ nguyên (không opinion về user's custom hooks).\n//\n// Returns: { migrated entries, count of dropped stale entries per event }\nfunction migrateStaleRelativePathHooks(entries: unknown[]): {\n entries: unknown[];\n droppedCount: number;\n} {\n // Group by normalized command form\n type IndexedEntry = { index: number; entry: unknown; commands: string[]; hasVar: boolean };\n const indexed: IndexedEntry[] = entries.map((entry, index) => {\n const commands = extractCommandsFromEntry(entry);\n const hasVar = commands.some((c) => c.includes(\"${CLAUDE_PROJECT_DIR}\"));\n return { index, entry, commands, hasVar };\n });\n\n // Build normalized-command → list of indexed entries\n const byNormalized = new Map<string, IndexedEntry[]>();\n for (const ie of indexed) {\n const key = ie.commands.map(normalizeCommandForMigration).sort().join(\"|\");\n if (!key) continue; // skip entries with no commands\n const list = byNormalized.get(key) ?? [];\n list.push(ie);\n byNormalized.set(key, list);\n }\n\n // Find indices to drop: groups with both old + new → drop old.\n const toDrop = new Set<number>();\n for (const [, items] of byNormalized) {\n if (items.length < 2) continue;\n const hasAnyNew = items.some((it) => it.hasVar);\n if (!hasAnyNew) continue;\n for (const it of items) {\n if (!it.hasVar) toDrop.add(it.index);\n }\n }\n\n if (toDrop.size === 0) {\n return { entries, droppedCount: 0 };\n }\n\n const filtered = entries.filter((_, i) => !toDrop.has(i));\n return { entries: filtered, droppedCount: toDrop.size };\n}\n\n// Merge hooks per-event: pack entries + user entries, dedupe theo (matcher + commands).\n// Schema Claude Code: { EventName: [ { matcher?: string, hooks: [{type, command}] } ] }\n//\n// v1.18.0: Sau khi union dedupe, chạy migrateStaleRelativePathHooks để xóa entries\n// relative-path cũ nếu đã có version ${CLAUDE_PROJECT_DIR} mới — fix cho upgrade\n// từ pack pre-v0.7.1 lên v0.7.1+ (settings.json không tự cleanup khi tool.json\n// đổi command string).\nfunction mergeHooksPerEvent(\n packHooks: Record<string, unknown[]>,\n userHooks: Record<string, unknown[]>,\n): { merged: Record<string, unknown[]>; touchedEvents: string[]; migratedCount: number } {\n const touched: string[] = [];\n const merged: Record<string, unknown[]> = { ...userHooks };\n let totalMigrated = 0;\n\n // First migrate existing user entries (handle case where user has stale entries\n // not from pack — eg. legacy install before sync).\n for (const event of Object.keys(merged)) {\n const userEntries = (merged[event] as unknown[]) || [];\n const { entries: migrated, droppedCount } = migrateStaleRelativePathHooks(userEntries);\n if (droppedCount > 0) {\n merged[event] = migrated;\n totalMigrated += droppedCount;\n if (!touched.includes(event)) touched.push(event);\n }\n }\n\n // Then union with pack entries + dedupe (existing behavior).\n for (const [event, packEntries] of Object.entries(packHooks)) {\n const userEntries = (merged[event] as unknown[]) || [];\n const union = unionDedupe(userEntries, packEntries);\n // Run migration again after union (in case pack new + user old now coexist).\n const { entries: postMigrate, droppedCount: postDropped } =\n migrateStaleRelativePathHooks(union);\n if (postDropped > 0) {\n totalMigrated += postDropped;\n }\n if (postMigrate.length !== userEntries.length) {\n if (!touched.includes(event)) touched.push(event);\n }\n merged[event] = postMigrate;\n }\n return { merged, touchedEvents: touched, migratedCount: totalMigrated };\n}\n\n// Main entry. Đọc pack template + user settings, merge, backup + write atomic.\n// Returns result để caller report.\nexport async function mergePackSettingsIntoProjectSettings(\n workspacePath: string,\n): Promise<MergeSettingsResult> {\n const packTemplatePath = join(workspacePath, \".claude\", \"pack\", \"templates\", \"settings.json.tpl\");\n const projectSettingsPath = join(workspacePath, \".claude\", \"settings.json\");\n\n // Pack chưa sync hoặc không có template → no-op.\n if (!(await pathExists(packTemplatePath))) {\n return { action: \"no-pack-template\", changes: [] };\n }\n\n // Pack template có thể chứa placeholders {{...}}; chúng ta merge nguyên trạng\n // (placeholder sẽ giữ literal trong project settings nếu user chưa thay).\n // Tuy nhiên hooks + statusLine + permissions không dùng placeholder → safe.\n let packTemplate: PackSettingsTemplate;\n try {\n const raw = await readText(packTemplatePath);\n packTemplate = JSON.parse(raw) as PackSettingsTemplate;\n } catch (err) {\n throw new Error(\n `Pack settings template không parse được JSON: ${(err as Error).message}. Path: ${packTemplatePath}`,\n );\n }\n\n // Project settings — nếu chưa có (vd init bị skip) thì coi như empty base.\n let userSettings: PackSettingsTemplate = {};\n let projectHasSettings = false;\n if (await pathExists(projectSettingsPath)) {\n projectHasSettings = true;\n try {\n userSettings = await readJson<PackSettingsTemplate>(projectSettingsPath);\n } catch (err) {\n throw new Error(\n `Project settings.json không parse được: ${(err as Error).message}. Manual fix trước khi sync.`,\n );\n }\n }\n\n const changes: string[] = [];\n const merged: PackSettingsTemplate = { ...userSettings };\n\n // 1. statusLine — pack wins nếu user chưa set, NHƯNG verify file ref tồn tại.\n // v1.7.0 fix: trước đây merge blindly → settings ref `node .claude/statusline.cjs`\n // nhưng pack KHÔNG ship file đó → Claude Code fail im lặng render statusbar.\n // Giờ check: command pack ref tồn tại trong workspace? Nếu không → skip + warn.\n if (packTemplate.statusLine && !userSettings.statusLine) {\n const statusLineOk = await isStatusLineCommandResolvable(\n workspacePath,\n packTemplate.statusLine.command,\n );\n if (statusLineOk) {\n merged.statusLine = packTemplate.statusLine;\n changes.push(\"statusLine added\");\n } else {\n changes.push(\n `statusLine SKIPPED (file ref '${packTemplate.statusLine.command}' không tồn tại)`,\n );\n }\n }\n\n // 2. includeCoAuthoredBy — pack wins nếu user chưa set.\n if (\n typeof packTemplate.includeCoAuthoredBy === \"boolean\" &&\n typeof userSettings.includeCoAuthoredBy !== \"boolean\"\n ) {\n merged.includeCoAuthoredBy = packTemplate.includeCoAuthoredBy;\n changes.push(\"includeCoAuthoredBy added\");\n }\n\n // 3. model — user wins; chỉ add nếu pack có và user chưa set.\n if (packTemplate.model && !userSettings.model) {\n merged.model = packTemplate.model;\n changes.push(\"model added\");\n }\n\n // 4. env — user wins entirely (giữ API key). Chỉ add var pack có mà user chưa có.\n // v1.20.0: Auto-rewrite legacy domain llm.nal.vn → ai.nal.vn nếu user có\n // settings.json từ pre-v1.20.0 (NAL gateway đổi domain). Silent migration,\n // không cần user confirm — cùng infra rename.\n if (packTemplate.env || userSettings.env) {\n const mergedEnv: Record<string, string> = { ...(userSettings.env || {}) };\n let envChanged = false;\n // Domain rewrite trên mọi string value\n for (const [k, v] of Object.entries(mergedEnv)) {\n if (typeof v === \"string\" && v.includes(\"llm.nal.vn\")) {\n mergedEnv[k] = v.replace(/llm\\.nal\\.vn/g, \"ai.nal.vn\");\n envChanged = true;\n }\n }\n if (envChanged) {\n changes.push(\"rewrote legacy llm.nal.vn → ai.nal.vn in env vars\");\n }\n // Pack env vars add nếu user chưa có\n if (packTemplate.env) {\n for (const [k, v] of Object.entries(packTemplate.env)) {\n if (!(k in mergedEnv)) {\n mergedEnv[k] = v;\n envChanged = true;\n }\n }\n }\n if (envChanged) {\n merged.env = mergedEnv;\n if (!changes.some((c) => c.includes(\"env vars\"))) {\n changes.push(\"env vars added from pack\");\n }\n }\n }\n\n // 5. permissions — union allow + union deny (dedupe).\n if (packTemplate.permissions) {\n const userAllow = userSettings.permissions?.allow || [];\n const userDeny = userSettings.permissions?.deny || [];\n const packAllow = packTemplate.permissions.allow || [];\n const packDeny = packTemplate.permissions.deny || [];\n const mergedAllow = unionDedupe(userAllow, packAllow);\n const mergedDeny = unionDedupe(userDeny, packDeny);\n if (mergedAllow.length !== userAllow.length || mergedDeny.length !== userDeny.length) {\n merged.permissions = { allow: mergedAllow, deny: mergedDeny };\n changes.push(\n `permissions union (+${mergedAllow.length - userAllow.length} allow, +${mergedDeny.length - userDeny.length} deny)`,\n );\n }\n }\n\n // 6. hooks — merge per-event với dedupe + migrate stale relative-path entries.\n if (packTemplate.hooks) {\n const userHooks = userSettings.hooks || {};\n const {\n merged: mergedHooks,\n touchedEvents,\n migratedCount,\n } = mergeHooksPerEvent(packTemplate.hooks, userHooks);\n if (touchedEvents.length > 0 || migratedCount > 0) {\n merged.hooks = mergedHooks;\n if (touchedEvents.length > 0) {\n changes.push(`hooks added for events: ${touchedEvents.join(\", \")}`);\n }\n if (migratedCount > 0) {\n changes.push(\n `migrated ${migratedCount} stale relative-path hook entries to use \\${CLAUDE_PROJECT_DIR} version (fixes hook failure when shell cwd changes)`,\n );\n }\n }\n }\n\n // Không có thay đổi gì → skip write.\n if (changes.length === 0) {\n return { action: \"no-change\", changes: [] };\n }\n\n // Backup + write.\n let backupPath: string | undefined;\n if (projectHasSettings) {\n backupPath = backupFilename(projectSettingsPath);\n await fs.copyFile(projectSettingsPath, backupPath);\n }\n await writeJsonAtomic(projectSettingsPath, merged);\n return { action: \"merged\", backupPath, changes };\n}\n","// tool-state-store.ts\n//\n// Đọc/ghi <project>/.claude/avatar-tools.json — state file project-level ghi\n// nhận tool nào ĐANG bật (ý định Avatar, tách khỏi settings.json của Claude Code).\n//\n// Vì sao project-level (không phải ~/.avatar): bật/tắt tool là quyết định CỦA\n// DỰ ÁN — cả team nên giống nhau → file commit được. `sync` chạy trong project\n// context nên đọc tự nhiên để re-apply.\n//\n// Schema:\n// {\n// \"tools\": {\n// \"prompt-scoring\": { \"enabled\": true, \"version\": \"0.1.0\", \"appliedAt\": \"ISO\" }\n// }\n// }\n//\n// Safety: atomic write (qua filesystem-helpers). Parse lỗi → coi như empty state\n// (fail-soft, không crash CLI vì 1 file state hỏng).\n\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const TOOL_STATE_RELATIVE_PATH = \".claude/avatar-tools.json\";\n\nexport interface ToolStateEntry {\n enabled: boolean;\n version: string;\n appliedAt: string; // ISO timestamp lần enable/disable gần nhất\n}\n\nexport interface ToolState {\n tools: Record<string, ToolStateEntry>;\n}\n\nfunction emptyState(): ToolState {\n return { tools: {} };\n}\n\nfunction stateFilePath(workspacePath: string): string {\n return join(workspacePath, TOOL_STATE_RELATIVE_PATH);\n}\n\n// Đọc state. File thiếu hoặc parse lỗi → empty state (fail-soft).\nexport async function readToolState(workspacePath: string): Promise<ToolState> {\n const path = stateFilePath(workspacePath);\n if (!(await pathExists(path))) {\n return emptyState();\n }\n try {\n const parsed = await readJson<Partial<ToolState>>(path);\n // Defensive: file tồn tại nhưng thiếu `tools` → normalize.\n return { tools: parsed.tools ?? {} };\n } catch {\n return emptyState();\n }\n}\n\n// Ghi state atomic. Giữ nguyên các field không quản lý nếu sau này mở rộng.\nexport async function writeToolState(workspacePath: string, state: ToolState): Promise<void> {\n await writeJsonAtomic(stateFilePath(workspacePath), state);\n}\n\n// Set 1 tool enabled/disabled + stamp version & appliedAt. Trả về state mới.\nexport async function setToolState(\n workspacePath: string,\n name: string,\n entry: { enabled: boolean; version: string },\n): Promise<ToolState> {\n const state = await readToolState(workspacePath);\n state.tools[name] = {\n enabled: entry.enabled,\n version: entry.version,\n appliedAt: new Date().toISOString(),\n };\n await writeToolState(workspacePath, state);\n return state;\n}\n\n// Tiện ích: danh sách tên tool đang enabled=true (dùng cho sync re-apply).\nexport async function listEnabledTools(workspacePath: string): Promise<string[]> {\n const state = await readToolState(workspacePath);\n return Object.entries(state.tools)\n .filter(([, entry]) => entry.enabled)\n .map(([name]) => name);\n}\n","// `avatar gitnexus` — Standalone command quản lý GitNexus integration (M10).\n// 3 subcommands:\n// - install : Run full setup phase (install + setup + analyze + wiki + MCP)\n// - status : Show index info (last commit, staleness, wiki age)\n// - analyze : Re-run analyze only (no wiki, không cost API)\n//\n// Cần chạy trong workspace dir — resolve qua resolveAvatarWorkspaceRootFromCwd\n// để strict detect Avatar workspace (không phải folder random có .claude/).\nimport { spawnSync } from \"node:child_process\";\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Command } from \"commander\";\nimport { pathExists, readJson } from \"../lib/filesystem-helpers.js\";\nimport { resolveAvatarWorkspaceRootFromCwd } from \"../lib/resolve-avatar-workspace-root-from-cwd.js\";\nimport { runGitnexusAnalyze } from \"../lib/run-gitnexus-setup-and-analyze.js\";\nimport { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\n// Resolve Avatar workspace từ cwd (walk-up + strict marker check).\n// Exit 1 với hint clear nếu không tìm thấy.\nfunction ensureWorkspaceCwd(): string {\n const cwd = process.cwd();\n const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);\n if (!workspaceRoot) {\n log.error(\n `Không tìm thấy Avatar workspace từ thư mục hiện tại.\n Avatar workspace cần có: .claude/ + CLAUDE.md + src/ (hoặc .gitmodules).\n Bạn đang ở: ${cwd}\n Cd vào workspace dir rồi chạy lại.`,\n );\n process.exit(1);\n }\n if (workspaceRoot !== cwd) {\n log.dim(`Detected workspace root: ${workspaceRoot}`);\n }\n return workspaceRoot;\n}\n\n// === install ===\n// Full setup phase — reuse orchestrator từ Phase 06.\nasync function runGitnexusInstall(): Promise<void> {\n const workspacePath = ensureWorkspaceCwd();\n const result = await runGitnexusSetupPhase({ workspacePath });\n if (result.ok) {\n log.success(\"GitNexus setup complete\");\n log.dim(\"Update CLAUDE.md để re-render section GitNexus: re-run avatar init hoặc chỉnh tay.\");\n } else {\n log.warn(`Setup không complete: ${result.reason ?? \"unknown\"}`);\n }\n}\n\n// === status ===\n// Show GitNexus index info + staleness warning + wiki age.\nasync function runGitnexusStatus(): Promise<void> {\n const workspacePath = ensureWorkspaceCwd();\n const metaPath = join(workspacePath, \".gitnexus\", \"meta.json\");\n\n if (!(await pathExists(metaPath))) {\n log.warn(`Chưa có ${metaPath}. Chạy: avatar gitnexus install`);\n return;\n }\n\n try {\n const meta = await readJson<{\n lastCommit?: string;\n indexedAt?: string;\n stats?: { files?: number; nodes?: number; edges?: number };\n }>(metaPath);\n\n log.info(`Project: ${workspacePath}`);\n log.info(`Last commit: ${meta.lastCommit?.slice(0, 7) ?? \"(unknown)\"}`);\n log.info(`Indexed at: ${meta.indexedAt ?? \"(unknown)\"}`);\n if (meta.stats) {\n log.info(\n `Stats: ${meta.stats.files ?? \"?\"} files · ${meta.stats.nodes ?? \"?\"} nodes · ${meta.stats.edges ?? \"?\"} edges`,\n );\n }\n\n // Staleness check qua git rev-parse HEAD vs meta.lastCommit.\n if (meta.lastCommit) {\n const headResult = spawnSync(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: workspacePath,\n encoding: \"utf8\",\n });\n if (headResult.status === 0) {\n const currentHead = headResult.stdout.trim();\n if (currentHead !== meta.lastCommit) {\n log.warn(\"⚠ Index stale — HEAD đã tiến từ lastCommit. Chạy: avatar gitnexus analyze\");\n } else {\n log.dim(\"Index fresh (HEAD === lastCommit)\");\n }\n }\n }\n\n // Wiki age check.\n const wikiPath = join(workspacePath, \".gitnexus\", \"wiki\", \"index.html\");\n if (await pathExists(wikiPath)) {\n const stat = await fs.stat(wikiPath);\n log.info(`Wiki: ${stat.mtime.toISOString()}`);\n } else {\n log.dim(\"Wiki: chưa generate (chạy gitnexus wiki manual)\");\n }\n } catch (err) {\n log.error(`Read meta.json fail: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\n// === analyze ===\n// Re-run analyze refresh index (no wiki — fast + zero API cost).\nasync function runGitnexusAnalyzeCommand(): Promise<void> {\n const workspacePath = ensureWorkspaceCwd();\n try {\n runGitnexusAnalyze(workspacePath);\n log.success(\"Index refreshed. Chạy `avatar gitnexus status` xem chi tiết.\");\n } catch (err) {\n log.error(`Analyze fail: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\nexport function registerGitnexusCommand(program: Command): void {\n const gx = program.command(\"gitnexus\").description(\"Quản lý GitNexus code intelligence (M10)\");\n\n gx.command(\"install\")\n .description(\"Cài + setup GitNexus cho workspace hiện tại\")\n .action(async () => {\n await runGitnexusInstall();\n });\n\n gx.command(\"status\")\n .description(\"Show index info + staleness warning\")\n .action(async () => {\n await runGitnexusStatus();\n });\n\n gx.command(\"analyze\")\n .description(\"Re-run analyze refresh index (no wiki)\")\n .action(async () => {\n await runGitnexusAnalyzeCommand();\n });\n}\n","// Orchestrator Phase 10 GitNexus Setup — compose phase 01-05 thành fail-soft flow.\n// Gọi sau `runAiSetupPhase` trong `finalizeWorkspaceScaffold`.\n//\n// Try/catch toàn bộ — KHÔNG throw. Workspace OK dù GitNexus fail:\n// - User decline install → return { ok: false, reason: \"user-declined\" }\n// - Install fail → recovery menu (retry/skip/abort) — abort throw UserAbortedRecoveryError\n// - Setup/analyze fail → recovery menu\n// - Wiki fail → log warn nhưng continue (wiki không critical)\n// - MCP register fail → log warn nhưng continue (user thấy workspace work)\n//\n// Audit log: gitnexus_setup,result=ok/skipped/failed,reason=...\nimport { confirm } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport { appendAuditEntry } from \"./audit-log-appender.js\";\nimport {\n detectGitnexusInstallation,\n invalidateGitnexusInstallationCache,\n} from \"./detect-gitnexus-installation.js\";\nimport { InstallGitnexusError, installGitnexusViaNpm } from \"./install-gitnexus-via-npm.js\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.js\";\nimport { registerGitnexusMcpServer } from \"./register-gitnexus-mcp-server.js\";\nimport {\n GitnexusOperationError,\n runGitnexusAnalyze,\n runGitnexusSetup,\n} from \"./run-gitnexus-setup-and-analyze.js\";\nimport { runGitnexusWikiConditional } from \"./run-gitnexus-wiki-conditional.js\";\nimport { chalk, log } from \"./terminal-logger.js\";\n\nexport interface GitnexusSetupArgs {\n workspacePath: string;\n // v3: init KHÔNG analyze (workspace src/ rỗng → index vô nghĩa). Chỉ install +\n // setup skills + MCP register. Analyze chạy per-repo khi `avatar add repo`.\n skipAnalyze?: boolean;\n}\n\nexport interface GitnexusSetupResult {\n ok: boolean;\n installed: boolean; // gitnexus binary có hay chưa\n analyzed: boolean; // .gitnexus/ created\n wikiGenerated: boolean; // wiki/index.html created\n mcpRegistered: boolean;\n reason?: string;\n}\n\n// Boxen prompt giải thích GitNexus + ask install. UX explicit consent.\nasync function promptInstallGitnexus(): Promise<boolean> {\n const lines = [\n chalk.bold(\"🧠 GitNexus chưa cài\"),\n \"\",\n \"GitNexus = code intelligence layer cho Claude Code:\",\n \" • Architectural awareness (impact analysis)\",\n \" • Call chain debug + blast radius trước refactor\",\n \" • Wiki HTML tự gen mô tả codebase\",\n \"\",\n `Sẽ cài: ${chalk.cyan(\"npm install -g gitnexus\")} (global)`,\n ];\n process.stdout.write(\n `${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\", borderColor: \"cyan\" })}\\n`,\n );\n return await confirm({ message: \"Cài GitNexus global?\", default: true });\n}\n\n// Helper — wrap install call với recovery menu (retry/skip/abort).\n// Trả true nếu cài thành công (sau retry), false nếu user skip.\nasync function installWithRecovery(): Promise<boolean> {\n while (true) {\n try {\n installGitnexusViaNpm();\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint =\n err instanceof InstallGitnexusError && err.reason === \"permission-denied\"\n ? \"Thử lại với sudo, hoặc fix npm prefix: npm config set prefix ~/.npm-global\"\n : \"Check log npm phía trên + thử lại.\";\n const action = await promptRetryOrSkip({\n taskName: \"Cài GitNexus qua npm\",\n reason: message,\n allowSkip: true,\n hint,\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước cài GitNexus.\");\n }\n if (action === \"skip\") return false;\n // retry → loop tiếp\n }\n }\n}\n\n// Helper — wrap analyze call với recovery menu.\nasync function analyzeWithRecovery(workspacePath: string): Promise<boolean> {\n while (true) {\n try {\n runGitnexusAnalyze(workspacePath);\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint =\n err instanceof GitnexusOperationError && err.reason === \"missing-output\"\n ? \"Repo có thể empty hoặc gitnexus version mismatch. Check `gitnexus --version`.\"\n : \"Network glitch? Retry thường work.\";\n const action = await promptRetryOrSkip({\n taskName: \"GitNexus analyze workspace\",\n reason: message,\n allowSkip: true,\n hint,\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước GitNexus analyze.\");\n }\n if (action === \"skip\") return false;\n }\n }\n}\n\n// Main orchestrator. Fail-soft semantic — return result, không throw (trừ user abort).\nexport async function runGitnexusSetupPhase(args: GitnexusSetupArgs): Promise<GitnexusSetupResult> {\n const result: GitnexusSetupResult = {\n ok: false,\n installed: false,\n analyzed: false,\n wikiGenerated: false,\n mcpRegistered: false,\n };\n\n try {\n log.info(\"=== Phase 10: GitNexus Setup ===\");\n\n // Step 1 — Detect + install nếu cần\n let info = detectGitnexusInstallation();\n if (!info.installed) {\n const shouldInstall = await promptInstallGitnexus();\n if (!shouldInstall) {\n await appendAuditEntry(\"gitnexus_setup\", \"result=skipped,reason=user-declined\");\n log.dim(\"Skip GitNexus. Cài sau qua `avatar gitnexus install`.\");\n result.reason = \"user-declined\";\n return result;\n }\n const installed = await installWithRecovery();\n if (!installed) {\n await appendAuditEntry(\"gitnexus_setup\", \"result=skipped,reason=install-skipped\");\n log.dim(\"Skip GitNexus install. Workspace OK không có codebase intelligence.\");\n result.reason = \"install-skipped\";\n return result;\n }\n // v1.13.0: Invalidate cache sau install để re-probe thấy binary mới.\n invalidateGitnexusInstallationCache();\n info = detectGitnexusInstallation();\n if (!info.installed) {\n throw new Error(\"Cài xong nhưng không detect được binary (PATH issue).\");\n }\n }\n result.installed = true;\n log.success(`GitNexus available${info.version ? ` v${info.version}` : \"\"}`);\n\n // Step 2 — `gitnexus setup` (global skills)\n // Wrap try/catch — setup fail không critical (skills không phải core), continue.\n try {\n runGitnexusSetup();\n } catch (err) {\n log.warn(`gitnexus setup fail: ${(err as Error).message}`);\n log.dim(\"Skip global skills install. Workspace vẫn dùng được.\");\n }\n\n // v3: skipAnalyze (init mỏng) → bỏ Step 3 (analyze) + Step 4 (wiki). Chỉ\n // install + skills + MCP. Analyze + wiki chạy per-repo khi `avatar add repo`.\n if (args.skipAnalyze) {\n log.dim(\"GitNexus: cài + MCP xong. Index sẽ chạy per-repo khi `avatar add repo`.\");\n } else {\n // Step 3 — `gitnexus analyze .` (workspace root)\n const analyzed = await analyzeWithRecovery(args.workspacePath);\n if (!analyzed) {\n await appendAuditEntry(\"gitnexus_setup\", \"result=skipped,reason=analyze-skipped\");\n log.dim(\"Skip analyze. GitNexus installed nhưng chưa index.\");\n result.reason = \"analyze-skipped\";\n return result;\n }\n result.analyzed = true;\n\n // Step 4 — Wiki conditional (LLMLite mode only)\n const wikiResult = await runGitnexusWikiConditional(args.workspacePath);\n result.wikiGenerated = wikiResult.ran;\n if (wikiResult.skipped && wikiResult.reason === \"fail\") {\n log.warn(`Wiki gen fail (workspace vẫn OK): ${wikiResult.detail ?? \"unknown\"}`);\n }\n }\n\n // Step 5 — Register MCP server\n try {\n const mcpResult = await registerGitnexusMcpServer();\n result.mcpRegistered = true;\n if (!mcpResult.wasUpdated) {\n log.dim(\"MCP server gitnexus đã registered trước đó.\");\n }\n } catch (err) {\n log.warn(`MCP server register fail: ${(err as Error).message}`);\n log.dim(\n \"Workspace OK nhưng Claude Code không tự attach MCP server. Manual add vào ~/.claude/mcp_servers.json.\",\n );\n }\n\n result.ok = true;\n await appendAuditEntry(\n \"gitnexus_setup\",\n `result=ok,analyzed=${result.analyzed},wiki=${result.wikiGenerated},mcp=${result.mcpRegistered}`,\n );\n log.success(\"GitNexus ready\");\n return result;\n } catch (err) {\n // User abort → re-throw để init.ts catch + exit 0 graceful (consistent với pattern).\n if (err instanceof UserAbortedRecoveryError) throw err;\n\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`GitNexus setup thất bại: ${message}`);\n log.dim(\"Workspace vẫn sẵn sàng. Setup sau qua `avatar gitnexus install`.\");\n await appendAuditEntry(\"gitnexus_setup\", `result=failed,error=${message.slice(0, 200)}`);\n result.reason = message;\n return result;\n }\n}\n","// Auto-install GitNexus CLI qua `npm install -g gitnexus`.\n// Stream stdio để user thấy npm progress. Sau khi cài xong → re-detect verify PATH.\n// Throws InstallGitnexusError với hint cụ thể cho EACCES / ENOSPC / timeout / generic.\n//\n// Pattern identical với install-claude-code-via-npm.ts.\nimport { spawnSync } from \"node:child_process\";\nimport {\n detectGitnexusInstallation,\n invalidateGitnexusInstallationCache,\n} from \"./detect-gitnexus-installation.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// 5 phút — npm global install có thể chậm trên mạng yếu.\nconst NPM_INSTALL_TIMEOUT_MS = 5 * 60 * 1000;\n\n// Package name chính thức của GitNexus trên npm.\nconst GITNEXUS_PACKAGE = \"gitnexus\";\n\nexport type InstallGitnexusReason =\n | \"permission-denied\" // EACCES → cần sudo hoặc fix npm prefix\n | \"disk-full\" // ENOSPC\n | \"timeout\" // > 5 phút\n | \"binary-not-in-path\" // install OK nhưng PATH chưa refresh\n | \"generic\"; // exit non-zero khác\n\nexport class InstallGitnexusError extends Error {\n reason: InstallGitnexusReason;\n exitCode: number | null;\n constructor(reason: InstallGitnexusReason, message: string, exitCode: number | null = null) {\n super(message);\n this.name = \"InstallGitnexusError\";\n this.reason = reason;\n this.exitCode = exitCode;\n }\n}\n\n// Phân loại exit từ npm install thành reason cụ thể để caller branch hint.\nfunction classifyNpmFailure(exitCode: number | null, stderrSample: string): InstallGitnexusError {\n const stderr = stderrSample.toLowerCase();\n if (stderr.includes(\"eacces\") || stderr.includes(\"permission denied\")) {\n return new InstallGitnexusError(\n \"permission-denied\",\n `npm install -g cần quyền. Thử: sudo npm install -g ${GITNEXUS_PACKAGE} hoặc fix npm prefix (npm config set prefix ~/.npm-global).`,\n exitCode,\n );\n }\n if (stderr.includes(\"enospc\") || stderr.includes(\"no space\")) {\n return new InstallGitnexusError(\"disk-full\", \"Đĩa đầy. Free disk space rồi thử lại.\", exitCode);\n }\n return new InstallGitnexusError(\n \"generic\",\n `npm install thất bại (exit ${exitCode ?? \"null\"}). Xem log npm phía trên.`,\n exitCode,\n );\n}\n\n// Main API: cài GitNexus global + verify. Throws InstallGitnexusError nếu fail.\n// Return version + path detect được (luôn non-null nếu success).\nexport function installGitnexusViaNpm(): { version: string | null; path: string } {\n log.info(\"Đang cài GitNexus qua npm (có thể mất 1-2 phút)...\");\n\n const result = spawnSync(\"npm\", [\"install\", \"-g\", GITNEXUS_PACKAGE], {\n stdio: [\"inherit\", \"inherit\", \"pipe\"],\n timeout: NPM_INSTALL_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.signal === \"SIGTERM\") {\n throw new InstallGitnexusError(\n \"timeout\",\n `npm install timeout sau ${NPM_INSTALL_TIMEOUT_MS / 1000}s. Check mạng rồi thử lại.`,\n null,\n );\n }\n\n if (result.status !== 0) {\n if (result.stderr) process.stderr.write(result.stderr);\n throw classifyNpmFailure(result.status, result.stderr || \"\");\n }\n\n // v1.14.1 BUG FIX: Invalidate memoize cache TRƯỚC re-detect.\n // Trước fix: cache giữ `{installed: false}` từ probe pre-install →\n // re-detect sau install trả false → throw \"binary-not-in-path\" SAI dù\n // binary đã thực sự cài vào /opt/homebrew/bin/gitnexus.\n invalidateGitnexusInstallationCache();\n\n // Re-detect — npm install OK không đồng nghĩa binary đã trong PATH (PATH cache).\n const probe = detectGitnexusInstallation();\n if (!probe.installed || !probe.path) {\n throw new InstallGitnexusError(\n \"binary-not-in-path\",\n \"npm cài xong nhưng `gitnexus` không trong PATH. Reload shell (source ~/.zshrc) hoặc thêm npm global bin vào PATH.\",\n null,\n );\n }\n\n log.success(`Đã cài GitNexus${probe.version ? ` v${probe.version}` : \"\"} tại ${probe.path}`);\n return { version: probe.version, path: probe.path };\n}\n","// Step 5 của Phase 10 — modify ~/.claude/mcp_servers.json add entry GitNexus\n// để Claude Code attach MCP server lúc start.\n//\n// Behavior:\n// - File chưa tồn tại → tạo mới với entry gitnexus duy nhất\n// - File có entries khác (vd tools, secrets) → merge giữ\n// - File có entry gitnexus đúng → no-op (idempotent)\n// - File có entry gitnexus khác → override (Q4 decision) + backup file cũ\n//\n// Atomic write qua tmp + rename + chmod 600 (consistent với pattern Avatar settings).\nimport { promises as fs } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// chmod 600 — consistent với .claude/settings.json pattern (chứa OAuth-like data).\nconst MCP_FILE_MODE = 0o600;\n\n// Entry expected mà Avatar muốn enforce — match GitNexus official MCP server spec.\nconst EXPECTED_GITNEXUS_ENTRY = {\n command: \"gitnexus\",\n args: [\"mcp\"],\n} as const;\n\nexport interface RegisterMcpResult {\n path: string; // ~/.claude/mcp_servers.json\n wasUpdated: boolean; // false nếu entry đã đúng (no-op)\n backup?: string; // path backup nếu file existed + cần modify\n}\n\n// Shape minimal của mcp_servers.json — index signature giữ key user khác.\ninterface McpServersConfig {\n mcp_servers?: Record<string, unknown>;\n [k: string]: unknown;\n}\n\n// Get path ~/.claude/mcp_servers.json cross-platform.\nfunction getMcpServersPath(): string {\n return join(homedir(), \".claude\", \"mcp_servers.json\");\n}\n\n// Deep equal cho 2 plain objects (đủ dùng cho entry compare, không cần full lodash).\nfunction isEntryEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n// Backup file existing với timestamp suffix (idempotent backup name).\nasync function backupExistingFile(path: string): Promise<string> {\n const ts = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupPath = `${path}.avatar-backup-${ts}`;\n await fs.copyFile(path, backupPath);\n return backupPath;\n}\n\n// Main API — register GitNexus MCP server entry idempotent.\n// Trả structured result để caller (orchestrator) log đúng action đã làm.\nexport async function registerGitnexusMcpServer(): Promise<RegisterMcpResult> {\n const path = getMcpServersPath();\n\n // Read existing (default {} nếu chưa tồn tại).\n let existing: McpServersConfig = {};\n let fileExisted = false;\n if (await pathExists(path)) {\n fileExisted = true;\n try {\n existing = await readJson<McpServersConfig>(path);\n } catch (err) {\n throw new Error(\n `MCP config corrupted (${path}): ${(err as Error).message}. Backup + xóa file để Avatar tạo lại.`,\n );\n }\n }\n\n // Idempotent check — nếu entry đã đúng thì no-op.\n const existingEntry = existing.mcp_servers?.gitnexus;\n if (existingEntry && isEntryEqual(existingEntry, EXPECTED_GITNEXUS_ENTRY)) {\n log.dim(`MCP entry gitnexus đã đúng tại ${path} (no-op)`);\n return { path, wasUpdated: false };\n }\n\n // Backup file cũ trước khi modify.\n let backup: string | undefined;\n if (fileExisted) {\n backup = await backupExistingFile(path);\n log.dim(`Backup ${path} → ${backup}`);\n }\n\n // Merge giữ entries khác user đã có.\n const merged: McpServersConfig = {\n ...existing,\n mcp_servers: {\n ...(existing.mcp_servers || {}),\n gitnexus: EXPECTED_GITNEXUS_ENTRY,\n },\n };\n\n await writeJsonAtomic(path, merged, MCP_FILE_MODE);\n // Re-chmod sau write — pattern consistent với write-claude-settings-json-per-project.\n try {\n await fs.chmod(path, MCP_FILE_MODE);\n } catch {\n // Windows / fs không support — bỏ qua.\n }\n\n log.success(`Registered MCP server: gitnexus → ${path}`);\n return { path, wasUpdated: true, backup };\n}\n","// `avatar init <workspace-name>` — v3 THIN INIT.\n//\n// 1 flow duy nhất (bỏ internal/client/library + existing-remote/folder/new-project).\n// Init chỉ dựng MÔI TRƯỜNG: login NAL → tạo workspace folder (KHÔNG git) → cài\n// team-ai-pack tarball → symlink + settings → GitNexus(install+MCP) → AI setup.\n//\n// Repo code cài SAU bằng `avatar add repo` (src/ rỗng sau init).\nimport { resolve } from \"node:path\";\nimport { input } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport { printAvatarBanner } from \"../lib/avatar-ascii-banner.js\";\nimport { RemoteAccessAbortedError } from \"../lib/handle-remote-access-failure-with-account-switch.js\";\nimport { UserAbortedRecoveryError } from \"../lib/prompt-recovery-action-on-failure.js\";\nimport { InitAbortedByUserError } from \"../lib/safe-bootstrap-for-dirty-folder.js\";\nimport { TeamPackAccessAbortedError } from \"../lib/team-pack-submodule-manager.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { isTokenExpired, readUserConfig } from \"../lib/user-config-store.js\";\nimport { promptTeamOwner, resolveWorkspacePath } from \"./init-success-rendering-and-helpers.js\";\nimport { runLogin } from \"./login.js\";\nimport { scaffoldThinWorkspace } from \"./workspace-scaffold-and-finalize-orchestrator.js\";\n\nexport interface InitOptions {\n workspaceParent?: string;\n packVersion?: string;\n teamOwner?: string;\n description?: string;\n skipTeamPack?: boolean;\n force?: boolean;\n yes?: boolean;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command(\"init\")\n .description(\"Khởi tạo Avatar workspace (môi trường) — repo cài sau bằng 'avatar add repo'\")\n .argument(\"[workspace-name]\", \"Tên workspace (bắt buộc, hỏi nếu thiếu)\")\n .option(\"--workspace-parent <path>\", \"Thư mục cha tạo workspace (mặc định . — CWD)\")\n .option(\"--pack-version <tag>\", \"Pin team-ai-pack vào tag cụ thể (vd v0.2.0-beta.0)\")\n .option(\"--team-owner <email>\", \"Email team owner (bỏ qua prompt)\")\n .option(\"--description <text>\", \"Mô tả 1 dòng của workspace\")\n .option(\"--skip-team-pack\", \"Bỏ qua tải team-ai-pack (test mode)\")\n .option(\"--force\", \"Bỏ qua prompt khi workspace path đã tồn tại\")\n .option(\"--yes\", \"Auto-confirm tất cả prompt\")\n .option(\"--ai-skip\", \"Bỏ qua phase AI setup (chạy 'avatar ai setup' sau)\")\n .option(\"--gitnexus-skip\", \"Bỏ qua phase GitNexus setup\")\n .action(async (workspaceName: string | undefined, opts: InitOptions) => {\n try {\n await runInit(workspaceName, opts);\n } catch (err) {\n if (\n err instanceof InitAbortedByUserError ||\n err instanceof TeamPackAccessAbortedError ||\n err instanceof RemoteAccessAbortedError ||\n err instanceof UserAbortedRecoveryError\n ) {\n log.dim(err.message);\n process.exit(0);\n }\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nasync function runInit(workspaceName: string | undefined, opts: InitOptions): Promise<void> {\n if (!opts.yes) printAvatarBanner({ tagline: \"Khởi tạo Avatar workspace\" });\n\n // Login NAL — bắt buộc (cần token để tải pack từ Supabase).\n let userConfig = await readUserConfig();\n while (!userConfig || isTokenExpired(userConfig)) {\n log.info(\"Chưa đăng nhập (hoặc token hết hạn) — chạy login trước...\");\n try {\n await runLogin({});\n } catch (err) {\n log.warn(`Login fail: ${(err as Error).message}`);\n }\n userConfig = await readUserConfig();\n if (userConfig && !isTokenExpired(userConfig)) break;\n throw new UserAbortedRecoveryError(\n \"Cần đăng nhập @nal.vn để tải team-ai-pack. Chạy 'avatar login' rồi 'avatar init' lại.\",\n );\n }\n\n // Tên workspace bắt buộc — hỏi nếu thiếu.\n const name =\n workspaceName ??\n (await input({\n message: \"Tên workspace:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Tên không được rỗng\"),\n }));\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(userConfig.email));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, name.trim(), opts.force);\n\n await scaffoldThinWorkspace({\n workspacePath,\n workspaceName: name.trim(),\n teamOwner,\n description: opts.description ?? `Avatar workspace: ${name.trim()}`,\n packVersion: opts.packVersion,\n autoYes: opts.yes,\n skipTeamPack: opts.skipTeamPack,\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n });\n}\n","// Avatar ASCII banner — gradient màu cam → tím để in ở các entry point chính:\n// `avatar --version`, `avatar init`, `avatar login`.\n//\n// Style: ANSI Shadow block characters (Unicode box-drawing).\n// Tự fallback sang plain text khi không phải TTY (CI, pipe ra file).\nimport chalk from \"chalk\";\n\n// 6 dòng chính của logo, mỗi dòng giữ độ rộng đồng nhất 50 ký tự để gradient đẹp.\nconst BANNER_LINES: readonly string[] = [\n \" █████╗ ██╗ ██╗ █████╗ ████████╗ █████╗ ██████╗ \",\n \"██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗\",\n \"███████║██║ ██║███████║ ██║ ███████║██████╔╝\",\n \"██╔══██║╚██╗ ██╔╝██╔══██║ ██║ ██╔══██║██╔══██╗\",\n \"██║ ██║ ╚████╔╝ ██║ ██║ ██║ ██║ ██║██║ ██║\",\n \"╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝\",\n] as const;\n\n// Gradient stops cam → tím (RGB triples). Mỗi dòng nội suy theo % chiều cao.\nconst GRADIENT_STOPS: ReadonlyArray<readonly [number, number, number]> = [\n [217, 79, 30], // cam-cháy (#d94f1e)\n [200, 70, 80], // cam-hồng\n [170, 70, 140], // hồng-tím\n [125, 88, 217], // tím (#7d58d9)\n];\n\n// Nội suy tuyến tính 1 kênh màu giữa 2 stop.\nfunction lerpChannel(a: number, b: number, t: number): number {\n return Math.round(a + (b - a) * t);\n}\n\n// Lấy màu RGB tại vị trí [0,1] trên gradient.\nfunction gradientAt(t: number): readonly [number, number, number] {\n const clamped = Math.max(0, Math.min(1, t));\n const scaled = clamped * (GRADIENT_STOPS.length - 1);\n const lo = Math.floor(scaled);\n const hi = Math.min(GRADIENT_STOPS.length - 1, lo + 1);\n const localT = scaled - lo;\n // Non-null safe: lo ∈ [0, len-1] qua Math.floor + clamp, hi ∈ [0, len-1] qua Math.min.\n // TS strict mode không narrow tuple index → assert.\n const a = GRADIENT_STOPS[lo]!;\n const b = GRADIENT_STOPS[hi]!;\n return [\n lerpChannel(a[0], b[0], localT),\n lerpChannel(a[1], b[1], localT),\n lerpChannel(a[2], b[2], localT),\n ];\n}\n\n// Build banner string đã tô màu sẵn. Trả empty khi terminal không hỗ trợ màu.\nexport function renderAvatarBanner(opts?: { tagline?: string }): string {\n const isTty = process.stdout.isTTY ?? false;\n const supportsColor = isTty && chalk.level > 0;\n\n // Plain fallback cho CI hoặc pipe.\n if (!supportsColor) {\n return [...BANNER_LINES, ...(opts?.tagline ? [\"\", opts.tagline] : [])].join(\"\\n\");\n }\n\n const colored = BANNER_LINES.map((line, idx) => {\n const t = BANNER_LINES.length === 1 ? 0 : idx / (BANNER_LINES.length - 1);\n const [r, g, b] = gradientAt(t);\n return chalk.rgb(r, g, b).bold(line);\n });\n\n if (opts?.tagline) {\n colored.push(\"\");\n colored.push(chalk.dim(opts.tagline));\n }\n\n return colored.join(\"\\n\");\n}\n\n// Tiện ích: in banner ra stdout với newline phía trước/sau cho thoáng.\nexport function printAvatarBanner(opts?: { tagline?: string }): void {\n process.stdout.write(`\\n${renderAvatarBanner(opts)}\\n\\n`);\n}\n","// Safe Bootstrap (Tier A) — bảo vệ folder user khỏi mất changes uncommitted khi\n// `avatar init` modify .gitignore + tạo initial commit.\n//\n// Flow:\n// 1. detectFolderGitState() → \"empty\" | \"untracked-only\" | \"clean\" | \"dirty\"\n// 2. empty/clean → bootstrap thẳng (không có rủi ro)\n// 3. dirty/untracked-only → promptBootstrapStrategy() → 4 lựa chọn\n// 4. executeBootstrapWithStrategy() → branch xử lý từng strategy\n//\n// 4 strategies:\n// - stash (DEFAULT highlight): git stash -u → bootstrap → stash pop\n// - commit-all (legacy v1.1.6): git add . && commit, all changes bundled\n// - skip: throw InitAbortedByUserError — user tự commit rồi chạy lại\n// - branch: checkout -b avatar/init → bootstrap → checkout back\n//\n// CLI flags:\n// --bootstrap-strategy <s> : preset, skip prompt\n// --preserve-uncommitted : alias cho --bootstrap-strategy=stash\n// --yes : CI mode → default stash (safest)\nimport { readdirSync } from \"node:fs\";\nimport { select } from \"@inquirer/prompts\";\nimport { type SimpleGit, simpleGit } from \"simple-git\";\nimport { appendAuditEntry } from \"./audit-log-appender.js\";\nimport { checkFolderHasGit } from \"./check-folder-has-git.js\";\nimport { createInitialGitCommit } from \"./create-initial-git-commit.js\";\nimport { detectFolderTechStack } from \"./detect-folder-tech-stack.js\";\nimport { composeGitignoreContent } from \"./gitignore-template-loader.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { writeOrMergeGitignore } from \"./write-or-merge-gitignore.js\";\n\nexport type GitState = \"empty\" | \"untracked-only\" | \"clean\" | \"dirty\";\n\nexport type BootstrapStrategy = \"stash\" | \"commit-all\" | \"skip\" | \"branch\";\n\nexport interface SafeBootstrapOptions {\n presetStrategy?: BootstrapStrategy;\n autoYes?: boolean;\n}\n\n// User chọn skip giữa wizard → caller catch để abort init.ts gracefully.\nexport class InitAbortedByUserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InitAbortedByUserError\";\n }\n}\n\n// === Step 1: detect git state ===\nexport async function detectFolderGitState(folderPath: string): Promise<GitState> {\n const hasGit = checkFolderHasGit(folderPath);\n if (!hasGit) {\n // Filter `.git` (rare gitlink file edge case) + dotfiles không count cho empty check.\n const entries = readdirSync(folderPath).filter((e) => e !== \".git\");\n return entries.length === 0 ? \"empty\" : \"untracked-only\";\n }\n const g = simpleGit({ baseDir: folderPath });\n const status = await g.status();\n return status.isClean() ? \"clean\" : \"dirty\";\n}\n\n// === Step 2: prompt strategy ===\nexport async function promptBootstrapStrategy(\n state: GitState,\n opts: SafeBootstrapOptions,\n): Promise<BootstrapStrategy> {\n // Preset flag → skip prompt hoàn toàn.\n if (opts.presetStrategy) return opts.presetStrategy;\n\n // CI mode (--yes) → safe default. Stash là an toàn nhất (reversible).\n if (opts.autoYes) return \"stash\";\n\n // Empty/clean state không gọi prompt (caller skip). Defensive return.\n if (state === \"empty\" || state === \"clean\") return \"commit-all\";\n\n return (await select({\n message:\n state === \"dirty\"\n ? \"Folder có changes chưa commit. Cách xử lý:\"\n : \"Folder có file chưa version. Cách xử lý:\",\n choices: [\n {\n value: \"stash\" as const,\n name: \"1. Stash changes → bootstrap → restore (KHUYẾN NGHỊ)\",\n },\n {\n value: \"commit-all\" as const,\n name: \"2. Commit toàn bộ vào initial commit (legacy v1.1.6)\",\n },\n {\n value: \"skip\" as const,\n name: \"3. Skip — tôi commit thủ công rồi chạy lại\",\n },\n {\n value: \"branch\" as const,\n name: \"4. Commit vào branch riêng `avatar/init` (main giữ sạch)\",\n },\n ],\n default: \"stash\",\n })) as BootstrapStrategy;\n}\n\n// === Helpers: stash + branch ===\n\n// Stash user changes (kể cả untracked) với name rõ ràng để track. Trả true nếu stash thật sự.\nasync function stashUserChanges(g: SimpleGit, stashName: string): Promise<boolean> {\n const status = await g.status();\n if (status.isClean() && status.not_added.length === 0) return false;\n await g.stash([\"push\", \"--include-untracked\", \"-m\", stashName]);\n log.info(`Stashed changes: ${stashName}`);\n return true;\n}\n\n// Restore stash. Nếu conflict → log warning + leave markers (KHÔNG tự revert).\nasync function restoreStash(g: SimpleGit, stashName: string): Promise<void> {\n try {\n await g.stash([\"pop\"]);\n log.success(`Restored stash: ${stashName}`);\n } catch (err) {\n log.warn(\n \"Restore stash conflict — files có Avatar tạo và stash của user xung đột. Stash giữ tại ref stash@{0}.\",\n );\n log.warn(\"Resolve: git stash show -p stash@{0} → fix conflict → git stash drop\");\n log.dim(`Detail: ${(err as Error).message}`);\n }\n}\n\n// Lấy branch hiện tại (HEAD). Fallback \"main\" nếu detached HEAD.\nasync function getCurrentBranch(g: SimpleGit): Promise<string> {\n try {\n const result = await g.revparse([\"--abbrev-ref\", \"HEAD\"]);\n const branch = result.trim();\n return branch === \"HEAD\" ? \"main\" : branch;\n } catch {\n return \"main\";\n }\n}\n\n// === Step 3: execute strategy ===\nasync function writeAvatarGitignore(folderPath: string): Promise<void> {\n const stacks = detectFolderTechStack(folderPath);\n log.info(`Tech stack: ${stacks.join(\", \")}`);\n writeOrMergeGitignore(folderPath, composeGitignoreContent(stacks));\n log.success(\".gitignore đã ghi (Avatar block)\");\n}\n\nexport async function executeBootstrapWithStrategy(\n folderPath: string,\n strategy: BootstrapStrategy,\n): Promise<void> {\n const g = simpleGit({ baseDir: folderPath });\n\n switch (strategy) {\n case \"skip\":\n throw new InitAbortedByUserError(\n \"Init aborted. Commit thủ công changes hiện tại rồi chạy lại `avatar init`.\",\n );\n\n case \"stash\": {\n const stashName = `avatar-init-backup-${Date.now()}`;\n // Stash cần initial commit để work. Nếu folder chưa có .git hoặc chưa có commit\n // → init + empty commit baseline trước, sau đó mới stash user changes.\n const hadGit = checkFolderHasGit(folderPath);\n if (!hadGit) {\n await g.init();\n await g.branch([\"-M\", \"main\"]).catch(() => undefined);\n }\n const hasCommit = (await g.raw([\"rev-list\", \"-n\", \"1\", \"--all\"]).catch(() => \"\")).trim();\n if (!hasCommit) {\n await g.commit(\"chore: avatar baseline (pre-stash)\", undefined, { \"--allow-empty\": null });\n }\n const stashed = await stashUserChanges(g, stashName);\n try {\n await writeAvatarGitignore(folderPath);\n await createInitialGitCommit(folderPath);\n } finally {\n // Always restore stash dù bootstrap fail giữa chừng.\n if (stashed) await restoreStash(g, stashName);\n }\n break;\n }\n\n case \"commit-all\": {\n // Legacy v1.1.6 behavior — KHÔNG dùng cho default mới nhưng giữ cho backward compat.\n await writeAvatarGitignore(folderPath);\n await createInitialGitCommit(folderPath);\n break;\n }\n\n case \"branch\": {\n // Cần repo trước. Nếu chưa có git thì init + commit baseline ở main đầu tiên.\n const hadGit = checkFolderHasGit(folderPath);\n if (!hadGit) {\n await g.init();\n await g.branch([\"-M\", \"main\"]);\n }\n const originalBranch = await getCurrentBranch(g);\n // checkoutLocalBranch fail nếu branch đã tồn tại — fallback checkout existing.\n try {\n await g.checkoutLocalBranch(\"avatar/init\");\n } catch {\n await g.checkout(\"avatar/init\");\n }\n await writeAvatarGitignore(folderPath);\n await createInitialGitCommit(folderPath);\n // Switch back. Nếu fail (vd no commits ở branch gốc) → ở lại avatar/init.\n try {\n await g.checkout(originalBranch);\n log.info(\n `Avatar init committed ở branch 'avatar/init'. Switch back về '${originalBranch}'. Merge khi sẵn sàng: git merge avatar/init`,\n );\n } catch {\n log.warn(\n `Không switch về '${originalBranch}' được — ở lại branch 'avatar/init'. Switch tay sau.`,\n );\n }\n break;\n }\n }\n}\n\n// === Main orchestrator (thay bootstrapGitInFolder cho dirty/untracked case) ===\nexport async function safeBootstrapGitInFolder(\n folderPath: string,\n opts: SafeBootstrapOptions = {},\n): Promise<void> {\n const state = await detectFolderGitState(folderPath);\n log.info(`Folder state: ${state}`);\n\n // Empty/clean → bootstrap thẳng, không prompt.\n if (state === \"empty\" || state === \"clean\") {\n await writeAvatarGitignore(folderPath);\n if (state === \"empty\") {\n await createInitialGitCommit(folderPath);\n }\n await appendAuditEntry(\"bootstrap\", `state=${state},strategy=auto`);\n return;\n }\n\n // Dirty/untracked → prompt + execute.\n const strategy = await promptBootstrapStrategy(state, opts);\n await executeBootstrapWithStrategy(folderPath, strategy);\n await appendAuditEntry(\"bootstrap\", `state=${state},strategy=${strategy}`);\n}\n","// Kiểm tra folder đã là git repo chưa. Đơn giản: check tồn tại của \".git\"\n// (folder hoặc file gitlink — cả hai đều hợp lệ với submodule).\nimport { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport function checkFolderHasGit(folderPath: string): boolean {\n const gitPath = join(folderPath, \".git\");\n if (!existsSync(gitPath)) return false;\n // .git có thể là directory (repo bình thường) hoặc file (submodule gitlink).\n // Cả 2 đều xem là \"đã có git\".\n const stat = statSync(gitPath);\n return stat.isDirectory() || stat.isFile();\n}\n","// Init git repo trong folder + tạo initial commit. Idempotent: skip nếu đã\n// có commit. Default branch \"main\".\nimport { simpleGit } from \"simple-git\";\n\nconst INITIAL_COMMIT_MESSAGE = \"chore: initial commit\";\n\nexport async function createInitialGitCommit(folderPath: string): Promise<void> {\n const g = simpleGit({ baseDir: folderPath });\n\n // Init nếu chưa có .git.\n const isRepo = await g.checkIsRepo().catch(() => false);\n if (!isRepo) {\n await g.init();\n }\n\n // Đảm bảo branch hiện tại là main (đổi master → main nếu cần).\n // Một số git config user có init.defaultBranch=master.\n try {\n await g.branch([\"-M\", \"main\"]);\n } catch {\n // Repo trống chưa có commit nào — branch -M sẽ fail. Bỏ qua, commit\n // đầu tiên dưới đây sẽ tạo branch main qua HEAD ref.\n }\n\n // Stage all + commit. Nếu không có file (folder rỗng) thì commit empty để\n // submodule add có HEAD reference dùng.\n await g.add(\".\");\n const status = await g.status();\n const hasCommits = (await g.raw([\"rev-list\", \"-n\", \"1\", \"--all\"]).catch(() => \"\")).trim();\n if (hasCommits) return; // Đã có commit, skip.\n\n if (status.files.length === 0) {\n await g.commit(INITIAL_COMMIT_MESSAGE, undefined, { \"--allow-empty\": null });\n } else {\n await g.commit(INITIAL_COMMIT_MESSAGE);\n }\n}\n","// Detect tech stack của folder qua signature file ở root. Trả về tất cả stack\n// match được (folder polyglot là chuyện thường — vd monorepo Node + Python).\n// Nếu không match gì → [\"generic\"].\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type TechStack = \"node\" | \"python\" | \"go\" | \"rust\" | \"java\" | \"ruby\" | \"generic\";\n\n// Bảng signature: stack → các file đủ điều kiện claim stack đó.\nconst SIGNATURES: Record<Exclude<TechStack, \"generic\">, string[]> = {\n node: [\"package.json\"],\n python: [\"pyproject.toml\", \"requirements.txt\", \"setup.py\", \"Pipfile\"],\n go: [\"go.mod\"],\n rust: [\"Cargo.toml\"],\n java: [\"pom.xml\", \"build.gradle\", \"build.gradle.kts\"],\n ruby: [\"Gemfile\"],\n};\n\nexport function detectFolderTechStack(folderPath: string): TechStack[] {\n const matched: TechStack[] = [];\n for (const [stack, files] of Object.entries(SIGNATURES) as [\n Exclude<TechStack, \"generic\">,\n string[],\n ][]) {\n if (files.some((f) => existsSync(join(folderPath, f)))) {\n matched.push(stack);\n }\n }\n return matched.length > 0 ? matched : [\"generic\"];\n}\n","// Load template gitignore từ src/templates/gitignore/<stack>.txt và compose\n// nội dung tổng hợp cho 1+ stack. Generic luôn được prepend.\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { TechStack } from \"./detect-folder-tech-stack.js\";\n\n// Resolve template dir tương đối với file này, không phải CWD.\n// v1.2.9 fix: tsup bundle thành `dist/index.js` (single file) → __dirname = `dist/`,\n// KHÔNG phải `dist/lib/`. Phải search cả 2 layout:\n// - Bundled (dist/index.js): __dirname/templates/gitignore (= dist/templates/gitignore)\n// - Dev (src/lib/*.ts): __dirname/../../src/templates/gitignore\n// - npm-installed src: package_root/src/templates/gitignore (qua files field)\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst CANDIDATE_DIRS = [\n // Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates\n join(__dirname, \"templates\", \"gitignore\"),\n // Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates\n join(__dirname, \"..\", \"templates\", \"gitignore\"),\n // Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/\n join(__dirname, \"..\", \"..\", \"src\", \"templates\", \"gitignore\"),\n // npm-installed alt: __dirname = .../dist/ → package_root/src/templates\n join(__dirname, \"..\", \"src\", \"templates\", \"gitignore\"),\n];\n\nconst AVATAR_MARKER_START = \"# === avatar ===\";\nconst AVATAR_MARKER_END = \"# === /avatar ===\";\n\nfunction readTemplate(stack: TechStack): string {\n for (const dir of CANDIDATE_DIRS) {\n try {\n return readFileSync(join(dir, `${stack}.txt`), \"utf8\");\n } catch {\n // continue\n }\n }\n throw new Error(`Không tìm thấy template gitignore cho stack \"${stack}\"`);\n}\n\n// Compose: generic luôn ở đầu, sau đó các stack khác theo thứ tự detect.\n// Wrap trong marker để uninstall biết range gỡ chính xác.\nexport function composeGitignoreContent(stacks: TechStack[]): string {\n const all: TechStack[] = [\"generic\", ...stacks.filter((s) => s !== \"generic\")];\n const sections = all.map((s) => `# --- ${s} ---\\n${readTemplate(s).trim()}`);\n return [AVATAR_MARKER_START, ...sections, AVATAR_MARKER_END, \"\"].join(\"\\n\");\n}\n\nexport { AVATAR_MARKER_START, AVATAR_MARKER_END };\n","// Ghi .gitignore: tạo mới nếu chưa có, merge content nếu đã có. Merge logic\n// dùng marker `# === avatar === ... # === /avatar ===`: replace block đó nếu\n// tồn tại, append nếu chưa.\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { AVATAR_MARKER_END, AVATAR_MARKER_START } from \"./gitignore-template-loader.js\";\n\nexport function writeOrMergeGitignore(folderPath: string, avatarBlock: string): void {\n const path = join(folderPath, \".gitignore\");\n\n if (!existsSync(path)) {\n writeFileSync(path, avatarBlock, \"utf8\");\n return;\n }\n\n const existing = readFileSync(path, \"utf8\");\n const startIdx = existing.indexOf(AVATAR_MARKER_START);\n const endIdx = existing.indexOf(AVATAR_MARKER_END);\n\n // Đã có marker → replace block giữa marker (giữ content trước/sau).\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + AVATAR_MARKER_END.length);\n writeFileSync(path, `${before.trimEnd()}\\n\\n${avatarBlock}${after.trimStart()}`, \"utf8\");\n return;\n }\n\n // Chưa có marker → append phía cuối, thêm newline phân cách.\n writeFileSync(path, `${existing.trimEnd()}\\n\\n${avatarBlock}`, \"utf8\");\n}\n","// team-pack-submodule-manager.ts\n//\n// v3: team-ai-pack KHÔNG còn là git submodule. Pack tải qua tarball từ Supabase\n// Edge Function get-pack (xem supabase-pack-downloader.ts). File này giữ tên +\n// các export cũ (6+ file import gián tiếp) nhưng ruột đổi sang tarball.\n//\n// Lịch sử: trước v3 dùng git submodule + GitHub access check. Cắt hẳn ở v3 —\n// auth chuyển sang Supabase id_token (login NAL), user không cần GitHub access pack.\nimport { join } from \"node:path\";\nimport { readPackVersionFromFile, writePackManifest } from \"./pack-manifest-store.js\";\nimport { downloadAndExtractPack, fetchSignedPackUrl } from \"./supabase-pack-downloader.js\";\nimport { getValidIdToken } from \"./user-config-store.js\";\n\n// GIỮ export — 6+ file import path này (init-success, scaffold, init-flow, sync, status, pack-status).\nexport const TEAM_PACK_RELATIVE_PATH = \".claude/pack\";\n\n// GIỮ export — init.ts catch để exit 0 graceful khi user abort.\nexport class TeamPackAccessAbortedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TeamPackAccessAbortedError\";\n }\n}\n\n// Install team-ai-pack vào project qua tarball Supabase.\n// Tên giữ \"addTeamPackSubmodule\" vì caller cũ import; ruột là tải tarball.\n//\n// version undefined → server trả stable mới nhất (stable/latest.txt).\n// version cụ thể (vd v0.2.0-beta.0) → pin bản đó.\n// Trả { pinnedTag } = version đã cài (cho caller in ra).\nexport async function addTeamPackSubmodule(\n projectRoot: string,\n version?: string,\n): Promise<{ pinnedTag: string | null }> {\n const idToken = await getValidIdToken();\n const info = await fetchSignedPackUrl(idToken, version);\n\n const packDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n await downloadAndExtractPack(info.url, packDir);\n\n // version cài được = từ object name (pack-vX.Y.Z.tar.gz) hoặc version pin.\n const installedVersion = version ?? objectToVersion(info.object);\n await writePackManifest(packDir, {\n version: installedVersion,\n downloadedObject: info.object,\n extractedAt: new Date().toISOString(),\n });\n\n return { pinnedTag: installedVersion };\n}\n\n// \"pack-v0.2.0.tar.gz\" → \"v0.2.0\"\nfunction objectToVersion(object: string): string {\n return object.replace(/^pack-/, \"\").replace(/\\.tar\\.gz$/, \"\");\n}\n\n// Đọc version pack đang cài. v3: đọc từ manifest (.avatar-pack-manifest.json) thay\n// vì git tag. GIỮ tên export — status.ts + pack-status-and-version-check.ts import.\nexport async function readPinnedPackVersion(projectRoot: string): Promise<string> {\n const packDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n const version = await readPackVersionFromFile(packDir);\n return version ?? \"(unknown)\";\n}\n","// pack-manifest-store.ts\n//\n// v3: Pack tải qua tarball Supabase (không còn git submodule) → metadata version\n// không đọc từ git tag được nữa. Thay bằng file manifest ghi sau khi extract.\n//\n// Manifest sống tại `.claude/pack/.avatar-pack-manifest.json`:\n// { version, downloadedObject, sha256?, extractedAt }\n//\n// Đọc version ưu tiên manifest; fallback `.pack-version` (file kèm trong tarball\n// do script release ghi) nếu manifest thiếu (vd pack cài tay).\nimport { join } from \"node:path\";\nimport { pathExists, readJson, readText, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const PACK_MANIFEST_FILENAME = \".avatar-pack-manifest.json\";\nexport const PACK_VERSION_FILENAME = \".pack-version\";\n\nexport interface PackManifest {\n version: string; // vd \"v0.2.0\"\n downloadedObject: string; // vd \"pack-v0.2.0.tar.gz\"\n sha256?: string; // hash tarball đã tải (nếu Supabase cung cấp)\n extractedAt: string; // ISO timestamp\n}\n\nfunction manifestPath(packDir: string): string {\n return join(packDir, PACK_MANIFEST_FILENAME);\n}\n\nexport async function writePackManifest(packDir: string, data: PackManifest): Promise<void> {\n await writeJsonAtomic(manifestPath(packDir), data);\n}\n\nexport async function readPackManifest(packDir: string): Promise<PackManifest | null> {\n const p = manifestPath(packDir);\n if (!(await pathExists(p))) return null;\n try {\n return await readJson<PackManifest>(p);\n } catch {\n return null;\n }\n}\n\n// Đọc version pack đang cài. Ưu tiên manifest → fallback .pack-version → null.\nexport async function readPackVersionFromFile(packDir: string): Promise<string | null> {\n const manifest = await readPackManifest(packDir);\n if (manifest?.version) return manifest.version;\n\n const versionFile = join(packDir, PACK_VERSION_FILENAME);\n if (await pathExists(versionFile)) {\n const v = (await readText(versionFile)).trim();\n if (v) return v;\n }\n return null;\n}\n","import { promises as fs } from \"node:fs\";\n// supabase-pack-downloader.ts\n//\n// v3: Tải team-ai-pack qua Supabase Edge Function thay git submodule.\n//\n// Flow:\n// 1. getValidIdToken() — id_token NAL còn hạn (tự refresh)\n// 2. fetchSignedPackUrl(idToken, version?) — POST get-pack → signed URL (300s)\n// 3. downloadAndExtractPack(signedUrl, packDir) — tải tarball vào temp TRONG\n// .claude/ (cùng filesystem để rename atomic) → giải nén temp → swap vào packDir\n// 4. caller ghi manifest (pack-manifest-store)\n//\n// Endpoint hardcode — hạ tầng NAL cố định (giống ORG_DEFAULT git URL cũ).\n// Override qua env AVATAR_GET_PACK_ENDPOINT cho test/staging.\nimport { join } from \"node:path\";\nimport {\n downloadFile,\n ensureDir,\n extractTarballToDir,\n pathExists,\n removeRecursive,\n} from \"./filesystem-helpers.js\";\n\nconst DEFAULT_GET_PACK_ENDPOINT = \"https://qpdbkewtpkkbcrptnlos.supabase.co/functions/v1/get-pack\";\n\nexport function getPackEndpoint(): string {\n return process.env.AVATAR_GET_PACK_ENDPOINT ?? DEFAULT_GET_PACK_ENDPOINT;\n}\n\n// ─── Error types — caller phân biệt để recovery đúng ─────────────────────────\n\n// id_token sai / hết hạn / email không @nal.vn → KHÔNG retry, prompt avatar login.\nexport class InvalidIdTokenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidIdTokenError\";\n }\n}\n\n// Network: timeout / 5xx / DNS / signed URL hết hạn → CÓ thể retry.\nexport class PackNetworkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PackNetworkError\";\n }\n}\n\n// Response lạ / tarball hỏng / sha mismatch → không retry mù.\nexport class PackParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PackParseError\";\n }\n}\n\nexport interface SignedPackInfo {\n url: string; // signed URL tải tarball (hết hạn ~300s)\n object: string; // vd \"pack-v0.2.0.tar.gz\"\n expiresIn: number;\n requestedBy: string; // email NAL đã verify\n}\n\n// Gọi get-pack lấy signed URL. version undefined → server trả stable mới nhất.\nexport async function fetchSignedPackUrl(\n idToken: string,\n version?: string,\n): Promise<SignedPackInfo> {\n let res: Response;\n try {\n res = await fetch(getPackEndpoint(), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id_token: idToken, version }),\n });\n } catch (err) {\n throw new PackNetworkError(\n `Không kết nối được Supabase get-pack: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n // 403 = token/domain reject → InvalidIdToken. 5xx = server → Network.\n if (res.status === 401 || res.status === 403) {\n const detail = await safeErrorText(res);\n throw new InvalidIdTokenError(\n `Supabase từ chối (${res.status}): ${detail}. Chạy 'avatar login' với tài khoản @nal.vn.`,\n );\n }\n if (res.status >= 500) {\n throw new PackNetworkError(`Supabase get-pack lỗi server (${res.status}). Thử lại sau.`);\n }\n if (!res.ok) {\n const detail = await safeErrorText(res);\n throw new PackParseError(`get-pack trả ${res.status}: ${detail}`);\n }\n\n let body: {\n url?: string;\n object?: string;\n expires_in?: number;\n requested_by?: string;\n error?: string;\n };\n try {\n body = (await res.json()) as typeof body;\n } catch {\n throw new PackParseError(\"get-pack trả response không phải JSON.\");\n }\n if (body.error) {\n throw new PackParseError(`get-pack báo lỗi: ${body.error}`);\n }\n if (!body.url || !body.object) {\n throw new PackParseError(\"get-pack thiếu url/object trong response.\");\n }\n return {\n url: body.url,\n object: body.object,\n expiresIn: body.expires_in ?? 0,\n requestedBy: body.requested_by ?? \"\",\n };\n}\n\n// Tải tarball + giải nén vào packDir, atomic swap để không hỏng pack đang có.\n//\n// Đặt temp TRONG thư mục cha của packDir (cùng filesystem) để rename atomic.\n// Bước: tải tarball → giải nén ra <packDir>.new → xóa packDir cũ → rename .new → packDir.\nexport async function downloadAndExtractPack(signedUrl: string, packDir: string): Promise<void> {\n const parent = join(packDir, \"..\");\n await ensureDir(parent);\n\n const tmpTarball = join(parent, `.pack-download-${process.pid}.tar.gz`);\n const tmpExtractDir = `${packDir}.new-${process.pid}`;\n\n try {\n // 1. Tải tarball.\n try {\n await downloadFile(signedUrl, tmpTarball);\n } catch (err) {\n throw new PackNetworkError(\n `Tải tarball thất bại: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n // 2. Giải nén vào temp dir.\n await removeRecursive(tmpExtractDir);\n try {\n await extractTarballToDir(tmpTarball, tmpExtractDir);\n } catch (err) {\n throw new PackParseError(\n `Giải nén tarball thất bại: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n // 3. Swap an toàn: GIỮ pack cũ tới khi pack mới vào xong, tránh mất pack\n // nếu rename fail giữa chừng. Đổi tên cũ → backup, rename mới vào, rồi mới xóa backup.\n const backupDir = `${packDir}.old-${process.pid}`;\n const hadOld = await pathExists(packDir);\n if (hadOld) {\n await fs.rename(packDir, backupDir); // pack cũ → backup (cùng fs, atomic)\n }\n try {\n await fs.rename(tmpExtractDir, packDir); // pack mới vào\n } catch (err) {\n // Rename mới fail → khôi phục pack cũ để workspace không mất pack.\n if (hadOld) {\n await fs.rename(backupDir, packDir).catch(() => {});\n }\n throw err;\n }\n // Mới vào OK → xóa backup.\n if (hadOld) {\n await removeRecursive(backupDir).catch(() => {});\n }\n } finally {\n // Dọn temp (tarball + extract dir nếu rename fail).\n await fs.rm(tmpTarball, { force: true }).catch(() => {});\n await removeRecursive(tmpExtractDir).catch(() => {});\n }\n}\n\nasync function safeErrorText(res: Response): Promise<string> {\n try {\n const body = await res.json();\n return (body as { error?: string }).error ?? JSON.stringify(body).slice(0, 200);\n } catch {\n return (await res.text().catch(() => \"\")).slice(0, 200);\n }\n}\n","// init-success-rendering-and-helpers.ts\n//\n// Render success box sau khi init hoàn thành + các helper nhỏ dùng chung:\n// - resolveWorkspacePath (exported): resolve + handle conflict tên workspace\n// - promptTeamOwner: prompt email team owner với default từ SSO\n// - maybeCommitWorkspace: commit baseline workspace (trừ khi --no-commit)\n// - formatAiStatusLine: format dòng trạng thái AI setup trong success box\n// - formatGitnexusStatusLine: format dòng trạng thái GitNexus trong success box\n// - printInitSuccessBox: in box thành công + cheatsheet commands\n//\n// Tách ra để workspace-scaffold-and-finalize-orchestrator.ts + init-flow-handlers\n// có thể import mà không tạo circular dependency với init.ts (entry point).\n\nimport { join, relative, resolve } from \"node:path\";\nimport { input, select } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport { ensureDir, pathExists } from \"../lib/filesystem-helpers.js\";\nimport { formatPackCommandsCheatsheetBox } from \"../lib/format-pack-commands-cheatsheet-box.js\";\nimport { UserAbortedRecoveryError } from \"../lib/prompt-recovery-action-on-failure.js\";\nimport type { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport type { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { TEAM_PACK_RELATIVE_PATH } from \"../lib/team-pack-submodule-manager.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\nimport {\n findAlternativeWorkspaceName,\n isEmptyOrMissing,\n} from \"./init-conflict-detection-helpers.js\";\n\n// ─── shared utilities ───────\n\n// v1.14.0 SECURITY: Path traversal guard. Workspace name từ user input (CLI flag\n// `--workspace-name` hoặc inquirer prompt) → nếu chứa `..` / `/` / `\\` → có thể\n// resolve ngoài parent dir, ghi settings.json/gitignore vào path attacker-chosen.\n// Block: name có separator hoặc resolve không bắt đầu bằng parent.\nfunction assertWorkspaceNameSafe(parent: string, name: string): void {\n // Reject ngay name chứa path separator hoặc relative parts.\n // Whitelist khuyến nghị: alphanumeric, dash, underscore, dot — không có / \\ ..\n if (/[\\/\\\\]/.test(name) || name === \".\" || name === \"..\" || name.includes(\"..\")) {\n throw new Error(\n `Tên workspace không an toàn (chứa path separator hoặc '..'): \"${name}\".\\nYêu cầu: chỉ chữ/số/dash/underscore/dot, không '/', '\\\\', '..'.`,\n );\n }\n // Defensive: resolve thực tế và assert vẫn nằm trong parent.\n const resolved = resolve(join(parent, name));\n const resolvedParent = resolve(parent);\n // Path resolution phải có root parent là prefix (đảm bảo không escape qua symlink).\n if (!resolved.startsWith(`${resolvedParent}/`) && resolved !== resolvedParent) {\n throw new Error(`Tên workspace \"${name}\" resolve ra ngoài parent dir (path traversal). Block.`);\n }\n}\n\nexport async function resolveWorkspacePath(\n parent: string,\n desiredName: string,\n force?: boolean,\n): Promise<string> {\n // v1.14.0 SECURITY: assert ngay khi entry với name từ CLI flag.\n assertWorkspaceNameSafe(parent, desiredName);\n const desired = join(parent, desiredName);\n if (await isEmptyOrMissing(desired)) return desired;\n\n log.warn(`Workspace path \"${desired}\" đã có nội dung.`);\n\n // Loop cho user chọn alt name / nhập tay / abort.\n // v1.2.5: không throw thẳng khi findAlternative fail hoặc user decline alt.\n while (true) {\n const alternative = await findAlternativeWorkspaceName(parent, desiredName);\n\n if (force && alternative) {\n log.info(`--force: dùng ${alternative}`);\n return alternative;\n }\n\n const choices: Array<{ name: string; value: string }> = [];\n if (alternative) {\n choices.push({ name: `Dùng \"${alternative}\" (suggest)`, value: \"use-alt\" });\n }\n choices.push({ name: \"Nhập tên workspace khác (manual)\", value: \"manual\" });\n choices.push({ name: \"Tạm ngưng init\", value: \"abort\" });\n\n const action = await select({\n message: \"Cách xử lý workspace path conflict?\",\n choices,\n });\n\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\n \"User abort tại bước resolve workspace path. Chạy lại với --workspace-name khác.\",\n );\n }\n if (action === \"use-alt\" && alternative) {\n return alternative;\n }\n // action === \"manual\" → prompt input tên mới + loop verify.\n // v1.14.0 SECURITY: inquirer validate reject path separator + .. ngay tại prompt.\n const newName = await input({\n message: \"Tên workspace mới:\",\n validate: (v) => {\n const trimmed = v.trim();\n if (trimmed.length === 0) return \"Tên không được rỗng\";\n if (\n /[\\/\\\\]/.test(trimmed) ||\n trimmed === \".\" ||\n trimmed === \"..\" ||\n trimmed.includes(\"..\")\n ) {\n return \"Tên không được chứa '/', '\\\\', '..' (path traversal)\";\n }\n return true;\n },\n });\n // Defensive recheck — inquirer validate đã pass nhưng assert lần nữa cho consistency.\n assertWorkspaceNameSafe(parent, newName.trim());\n const newPath = join(parent, newName.trim());\n if (await isEmptyOrMissing(newPath)) return newPath;\n log.warn(`\"${newPath}\" cũng đã có nội dung. Thử tên khác.`);\n // Loop tiếp.\n }\n}\n\nexport async function promptTeamOwner(currentUserEmail: string): Promise<string> {\n return await input({ message: \"Team owner email:\", default: currentUserEmail });\n}\n\n// v3: workspace KHÔNG git → maybeCommitWorkspace BỎ. Tạo workspace folder + báo lỗi\n// rõ ràng nếu không tạo được (trước đây git().init() ngầm gánh việc này).\nexport async function ensureWorkspaceFolder(workspacePath: string): Promise<void> {\n try {\n await ensureDir(workspacePath);\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"EACCES\" || e.code === \"EPERM\") {\n throw new Error(`Không tạo được workspace \"${workspacePath}\": không có quyền ghi.`);\n }\n if (e.code === \"ENAMETOOLONG\") {\n throw new Error(`Đường dẫn workspace quá dài: \"${workspacePath}\".`);\n }\n if (e.code === \"ENOSPC\") {\n throw new Error(\"Hết dung lượng đĩa, không tạo được workspace.\");\n }\n throw new Error(`Không tạo được workspace \"${workspacePath}\": ${e.message}`);\n }\n}\n\n// AI status line tùy theo kết quả runAiSetupPhase (hoặc null nếu --ai-skip).\nexport function formatAiStatusLine(\n aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null,\n): string {\n if (aiResult === null) {\n return ` ${chalk.yellow(\"AI:\")} skipped · ${chalk.cyan(\"avatar ai setup\")} để config sau`;\n }\n if (aiResult.ok) {\n const modelPart = aiResult.model ? ` · model=${aiResult.model}` : \"\";\n return ` ${chalk.green(\"AI:\")} ready · ${aiResult.provider}${modelPart}`;\n }\n return ` ${chalk.yellow(\"AI:\")} failed (${aiResult.reason.slice(0, 60)}) · thử ${chalk.cyan(\"avatar ai setup\")}`;\n}\n\n// v1.4.0: GitNexus status line tương tự pattern formatAiStatusLine.\nexport function formatGitnexusStatusLine(\n result: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null,\n): string {\n if (result === null) {\n return ` ${chalk.yellow(\"GitNexus:\")} skipped · ${chalk.cyan(\"avatar gitnexus install\")} để setup sau`;\n }\n if (result.ok) {\n const parts: string[] = [\"ready\"];\n if (result.analyzed) parts.push(\"indexed\");\n if (result.wikiGenerated) parts.push(\"wiki\");\n if (result.mcpRegistered) parts.push(\"mcp\");\n return ` ${chalk.green(\"GitNexus:\")} ${parts.join(\" · \")}`;\n }\n return ` ${chalk.yellow(\"GitNexus:\")} skipped (${(result.reason ?? \"unknown\").slice(0, 40)}) · thử ${chalk.cyan(\"avatar gitnexus install\")}`;\n}\n\nexport async function printInitSuccessBox(\n rootPath: string,\n aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null = null,\n gitnexusResult: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null = null,\n): Promise<void> {\n const lines: string[] = [\n `${chalk.green(\"✓\")} Workspace sẵn sàng: ${relative(process.cwd(), rootPath) || rootPath}`,\n formatAiStatusLine(aiResult),\n formatGitnexusStatusLine(gitnexusResult),\n \"\",\n ` ${chalk.cyan(`cd ${rootPath}`)}`,\n ` ${chalk.cyan(\"avatar add repo\")} Thêm repo code vào src/`,\n ` ${chalk.cyan(\"claude\")} Mở Claude Code ở workspace root`,\n \"\",\n ` ${chalk.cyan(\"avatar sync\")} Cập nhật team-ai-pack mới`,\n ` ${chalk.cyan(\"avatar uninstall\")} Gỡ Avatar (giữ code)`,\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n\n // v1.9.0: Hiển thị cheatsheet 13 commands cốt lõi từ pack — chỉ khi pack có.\n // Giúp user mới biết ngay những lệnh có thể gõ trong Claude Code session.\n const packDir = join(rootPath, TEAM_PACK_RELATIVE_PATH);\n if (await pathExists(packDir)) {\n process.stdout.write(`\\n${formatPackCommandsCheatsheetBox()}\\n`);\n }\n}\n","// Format cheatsheet box hiển thị 13 slash commands của team-ai-pack sau khi\n// `avatar init` thành công. Mục đích: user mới biết ngay những lệnh cốt lõi\n// có thể gõ trong Claude Code để dùng pack.\n//\n// Descriptions extracted từ team-ai-pack/commands/avatar/*.md frontmatter\n// (v0.4.0 snapshot). Nếu pack đổi description → cheatsheet không tự update.\n// Trade-off: hardcode đơn giản, không phải parse runtime — KISS.\n//\n// Khi nào hiển thị:\n// - Sau printInitSuccessBox khi pack đã sync (có .claude/pack/)\n// - Skip nếu --skip-team-pack (workspace không có pack content)\nimport boxen from \"boxen\";\nimport { chalk } from \"./terminal-logger.js\";\n\n// Cheatsheet entries — mỗi entry = { command, vietnameseDesc }.\n// Order theo workflow tự nhiên: planning → coding → debug → status.\nconst PACK_COMMAND_CHEATSHEET: Array<{ cmd: string; desc: string }> = [\n { cmd: \"/avatar:help\", desc: \"Hướng dẫn sử dụng Avatar — chỉ cần gõ tự nhiên\" },\n { cmd: \"/avatar:brainstorm\", desc: \"Brainstorm ý tưởng cho một tool\" },\n { cmd: \"/avatar:plan\", desc: \"Tạo plan thông minh với prompt enhancement\" },\n { cmd: \"/avatar:scout\", desc: \"Tìm file liên quan trong codebase, tiết kiệm token\" },\n { cmd: \"/avatar:implement\", desc: \"Bắt đầu code & test theo plan có sẵn\" },\n { cmd: \"/avatar:build-full-flow\", desc: \"Implement một tool từng bước (end-to-end)\" },\n { cmd: \"/avatar:fix\", desc: \"Phân tích và fix vấn đề tự điều hướng\" },\n { cmd: \"/avatar:debug\", desc: \"Debug vấn đề kỹ thuật + đưa ra giải pháp\" },\n { cmd: \"/avatar:test\", desc: \"Chạy test trên máy + phân tích báo cáo tổng hợp\" },\n { cmd: \"/avatar:design:good\", desc: \"Tạo thiết kế chỉn chu, sống động\" },\n { cmd: \"/avatar:docs:init\", desc: \"Phân tích codebase + tạo tài liệu khởi đầu\" },\n { cmd: \"/avatar:status\", desc: \"Xem lại thay đổi gần đây + tổng kết công việc\" },\n { cmd: \"/avatar:journal\", desc: \"Ghi nhật ký session\" },\n];\n\n// Build cheatsheet box. Width auto theo command dài nhất.\nexport function formatPackCommandsCheatsheetBox(): string {\n const maxCmdWidth = Math.max(...PACK_COMMAND_CHEATSHEET.map((e) => e.cmd.length));\n\n const header = chalk.bold(\"🎯 Slash commands từ team-ai-pack\");\n const subheader = chalk.dim(\"Gõ trong Claude Code session để gọi capability của pack:\");\n\n const lines = PACK_COMMAND_CHEATSHEET.map((e) => {\n const cmdPadded = chalk.cyan(e.cmd.padEnd(maxCmdWidth));\n return ` ${cmdPadded} ${chalk.dim(e.desc)}`;\n });\n\n const footer = chalk.dim(\n \"Catalog đầy đủ 46 commands: cat .claude/pack/scripts/commands_data.yaml\",\n );\n\n const content = [header, subheader, \"\", ...lines, \"\", footer].join(\"\\n\");\n\n return boxen(content, {\n padding: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n });\n}\n","// Pure helpers for `avatar init` conflict detection and workspace path\n// resolution. Extracted from init.ts so they're unit-testable without\n// triggering inquirer prompts or git operations.\n\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { AVATAR_MANAGED_PATHS } from \"../lib/project-tree-scaffolder.js\";\n\n// True if path doesn't exist OR is an empty directory (ignoring dotfiles and\n// Windows Thumbs.db noise). Safe to scaffold into. macOS .DS_Store and\n// .localized are already filtered by the dotfile rule.\nexport async function isEmptyOrMissing(path: string): Promise<boolean> {\n if (!(await pathExists(path))) return true;\n try {\n const entries = await readdir(path);\n const meaningful = entries.filter((e) => !e.startsWith(\".\") && e !== \"Thumbs.db\");\n return meaningful.length === 0;\n } catch {\n return false;\n }\n}\n\n// Return Avatar-managed top-level paths that already exist in projectRoot.\n// Caller decides whether to abort, prompt, or auto-backup.\nexport async function detectAvatarConflicts(projectRoot: string): Promise<string[]> {\n const found: string[] = [];\n for (const rel of AVATAR_MANAGED_PATHS) {\n if (await pathExists(join(projectRoot, rel))) found.push(rel);\n }\n return found;\n}\n\n// Find first numbered alternative path (e.g. \"foo-2\", \"foo-3\", ...) under\n// `parent` that is empty or missing. Returns null if exhausted.\n// maxAttempts defaults to 10; if a user has 9+ workspaces with the same\n// base name, something else is wrong.\nexport async function findAlternativeWorkspaceName(\n parent: string,\n desiredName: string,\n maxAttempts = 10,\n): Promise<string | null> {\n for (let i = 2; i < maxAttempts; i++) {\n const candidate = join(parent, `${desiredName}-${i}`);\n if (await isEmptyOrMissing(candidate)) return candidate;\n }\n return null;\n}\n","import boxen from \"boxen\";\n// `avatar login [--reset]` — Command 01 spec.\n// Implements the user-facing flow: announce verification URL, open browser,\n// poll Google until token returned, validate domain, persist credentials.\nimport type { Command } from \"commander\";\nimport open from \"open\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { printAvatarBanner } from \"../lib/avatar-ascii-banner.js\";\nimport {\n buildUserConfig,\n buildVerificationUrl,\n decodeIdToken,\n pollForToken,\n requestDeviceCode,\n revokeToken,\n verifyHostedDomain,\n} from \"../lib/google-oauth-device-flow.js\";\nimport { chalk, log, spinner } from \"../lib/terminal-logger.js\";\nimport {\n USER_CONFIG_PATH,\n clearUserConfig,\n isTokenExpired,\n readUserConfig,\n writeUserConfig,\n} from \"../lib/user-config-store.js\";\n\nexport function registerLoginCommand(program: Command): void {\n program\n .command(\"login\")\n .description(\"Đăng nhập Google SSO (workspace @nal.vn)\")\n .option(\"--reset\", \"Xóa credential cũ và đăng nhập lại\")\n .action(async (opts: { reset?: boolean }) => {\n try {\n await runLogin(opts);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\n// Exported để các command khác (vd init) reuse login flow khi user chưa login.\n// Throw nếu login fail, hoặc return void khi success (user config đã write xong).\nexport async function runLogin(opts: { reset?: boolean }): Promise<void> {\n // Banner trước khi vào device-code flow để user nhận diện thương hiệu.\n printAvatarBanner({ tagline: \"Đăng nhập Google SSO · workspace @nal.vn\" });\n\n // Step 1 of spec: short-circuit if already logged in and token is still good.\n if (opts.reset) {\n await clearUserConfig();\n await appendAuditEntry(\"login_reset\");\n } else {\n const existing = await readUserConfig();\n if (existing && !isTokenExpired(existing)) {\n log.success(`Đã đăng nhập: ${existing.email}`);\n return;\n }\n }\n\n // Step 2: request device + user code.\n const deviceSpinner = spinner(\"Đang yêu cầu device code từ Google...\");\n let deviceCode: Awaited<ReturnType<typeof requestDeviceCode>>;\n try {\n deviceCode = await requestDeviceCode();\n deviceSpinner.succeed(\"Nhận device code\");\n } catch (err) {\n deviceSpinner.fail(\"Không kết nối được Google\");\n throw err;\n }\n\n // Step 3: display instructions to user.\n const verificationUrl = buildVerificationUrl(deviceCode);\n const instructions = [\n `1. Truy cập: ${chalk.cyan(deviceCode.verification_url)}`,\n `2. Nhập code: ${chalk.bold.yellow(deviceCode.user_code)}`,\n \"\",\n `Hoặc Avatar tự mở browser, click ${chalk.green(\"Allow\")}...`,\n ].join(\"\\n\");\n process.stdout.write(`${boxen(instructions, { padding: 1, borderStyle: \"round\" })}\\n`);\n\n // Step 4: open browser. Failure here is non-fatal — user can copy URL manually.\n void open(verificationUrl).catch(() => {\n log.dim(\"(Không mở được browser tự động — copy URL ở trên)\");\n });\n\n // Step 5: poll token endpoint until success or expiry.\n const waitSpinner = spinner(\"Đang chờ xác nhận trong browser...\");\n const intervalMs = deviceCode.interval * 1000;\n const deadline = Date.now() + deviceCode.expires_in * 1000;\n\n let token = null;\n while (Date.now() < deadline) {\n await sleep(intervalMs);\n try {\n token = await pollForToken(deviceCode.device_code);\n if (token) break;\n } catch (err) {\n waitSpinner.fail(\"Xác thực thất bại\");\n throw err;\n }\n }\n if (!token) {\n waitSpinner.fail(\"Hết hạn chờ (5 phút). Chạy lại 'avatar login'.\");\n process.exit(1);\n }\n waitSpinner.succeed(\"Đã nhận token từ Google\");\n\n // Step 6: verify hosted domain. Revoke token if claim is wrong.\n const claims = decodeIdToken(token.id_token);\n try {\n verifyHostedDomain(claims);\n } catch (err) {\n await revokeToken(token.access_token);\n throw err;\n }\n\n // Step 7: persist credentials with chmod 600.\n const userConfig = buildUserConfig(token, claims);\n await writeUserConfig(userConfig);\n await appendAuditEntry(\"login\", userConfig.email);\n\n log.success(`Xác thực thành công: ${userConfig.email}`);\n log.success(`Verify hosted domain: ${claims.hd} ✓`);\n log.success(`Lưu credential vào ${USER_CONFIG_PATH} (chmod 600)`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// workspace-scaffold-and-finalize-orchestrator.ts\n//\n// v3 THIN INIT: workspace KHÔNG git. Scaffold = tạo folder + cài pack tarball +\n// symlink + settings + AI setup + GitNexus(install+MCP, KHÔNG analyze — src/ rỗng).\n//\n// Bỏ HẾT so với v2: git().init(), submodule add src/, installGitHook (post-merge/\n// pre-push), maybeCommitWorkspace, maybeCreateWorkspaceRemote. Repo code cài sau\n// bằng `avatar add repo` (Phase 05).\nimport { join } from \"node:path\";\nimport { addTeamPackSubmoduleWithRetryOnNetworkFail } from \"../lib/add-team-pack-submodule-with-retry-on-network-fail.js\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { ensureDir, pathExists } from \"../lib/filesystem-helpers.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport {\n createClaudeDirTree,\n writeClaudeGitignore,\n writeProjectKnowledgeFiles,\n writeProjectSettings,\n writeRootClaudeMd,\n} from \"../lib/project-tree-scaffolder.js\";\nimport { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { setupDefaultToolsOnInit } from \"../lib/setup-default-tools-on-init.js\";\nimport { syncAllMountDirs } from \"../lib/symlink-farm-for-team-pack-mount-dirs.js\";\nimport { TEAM_PACK_RELATIVE_PATH } from \"../lib/team-pack-submodule-manager.js\";\nimport { log, spinner } from \"../lib/terminal-logger.js\";\nimport { buildScaffoldVariables } from \"./init-scaffold-variable-builders.js\";\nimport {\n ensureWorkspaceFolder,\n printInitSuccessBox,\n} from \"./init-success-rendering-and-helpers.js\";\n\n// Scaffold workspace v3 (1 flow duy nhất). Tạo folder rỗng + cài pack + môi trường.\nexport async function scaffoldThinWorkspace(args: {\n workspacePath: string;\n workspaceName: string;\n teamOwner: string;\n description: string;\n packVersion?: string;\n autoYes?: boolean;\n skipTeamPack?: boolean;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n}): Promise<void> {\n // 1. Tạo workspace folder + src/ rỗng + notes/ (KHÔNG git init).\n await ensureWorkspaceFolder(args.workspacePath);\n await ensureDir(join(args.workspacePath, \"src\"));\n await ensureDir(join(args.workspacePath, \"notes\"));\n\n // 2. Cài team-ai-pack tarball.\n let pinnedTag = \"(skipped)\";\n if (!args.skipTeamPack) {\n const sp = spinner(\"Tải team-ai-pack từ Supabase...\");\n sp.stop(); // dừng spinner để menu recovery (nếu cần) không bị đè.\n const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(\n args.workspacePath,\n args.packVersion,\n );\n pinnedTag = result.pinnedTag ?? \"(skipped)\";\n if (!result.skipped) log.success(`team-ai-pack: ${pinnedTag}`);\n } else {\n log.dim(\"Skip team-ai-pack (--skip-team-pack).\");\n }\n\n // 3. Scaffold files (CLAUDE.md, settings, knowledge, gitignore).\n const vars = buildScaffoldVariables({\n projectName: args.workspaceName,\n projectDescription: args.description,\n teamOwner: args.teamOwner,\n packVersion: pinnedTag,\n });\n await createClaudeDirTree(args.workspacePath);\n await writeProjectKnowledgeFiles(args.workspacePath, vars);\n await writeRootClaudeMd(args.workspacePath, vars);\n await writeProjectSettings(args.workspacePath, vars);\n await writeClaudeGitignore(args.workspacePath);\n\n // 4. Auto-sync pack content → symlink farm + settings merge + default tools.\n await autoSyncPackOnInit(args.workspacePath, args.autoYes);\n\n await appendAuditEntry(\"init\", `workspace=${args.workspaceName}`);\n\n // 5. AI setup phase.\n let aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null = null;\n if (args.aiSkip) {\n log.dim(\"Bỏ qua AI setup (--ai-skip). Setup sau: avatar ai setup\");\n } else {\n aiResult = await runAiSetupPhase({ workspacePath: args.workspacePath });\n }\n\n // 6. GitNexus: install + MCP register THÔI. KHÔNG analyze (src/ rỗng — index per-repo\n // chạy khi `avatar add repo`). Auto-skip nếu --ai-skip (wiki cần API key từ AI phase).\n let gitnexusResult: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null = null;\n const skipGitnexus = args.aiSkip || args.gitnexusSkip;\n if (skipGitnexus) {\n log.dim(\n args.gitnexusSkip\n ? \"Bỏ qua GitNexus (--gitnexus-skip). Setup sau: avatar gitnexus install\"\n : \"Bỏ qua GitNexus (auto-skip do --ai-skip).\",\n );\n } else {\n gitnexusResult = await runGitnexusSetupPhase({\n workspacePath: args.workspacePath,\n skipAnalyze: true, // v3: init không analyze workspace rỗng.\n });\n }\n\n await printInitSuccessBox(args.workspacePath, aiResult, gitnexusResult);\n}\n\n// Auto-sync pack content vào convention paths Claude Code scan (symlink + settings).\nexport async function autoSyncPackOnInit(workspacePath: string, autoYes?: boolean): Promise<void> {\n const packDir = join(workspacePath, TEAM_PACK_RELATIVE_PATH);\n if (!(await pathExists(packDir))) {\n log.dim(\"Pack chưa cài (skip auto-sync). Chạy `avatar sync` sau.\");\n return;\n }\n\n const claudeDir = join(workspacePath, \".claude\");\n log.info(\"Auto-sync pack content vào .claude/ (symlinks + settings)...\");\n\n try {\n const results = await syncAllMountDirs(packDir, claudeDir, false);\n const created = results.filter((r) => r.action === \"created\" || r.action === \"updated\").length;\n const missing = results.filter((r) => r.action === \"source-missing\").length;\n log.success(\n ` ✓ Symlinks: ${created} created${missing > 0 ? `, ${missing} source-missing` : \"\"}`,\n );\n\n const mergeResult = await mergePackSettingsIntoProjectSettings(workspacePath);\n if (mergeResult.action === \"merged\") {\n log.success(` ✓ settings.json merged (${mergeResult.changes.join(\"; \")})`);\n }\n\n await setupDefaultToolsOnInit(workspacePath, { autoYes });\n } catch (err) {\n log.warn(\n `Auto-sync pack fail: ${err instanceof Error ? err.message : err}. Chạy \\`avatar sync\\` để retry.`,\n );\n }\n}\n","// add-team-pack-submodule-with-retry-on-network-fail.ts\n//\n// v3: Wrapper retry cho addTeamPackSubmodule (giờ tải tarball, không git submodule).\n// Phân loại lỗi từ downloader:\n// - InvalidIdTokenError → token sai/hết hạn/sai domain → KHÔNG retry, prompt avatar login\n// - PackNetworkError → mạng/Supabase down/signed URL hết hạn → retry backoff\n// - PackParseError → tarball hỏng → menu retry/skip/abort\n//\n// GIỮ tên file + chữ ký export (init-flow-handlers + scaffold-orchestrator import).\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.js\";\nimport { InvalidIdTokenError, PackNetworkError } from \"./supabase-pack-downloader.js\";\nimport { TeamPackAccessAbortedError, addTeamPackSubmodule } from \"./team-pack-submodule-manager.js\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface AddTeamPackResult {\n pinnedTag: string | null;\n skipped: boolean;\n}\n\nconst MAX_NETWORK_RETRIES = 3;\n\n// Wrap addTeamPackSubmodule với retry. Chữ ký giữ nguyên cho caller cũ.\n// (ssoEmail + latest từ v2 không còn dùng — giữ param để không vỡ call site, ignore.)\nexport async function addTeamPackSubmoduleWithRetryOnNetworkFail(\n projectRoot: string,\n version?: string,\n _ssoEmail?: string,\n _latest = false,\n): Promise<AddTeamPackResult> {\n let networkAttempt = 0;\n\n while (true) {\n try {\n const result = await addTeamPackSubmodule(projectRoot, version);\n return { pinnedTag: result.pinnedTag, skipped: false };\n } catch (err) {\n if (err instanceof TeamPackAccessAbortedError) throw err;\n\n // Token sai/hết hạn → KHÔNG retry (retry vô ích, cần login lại).\n if (err instanceof InvalidIdTokenError) {\n log.error(err.message);\n const action = await promptRetryOrSkip({\n taskName: \"Tải team-ai-pack (xác thực NAL)\",\n reason: err.message,\n allowSkip: true,\n hint: \"Chạy 'avatar login' với tài khoản @nal.vn rồi thử lại. Hoặc skip + 'avatar sync' sau.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước xác thực tải pack.\");\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng chưa có pack. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n continue; // retry-same: user đã login lại → thử lại.\n }\n\n // Mạng → retry tự động backoff vài lần, sau đó hỏi user.\n if (err instanceof PackNetworkError && networkAttempt < MAX_NETWORK_RETRIES) {\n networkAttempt += 1;\n log.warn(\n `Tải pack lỗi mạng (lần ${networkAttempt}/${MAX_NETWORK_RETRIES}): ${err.message}`,\n );\n await sleep(1000 * networkAttempt); // backoff tuyến tính 1s/2s/3s.\n continue;\n }\n\n // Hết retry mạng HOẶC lỗi khác (parse) → menu retry/skip/abort.\n const message = err instanceof Error ? err.message : String(err);\n const action = await promptRetryOrSkip({\n taskName: \"Tải team-ai-pack tarball\",\n reason: message,\n allowSkip: true,\n hint: \"Network glitch? Retry thường work. Skip → dùng `avatar sync` sau.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước tải team-ai-pack.\");\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng chưa có pack. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n networkAttempt = 0; // reset đếm sau khi user chủ động retry.\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// setup-default-tools-on-init.ts\n//\n// Bước init: hỏi user có bật các default tool (đọc từ pack tools/defaults.json)\n// không, rồi enable cái được đồng ý. Mặc định YES.\n//\n// - Interactive: hỏi \"Do you want to setup <tool> (y/n)\" default yes mỗi tool.\n// - Non-interactive (autoYes / CI): tự động yes (giữ default bật), không hỏi.\n// - Fail-mềm: pack chưa có tools/ → defaults rỗng → no-op im lặng.\n\nimport { confirm } from \"@inquirer/prompts\";\nimport { readDefaultTools } from \"./discover-pack-tools-and-defaults.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { enableToolByName } from \"./tool-enable-disable-orchestrator.js\";\n\n// Tên hiển thị thân thiện cho prompt (fallback = tên tool).\nconst DISPLAY_NAME: Record<string, string> = {\n \"prompt-scoring\": \"prompt scoring\",\n};\n\nexport async function setupDefaultToolsOnInit(\n workspacePath: string,\n opts: { autoYes?: boolean } = {},\n): Promise<void> {\n const defaults = await readDefaultTools(workspacePath);\n if (defaults.length === 0) {\n return; // pack chưa có tools/defaults.json → no-op.\n }\n\n for (const name of defaults) {\n const display = DISPLAY_NAME[name] ?? name;\n let wantEnable: boolean;\n if (opts.autoYes) {\n wantEnable = true; // CI / --yes: giữ default bật, không hỏi.\n } else {\n wantEnable = await confirm({\n message: `Do you want to setup ${display} (y/n)`,\n default: true,\n });\n }\n if (!wantEnable) {\n log.dim(` Skip tool '${name}' (user opt-out).`);\n continue;\n }\n // silent: tool default có thể chưa có trong pack cũ → không spam warning.\n await enableToolByName(workspacePath, name, { silent: true });\n }\n}\n","// discover-pack-tools-and-defaults.ts\n//\n// Quét pack/tools/ để biết tool nào CÓ SẴN + đọc danh sách tool init bật\n// mặc định (tools/defaults.json). Đọc default từ pack (không hardcode trong CLI)\n// → đổi default không cần release CLI mới.\n\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\nimport { listEnabledTools } from \"./tool-state-store.js\";\n\nconst PACK_FEATURES_REL = [\".claude\", \"pack\", \"tools\"];\n\nfunction packToolsDir(workspacePath: string): string {\n return join(workspacePath, ...PACK_FEATURES_REL);\n}\n\n// Liệt kê tool có manifest trong pack (mỗi dir con có tool.json).\nexport async function listAvailableTools(workspacePath: string): Promise<string[]> {\n const dir = packToolsDir(workspacePath);\n if (!(await pathExists(dir))) {\n return [];\n }\n const entries = await fs.readdir(dir, { withFileTypes: true });\n const names: string[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (await pathExists(join(dir, entry.name, \"tool.json\"))) {\n names.push(entry.name);\n }\n }\n return names.sort();\n}\n\ninterface DefaultsFile {\n defaultTools?: string[];\n}\n\n// Đọc tools/defaults.json → danh sách tool init bật mặc định. Thiếu → [].\nexport async function readDefaultTools(workspacePath: string): Promise<string[]> {\n const path = join(packToolsDir(workspacePath), \"defaults.json\");\n if (!(await pathExists(path))) {\n return [];\n }\n try {\n const parsed = await readJson<DefaultsFile>(path);\n return parsed.defaultTools ?? [];\n } catch {\n return [];\n }\n}\n\nexport interface ToolsOverview {\n available: string[]; // có manifest trong pack\n enabled: string[]; // enabled=true trong state\n}\n\n// Tổng hợp cho status/doctor: tool có sẵn (pack) + đang bật (state).\nexport async function discoverEnabledAndAvailableTools(\n workspacePath: string,\n): Promise<ToolsOverview> {\n const [available, enabled] = await Promise.all([\n listAvailableTools(workspacePath),\n listEnabledTools(workspacePath),\n ]);\n return { available, enabled };\n}\n","// tool-enable-disable-orchestrator.ts\n//\n// Orchestrate 1 lần enable/disable tool: đọc manifest → (kiểm runtime) → apply\n// vào settings.json → cập nhật avatar-tools.json → log. Tách khỏi command để\n// init/sync tái dùng đúng path enable (DRY).\n//\n// Fail-mềm: pack chưa có tools/ (version skew) → log warn + return false,\n// KHÔNG throw (init không vỡ vì pack cũ).\n\nimport { spawnSync } from \"node:child_process\";\nimport {\n type ApplyToolResult,\n disableTool,\n enableTool,\n readToolManifest,\n} from \"./apply-tool-manifest-to-settings.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { readToolState, setToolState } from \"./tool-state-store.js\";\n\n// Kiểm runtime manifest.requires (vd python3) có trên PATH. Chỉ cảnh báo, không chặn.\nfunction warnIfRuntimeMissing(runtime?: string): void {\n if (!runtime) return;\n const probe = spawnSync(runtime, [\"--version\"], { stdio: \"ignore\" });\n if (probe.status !== 0 || probe.error) {\n log.warn(\n `Tool yêu cầu runtime '${runtime}' nhưng không tìm thấy trên PATH. ` +\n `Hook có thể fail khi chạy. Cài '${runtime}' rồi thử lại.`,\n );\n }\n}\n\nfunction logApplyResult(name: string, result: ApplyToolResult): void {\n switch (result.action) {\n case \"enabled\":\n log.success(`Tool '${name}' enabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"disabled\":\n log.success(`Tool '${name}' disabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"no-change\":\n log.dim(`Tool '${name}': settings.json đã đúng trạng thái, không thay đổi.`);\n break;\n case \"no-manifest\":\n break; // handled by caller\n }\n}\n\n// Enable 1 tool. Trả về true nếu manifest tồn tại (đã xử lý), false nếu pack\n// chưa hỗ trợ (fail-mềm). `silent` = không in dòng \"not supported\" (dùng cho init\n// khi tool default có thể chưa có trong pack cũ).\nexport async function enableToolByName(\n workspacePath: string,\n name: string,\n opts: { silent?: boolean } = {},\n): Promise<boolean> {\n const manifest = await readToolManifest(workspacePath, name);\n if (!manifest) {\n if (!opts.silent) {\n log.warn(\n `Pack version hiện tại chưa hỗ trợ tool '${name}' (thiếu .claude/pack/tools/${name}/tool.json). Chạy 'avatar sync' để pull pack mới, hoặc kiểm tên tool.`,\n );\n }\n return false;\n }\n warnIfRuntimeMissing(manifest.requires?.runtime);\n const result = await enableTool(workspacePath, manifest);\n logApplyResult(name, result);\n await setToolState(workspacePath, name, { enabled: true, version: manifest.version });\n return true;\n}\n\n// Disable 1 tool. Đọc manifest để biết CHÍNH XÁC entry nào của tool.\nexport async function disableToolByName(workspacePath: string, name: string): Promise<boolean> {\n const manifest = await readToolManifest(workspacePath, name);\n if (!manifest) {\n log.warn(\n `Không tìm thấy manifest tool '${name}' để biết entry nào cần rút. Nếu pack đã đổi, state vẫn được set disabled nhưng settings.json có thể còn sót entry.`,\n );\n // Vẫn set state disabled để phản ánh ý định; settings không đổi (không có manifest).\n const state = await readToolState(workspacePath);\n const prevVersion = state.tools[name]?.version ?? \"unknown\";\n await setToolState(workspacePath, name, { enabled: false, version: prevVersion });\n return false;\n }\n const result = await disableTool(workspacePath, manifest);\n logApplyResult(name, result);\n await setToolState(workspacePath, name, { enabled: false, version: manifest.version });\n return true;\n}\n","// Create symlink farm for team-ai-pack mount dirs.\n//\n// Links project/.claude/<dir> → project/.claude/pack/<dir> for each of:\n// skills, agents, commands, hooks, workflows, scripts.\n//\n// Why symlinks vs copy: Claude Code reads .claude/{skills,agents,...} by\n// convention. Submodule lives at .claude/pack/. Symlinks let Claude see the\n// pack content at expected paths without duplication. When pack updates, all\n// projects pick up changes via the symlink.\n//\n// Conflict handling: if .claude/<dir> exists as a real directory (not symlink),\n// caller must pass force=true to backup and replace. Otherwise we return a\n// conflict report and let CLI prompt the user.\nimport { promises as fs } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\n\nimport { backupDirBeforeReplace } from \"./backup-existing-dir-before-symlink-override.js\";\nimport { pathExists } from \"./filesystem-helpers.js\";\n\n// Mount dirs: Claude Code convention paths + Avatar-specific extras.\n// - skills/agents/commands/hooks → Claude Code scan tự động\n// - workflows/scripts → Avatar workflow refs + hook companion scripts\n// - knowledge → docs team-shared (coding standards, security policy...) — Claude Code\n// không auto-scan nhưng CLAUDE.md + agents reference được qua .claude/knowledge/\nexport const TEAM_PACK_MOUNT_DIRS = [\n \"skills\",\n \"agents\",\n \"commands\",\n \"hooks\",\n \"workflows\",\n \"scripts\",\n \"knowledge\",\n] as const;\n\nexport interface SyncMountedDirResult {\n dir: string;\n action: \"created\" | \"updated\" | \"skipped-conflict\" | \"backed-up-and-linked\" | \"source-missing\";\n backupPath?: string;\n}\n\n// Check whether a path is an existing symlink. fs.lstat doesn't follow symlinks.\nasync function isSymbolicLink(path: string): Promise<boolean> {\n try {\n const st = await fs.lstat(path);\n return st.isSymbolicLink();\n } catch {\n return false;\n }\n}\n\n// Sync one mount dir: pack/<dir> → .claude/<dir> (relative symlink).\n//\n// Cases:\n// 1. source doesn't exist → action: source-missing\n// 2. dest doesn't exist → create symlink, action: created\n// 3. dest is existing symlink → recreate (idempotent), action: updated\n// 4. dest is real dir, !force → action: skipped-conflict (caller prompts user)\n// 5. dest is real dir, force → backup + create symlink, action: backed-up-and-linked\nexport async function syncMountedDir(\n source: string,\n dest: string,\n force: boolean,\n): Promise<SyncMountedDirResult> {\n const dir = relative(dirname(dest), dest) || dest;\n\n if (!(await pathExists(source))) {\n return { dir, action: \"source-missing\" };\n }\n\n if (await pathExists(dest)) {\n if (await isSymbolicLink(dest)) {\n await fs.unlink(dest);\n } else if (force) {\n const backupPath = await backupDirBeforeReplace(dest);\n const relativeSource = relative(dirname(dest), source);\n await fs.symlink(relativeSource, dest);\n return { dir, action: \"backed-up-and-linked\", backupPath };\n } else {\n return { dir, action: \"skipped-conflict\" };\n }\n }\n\n const relativeSource = relative(dirname(dest), source);\n await fs.symlink(relativeSource, dest);\n return { dir, action: \"created\" };\n}\n\n// Sync all configured mount dirs. Returns aggregated results.\nexport async function syncAllMountDirs(\n packDir: string,\n claudeDir: string,\n force: boolean,\n): Promise<SyncMountedDirResult[]> {\n const results: SyncMountedDirResult[] = [];\n for (const dir of TEAM_PACK_MOUNT_DIRS) {\n const source = join(packDir, dir);\n const dest = join(claudeDir, dir);\n results.push(await syncMountedDir(source, dest, force));\n }\n return results;\n}\n","// Backup an existing real directory before replacing it with a symlink.\n//\n// Use case: avatar sync --force needs to override .claude/skills/ (which user\n// may have manually populated) with a symlink to .claude/pack/skills/. We must\n// NOT silently delete user content — instead rename to a timestamped backup so\n// they can restore.\n//\n// Backup naming: <dir>.backup-YYYY-MM-DD-HHMM\nimport { promises as fs } from \"node:fs\";\n\n// Build timestamp suffix in local time. Format: YYYY-MM-DD-HHMM\nfunction timestamp(): string {\n const d = new Date();\n const pad = (n: number) => n.toString().padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +\n `-${pad(d.getHours())}${pad(d.getMinutes())}`\n );\n}\n\n// Rename targetPath to \"<targetPath>.backup-<timestamp>\". Returns backup path.\n// Caller is responsible for any subsequent symlink/dir creation at targetPath.\nexport async function backupDirBeforeReplace(targetPath: string): Promise<string> {\n const backupPath = `${targetPath}.backup-${timestamp()}`;\n await fs.rename(targetPath, backupPath);\n return backupPath;\n}\n","// Single source of truth cho CLI version — đọc runtime từ package.json.\n//\n// Lý do: trước đây version hardcode ở nhiều file (index.ts, status.ts,\n// init-scaffold-variable-builders.ts, print-welcome-screen.ts) → drift bug khi\n// bump version chỉ ở 1 chỗ, các chỗ khác in version cũ.\n//\n// Layout runtime:\n// bin/avatar.js → dist/index.js (bundled) → package.json ở \"../package.json\"\n// từ dist/. Dev mode (vitest) → src/lib/*.ts → package.json ở \"../../package.json\".\n// Caller dùng helper này không cần biết path, chỉ gọi readCliVersion().\n\nimport { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n// Cache để tránh đọc disk mỗi lần gọi (CLI có thể gọi nhiều lần trong 1 process).\nlet cachedVersion: string | null = null;\n\nexport function readCliVersion(): string {\n if (cachedVersion !== null) return cachedVersion;\n\n // Tìm package.json bằng cách đi ngược lên từ vị trí file này.\n // Production (bundled): dist/index.js → ../package.json\n // Dev (vitest): src/lib/this-file.ts → ../../package.json\n // Walk up tối đa 5 level để robust với cả 2 layout:\n // - Bundled (production): dist/index.js → ../package.json (i=1)\n // - Dev (vitest/tsx): src/lib/*.ts → ../../package.json (i=2)\n // i=0 (cùng dir) cover trường hợp file move ngang sang root tương lai.\n const here = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(here, ...Array(i).fill(\"..\"), \"package.json\");\n try {\n const raw = readFileSync(candidate, \"utf8\");\n const pkg = JSON.parse(raw) as { name?: string; version?: string };\n // Verify đúng package (tránh nhặt nhầm package.json của repo cha).\n if (pkg.name === \"@nalvietnam/avatar-cli\" && typeof pkg.version === \"string\") {\n cachedVersion = pkg.version;\n return cachedVersion;\n }\n } catch {\n // File không tồn tại hoặc JSON invalid → thử level tiếp theo.\n }\n }\n\n // Fallback: nếu không tìm thấy (case edge — symlink lạ, sandbox), trả \"unknown\"\n // thay vì throw. Status command vẫn hoạt động, chỉ in \"unknown\" thay vì version.\n cachedVersion = \"unknown\";\n return cachedVersion;\n}\n","// Pure transformation helpers used by `avatar init` to derive project names,\n// workspace names from git URLs, and build the ScaffoldVariables struct for\n// template rendering. Extracted from init.ts for testability — no IO,\n// no prompts, no git calls.\n\nimport type { ScaffoldVariables } from \"../lib/project-tree-scaffolder.js\";\nimport { readCliVersion } from \"../lib/read-cli-version-from-package-json.js\";\n\n// v1.4.0: Render GitNexus section content cho CLAUDE.md template.\n// Trả empty string nếu user skip GitNexus → template không có section dangling.\n// Strong instruction Q4 confirmed: \"soft\" wording — Claude tự action khi thấy\n// staleness hint từ MCP resource.\nexport function buildGitnexusSection(gitnexusReady: boolean): string {\n if (!gitnexusReady) return \"\";\n return `\n### 🧠 CODEBASE INTELLIGENCE — GitNexus\n\nWorkspace có GitNexus index tại \\`.gitnexus/\\` cung cấp architectural awareness\ncho Claude Code (impact analysis, call chains, blast radius).\n\n**Hướng dẫn cho Claude:**\n\n- Khi cần hiểu codebase architecture (vd: trước refactor, debug call chain) →\n query GitNexus MCP resource \\`gitnexus://repo/<name>/context\\`\n- Khi resource trả staleness hint \"⚠️ Index is N commit(s) behind HEAD\" →\n TỰ chạy \\`gitnexus analyze .\\` (trong workspace root) trước khi tiếp tục task\n- Sau khi \\`gitnexus analyze\\` xong: **KHÔNG tự chạy \\`gitnexus wiki\\`**\n (cost cao, có thể block session 2-5 phút) — user sẽ chạy manual khi cần\n\n**Folders Claude scan auto cho skills:**\n\n- \\`~/.claude/skills/gitnexus-*/\\` — GitNexus global skills (exploring, debugging, ...)\n- \\`.claude/pack/skills/\\` — NAL team-shared skills (qua team-ai-pack)\n- Cả 2 đều được scan, không xung đột (different naming prefix)\n\n**Manual wiki update:**\n\nKhi user cần regenerate wiki sau refactor lớn — chạy:\n\n\\`\\`\\`bash\ngitnexus wiki . --api-key <key> --base-url <url>\n\\`\\`\\`\n`;\n}\n\n// Build the template-rendering variable bag. lastScan stamps \"now\" — tests\n// that need a deterministic value should freeze time via vi.useFakeTimers().\n// v1.4.0: gitnexusReady flag controls render GitNexus section vào CLAUDE.md.\n// v3: bỏ `mode` (InitMode internal/client/library dead sau gộp 1 flow).\nexport function buildScaffoldVariables(args: {\n projectName: string;\n projectDescription: string;\n teamOwner: string;\n packVersion: string;\n gitnexusReady?: boolean;\n}): ScaffoldVariables {\n return {\n projectName: args.projectName,\n projectDescription: args.projectDescription,\n teamOwner: args.teamOwner,\n avatarVersion: readCliVersion(),\n packVersion: args.packVersion,\n lastScan: new Date().toISOString(),\n gitnexusSection: buildGitnexusSection(args.gitnexusReady ?? false),\n };\n}\n","// Shared stub action for commands wired into commander but not yet implemented.\n// Prints a consistent message + exit code so users know it's coming.\nimport { chalk } from \"./terminal-logger.js\";\n\nexport function notImplementedYet(commandName: string, milestone?: string): () => void {\n return () => {\n process.stdout.write(\n `${chalk.yellow(\"⏳\")} ${chalk.bold(`avatar ${commandName}`)} — chưa implement ở milestone hiện tại.\\n`,\n );\n if (milestone) {\n process.stdout.write(` Dự kiến: ${chalk.cyan(milestone)}\\n`);\n }\n process.stdout.write(\" Spec đã có trong avatar-cli-implementation_4.html.\\n\");\n process.exit(0);\n };\n}\n","// `avatar mcp-run <tool-id>` — hidden command from Chapter 13 roadmap.\n// Wrapper used by ~/.claude.json entries: Avatar injects secrets from keychain\n// then spawns the underlying MCP process with stdio piped to Claude Code.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerMcpRunCommand(program: Command): void {\n program\n .command(\"mcp-run <tool-id>\", { hidden: true })\n .description(\"[internal] Spawn MCP với secrets injected (M09)\")\n .action(notImplementedYet(\"mcp-run\", \"Milestone 09\"));\n}\n","// `avatar pack status` — hiển thị version team-ai-pack đang cài.\n//\n// v3: pack là tarball (không git). Không còn so sánh tag remote (chưa có endpoint\n// list-versions). Chỉ show version từ manifest + gợi ý `avatar sync` để cập nhật.\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { readPinnedPackVersion } from \"../lib/team-pack-submodule-manager.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\n\nconst PACK_RELATIVE_PATH = \".claude/pack\";\n\nexport function registerPackCommand(program: Command): void {\n const pack = program.command(\"pack\").description(\"Quản lý team-ai-pack (status, ...)\");\n\n pack\n .command(\"status\")\n .description(\"Hiển thị version pack đang cài\")\n .option(\"--json\", \"Output JSON cho script\")\n .action(async (opts: { json?: boolean }) => {\n try {\n const snap = await gatherPackStatus(process.cwd());\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(snap, null, 2)}\\n`);\n } else {\n renderPackStatus(snap);\n }\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\ninterface PackStatus {\n installed: boolean;\n currentVersion: string | null; // null khi chưa cài\n}\n\nasync function gatherPackStatus(cwd: string): Promise<PackStatus> {\n const packDir = join(cwd, PACK_RELATIVE_PATH);\n if (!(await pathExists(packDir))) {\n return { installed: false, currentVersion: null };\n }\n const currentVersion = await readPinnedPackVersion(cwd).catch(() => null);\n return { installed: true, currentVersion };\n}\n\nfunction renderPackStatus(s: PackStatus): void {\n if (!s.installed) {\n const lines = [\n `${chalk.bold(\"team-ai-pack\")} · ${chalk.yellow(\"chưa cài\")}`,\n \"─\".repeat(48),\n chalk.dim(\"Chạy `avatar init` hoặc `avatar sync` để cài pack.\"),\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n return;\n }\n\n const current = s.currentVersion ?? chalk.yellow(\"(unknown)\");\n const lines = [\n `${chalk.bold(\"team-ai-pack status\")}`,\n \"─\".repeat(48),\n `${chalk.dim(\"Version hiện tại:\")} ${current}`,\n \"\",\n chalk.dim(\"Cập nhật mới nhất: avatar sync\"),\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n","// `avatar restore [--backup <name>] [--list]` — Command 08 spec.\n// Restore .claude/pack/ from a previous backup snapshot.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerRestoreCommand(program: Command): void {\n program\n .command(\"restore\")\n .description(\"Khôi phục .claude/pack/ từ backup (M08)\")\n .option(\"--backup <name>\", \"Tên backup folder trong .claude/_backup/\")\n .option(\"--list\", \"Liệt kê các backup hiện có\")\n .action(notImplementedYet(\"restore\", \"Milestone 08\"));\n}\n","// `avatar review [--accept-all|--reject-all]` — Command 05 spec.\n// Interactive review of .claude/_pending/*.diff.md proposals.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerReviewCommand(program: Command): void {\n program\n .command(\"review\")\n .description(\"Review pending proposals từ avatar scan (M08)\")\n .option(\"--accept-all\", \"Approve mọi pending không hỏi (CI mode)\")\n .option(\"--reject-all\", \"Xóa mọi pending không hỏi\")\n .action(notImplementedYet(\"review\", \"Milestone 08\"));\n}\n","// `avatar scan [--incremental|--full] [--scanners <list>]` — Command 04 spec.\n// Runs 5 project scanners and writes proposals to .claude/_pending/.\n// Implementation deferred — scanner files in src/scanners/ are stubbed.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerScanCommand(program: Command): void {\n program\n .command(\"scan\")\n .description(\"Chạy project scanner và đề xuất knowledge update (M06)\")\n .option(\"--incremental\", \"Chỉ scan các file thay đổi từ commit cuối\")\n .option(\"--full\", \"Scan toàn bộ dự án (default)\")\n .option(\"--scanners <list>\", \"tech-stack,conventions,architecture,domain,git-pattern\")\n .option(\"--quiet\", \"Chạy ngầm, ít output (dùng cho git hook)\")\n .action(notImplementedYet(\"scan\", \"Milestone 06\"));\n}\n","// `avatar status [--json]` — Command 06 spec.\n// Read-only snapshot: project name, CLI version, pack version, pending count,\n// backup count, tech-stack first-line. No mutations.\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { discoverEnabledAndAvailableTools } from \"../lib/discover-pack-tools-and-defaults.js\";\nimport { pathExists, readText } from \"../lib/filesystem-helpers.js\";\nimport { listBackups } from \"../lib/pack-backup-manager.js\";\nimport { readCliVersion } from \"../lib/read-cli-version-from-package-json.js\";\nimport { readPinnedPackVersion } from \"../lib/team-pack-submodule-manager.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command(\"status\")\n .description(\"Snapshot tức thì: project, pack version, pending, backup\")\n .option(\"--json\", \"Output JSON cho script\")\n .action(async (opts: { json?: boolean }) => {\n try {\n const snapshot = await gatherStatus(process.cwd());\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(snapshot, null, 2)}\\n`);\n } else {\n renderStatusBox(snapshot);\n }\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\ninterface StatusSnapshot {\n projectName: string;\n cliVersion: string;\n packVersion: string | null;\n pendingCount: number;\n backupCount: number;\n techStackSummary: string;\n hasAvatar: boolean;\n toolsEnabled: string[];\n toolsAvailableCount: number;\n}\n\nasync function gatherStatus(cwd: string): Promise<StatusSnapshot> {\n const projectName = cwd.split(\"/\").filter(Boolean).pop() ?? \"unknown\";\n const claudeRoot = join(cwd, \".claude\");\n const hasAvatar = await pathExists(claudeRoot);\n if (!hasAvatar) {\n return {\n projectName,\n cliVersion: readCliVersion(),\n packVersion: null,\n pendingCount: 0,\n backupCount: 0,\n techStackSummary: \"(Avatar chưa init)\",\n hasAvatar: false,\n toolsEnabled: [],\n toolsAvailableCount: 0,\n };\n }\n\n // v1.13.0 perf: 4 probes độc lập (pack, pending, backup, tech-stack) chạy\n // parallel qua Promise.all thay vì sequential. Save ~100-200ms trên repo\n // có nhiều file/backup. Mỗi probe self-catch (.catch → safe fallback) để\n // 1 probe fail không kill cả status command.\n const pendingDir = join(claudeRoot, \"_pending\");\n const [packVersion, pendingCount, backupCount, techStackSummary, tools] = await Promise.all([\n (async (): Promise<string | null> => {\n // v3: pack là tarball (không git) → đọc version từ manifest. null nếu chưa cài.\n if (!(await pathExists(join(claudeRoot, \"pack\")))) return null;\n return readPinnedPackVersion(cwd).catch(() => null);\n })(),\n (async (): Promise<number> => {\n if (!(await pathExists(pendingDir))) return 0;\n const entries = await fs.readdir(pendingDir);\n return entries.filter((n) => n.endsWith(\".diff.md\")).length;\n })(),\n listBackups(cwd)\n .then((b) => b.length)\n .catch(() => 0),\n readTechStackFirstLine(claudeRoot).catch(() => \"(read error)\"),\n discoverEnabledAndAvailableTools(cwd).catch(() => ({ available: [], enabled: [] })),\n ]);\n\n return {\n projectName,\n cliVersion: readCliVersion(),\n packVersion,\n pendingCount,\n backupCount,\n techStackSummary,\n hasAvatar: true,\n toolsEnabled: tools.enabled,\n toolsAvailableCount: tools.available.length,\n };\n}\n\nasync function readTechStackFirstLine(claudeRoot: string): Promise<string> {\n const techStackPath = join(claudeRoot, \"project\", \"tech-stack.md\");\n if (!(await pathExists(techStackPath))) return \"(no tech-stack.md)\";\n const content = await readText(techStackPath);\n const firstNonHeaderLine = content\n .split(\"\\n\")\n .find((l) => l.trim() && !l.startsWith(\"#\") && !l.startsWith(\">\"));\n return firstNonHeaderLine?.trim() ?? \"(empty)\";\n}\n\n// \"prompt-scoring (1/1 available)\" hoặc \"none (1 available)\" / \"none\".\nfunction formatTools(s: StatusSnapshot): string {\n if (s.toolsEnabled.length > 0) {\n return `${s.toolsEnabled.join(\", \")} (${s.toolsEnabled.length}/${s.toolsAvailableCount} available)`;\n }\n if (s.toolsAvailableCount > 0) {\n return `none enabled (${s.toolsAvailableCount} available — avatar tool list)`;\n }\n return \"none\";\n}\n\nfunction renderStatusBox(s: StatusSnapshot): void {\n const lines = [\n `${chalk.bold(\"Avatar Status\")} · ${chalk.cyan(s.projectName)}`,\n \"─\".repeat(48),\n `${chalk.dim(\"CLI version:\")} ${s.cliVersion}`,\n `${chalk.dim(\"Pack version:\")} ${s.packVersion ?? chalk.yellow(\"not installed\")}`,\n `${chalk.dim(\"Pending changes:\")} ${s.pendingCount}${s.pendingCount > 0 ? chalk.dim(\" (avatar review)\") : \"\"}`,\n `${chalk.dim(\"Backups:\")} ${s.backupCount}`,\n `${chalk.dim(\"Tech stack:\")} ${s.techStackSummary}`,\n `${chalk.dim(\"Tools:\")} ${formatTools(s)}`,\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n","import { promises as fs } from \"node:fs\";\n// Backup .claude/pack/ before `avatar sync --force` so user can `avatar restore`\n// if the sync goes wrong. Naming convention: pack-{currentVersion}-{YYYYMMDD-HHmm}.\nimport { join } from \"node:path\";\nimport { copyDirRecursive, ensureDir, pathExists } from \"./filesystem-helpers.js\";\n\nexport const BACKUP_DIR_NAME = \"_backup\";\n\nfunction timestamp(): string {\n const now = new Date();\n const y = now.getFullYear();\n const m = String(now.getMonth() + 1).padStart(2, \"0\");\n const d = String(now.getDate()).padStart(2, \"0\");\n const h = String(now.getHours()).padStart(2, \"0\");\n const min = String(now.getMinutes()).padStart(2, \"0\");\n return `${y}${m}${d}-${h}${min}`;\n}\n\nexport function buildBackupName(currentVersion: string): string {\n // Strip any leading \"v\" so we don't get pack-vv1.2.3 if someone passes \"v1.2.3\".\n const cleanVersion = currentVersion.replace(/^v/, \"\");\n return `pack-v${cleanVersion}-${timestamp()}`;\n}\n\n// Backup .claude/pack/ to .claude/_backup/{name}/, excluding the submodule's\n// own .git directory (we restore content only, not git history).\nexport async function backupPack(projectRoot: string, currentVersion: string): Promise<string> {\n const name = buildBackupName(currentVersion);\n const srcPath = join(projectRoot, \".claude\", \"pack\");\n const dstPath = join(projectRoot, \".claude\", BACKUP_DIR_NAME, name);\n if (!(await pathExists(srcPath))) {\n throw new Error(\"Không tìm thấy .claude/pack/ để backup\");\n }\n await ensureDir(dstPath);\n await copyDirRecursive(srcPath, dstPath, [\".git\"]);\n return name;\n}\n\nexport async function listBackups(projectRoot: string): Promise<string[]> {\n const dir = join(projectRoot, \".claude\", BACKUP_DIR_NAME);\n if (!(await pathExists(dir))) return [];\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort()\n .reverse();\n}\n","// `avatar sync [--version <tag>] [--dry-run] [--force]` — v3.\n//\n// Tải team-ai-pack tarball mới từ Supabase (thay git submodule update) + tạo\n// symlink farm + merge settings + re-apply tools.\n//\n// Flow:\n// 1. Locate project root (cwd có .claude/pack/)\n// 2. getValidIdToken → fetchSignedPackUrl(version?) → download+extract → manifest\n// (--version pin bản cụ thể; không flag → server trả stable mới nhất)\n// 3. --dry-run? show preview (version diff) và exit\n// 4. syncAllMountDirs() → symlink\n// 5. merge settings + re-apply tools\nimport { join } from \"node:path\";\n\nimport type { Command } from \"commander\";\n\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport { writePackManifest } from \"../lib/pack-manifest-store.js\";\nimport { buildSyncPreview } from \"../lib/preview-team-pack-sync-changes-for-dry-run.js\";\nimport { reapplyEnabledToolsAfterSync } from \"../lib/reapply-enabled-tools-after-sync.js\";\nimport {\n InvalidIdTokenError,\n downloadAndExtractPack,\n fetchSignedPackUrl,\n} from \"../lib/supabase-pack-downloader.js\";\nimport {\n type SyncMountedDirResult,\n syncAllMountDirs,\n} from \"../lib/symlink-farm-for-team-pack-mount-dirs.js\";\nimport { TEAM_PACK_RELATIVE_PATH } from \"../lib/team-pack-submodule-manager.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { getValidIdToken } from \"../lib/user-config-store.js\";\n\ninterface SyncOptions {\n force?: boolean;\n version?: string;\n dryRun?: boolean;\n}\n\nfunction objectToVersion(object: string): string {\n return object.replace(/^pack-/, \"\").replace(/\\.tar\\.gz$/, \"\");\n}\n\nasync function syncAction(opts: SyncOptions): Promise<void> {\n const projectRoot = process.cwd();\n const claudeDir = join(projectRoot, \".claude\");\n const packDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n\n if (!(await pathExists(claudeDir))) {\n log.error(\n \"Không thấy .claude/ — không phải Avatar workspace.\\n Chạy 'avatar init' để khởi tạo.\",\n );\n process.exit(1);\n }\n\n // --dry-run: chỉ preview, không tải.\n if (opts.dryRun) {\n const preview = await buildSyncPreview(packDir, claudeDir, opts.version);\n log.info(`Pack version hiện tại: ${preview.currentVersion}`);\n log.info(`Target version: ${preview.targetVersion}`);\n log.info(\"\\nMount dir statuses:\");\n for (const m of preview.mountDirStatuses) {\n console.log(` ${m.dir.padEnd(12)} ${m.status}`);\n }\n log.info(\"\\nDry-run done. Không apply. Bỏ --dry-run để thực thi.\");\n return;\n }\n\n // Tải tarball mới.\n let idToken: string;\n try {\n idToken = await getValidIdToken();\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n\n log.info(\n opts.version\n ? `Tải team-ai-pack ${opts.version} từ Supabase...`\n : \"Tải team-ai-pack (stable mới nhất) từ Supabase...\",\n );\n let installedVersion: string;\n try {\n const info = await fetchSignedPackUrl(idToken, opts.version);\n await downloadAndExtractPack(info.url, packDir);\n installedVersion = opts.version ?? objectToVersion(info.object);\n await writePackManifest(packDir, {\n version: installedVersion,\n downloadedObject: info.object,\n extractedAt: new Date().toISOString(),\n });\n log.success(`Đã cài pack ${installedVersion}`);\n } catch (err) {\n if (err instanceof InvalidIdTokenError) {\n log.error(`${err.message}`);\n } else {\n log.error(`Tải pack thất bại: ${err instanceof Error ? err.message : err}`);\n }\n process.exit(1);\n }\n\n log.info(\"Tạo symlink farm...\");\n const results = await syncAllMountDirs(packDir, claudeDir, opts.force === true);\n reportResults(results, opts.force === true);\n\n // Merge settings + re-apply tools (giữ nguyên logic v2).\n log.info(\"Merge pack settings.json template vào project settings.json...\");\n try {\n const mergeResult = await mergePackSettingsIntoProjectSettings(projectRoot);\n switch (mergeResult.action) {\n case \"merged\":\n log.success(` ✓ settings.json merged (${mergeResult.changes.join(\"; \")})`);\n break;\n case \"no-change\":\n log.info(\" - settings.json đã sync.\");\n break;\n case \"no-pack-template\":\n log.dim(\" - Pack không có templates/settings.json.tpl, skip.\");\n break;\n }\n } catch (err) {\n log.warn(` ! Merge settings.json fail: ${err instanceof Error ? err.message : err}.`);\n }\n\n try {\n await reapplyEnabledToolsAfterSync(projectRoot);\n } catch (err) {\n log.warn(` ! Re-apply tools fail: ${err instanceof Error ? err.message : err}.`);\n }\n\n log.success(`Synced team-ai-pack → ${installedVersion}.`);\n}\n\nfunction reportResults(results: SyncMountedDirResult[], force: boolean): void {\n for (const r of results) {\n switch (r.action) {\n case \"created\":\n log.info(` ✓ ${r.dir} → symlinked (new)`);\n break;\n case \"updated\":\n log.info(` ✓ ${r.dir} → symlink refreshed`);\n break;\n case \"backed-up-and-linked\":\n log.info(` ✓ ${r.dir} → symlinked (backup: ${r.backupPath})`);\n break;\n case \"source-missing\":\n log.warn(` - ${r.dir} → pack không có dir này, skip`);\n break;\n case \"skipped-conflict\":\n log.warn(` ! ${r.dir} → CONFLICT: existing real dir. Dùng --force để backup + override.`);\n break;\n }\n }\n const conflicts = results.filter((r) => r.action === \"skipped-conflict\").length;\n if (conflicts > 0 && !force) {\n log.warn(`${conflicts} mount dir(s) skip do conflict. Chạy lại với --force.`);\n }\n}\n\nexport function registerSyncCommand(program: Command): void {\n program\n .command(\"sync\")\n .description(\"Tải team-ai-pack mới nhất (tarball Supabase) + tạo symlink farm\")\n .option(\"--force\", \"Override .claude/<dir>/ nếu là real dir (backup trước)\")\n .option(\"--version <tag>\", \"Pin version cụ thể (vd: v0.2.0 hoặc v0.2.0-beta.0)\")\n .option(\"--dry-run\", \"Hiển thị preview, không apply\")\n .action(syncAction);\n}\n","// Preview what `avatar sync` would do, without modifying filesystem.\n//\n// v3: pack là tarball (không git) → không còn git log diff. So version manifest\n// hiện tại vs target. mountDir status không đổi (symlink farm pack-agnostic).\nimport { join } from \"node:path\";\n\nimport { pathExists } from \"./filesystem-helpers.js\";\nimport { readPackVersionFromFile } from \"./pack-manifest-store.js\";\nimport { TEAM_PACK_MOUNT_DIRS } from \"./symlink-farm-for-team-pack-mount-dirs.js\";\n\nexport interface SyncPreview {\n currentVersion: string;\n targetVersion: string;\n mountDirStatuses: Array<{ dir: string; status: string }>;\n}\n\n// Look up which mount dirs already have symlinks vs missing vs conflict.\nasync function inspectMountDir(packDir: string, claudeDir: string, dir: string): Promise<string> {\n const source = join(packDir, dir);\n const dest = join(claudeDir, dir);\n if (!(await pathExists(source))) return \"source-missing\";\n if (!(await pathExists(dest))) return \"needs-creation\";\n const { promises: fs } = await import(\"node:fs\");\n const st = await fs.lstat(dest);\n if (st.isSymbolicLink()) return \"already-linked\";\n return \"conflict-real-dir\";\n}\n\nexport async function buildSyncPreview(\n packDir: string,\n claudeDir: string,\n targetVersion?: string,\n): Promise<SyncPreview> {\n // v3: version hiện tại đọc từ manifest. target = version pin (nếu có) hoặc\n // \"stable mới nhất\" (server resolve — client không biết trước số cụ thể).\n const currentVersion = (await readPackVersionFromFile(packDir)) ?? \"(chưa cài)\";\n const target = targetVersion ?? \"stable mới nhất (server resolve)\";\n\n const mountStatuses = [];\n for (const dir of TEAM_PACK_MOUNT_DIRS) {\n mountStatuses.push({\n dir,\n status: await inspectMountDir(packDir, claudeDir, dir),\n });\n }\n\n return {\n currentVersion,\n targetVersion: target,\n mountDirStatuses: mountStatuses,\n };\n}\n","// reapply-enabled-tools-after-sync.ts\n//\n// Sau khi sync merge lại settings.json.tpl (có thể đổi/ghi đè settings), re-apply\n// mọi tool đang enabled=true để hook tool KHÔNG bị mất. Không có bước này,\n// tool \"biến mất\" sau mỗi `avatar sync`.\n//\n// Idempotent: enable lại không nhân đôi entry (dedupe trong enableTool lo).\n// Fail-mềm: tool trong state nhưng pack đã bỏ manifest → enableToolByName\n// trả false (warn), không crash sync.\n\nimport { log } from \"./terminal-logger.js\";\nimport { enableToolByName } from \"./tool-enable-disable-orchestrator.js\";\nimport { listEnabledTools } from \"./tool-state-store.js\";\n\nexport async function reapplyEnabledToolsAfterSync(workspacePath: string): Promise<void> {\n const enabled = await listEnabledTools(workspacePath);\n if (enabled.length === 0) {\n return;\n }\n log.info(`Re-apply ${enabled.length} tool(s) đang bật vào settings.json...`);\n for (const name of enabled) {\n await enableToolByName(workspacePath, name);\n }\n}\n","// `avatar tool <enable|disable|list> [name]` — quản lý tool toggle.\n//\n// Tool toggle = gói tự-đủ trong pack (tools/<name>/) bật/tắt nguyên khối\n// qua merge/rút manifest vào project settings.json. Khác hook hạ-tầng luôn-bật\n// (scout-block, dev-rules-reminder nằm trong settings.json.tpl).\n//\n// Subcommands:\n// enable <name> merge manifest hooks + deny vào settings.json + state enabled\n// disable <name> rút đúng entry tool ra + state disabled\n// list bảng: tool | available | enabled | version\n// add multi-select tool available để cài (UX, bọc enable)\n// remove multi-select tool đang bật để gỡ (UX, bọc disable)\n//\n// Flags: --target <path>; add/remove thêm --all (chọn hết, auto khi non-TTY) + --yes.\n// Idempotent: enable lại không nhân đôi; disable tool chưa bật → no-op.\n// add/remove tái dùng enableToolByName/disableToolByName (DRY).\n\nimport { resolve } from \"node:path\";\nimport { checkbox, confirm } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport {\n discoverEnabledAndAvailableTools,\n listAvailableTools,\n} from \"../lib/discover-pack-tools-and-defaults.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport {\n buildAddChoices,\n buildRemoveChoices,\n resolveNonInteractiveSelection,\n} from \"../lib/tool-add-remove-selection.js\";\nimport { disableToolByName, enableToolByName } from \"../lib/tool-enable-disable-orchestrator.js\";\nimport { readToolState } from \"../lib/tool-state-store.js\";\n\ninterface ToolOptions {\n target?: string;\n}\n\ninterface AddRemoveOptions extends ToolOptions {\n all?: boolean;\n yes?: boolean;\n}\n\nfunction resolveWorkspace(opts: ToolOptions): string {\n return resolve(opts.target ?? process.cwd());\n}\n\n// Quyết định selection: --all hoặc non-TTY → chọn hết candidate; ngược lại prompt\n// checkbox. Trả về danh sách tên tool đã chọn (có thể rỗng).\nasync function selectTools(\n candidates: string[],\n preChecked: string[],\n opts: AddRemoveOptions,\n verb: \"add\" | \"remove\",\n): Promise<string[]> {\n const nonInteractive = opts.all === true || !process.stdout.isTTY;\n if (nonInteractive) {\n return resolveNonInteractiveSelection(candidates);\n }\n const choices =\n verb === \"add\" ? buildAddChoices(candidates, preChecked) : buildRemoveChoices(candidates);\n return await checkbox<string>({\n message:\n verb === \"add\"\n ? \"Chọn tool để cài (space chọn, enter xác nhận):\"\n : \"Chọn tool để gỡ (space chọn, enter xác nhận):\",\n choices,\n });\n}\n\n// `avatar tool add` — list available, multi-select để enable 1-n.\nasync function runAdd(opts: AddRemoveOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const { available, enabled } = await discoverEnabledAndAvailableTools(ws);\n if (available.length === 0) {\n log.dim(\"Không có tool available (pack chưa có tools/, hoặc chưa sync).\");\n return;\n }\n const selected = await selectTools(available, enabled, opts, \"add\");\n if (selected.length === 0) {\n log.dim(\"Không chọn tool nào — hủy, không thay đổi.\");\n return;\n }\n for (const name of selected) {\n await enableToolByName(ws, name);\n }\n}\n\n// `avatar tool remove` — list enabled, multi-select để disable 1-n.\nasync function runRemove(opts: AddRemoveOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const { enabled } = await discoverEnabledAndAvailableTools(ws);\n if (enabled.length === 0) {\n log.dim(\"Không có tool nào đang bật — không có gì để gỡ.\");\n return;\n }\n const selected = await selectTools(enabled, [], opts, \"remove\");\n if (selected.length === 0) {\n log.dim(\"Không chọn tool nào — hủy, không thay đổi.\");\n return;\n }\n // remove sửa settings.json → xác nhận trước (trừ khi --yes hoặc non-TTY).\n const needConfirm = opts.yes !== true && opts.all !== true && process.stdout.isTTY;\n if (needConfirm) {\n const ok = await confirm({\n message: `Gỡ ${selected.length} tool: ${selected.join(\", \")}?`,\n default: true,\n });\n if (!ok) {\n log.dim(\"Hủy — không gỡ tool nào.\");\n return;\n }\n }\n for (const name of selected) {\n await disableToolByName(ws, name);\n }\n}\n\nasync function runList(opts: ToolOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const available = await listAvailableTools(ws);\n const state = await readToolState(ws);\n\n if (available.length === 0 && Object.keys(state.tools).length === 0) {\n log.dim(\"Không có tool nào (pack chưa có tools/, hoặc chưa sync). Chạy 'avatar sync'.\");\n return;\n }\n\n // Union tên: tool có trong pack + tool từng ghi trong state.\n const names = [...new Set([...available, ...Object.keys(state.tools)])].sort();\n log.info(\"Tools:\");\n console.log(` ${\"NAME\".padEnd(20)} ${\"AVAILABLE\".padEnd(10)} ${\"ENABLED\".padEnd(8)} VERSION`);\n for (const name of names) {\n const isAvailable = available.includes(name) ? \"yes\" : \"no\";\n const entry = state.tools[name];\n const enabled = entry?.enabled ? \"yes\" : \"no\";\n const version = entry?.version ?? \"-\";\n console.log(` ${name.padEnd(20)} ${isAvailable.padEnd(10)} ${enabled.padEnd(8)} ${version}`);\n }\n}\n\nexport function registerToolsCommand(program: Command): void {\n const tools = program\n .command(\"tools\")\n .description(\"Quản lý tool toggle (prompt-scoring, ...) bật/tắt nguyên khối\");\n\n tools\n .command(\"enable <name>\")\n .description(\"Bật tool: merge manifest vào settings.json + ghi state\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (name: string, opts: ToolOptions) => {\n const ws = resolveWorkspace(opts);\n const ok = await enableToolByName(ws, name);\n if (!ok) process.exit(1);\n });\n\n tools\n .command(\"disable <name>\")\n .description(\"Tắt tool: rút đúng entry manifest ra khỏi settings.json + ghi state\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (name: string, opts: ToolOptions) => {\n const ws = resolveWorkspace(opts);\n await disableToolByName(ws, name);\n });\n\n tools\n .command(\"list\")\n .description(\"Liệt kê tool: available | enabled | version\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (opts: ToolOptions) => {\n await runList(opts);\n });\n\n tools\n .command(\"add\")\n .description(\"Chọn (multi-select) tool available để cài\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Cài hết tool available (bỏ qua prompt; auto khi non-TTY)\")\n .option(\"--yes\", \"Bỏ xác nhận\")\n .action(async (opts: AddRemoveOptions) => {\n await runAdd(opts);\n });\n\n tools\n .command(\"remove\")\n .description(\"Chọn (multi-select) tool đang bật để gỡ\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Gỡ hết tool đang bật (bỏ qua prompt; auto khi non-TTY)\")\n .option(\"--yes\", \"Bỏ xác nhận\")\n .action(async (opts: AddRemoveOptions) => {\n await runRemove(opts);\n });\n}\n","// tool-add-remove-selection.ts\n//\n// Logic THUẦN (không UI) cho `avatar tool add` / `remove`:\n// - buildAddChoices: từ available (pack) + enabled (state) → list choice cho\n// checkbox. Cái đã cài hiện \"(installed)\" + pre-checked.\n// - buildRemoveChoices: từ enabled → list choice để gỡ.\n// - resolveNonInteractiveSelection: khi --all hoặc không TTY → trả selection\n// tự động (không hỏi).\n//\n// Tách khỏi command để test được mà không cần stub prompt UI.\n\nexport interface ToolChoice {\n name: string; // label hiển thị\n value: string; // tên tool\n checked: boolean; // pre-check trong checkbox\n}\n\n// ADD: list TẤT CẢ available; cái đang enabled → \"(installed)\" + pre-check.\nexport function buildAddChoices(available: string[], enabled: string[]): ToolChoice[] {\n const enabledSet = new Set(enabled);\n return available.map((name) => ({\n name: enabledSet.has(name) ? `${name} (installed)` : name,\n value: name,\n checked: enabledSet.has(name),\n }));\n}\n\n// REMOVE: chỉ list tool đang enabled (không pre-check — user chủ động chọn gỡ).\nexport function buildRemoveChoices(enabled: string[]): ToolChoice[] {\n return enabled.map((name) => ({ name, value: name, checked: false }));\n}\n\n// Khi --all hoặc non-TTY: chọn hết các giá trị candidate (không prompt).\n// add → tất cả available; remove → tất cả enabled.\nexport function resolveNonInteractiveSelection(candidates: string[]): string[] {\n return [...candidates];\n}\n","// `avatar uninstall` — Command 13 (v1.1).\n// Gỡ Avatar khỏi project + auto-backup vào ~/.avatar/uninstall-backups/.\n// Đối ứng với `avatar init`. Code khách (src/) giữ nguyên.\n\nimport { relative } from \"node:path\";\nimport { confirm } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { createUninstallBackupSnapshot } from \"../lib/create-uninstall-backup-snapshot.js\";\nimport { detectAvatarProjectArtifacts } from \"../lib/detect-avatar-project-artifacts.js\";\nimport { executeUninstallDeletion } from \"../lib/execute-uninstall-deletion.js\";\nimport { readCliVersion } from \"../lib/read-cli-version-from-package-json.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\n\ninterface UninstallOptions {\n yes?: boolean;\n noBackup?: boolean;\n keepSubmodule?: boolean;\n keepHooks?: boolean;\n dryRun?: boolean;\n}\n\nexport function registerUninstallCommand(program: Command): void {\n program\n .command(\"uninstall\")\n .description(\"Gỡ Avatar khỏi project — backup tự động (M11)\")\n .option(\"--yes\", \"Skip confirm prompt\")\n .option(\"--no-backup\", \"Không tạo backup trước khi xóa (nguy hiểm)\")\n .option(\"--keep-submodule\", \"Giữ submodule .claude/pack/\")\n .option(\"--keep-hooks\", \"Giữ git hooks post-merge, pre-push\")\n .option(\"--dry-run\", \"Hiển thị danh sách sẽ xóa, không thực thi\")\n .action(async (opts: UninstallOptions) => {\n try {\n await runUninstall(opts);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nasync function runUninstall(opts: UninstallOptions): Promise<void> {\n const projectRoot = process.cwd();\n const artifacts = detectAvatarProjectArtifacts(projectRoot);\n\n if (!artifacts.hasAnyArtifact) {\n log.info(\"Project chưa cài Avatar — không có gì để gỡ.\");\n return;\n }\n\n // Show summary.\n printUninstallSummary(projectRoot, artifacts, opts);\n\n if (opts.dryRun) {\n log.dim(\"--dry-run: kết thúc, không xóa.\");\n return;\n }\n\n // Confirm.\n if (!opts.yes) {\n const ok = await confirm({\n message: \"Tiếp tục gỡ Avatar?\",\n default: false,\n });\n if (!ok) {\n log.info(\"Đã hủy.\");\n return;\n }\n }\n\n // Backup (trừ khi --no-backup).\n let backupPath: string | null = null;\n if (!opts.noBackup) {\n backupPath = await createUninstallBackupSnapshot(projectRoot, artifacts, readCliVersion());\n log.success(`Backup tạo tại: ${backupPath}`);\n }\n\n // Delete artifacts.\n await executeUninstallDeletion(artifacts, {\n keepSubmodule: opts.keepSubmodule,\n keepHooks: opts.keepHooks,\n });\n\n await appendAuditEntry(\"uninstall\", `project=${projectRoot},backup=${backupPath ?? \"skipped\"}`);\n\n printUninstallSuccessBox(backupPath);\n}\n\nfunction printUninstallSummary(\n projectRoot: string,\n artifacts: ReturnType<typeof detectAvatarProjectArtifacts>,\n opts: UninstallOptions,\n): void {\n log.info(`Project: ${projectRoot}`);\n log.plain(\"\");\n log.plain(\"Các artifact sẽ gỡ:\");\n if (artifacts.claudeDir)\n log.plain(` ${chalk.red(\"✗\")} ${relative(projectRoot, artifacts.claudeDir) || \".claude/\"}`);\n if (artifacts.claudeMd) log.plain(` ${chalk.red(\"✗\")} CLAUDE.md`);\n if (artifacts.postMergeHook && !opts.keepHooks) {\n log.plain(` ${chalk.red(\"✗\")} .git/hooks/post-merge`);\n }\n if (artifacts.prePushHook && !opts.keepHooks) {\n log.plain(` ${chalk.red(\"✗\")} .git/modules/src/hooks/pre-push`);\n }\n if (artifacts.gitignorePath) log.plain(` ${chalk.yellow(\"✎\")} .gitignore (gỡ Avatar block)`);\n if (artifacts.gitmodulesPath && !opts.keepSubmodule) {\n log.plain(` ${chalk.yellow(\"✎\")} .gitmodules (gỡ entry .claude/pack)`);\n }\n log.plain(\"\");\n log.plain(\"Không đụng:\");\n log.plain(` ${chalk.green(\"✓\")} src/ (code khách)`);\n log.plain(` ${chalk.green(\"✓\")} Git history`);\n log.plain(` ${chalk.green(\"✓\")} ~/.avatar/config.json (token SSO)`);\n log.plain(` ${chalk.green(\"✓\")} Secrets trong keychain`);\n log.plain(\"\");\n}\n\nfunction printUninstallSuccessBox(backupPath: string | null): void {\n const lines: string[] = [`${chalk.green(\"✓\")} Avatar đã được gỡ khỏi project`];\n if (backupPath) {\n lines.push(\"\");\n lines.push(` ${chalk.dim(\"Backup:\")} ${backupPath}`);\n lines.push(` ${chalk.dim(\"Restore:\")} ${chalk.cyan(`cp -r \"${backupPath}\"/* .`)}`);\n }\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n","// Tạo snapshot backup trước khi uninstall. Folder: ~/.avatar/uninstall-backups/\n// <project-name>-<ts>/ với cấu trúc trong spec doc.\nimport { cp, mkdir, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, join } from \"node:path\";\nimport type { AvatarProjectArtifacts } from \"./detect-avatar-project-artifacts.js\";\n\nexport interface BackupManifest {\n projectName: string;\n projectPath: string;\n timestamp: string;\n avatarVersion: string;\n artifacts: {\n claudeDir: boolean;\n claudeMd: boolean;\n postMergeHook: boolean;\n prePushHook: boolean;\n };\n}\n\nconst UNINSTALL_BACKUPS_DIR = join(homedir(), \".avatar\", \"uninstall-backups\");\n\nexport async function createUninstallBackupSnapshot(\n projectRoot: string,\n artifacts: AvatarProjectArtifacts,\n avatarVersion: string,\n): Promise<string> {\n const projectName = basename(projectRoot);\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupDir = join(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);\n\n await mkdir(backupDir, { recursive: true, mode: 0o700 });\n\n // Copy .claude/ và CLAUDE.md nếu tồn tại.\n if (artifacts.claudeDir) {\n await cp(artifacts.claudeDir, join(backupDir, \".claude\"), { recursive: true });\n }\n if (artifacts.claudeMd) {\n await cp(artifacts.claudeMd, join(backupDir, \"CLAUDE.md\"));\n }\n\n // Copy hooks sang backup/hooks/.\n if (artifacts.postMergeHook || artifacts.prePushHook) {\n const hooksBackupDir = join(backupDir, \"hooks\");\n await mkdir(hooksBackupDir, { recursive: true });\n if (artifacts.postMergeHook) {\n await cp(artifacts.postMergeHook, join(hooksBackupDir, \"post-merge\"));\n }\n if (artifacts.prePushHook) {\n await cp(artifacts.prePushHook, join(hooksBackupDir, \"pre-push\"));\n }\n }\n\n // Write manifest.\n const manifest: BackupManifest = {\n projectName,\n projectPath: projectRoot,\n timestamp,\n avatarVersion,\n artifacts: {\n claudeDir: !!artifacts.claudeDir,\n claudeMd: !!artifacts.claudeMd,\n postMergeHook: !!artifacts.postMergeHook,\n prePushHook: !!artifacts.prePushHook,\n },\n };\n await writeFile(join(backupDir, \"manifest.json\"), JSON.stringify(manifest, null, 2), \"utf8\");\n\n return backupDir;\n}\n","// Scan project root để liệt kê các file/folder Avatar đã tạo. Output là blueprint\n// cho uninstall: cái gì sẽ xóa + cái gì sẽ edit (gitignore, gitmodules).\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface AvatarProjectArtifacts {\n hasAnyArtifact: boolean;\n claudeDir: string | null; // .claude/\n claudeMd: string | null; // CLAUDE.md\n postMergeHook: string | null; // .git/hooks/post-merge\n prePushHook: string | null; // .git/modules/src/hooks/pre-push\n gitignorePath: string | null; // .gitignore (nếu có Avatar block)\n gitmodulesPath: string | null; // .gitmodules (nếu có submodule .claude/pack)\n notesDir: string | null; // notes/ (workspace mode)\n scriptsDir: string | null; // scripts/ (workspace mode)\n}\n\nfunction existsOrNull(path: string): string | null {\n return existsSync(path) ? path : null;\n}\n\nexport function detectAvatarProjectArtifacts(projectRoot: string): AvatarProjectArtifacts {\n const claudeDir = existsOrNull(join(projectRoot, \".claude\"));\n const claudeMd = existsOrNull(join(projectRoot, \"CLAUDE.md\"));\n const postMergeHook = existsOrNull(join(projectRoot, \".git\", \"hooks\", \"post-merge\"));\n const prePushHook = existsOrNull(\n join(projectRoot, \".git\", \"modules\", \"src\", \"hooks\", \"pre-push\"),\n );\n const gitignorePath = existsOrNull(join(projectRoot, \".gitignore\"));\n const gitmodulesPath = existsOrNull(join(projectRoot, \".gitmodules\"));\n const notesDir = existsOrNull(join(projectRoot, \"notes\"));\n const scriptsDir = existsOrNull(join(projectRoot, \"scripts\"));\n\n const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);\n\n return {\n hasAnyArtifact,\n claudeDir,\n claudeMd,\n postMergeHook,\n prePushHook,\n gitignorePath,\n gitmodulesPath,\n notesDir,\n scriptsDir,\n };\n}\n","// Atomic delete các artifact Avatar khỏi project. Gỡ marker block trong\n// .gitignore, remove submodule entry trong .gitmodules. Không đụng src/ + git\n// history + user config.\nimport { readFile, rm, writeFile } from \"node:fs/promises\";\nimport type { AvatarProjectArtifacts } from \"./detect-avatar-project-artifacts.js\";\nimport { AVATAR_MARKER_END, AVATAR_MARKER_START } from \"./gitignore-template-loader.js\";\n\nexport interface UninstallFlags {\n keepSubmodule?: boolean;\n keepHooks?: boolean;\n}\n\nexport async function executeUninstallDeletion(\n artifacts: AvatarProjectArtifacts,\n flags: UninstallFlags,\n): Promise<void> {\n // Delete .claude/ (trừ khi --keep-submodule muốn giữ pack/ — thực tế cả\n // .claude/ chứa nhiều thứ khác, nên --keep-submodule chỉ giữ pack/).\n if (artifacts.claudeDir) {\n if (flags.keepSubmodule) {\n // Chỉ xóa các file/folder không phải pack/ trong .claude/.\n const { readdir } = await import(\"node:fs/promises\");\n const { join } = await import(\"node:path\");\n const entries = await readdir(artifacts.claudeDir);\n for (const entry of entries) {\n if (entry === \"pack\") continue;\n await rm(join(artifacts.claudeDir, entry), { recursive: true, force: true });\n }\n } else {\n await rm(artifacts.claudeDir, { recursive: true, force: true });\n }\n }\n\n if (artifacts.claudeMd) {\n await rm(artifacts.claudeMd, { force: true });\n }\n\n if (!flags.keepHooks) {\n if (artifacts.postMergeHook) await rm(artifacts.postMergeHook, { force: true });\n if (artifacts.prePushHook) await rm(artifacts.prePushHook, { force: true });\n }\n\n // Strip Avatar block khỏi .gitignore (giữ rest).\n if (artifacts.gitignorePath) {\n await stripAvatarBlockFromGitignore(artifacts.gitignorePath);\n }\n\n // Remove submodule entry .claude/pack khỏi .gitmodules (nếu xóa cả pack).\n if (artifacts.gitmodulesPath && !flags.keepSubmodule) {\n await removeSubmoduleEntry(artifacts.gitmodulesPath, \".claude/pack\");\n }\n\n // Workspace mode có notes/, scripts/. Chỉ xóa nếu rỗng (user có thể đã add file).\n for (const dir of [artifacts.notesDir, artifacts.scriptsDir]) {\n if (!dir) continue;\n const { readdir } = await import(\"node:fs/promises\");\n const entries = await readdir(dir);\n if (entries.length === 0) {\n await rm(dir, { recursive: true, force: true });\n }\n }\n}\n\nasync function stripAvatarBlockFromGitignore(path: string): Promise<void> {\n const content = await readFile(path, \"utf8\");\n const startIdx = content.indexOf(AVATAR_MARKER_START);\n const endIdx = content.indexOf(AVATAR_MARKER_END);\n if (startIdx === -1 || endIdx === -1) return;\n\n const before = content.slice(0, startIdx);\n const after = content.slice(endIdx + AVATAR_MARKER_END.length);\n const cleaned = `${before.trimEnd()}\\n${after.trimStart()}`.trim();\n if (cleaned.length === 0) {\n await rm(path, { force: true });\n } else {\n await writeFile(path, `${cleaned}\\n`, \"utf8\");\n }\n}\n\nasync function removeSubmoduleEntry(gitmodulesPath: string, submodulePath: string): Promise<void> {\n const content = await readFile(gitmodulesPath, \"utf8\");\n const lines = content.split(\"\\n\");\n const result: string[] = [];\n let skip = false;\n for (const line of lines) {\n if (line.trim().startsWith(\"[submodule\") && line.includes(submodulePath)) {\n skip = true;\n continue;\n }\n if (skip && line.trim().startsWith(\"[submodule\")) {\n skip = false;\n }\n if (!skip) result.push(line);\n }\n const cleaned = result.join(\"\\n\").trim();\n if (cleaned.length === 0) {\n await rm(gitmodulesPath, { force: true });\n } else {\n await writeFile(gitmodulesPath, `${cleaned}\\n`, \"utf8\");\n }\n}\n"],"mappings":"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","names":["spawn","spawnSync","createHash","constants","fs","createReadStream","dirname","join","relative","Readable","pathExists","path","ensureDir","readText","readJson","writeTextAtomic","content","mode","tmp","writeJsonAtomic","data","removeRecursive","downloadFile","url","dest","timeoutMs","DOWNLOAD_TIMEOUT_MS","controller","timer","createWriteStream","res","fileStream","resolve","reject","src","err","tarAvailable","tarAvailableCache","extractTarballToDir","tarball","destDir","child","stderr","chunk","code","init_filesystem_helpers","__esmMin","join","simpleGit","git","cwd","init_git_operations","__esmMin","init_filesystem_helpers","renderTemplate","source","variables","TEMPLATE_PATTERN","match","key","value","init_mustache_template_engine","__esmMin","existsSync","dirname","join","fileURLToPath","findPackageRoot","startDir","dir","parent","loadTemplate","name","readText","TEMPLATES_ROOT","renderTemplateByName","variables","source","renderTemplate","loadHook","HOOKS_ROOT","HERE","PACKAGE_ROOT","init_template_bundle_loader","__esmMin","init_filesystem_helpers","init_mustache_template_engine","project_tree_scaffolder_exports","__export","AVATAR_MANAGED_PATHS","backupIfExists","createClaudeDirTree","installGitHook","writeClaudeGitignore","writeProjectKnowledgeFiles","writeProjectSettings","writeRootClaudeMd","fs","join","path","pathExists","ts","basePath","backupPath","counter","writeWithBackup","content","mode","backup","writeTextAtomic","projectRoot","claudeRoot","ensureDir","sub","CLAUDE_SUBDIRS","dir","_projectRoot","_vars","vars","renderTemplateByName","CLAUDE_GITIGNORE_CONTENT","gitDir","hookName","loadHook","hooksDir","dest","init_project_tree_scaffolder","__esmMin","init_filesystem_helpers","init_template_bundle_loader","join","manifestPath","workspaceRoot","REPOS_MANIFEST_RELATIVE","readReposManifest","p","pathExists","data","readJson","addRepoToManifest","entry","filtered","r","writeJsonAtomic","repoNameExists","name","init_workspace_repos_manifest_store","__esmMin","init_filesystem_helpers","clone_code_repo_into_src_exports","__export","cloneCodeRepoIntoSrc","folderDirtyStatus","inferRepoNameFromUrl","readFolderRemoteUrl","validateRepoName","join","name","url","args","nameError","repoNameExists","srcDir","ensureDir","destPath","pathExists","git","gitDir","installGitHook","addRepoToManifest","folderPath","status","f","origin","init_clone_code_repo_into_src","__esmMin","init_filesystem_helpers","init_git_operations","init_project_tree_scaffolder","init_workspace_repos_manifest_store","chalk","ora","spinner","text","spinnerWithElapsed","prefix","startMs","sp","formatElapsed","sec","m","s","interval","log","init_terminal_logger","__esmMin","spawnSync","resolveGithubUsernameDefault","r","init_resolve_github_username_default","__esmMin","create_empty_github_repo_exports","__export","createEmptyGithubRepoAndGetUrl","spawnSync","name","visibility","org","fullName","resolveGithubUsernameDefault","log","r","init_create_empty_github_repo","__esmMin","init_resolve_github_username_default","init_terminal_logger","google_oauth_device_flow_exports","__export","HOSTED_DOMAIN","SCOPES","buildUserConfig","buildVerificationUrl","decodeIdToken","pollForToken","refreshAccessToken","requestDeviceCode","revokeToken","verifyHostedDomain","verifyIdTokenClaims","body","GOOGLE_CLIENT_ID","res","DEVICE_CODE_URL","text","deviceCode","GOOGLE_CLIENT_SECRET","TOKEN_URL","errorCode","idToken","parts","payload","base64","json","claims","VALID_ISSUERS","nowSec","CLOCK_SKEW_SECONDS","ageSec","token","expiresAt","refreshToken","REVOKE_URL","response","url","init_google_oauth_device_flow","__esmMin","Command","init_clone_code_repo_into_src","resolve","confirm","input","select","init_clone_code_repo_into_src","init_filesystem_helpers","init_terminal_logger","spawnSync","join","confirm","input","select","spawnSync","TIMEOUT_MS","classifyRemoteError","stderr","text","tryVerifyGitRemoteAccessible","url","r","spawnSync","TIMEOUT_MS","err","explainRemoteFailure","url","reason","detail","log","triggerGhAuthLogin","r","spawnSync","promptCloneRecovery","choices","select","cloneRepoWithRecovery","args","name","verify","tryVerifyGitRemoteAccessible","action","input","v","confirm","inferRepoNameFromUrl","cloneCodeRepoIntoSrc","err","message","removeRecursive","join","spawnSync","platform","detectHostPlatform","p","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeGitnexusBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeGitnexusVersion","cachedInfo","detectGitnexusInstallation","path","invalidateGitnexusInstallationCache","spawnSync","checkGhCliAuthStatus","r","spawnSync","hasBinary","name","platform","detectHostPlatform","spawnSync","detectPackageManager","candidates","pm","spawnSync","input","select","spawnSync","fs","basename","join","confirm","select","spawnSync","RepoAlreadyExistsError","fullName","executeGhRepoCreate","input","args","init_resolve_github_username_default","init_terminal_logger","REPO_NAME_REGEX","InvalidRepoNameError","name","validateRepoName","validateRepoVisibility","v","createGithubRemoteFromFolder","input","validateRepoName","validateRepoVisibility","org","resolveGithubUsernameDefault","log","urls","executeGhRepoCreate","init_filesystem_helpers","init_terminal_logger","backupTimestamp","d","backupExistingDotGit","folderPath","gitDir","join","pathExists","backupName","backupPath","fs","log","reinitGitInFolder","r1","spawnSync","r3","resetFolderGitAndCreateNewRemoteUnderCurrentUser","opts","folderName","basename","repoName","confirm","visibility","select","createGithubRemoteFromFolder","init_terminal_logger","RemoteAccessAbortedError","message","getCurrentGhUser","r","spawnSync","triggerGhAuthLoginInteractive","log","getReasonHint","reason","url","ghUser","isValidGitUrl","trimmed","handleRemoteAccessFailureWithAccountSwitch","args","currentUrl","detail","choices","action","select","reset","resetFolderGitAndCreateNewRemoteUnderCurrentUser","err","input","v","result","tryVerifyGitRemoteAccessible","init_terminal_logger","spawnSync","INSTALL_COMMANDS","installGhCliViaPackageManager","pm","spec","log","r","init_terminal_logger","input","select","UserAbortedRecoveryError","message","promptRetryOrSkip","args","log","choices","init_terminal_logger","spawnSync","setupGitCredentialViaGh","log","init_terminal_logger","init_terminal_logger","spawnSync","triggerGhCliAuthLogin","log","r","ensureGitHubReady","remoteUrl","checkGhCliAuthStatus","log","pm","detectPackageManager","promptRetryOrSkip","UserAbortedRecoveryError","installGhCliViaPackageManager","err","triggerGhCliAuthLogin","setupGitCredentialViaGh","result","tryVerifyGitRemoteAccessible","handleRemoteAccessFailureWithAccountSwitch","existsSync","dirname","join","MAX_WALKUP_LEVELS","isAvatarWorkspace","dir","hasClaudeDir","hasClaudeMd","hasSrcDir","resolveAvatarWorkspaceRootFromCwd","startDir","current","i","parent","init_terminal_logger","spawnSync","existsSync","join","SETUP_TIMEOUT_MS","ANALYZE_TIMEOUT_MS","GitnexusOperationError","operation","reason","message","exitCode","stderr","classifyOperationFailure","signal","stderrSample","tailLines","text","n","runGitnexusSetup","sp","spinnerWithElapsed","result","stdout","runGitnexusAnalyze","workspacePath","metaPath","spawnSync","existsSync","join","confirm","REASONING_PATTERNS","isReasoningModel","modelName","pattern","init_filesystem_helpers","init_terminal_logger","fs","homedir","dirname","join","GITNEXUS_CONFIG_DIR","GITNEXUS_CONFIG_PATH","readExistingConfig","raw","parsed","writeConfigAtomic","content","tmpPath","writeGitnexusConfigForWikiRun","payload","merged","WIKI_TIMEOUT_MS","FALLBACK_LLMLITE_MODEL","FALLBACK_ANTHROPIC_MODEL","normalizeAnthropicBaseUrl","rawBaseUrl","cleaned","readSettingsForWikiCredentials","workspacePath","settingsPath","join","pathExists","settings","readJson","env","baseUrl","topLevelModel","envModel","userModel","resolveKey","envVarName","fromProcess","fromSettings","providerHint","baseUrlSuggestsAnthropic","anthropicKey","llmliteToken","confirmWikiGeneration","model","confirm","tailLines","text","n","runGitnexusWikiConditional","runDir","creds","log","reasoningMode","isReasoningModel","writeGitnexusConfigForWikiRun","args","sp","spinnerWithElapsed","result","spawnSync","reason","stderr","stdout","wikiPath","existsSync","init_terminal_logger","registerAddCommand","program","opts","runAddRepo","err","log","ensureWorkspace","root","resolveAvatarWorkspaceRootFromCwd","workspaceRoot","addMore","firstUrl","firstName","url","resolveRepoUrl","name","promptRepoName","cloned","skipped","cloneRepoWithRecovery","maybeIndexRepo","confirm","presetUrl","source","select","input","v","folderPath","resolve","dirty","folderDirtyStatus","remote","readFolderRemoteUrl","ensureGitHubReady","projectName","visibility","createEmptyGithubRepoAndGetUrl","inferred","inferRepoNameFromUrl","repoPath","autoYes","detectGitnexusInstallation","runGitnexusAnalyze","wiki","runGitnexusWikiConditional","init_filesystem_helpers","fs","join","confirm","fs","homedir","join","z","userConfigSchema","userStateSchema","projectSettingsSchema","initModeSchema","init_filesystem_helpers","AVATAR_HOME","join","homedir","USER_CONFIG_PATH","USER_STATE_PATH","AUDIT_LOG_PATH","BACKUPS_DIR","SECRET_FILE_MODE","ensureAvatarHome","ensureDir","readUserConfig","pathExists","raw","readJson","parsed","userConfigSchema","writeUserConfig","config","writeJsonAtomic","clearUserConfig","fs","isTokenExpired","config","expiresAt","NoValidTokenError","message","idTokenIsExpired","idToken","decodeIdToken","claims","nowSec","getValidIdToken","readUserConfig","refreshAccessToken","verifyIdTokenClaims","refreshed","err","updated","writeUserConfig","ensureAuditLogPermissions","fs","AUDIT_LOG_PATH","appendAuditEntry","action","detail","ensureAvatarHome","entry","line","init_terminal_logger","spawnSync","QUOTA_VERIFY_TIMEOUT_MS","QUOTA_VERIFY_PROMPT","readClaudeCodeAuthInfo","result","spawnSync","stdout","parsed","triggerClaudeCodeAuthLogin","log","classifyQuotaError","combinedOutput","text","getQuotaErrorHint","reason","verifyClaudeCodeQuota","QUOTA_VERIFY_PROMPT","QUOTA_VERIFY_TIMEOUT_MS","stderr","stdoutTrimmed","stderrLower","spawnSync","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeClaudeBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeClaudeVersion","cachedInfo","detectClaudeCodeInstallation","path","invalidateClaudeCodeInstallationCache","spawnSync","init_terminal_logger","NPM_INSTALL_TIMEOUT_MS","CLAUDE_CODE_PACKAGE","InstallClaudeCodeError","reason","message","exitCode","classifyNpmFailure","stderrSample","stderr","installClaudeCodeViaNpm","log","result","spawnSync","invalidateClaudeCodeInstallationCache","probe","detectClaudeCodeInstallation","readFileSync","homedir","join","select","getGlobalSettingsPath","detectGlobalClaudeSettings","path","raw","parsed","env","baseUrl","hasToken","model","promptAiProviderChoice","globalInfo","init_terminal_logger","password","select","ANTHROPIC_BASE_URL","ANTHROPIC_API_VERSION","FETCH_TIMEOUT_MS","maskAnthropicKey","key","validateAnthropicKeyFormat","trimmed","promptAnthropicKeyHidden","fetchAnthropicModels","apiKey","controller","timer","res","models","m","id","err","promptAnthropicModelChoice","only","log","sorted","a","b","score","lower","setupAnthropicApiKeyAndModel","model","init_terminal_logger","input","password","select","DEFAULT_BASE_URL","FETCH_TIMEOUT_MS","maskApiKey","key","promptApiKeyHidden","v","promptBaseUrl","defaultUrl","fetchAvailableModels","baseUrl","apiKey","controller","timer","res","models","m","id","err","vpnHint","errMsg","promptModelChoice","claudeAliases","only","log","choiceList","setupLLMLiteApiKeyAndModel","model","init_terminal_logger","init_filesystem_helpers","fs","join","SECRET_FILE_MODE","getClaudeSettingsPath","workspacePath","readExistingSettings","path","pathExists","readJson","err","applySubscription","existing","model","existingEnv","rest","merged","_b","_t","_k","envRest","stripAllProviderKeys","env","applyLLMLite","apiKey","baseUrl","skipApiKey","applyAnthropic","applyUseGlobal","source","sourceEnv","sourceModel","writeClaudeSettings","input","writeJsonAtomic","SUBSCRIPTION_DEFAULT_MODEL","runAiSetupPhase","args","log","info","detectClaudeCodeInstallation","installClaudeCodeViaNpm","invalidateClaudeCodeInstallationCache","globalInfo","detectGlobalClaudeSettings","promptAiProviderChoice","authInfo","readClaudeCodeAuthInfo","triggerClaudeCodeAuthLogin","writeClaudeSettings","appendAuditEntry","quota","verifyClaudeCodeQuota","reason","getQuotaErrorHint","llmConfig","setupLLMLiteApiKeyAndModel","anthropicConfig","setupAnthropicApiKeyAndModel","err","message","init_terminal_logger","spawnSync","init_terminal_logger","FETCH_TIMEOUT_MS","CLAUDE_PRINT_TIMEOUT_MS","TEST_CHAT_MAX_TOKENS","TEST_CHAT_PROMPT","ANTHROPIC_API_VERSION","testLLMLiteProvider","baseUrl","token","model","log","maskApiKey","controller","timer","modelsRes","models","m","id","preview","more","chatRes","errBody","chatJson","reply","tokens","err","testSubscriptionProvider","result","spawnSync","stderr","testAnthropicProvider","apiKey","msgRes","errText","text","c","testAiProviderByDetectedMode","settings","env","ensureWorkspaceCwd","cwd","workspaceRoot","resolveAvatarWorkspaceRootFromCwd","log","readWorkspaceSettings","workspacePath","settingsPath","join","pathExists","readJson","runAiSetup","runAiSetupPhase","runAiStatus","settings","env","baseUrl","token","apiKey","model","provider","credentialDisplay","maskApiKey","runAiTest","result","testAiProviderByDetectedMode","err","runAiReset","opts","confirm","existingEnv","rest","reset","_b","_t","_k","envRest","fs","writeJsonAtomic","registerAiCommand","program","ai","spawnSync","fs","join","boxen","join","init_filesystem_helpers","fs","join","init_filesystem_helpers","init_terminal_logger","fs","path","join","isStatusLineCommandResolvable","workspacePath","command","match","filePath","log","fullPath","wsResolved","targetResolved","pathExists","backupFilename","originalPath","d","stamp","unionDedupe","a","b","seen","out","item","key","normalizeCommandForMigration","extractCommandsFromEntry","entry","obj","hooks","cmds","h","cmd","migrateStaleRelativePathHooks","entries","indexed","index","commands","hasVar","c","byNormalized","ie","list","toDrop","items","it","_","i","mergeHooksPerEvent","packHooks","userHooks","touched","merged","totalMigrated","event","userEntries","migrated","droppedCount","packEntries","union","postMigrate","postDropped","mergePackSettingsIntoProjectSettings","packTemplatePath","projectSettingsPath","packTemplate","raw","readText","err","userSettings","projectHasSettings","readJson","changes","mergedEnv","envChanged","k","v","userAllow","userDeny","packAllow","packDeny","mergedAllow","mergedDeny","mergedHooks","touchedEvents","migratedCount","backupPath","writeJsonAtomic","featureManifestPath","workspacePath","name","join","readToolManifest","path","pathExists","readJson","PROJECT_SETTINGS_REL","projectSettingsPath","hookEntryKey","entry","commands","h","loadProjectSettings","err","backupAndWrite","settings","existed","backupPath","backupFilename","fs","writeJsonAtomic","normalizeHookEntryForMigration","entryUsesProjectDirVar","migrateStaleEntries","userEntries","featureEntries","featureNormalized","fe","dropped","ue","enableTool","manifest","merged","changes","mHooks","userHooks","outHooks","touched","totalMigrated","event","featEntries","existing","migratedUser","droppedCount","seen","toAdd","e","featDeny","userDeny","unionDeny","unionDedupe","disableTool","_h","_p","rest","removed","entries","featKeys","kept","keptDeny","d","_d","permRest","newPerms","init_filesystem_helpers","init_filesystem_helpers","join","TOOL_STATE_RELATIVE_PATH","emptyState","stateFilePath","workspacePath","readToolState","path","pathExists","readJson","writeToolState","state","writeJsonAtomic","setToolState","name","entry","listEnabledTools","manifestHooksPresent","settings","manifest","mHooks","sHooks","event","featEntries","present","presentCommands","e","h","entry","checkEnabledToolsHealth","cwd","enabled","listEnabledTools","settingsPath","join","pathExists","readJson","checks","name","readToolManifest","enableTool","init_filesystem_helpers","init_terminal_logger","registerDoctorCommand","program","opts","checks","runChecks","renderChecks","applyFixes","err","log","cwd","nodeVer","major","minor","n","nodeOk","config","readUserConfig","isTokenExpired","packPath","join","claudeMdPath","hasPack","hasClaudeMd","pathExists","claudeGitignorePath","settingsProtected","fs","writeClaudeGitignore","pythonCheck","spawnSync","python3Check","hasPython","hasPython3","settingsPath","settingsRaw","settings","match","refFile","fullPath","fileExists","which","hasClaudeCli","toolChecks","checkEnabledToolsHealth","lines","chalk","passed","issues","fixable","c","icon","boxen","count","init_filesystem_helpers","spawnSync","fs","join","confirm","boxen","spawnSync","init_terminal_logger","NPM_INSTALL_TIMEOUT_MS","GITNEXUS_PACKAGE","InstallGitnexusError","reason","message","exitCode","classifyNpmFailure","stderrSample","stderr","installGitnexusViaNpm","log","result","spawnSync","invalidateGitnexusInstallationCache","probe","detectGitnexusInstallation","init_filesystem_helpers","init_terminal_logger","fs","homedir","join","MCP_FILE_MODE","EXPECTED_GITNEXUS_ENTRY","getMcpServersPath","isEntryEqual","a","b","backupExistingFile","path","ts","backupPath","registerGitnexusMcpServer","existing","fileExisted","pathExists","readJson","err","existingEntry","log","backup","merged","writeJsonAtomic","init_terminal_logger","promptInstallGitnexus","lines","chalk","boxen","confirm","installWithRecovery","installGitnexusViaNpm","err","message","hint","InstallGitnexusError","action","promptRetryOrSkip","UserAbortedRecoveryError","analyzeWithRecovery","workspacePath","runGitnexusAnalyze","GitnexusOperationError","runGitnexusSetupPhase","args","result","log","info","detectGitnexusInstallation","appendAuditEntry","invalidateGitnexusInstallationCache","runGitnexusSetup","wikiResult","runGitnexusWikiConditional","mcpResult","registerGitnexusMcpServer","init_terminal_logger","ensureWorkspaceCwd","cwd","workspaceRoot","resolveAvatarWorkspaceRootFromCwd","log","runGitnexusInstall","workspacePath","result","runGitnexusSetupPhase","runGitnexusStatus","metaPath","join","pathExists","meta","readJson","headResult","spawnSync","wikiPath","stat","fs","err","runGitnexusAnalyzeCommand","runGitnexusAnalyze","registerGitnexusCommand","program","gx","resolve","input","chalk","BANNER_LINES","GRADIENT_STOPS","lerpChannel","a","b","t","gradientAt","scaled","lo","hi","localT","renderAvatarBanner","opts","colored","line","idx","r","g","printAvatarBanner","readdirSync","select","simpleGit","existsSync","statSync","join","simpleGit","existsSync","join","readFileSync","dirname","join","fileURLToPath","__dirname","CANDIDATE_DIRS","AVATAR_MARKER_START","AVATAR_MARKER_END","init_terminal_logger","existsSync","readFileSync","writeFileSync","join","InitAbortedByUserError","message","join","init_filesystem_helpers","join","PACK_MANIFEST_FILENAME","PACK_VERSION_FILENAME","manifestPath","packDir","writePackManifest","data","writeJsonAtomic","readPackManifest","p","pathExists","readJson","readPackVersionFromFile","manifest","versionFile","v","readText","init_filesystem_helpers","fs","join","DEFAULT_GET_PACK_ENDPOINT","getPackEndpoint","InvalidIdTokenError","message","PackNetworkError","PackParseError","fetchSignedPackUrl","idToken","version","res","err","detail","safeErrorText","body","downloadAndExtractPack","signedUrl","packDir","parent","ensureDir","tmpTarball","tmpExtractDir","downloadFile","removeRecursive","extractTarballToDir","backupDir","hadOld","pathExists","TEAM_PACK_RELATIVE_PATH","TeamPackAccessAbortedError","message","addTeamPackSubmodule","projectRoot","version","idToken","getValidIdToken","info","fetchSignedPackUrl","packDir","join","downloadAndExtractPack","installedVersion","objectToVersion","writePackManifest","object","readPinnedPackVersion","readPackVersionFromFile","init_terminal_logger","init_filesystem_helpers","join","relative","resolve","input","select","boxen","init_terminal_logger","boxen","PACK_COMMAND_CHEATSHEET","formatPackCommandsCheatsheetBox","maxCmdWidth","e","header","chalk","subheader","lines","footer","content","init_terminal_logger","init_filesystem_helpers","init_project_tree_scaffolder","readdir","join","isEmptyOrMissing","path","pathExists","e","findAlternativeWorkspaceName","parent","desiredName","maxAttempts","i","candidate","join","isEmptyOrMissing","assertWorkspaceNameSafe","parent","name","resolved","resolve","join","resolvedParent","resolveWorkspacePath","desiredName","force","desired","isEmptyOrMissing","log","alternative","findAlternativeWorkspaceName","choices","action","select","UserAbortedRecoveryError","newName","input","v","trimmed","newPath","promptTeamOwner","currentUserEmail","ensureWorkspaceFolder","workspacePath","ensureDir","err","e","formatAiStatusLine","aiResult","chalk","modelPart","formatGitnexusStatusLine","result","parts","printInitSuccessBox","rootPath","gitnexusResult","lines","relative","boxen","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","formatPackCommandsCheatsheetBox","boxen","open","init_google_oauth_device_flow","init_terminal_logger","registerLoginCommand","program","opts","runLogin","err","log","printAvatarBanner","clearUserConfig","appendAuditEntry","existing","readUserConfig","isTokenExpired","deviceSpinner","spinner","deviceCode","requestDeviceCode","verificationUrl","buildVerificationUrl","instructions","chalk","boxen","open","waitSpinner","intervalMs","deadline","token","sleep","pollForToken","claims","decodeIdToken","verifyHostedDomain","revokeToken","userConfig","buildUserConfig","writeUserConfig","USER_CONFIG_PATH","ms","resolve","join","init_terminal_logger","MAX_NETWORK_RETRIES","addTeamPackSubmoduleWithRetryOnNetworkFail","projectRoot","version","_ssoEmail","_latest","networkAttempt","addTeamPackSubmodule","err","TeamPackAccessAbortedError","InvalidIdTokenError","log","action","promptRetryOrSkip","UserAbortedRecoveryError","PackNetworkError","sleep","message","ms","resolve","init_filesystem_helpers","init_project_tree_scaffolder","confirm","init_filesystem_helpers","fs","join","PACK_FEATURES_REL","packToolsDir","workspacePath","join","listAvailableTools","dir","pathExists","entries","fs","names","entry","readDefaultTools","path","readJson","discoverEnabledAndAvailableTools","available","enabled","listEnabledTools","init_terminal_logger","spawnSync","init_terminal_logger","warnIfRuntimeMissing","runtime","probe","spawnSync","log","logApplyResult","name","result","enableToolByName","workspacePath","opts","manifest","readToolManifest","enableTool","setToolState","disableToolByName","prevVersion","readToolState","disableTool","DISPLAY_NAME","setupDefaultToolsOnInit","workspacePath","opts","defaults","readDefaultTools","name","display","wantEnable","confirm","log","enableToolByName","fs","dirname","join","relative","fs","timestamp","d","pad","backupDirBeforeReplace","targetPath","backupPath","init_filesystem_helpers","TEAM_PACK_MOUNT_DIRS","isSymbolicLink","path","fs","syncMountedDir","source","dest","force","dir","relative","dirname","pathExists","backupPath","backupDirBeforeReplace","relativeSource","syncAllMountDirs","packDir","claudeDir","results","join","init_terminal_logger","readFileSync","dirname","resolve","fileURLToPath","cachedVersion","readCliVersion","here","i","candidate","raw","pkg","buildGitnexusSection","gitnexusReady","buildScaffoldVariables","args","readCliVersion","scaffoldThinWorkspace","args","ensureWorkspaceFolder","ensureDir","join","pinnedTag","log","spinner","result","addTeamPackSubmoduleWithRetryOnNetworkFail","vars","buildScaffoldVariables","createClaudeDirTree","writeProjectKnowledgeFiles","writeRootClaudeMd","writeProjectSettings","writeClaudeGitignore","autoSyncPackOnInit","appendAuditEntry","aiResult","runAiSetupPhase","gitnexusResult","runGitnexusSetupPhase","printInitSuccessBox","workspacePath","autoYes","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","claudeDir","results","syncAllMountDirs","created","r","missing","mergeResult","mergePackSettingsIntoProjectSettings","setupDefaultToolsOnInit","err","registerInitCommand","program","workspaceName","opts","runInit","err","InitAbortedByUserError","TeamPackAccessAbortedError","RemoteAccessAbortedError","UserAbortedRecoveryError","log","printAvatarBanner","userConfig","readUserConfig","isTokenExpired","runLogin","name","input","v","teamOwner","promptTeamOwner","workspaceParent","resolve","workspacePath","resolveWorkspacePath","scaffoldThinWorkspace","init_terminal_logger","notImplementedYet","commandName","milestone","chalk","registerMcpRunCommand","program","notImplementedYet","init_filesystem_helpers","join","boxen","init_terminal_logger","PACK_RELATIVE_PATH","registerPackCommand","program","opts","snap","gatherPackStatus","renderPackStatus","err","log","cwd","packDir","join","pathExists","readPinnedPackVersion","s","lines","chalk","boxen","current","registerRestoreCommand","program","notImplementedYet","registerReviewCommand","program","notImplementedYet","registerScanCommand","program","notImplementedYet","fs","join","boxen","init_filesystem_helpers","init_filesystem_helpers","fs","join","BACKUP_DIR_NAME","listBackups","projectRoot","dir","join","BACKUP_DIR_NAME","pathExists","fs","e","init_terminal_logger","registerStatusCommand","program","opts","snapshot","gatherStatus","renderStatusBox","err","log","cwd","projectName","claudeRoot","join","pathExists","readCliVersion","pendingDir","packVersion","pendingCount","backupCount","techStackSummary","tools","readPinnedPackVersion","fs","n","listBackups","b","readTechStackFirstLine","discoverEnabledAndAvailableTools","techStackPath","readText","l","formatTools","s","lines","chalk","boxen","init_filesystem_helpers","join","init_filesystem_helpers","join","inspectMountDir","packDir","claudeDir","dir","source","join","dest","pathExists","fs","buildSyncPreview","targetVersion","currentVersion","readPackVersionFromFile","target","mountStatuses","TEAM_PACK_MOUNT_DIRS","init_terminal_logger","reapplyEnabledToolsAfterSync","workspacePath","enabled","listEnabledTools","log","name","enableToolByName","init_terminal_logger","objectToVersion","object","syncAction","opts","projectRoot","claudeDir","join","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","log","preview","buildSyncPreview","m","idToken","getValidIdToken","err","installedVersion","info","fetchSignedPackUrl","downloadAndExtractPack","writePackManifest","InvalidIdTokenError","results","syncAllMountDirs","reportResults","mergeResult","mergePackSettingsIntoProjectSettings","reapplyEnabledToolsAfterSync","force","conflicts","registerSyncCommand","program","resolve","checkbox","confirm","init_terminal_logger","buildAddChoices","available","enabled","enabledSet","name","buildRemoveChoices","resolveNonInteractiveSelection","candidates","resolveWorkspace","opts","resolve","selectTools","candidates","preChecked","verb","resolveNonInteractiveSelection","choices","buildAddChoices","buildRemoveChoices","checkbox","runAdd","ws","available","enabled","discoverEnabledAndAvailableTools","log","selected","name","enableToolByName","runRemove","confirm","disableToolByName","runList","listAvailableTools","state","readToolState","names","isAvailable","entry","version","registerToolsCommand","program","tools","relative","confirm","boxen","cp","mkdir","writeFile","homedir","basename","join","UNINSTALL_BACKUPS_DIR","createUninstallBackupSnapshot","projectRoot","artifacts","avatarVersion","projectName","timestamp","backupDir","hooksBackupDir","manifest","existsSync","join","existsOrNull","path","detectAvatarProjectArtifacts","projectRoot","claudeDir","claudeMd","postMergeHook","prePushHook","gitignorePath","gitmodulesPath","notesDir","scriptsDir","readFile","rm","writeFile","executeUninstallDeletion","artifacts","flags","readdir","join","entries","entry","rm","stripAvatarBlockFromGitignore","removeSubmoduleEntry","dir","path","content","readFile","startIdx","AVATAR_MARKER_START","endIdx","AVATAR_MARKER_END","before","after","cleaned","writeFile","gitmodulesPath","submodulePath","lines","result","skip","line","init_terminal_logger","registerUninstallCommand","program","opts","runUninstall","err","log","projectRoot","artifacts","detectAvatarProjectArtifacts","printUninstallSummary","confirm","backupPath","createUninstallBackupSnapshot","readCliVersion","executeUninstallDeletion","appendAuditEntry","printUninstallSuccessBox","chalk","relative","lines","boxen","CLI_VERSION","readCliVersion","program","Command","renderAvatarBanner","isVersionCall","printAvatarBanner","registerLoginCommand","registerInitCommand","registerSyncCommand","registerScanCommand","registerReviewCommand","registerStatusCommand","registerDoctorCommand","registerRestoreCommand","registerToolsCommand","registerMcpRunCommand","registerAiCommand","registerGitnexusCommand","registerPackCommand","registerAddCommand","registerUninstallCommand","err","msg"]}
1
+ {"version":3,"sources":["../src/lib/filesystem-helpers.ts","../src/lib/git-operations.ts","../src/lib/mustache-template-engine.ts","../src/lib/template-bundle-loader.ts","../src/lib/project-tree-scaffolder.ts","../src/lib/workspace-repos-manifest-store.ts","../src/lib/clone-code-repo-into-src.ts","../src/lib/terminal-logger.ts","../src/lib/execute-gh-repo-create.ts","../src/lib/resolve-github-username-default.ts","../src/lib/validate-repo-name-and-visibility.ts","../src/lib/create-github-remote-from-folder.ts","../src/lib/create-empty-github-repo.ts","../src/lib/google-oauth-device-flow.ts","../src/index.ts","../src/commands/add-repo.ts","../src/lib/clone-repo-with-recovery-menu.ts","../src/lib/verify-git-remote-accessible.ts","../src/lib/detect-gitnexus-installation.ts","../src/lib/detect-host-platform.ts","../src/lib/check-gh-cli-auth-status.ts","../src/lib/detect-package-manager.ts","../src/lib/handle-remote-access-failure-with-account-switch.ts","../src/lib/reset-folder-git-and-create-new-remote-under-current-user.ts","../src/lib/install-gh-cli-via-package-manager.ts","../src/lib/prompt-recovery-action-on-failure.ts","../src/lib/setup-git-credential-via-gh.ts","../src/lib/git-auth-and-install-orchestrator.ts","../src/lib/trigger-gh-cli-auth-login.ts","../src/lib/resolve-avatar-workspace-root-from-cwd.ts","../src/lib/run-gitnexus-setup-and-analyze.ts","../src/lib/run-gitnexus-wiki-conditional.ts","../src/lib/detect-reasoning-model-from-name.ts","../src/lib/write-gitnexus-config-to-avoid-cli-arg-key-leak.ts","../src/commands/ai.ts","../src/lib/audit-log-appender.ts","../src/lib/user-config-store.ts","../src/types/config-schema.ts","../src/lib/check-claude-code-subscription-and-quota.ts","../src/lib/detect-claude-code-installation.ts","../src/lib/install-claude-code-via-npm.ts","../src/lib/prompt-ai-provider-choice.ts","../src/lib/setup-anthropic-api-key-and-model.ts","../src/lib/setup-llmlite-api-key-and-model.ts","../src/lib/run-ai-setup-phase.ts","../src/lib/write-claude-settings-json-per-project.ts","../src/lib/test-ai-provider-by-detected-mode.ts","../src/commands/doctor.ts","../src/lib/check-enabled-tools-health.ts","../src/lib/apply-tool-manifest-to-settings.ts","../src/lib/merge-pack-settings-into-project-settings.ts","../src/lib/tool-state-store.ts","../src/commands/gitnexus.ts","../src/lib/run-gitnexus-setup-phase.ts","../src/lib/install-gitnexus-via-npm.ts","../src/lib/register-gitnexus-mcp-server.ts","../src/commands/init.ts","../src/lib/avatar-ascii-banner.ts","../src/lib/safe-bootstrap-for-dirty-folder.ts","../src/lib/check-folder-has-git.ts","../src/lib/create-initial-git-commit.ts","../src/lib/detect-folder-tech-stack.ts","../src/lib/gitignore-template-loader.ts","../src/lib/write-or-merge-gitignore.ts","../src/lib/team-pack-submodule-manager.ts","../src/lib/pack-manifest-store.ts","../src/lib/supabase-pack-downloader.ts","../src/commands/init-success-rendering-and-helpers.ts","../src/lib/format-pack-commands-cheatsheet-box.ts","../src/commands/init-conflict-detection-helpers.ts","../src/commands/login.ts","../src/commands/workspace-scaffold-and-finalize-orchestrator.ts","../src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts","../src/lib/setup-default-tools-on-init.ts","../src/lib/discover-pack-tools-and-defaults.ts","../src/lib/tool-enable-disable-orchestrator.ts","../src/lib/symlink-farm-for-team-pack-mount-dirs.ts","../src/lib/backup-existing-dir-before-symlink-override.ts","../src/lib/read-cli-version-from-package-json.ts","../src/commands/init-scaffold-variable-builders.ts","../src/lib/not-implemented-stub.ts","../src/commands/mcp-run.ts","../src/commands/pack-status-and-version-check.ts","../src/commands/restore.ts","../src/commands/review.ts","../src/commands/scan.ts","../src/commands/status.ts","../src/lib/pack-backup-manager.ts","../src/commands/sync.ts","../src/lib/preview-team-pack-sync-changes-for-dry-run.ts","../src/lib/reapply-enabled-tools-after-sync.ts","../src/commands/tools.ts","../src/lib/tool-add-remove-selection.ts","../src/commands/uninstall.ts","../src/lib/create-uninstall-backup-snapshot.ts","../src/lib/detect-avatar-project-artifacts.ts","../src/lib/execute-uninstall-deletion.ts"],"sourcesContent":["import { spawn, spawnSync } from \"node:child_process\";\n// Thin promise-based wrappers around node:fs/promises with the patterns Avatar\n// uses most: ensure-dir, read-text, write-text-atomic, copy-dir-recursive.\n// Atomic write writes to a temp file then renames to avoid corruption if Avatar\n// crashes mid-write.\nimport { createHash } from \"node:crypto\";\nimport { constants, promises as fs, createReadStream } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\nimport { Readable } from \"node:stream\";\n\nexport async function pathExists(path: string): Promise<boolean> {\n try {\n await fs.access(path, constants.F_OK);\n return true;\n } catch {\n return false;\n }\n}\n\nexport async function ensureDir(path: string): Promise<void> {\n await fs.mkdir(path, { recursive: true });\n}\n\nexport async function readText(path: string): Promise<string> {\n return await fs.readFile(path, \"utf8\");\n}\n\nexport async function readJson<T>(path: string): Promise<T> {\n return JSON.parse(await readText(path)) as T;\n}\n\n// Atomic write: write to temp file then rename. Prevents corruption if process\n// is killed between open() and close(). chmod is applied AFTER rename.\nexport async function writeTextAtomic(path: string, content: string, mode?: number): Promise<void> {\n await ensureDir(dirname(path));\n const tmp = `${path}.tmp-${process.pid}-${Date.now()}`;\n await fs.writeFile(tmp, content, \"utf8\");\n if (mode !== undefined) {\n await fs.chmod(tmp, mode);\n }\n await fs.rename(tmp, path);\n}\n\nexport async function writeJsonAtomic(path: string, data: unknown, mode?: number): Promise<void> {\n await writeTextAtomic(path, `${JSON.stringify(data, null, 2)}\\n`, mode);\n}\n\n// Recursive copy. excludeNames filters by basename at any depth (e.g. \".git\").\nexport async function copyDirRecursive(\n source: string,\n destination: string,\n excludeNames: string[] = [],\n): Promise<void> {\n await ensureDir(destination);\n const entries = await fs.readdir(source, { withFileTypes: true });\n for (const entry of entries) {\n if (excludeNames.includes(entry.name)) continue;\n const src = join(source, entry.name);\n const dst = join(destination, entry.name);\n if (entry.isDirectory()) {\n await copyDirRecursive(src, dst, excludeNames);\n } else if (entry.isSymbolicLink()) {\n const target = await fs.readlink(src);\n await fs.symlink(target, dst);\n } else {\n await fs.copyFile(src, dst);\n }\n }\n}\n\nexport async function removeRecursive(path: string): Promise<void> {\n await fs.rm(path, { recursive: true, force: true });\n}\n\nexport function relativeFromCwd(path: string): string {\n return relative(process.cwd(), path);\n}\n\n// ─── Pack download/extract helpers (v3: tải tarball pack từ Supabase) ────────\n\n// Tải file từ URL về `dest`. Stream body ra file (không nạp hết vào memory).\n// S3: AbortController timeout (default 300s = thời hạn signed URL Supabase) +\n// dọn partial file nếu fail giữa chừng. Tránh treo vô hạn / để lại file rác.\nconst DOWNLOAD_TIMEOUT_MS = 300_000;\n\nexport async function downloadFile(\n url: string,\n dest: string,\n timeoutMs = DOWNLOAD_TIMEOUT_MS,\n): Promise<void> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), timeoutMs);\n await ensureDir(dirname(dest));\n const { createWriteStream } = await import(\"node:fs\");\n\n try {\n const res = await fetch(url, { signal: controller.signal });\n if (!res.ok) {\n throw new Error(`Download fail (${res.status}) từ ${url.slice(0, 80)}...`);\n }\n if (!res.body) {\n throw new Error(\"Response không có body để tải.\");\n }\n const fileStream = createWriteStream(dest);\n await new Promise<void>((resolve, reject) => {\n const src = Readable.fromWeb(res.body as Parameters<typeof Readable.fromWeb>[0]);\n src.on(\"error\", reject); // S-fix: bắt lỗi mid-stream của source.\n fileStream.on(\"error\", reject);\n fileStream.on(\"finish\", () => resolve());\n src.pipe(fileStream);\n });\n } catch (err) {\n // Dọn partial file để không để lại tarball hỏng.\n await fs.rm(dest, { force: true }).catch(() => {});\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Tải file quá ${timeoutMs / 1000}s (signed URL có thể đã hết hạn).`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Giải nén tarball .tar.gz vào `destDir` bằng system `tar`. macOS/Linux có sẵn.\n// S4: Windows mặc định không có `tar` ở mọi phiên bản → probe trước, throw rõ ràng.\nlet tarAvailableCache: boolean | null = null;\nfunction tarAvailable(): boolean {\n if (tarAvailableCache !== null) return tarAvailableCache;\n const r = spawnSync(\"tar\", [\"--version\"], { stdio: \"ignore\" });\n tarAvailableCache = r.status === 0;\n return tarAvailableCache;\n}\n\nexport async function extractTarballToDir(tarball: string, destDir: string): Promise<void> {\n if (!tarAvailable()) {\n throw new Error(\n \"Không tìm thấy lệnh `tar` trên hệ thống. Cần để giải nén pack.\\n\" +\n \" Windows: cài qua WSL2, Git Bash, hoặc MSYS2.\\n\" +\n \" macOS/Linux: tar thường có sẵn — kiểm tra PATH.\",\n );\n }\n await ensureDir(destDir);\n await new Promise<void>((resolve, reject) => {\n const child = spawn(\"tar\", [\"-xzf\", tarball, \"-C\", destDir], {\n stdio: [\"ignore\", \"ignore\", \"pipe\"],\n });\n let stderr = \"\";\n child.stderr.on(\"data\", (chunk) => {\n stderr += chunk.toString();\n });\n child.on(\"error\", reject);\n child.on(\"close\", (code) => {\n if (code === 0) resolve();\n else reject(new Error(`tar extract exit ${code}: ${stderr.trim()}`));\n });\n });\n}\n\n// Tính SHA-256 của file (hex). Để verify integrity tarball trước khi giải nén.\n// HIỆN CHƯA DÙNG: get-pack endpoint Supabase chưa trả sha256 trong response.\n// Giữ lại để bật verify khi endpoint bổ sung field sha (roadmap S5). Khi đó:\n// downloadAndExtractPack nhận expectedSha → so với computeFileSha256(tarball) trước extract.\nexport async function computeFileSha256(path: string): Promise<string> {\n return await new Promise<string>((resolve, reject) => {\n const hash = createHash(\"sha256\");\n const stream = createReadStream(path);\n stream.on(\"data\", (chunk) => hash.update(chunk));\n stream.on(\"end\", () => resolve(hash.digest(\"hex\")));\n stream.on(\"error\", reject);\n });\n}\n","import { join } from \"node:path\";\n// Wrapper around simple-git for the operations Avatar uses repeatedly:\n// repo detection, submodule add/update, status, log, tag listing.\n// Centralised so error formatting and cwd handling stays consistent.\nimport { type SimpleGit, simpleGit } from \"simple-git\";\nimport { pathExists } from \"./filesystem-helpers.js\";\n\nexport function git(cwd: string = process.cwd()): SimpleGit {\n return simpleGit({ baseDir: cwd, binary: \"git\" });\n}\n\nexport async function isGitRepo(cwd: string = process.cwd()): Promise<boolean> {\n return await pathExists(join(cwd, \".git\"));\n}\n\nexport async function currentBranch(cwd: string = process.cwd()): Promise<string> {\n const result = await git(cwd).revparse([\"--abbrev-ref\", \"HEAD\"]);\n return result.trim();\n}\n\nexport async function addSubmodule(\n repoUrl: string,\n destPath: string,\n cwd: string = process.cwd(),\n): Promise<void> {\n await git(cwd).subModule([\"add\", repoUrl, destPath]);\n}\n\n// Checkout a tag inside a submodule. Used after `addSubmodule` to pin to a\n// specific team-ai-pack release.\nexport async function checkoutTagInSubmodule(\n submodulePath: string,\n tag: string,\n cwd: string = process.cwd(),\n): Promise<void> {\n const submoduleCwd = join(cwd, submodulePath);\n await git(submoduleCwd).fetch([\"--tags\"]);\n await git(submoduleCwd).checkout(tag);\n}\n\n// v1.8.0: Checkout HEAD của branch (default main) thay vì tag.\n// Dùng cho `avatar sync --latest` — pull commits mới ngay khi pack team push,\n// không cần đợi tag SemVer mới. Bleeding-edge mode cho member dev.\n//\n// Flow:\n// 1. fetch origin (lấy commits mới + tags)\n// 2. checkout <branch> (di chuyển HEAD tới branch local, tạo nếu thiếu)\n// 3. reset --hard origin/<branch> (force align với remote, xóa local divergence)\n//\n// reset --hard cần thiết vì submodule có thể đang detached HEAD (sau checkout tag).\n// Pack thường KHÔNG có local commits → reset --hard safe.\nexport async function checkoutBranchHeadInSubmodule(\n submodulePath: string,\n branch: string,\n cwd: string = process.cwd(),\n): Promise<void> {\n const submoduleCwd = join(cwd, submodulePath);\n await git(submoduleCwd).fetch([\"origin\"]);\n // Tạo / switch local branch tracking remote.\n await git(submoduleCwd).checkout([\"-B\", branch, `origin/${branch}`]);\n}\n\nexport async function listTags(cwd: string = process.cwd()): Promise<string[]> {\n const result = await git(cwd).tags();\n return result.all;\n}\n\n// v1.7.1: Return tag tại HEAD nếu có (semantics khác latestTag).\n// Dùng cho avatar status / readPinnedPackVersion — báo user đang ở version nào.\nexport async function tagAtHead(cwd: string = process.cwd()): Promise<string | null> {\n try {\n const result = await git(cwd).raw([\"describe\", \"--tags\", \"--exact-match\", \"HEAD\"]);\n return result.trim() || null;\n } catch {\n // HEAD không trùng tag nào → return null, caller fallback dùng SHA.\n return null;\n }\n}\n\nexport async function currentCommitSha(cwd: string = process.cwd()): Promise<string> {\n const result = await git(cwd).revparse([\"HEAD\"]);\n return result.trim();\n}\n\nexport async function workingTreeIsDirty(cwd: string = process.cwd()): Promise<boolean> {\n const status = await git(cwd).status();\n return !status.isClean();\n}\n","// Minimal mustache-style {{variable}} replacement engine. Avoids pulling in\n// full mustache dependency — Avatar templates only need flat variable\n// substitution, no loops or conditionals.\n//\n// Unknown variables left as-is, NOT replaced with empty string. This makes\n// missing variables visible in output for easier debugging.\n\nconst TEMPLATE_PATTERN = /\\{\\{\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\}\\}/g;\n\nexport function renderTemplate(\n source: string,\n variables: Record<string, string | number | undefined>,\n): string {\n return source.replace(TEMPLATE_PATTERN, (match, key: string) => {\n const value = variables[key];\n if (value === undefined) return match;\n return String(value);\n });\n}\n\n// Extract the variable names referenced by a template — useful for\n// scaffolding tests and validating that callers pass every required key.\nexport function extractVariables(source: string): string[] {\n const found = new Set<string>();\n for (const match of source.matchAll(TEMPLATE_PATTERN)) {\n if (match[1]) found.add(match[1]);\n }\n return Array.from(found).sort();\n}\n","// Resolve template files bundled inside the Avatar CLI package. tsup bundles\n// src/ into dist/index.js but does NOT bundle template files — they ship as\n// loose files via package.json `files` field (src/templates, src/hooks).\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport { readText } from \"./filesystem-helpers.js\";\nimport { renderTemplate } from \"./mustache-template-engine.js\";\n\n// Templates ship in src/templates/ and src/hooks/ at runtime (see\n// package.json `files` field — both src/templates and src/hooks are included\n// in the npm tarball). Locate them by walking up from this file to the\n// package root (folder containing package.json), then descending to src/.\n//\n// This works identically from dist/index.js (npm-installed) and from\n// src/lib/*.ts (vitest/tsx) without path string sniffing.\nconst HERE = dirname(fileURLToPath(import.meta.url));\nconst PACKAGE_ROOT = findPackageRoot(HERE);\nconst TEMPLATES_ROOT = join(PACKAGE_ROOT, \"src\", \"templates\");\nconst HOOKS_ROOT = join(PACKAGE_ROOT, \"src\", \"hooks\");\n\nfunction findPackageRoot(startDir: string): string {\n let dir = startDir;\n while (true) {\n if (existsSync(join(dir, \"package.json\"))) return dir;\n const parent = dirname(dir);\n if (parent === dir) {\n throw new Error(`Cannot locate package root from ${startDir}`);\n }\n dir = parent;\n }\n}\n\nexport type TemplateName =\n | \"CLAUDE.md\"\n | \"settings.json\"\n | \"project/tech-stack.md\"\n | \"project/conventions.md\"\n | \"project/architecture.md\"\n | \"project/domain.md\"\n | \"project/gotchas.md\";\n\nexport type HookName = \"post-merge\" | \"pre-push\";\n\nexport async function loadTemplate(name: TemplateName): Promise<string> {\n return await readText(join(TEMPLATES_ROOT, `${name}.tpl`));\n}\n\nexport async function renderTemplateByName(\n name: TemplateName,\n variables: Record<string, string | number | undefined>,\n): Promise<string> {\n const source = await loadTemplate(name);\n return renderTemplate(source, variables);\n}\n\nexport async function loadHook(name: HookName): Promise<string> {\n return await readText(join(HOOKS_ROOT, `${name}.sh.tpl`));\n}\n","import { promises as fs } from \"node:fs\";\n// Scaffold the .claude/ directory tree inside a project.\n// v3: workspace KHÔNG git, 1 flow init duy nhất (bỏ internal/client/library modes).\nimport { join } from \"node:path\";\nimport { ensureDir, pathExists, writeTextAtomic } from \"./filesystem-helpers.js\";\nimport { type TemplateName, loadHook, renderTemplateByName } from \"./template-bundle-loader.js\";\n\n// Top-level paths Avatar will create or overwrite during init.\n// init.ts imports this for conflict detection so the list stays in sync.\n// v3: workspace KHÔNG git → bỏ .gitmodules (không còn submodule).\nexport const AVATAR_MANAGED_PATHS = [\".claude\", \"CLAUDE.md\"] as const;\n\n// Rename `path` to `{path}.avatar-backup-{ISO timestamp}` if it exists.\n// Returns the backup path if created, null if source didn't exist.\n// Caller logs the backup so the user knows where their original file went.\n//\n// Collision handling: if the timestamped backup name already exists (two\n// backups within the same millisecond, or pre-existing leftover from prior\n// run), appends `-{counter}` until a free name is found. Prevents silent\n// overwrite of a previous backup.\nexport async function backupIfExists(path: string): Promise<string | null> {\n if (!(await pathExists(path))) return null;\n const ts = new Date().toISOString().replace(/[:.]/g, \"-\");\n const basePath = `${path}.avatar-backup-${ts}`;\n let backupPath = basePath;\n let counter = 1;\n while (await pathExists(backupPath)) {\n backupPath = `${basePath}-${counter}`;\n counter++;\n if (counter > 5) {\n throw new Error(`Could not find free backup name for ${path}`);\n }\n }\n await fs.rename(path, backupPath);\n return backupPath;\n}\n\n// Backup-aware atomic write. Used by all scaffolder write functions so user's\n// pre-existing files in `.claude/` are preserved when Avatar re-scaffolds.\n// Returns the backup path if a backup was created, null otherwise.\nasync function writeWithBackup(\n path: string,\n content: string,\n mode?: number,\n): Promise<string | null> {\n const backup = await backupIfExists(path);\n await writeTextAtomic(path, content, mode);\n return backup;\n}\n\n// Subdirectories Avatar creates inside .claude/ on init. `pack/` is added\n// separately (tải tarball qua addTeamPackSubmodule) — không liệt kê ở đây.\n// v1.3.3: Bỏ \"project\" subdir vì CLAUDE.md template không còn reference các knowledge file\n// trong đó (team-ai-pack/knowledge auto-scan chưa implement). Khi implement xong sẽ\n// add lại + writeProjectKnowledgeFiles.\nconst CLAUDE_SUBDIRS = [\"state\", \"_pending\", \"_backup\"] as const;\n\nexport interface ScaffoldVariables {\n projectName: string;\n projectDescription: string;\n teamOwner: string;\n avatarVersion: string;\n packVersion: string;\n lastScan: string;\n // v1.4.0 — GitNexus section content (empty string nếu không cài).\n // Mustache engine không support conditional → render pre-built string.\n gitnexusSection: string;\n // Index signature — `renderTemplateByName` consume Record<string, string | number | undefined>.\n // Cho phép template engine truy cập field bằng string key động.\n [key: string]: string | number | undefined;\n}\n\n// Create .claude/{project,state,_pending,_backup}/ and a .gitkeep in each\n// so they survive a fresh checkout.\nexport async function createClaudeDirTree(projectRoot: string): Promise<void> {\n const claudeRoot = join(projectRoot, \".claude\");\n await ensureDir(claudeRoot);\n for (const sub of CLAUDE_SUBDIRS) {\n const dir = join(claudeRoot, sub);\n await ensureDir(dir);\n await writeTextAtomic(join(dir, \".gitkeep\"), \"\");\n }\n}\n\n// v1.3.3-v1.11.1: STUB. Project knowledge files (tech-stack/conventions/architecture/\n// domain/gotchas) chờ `avatar scan` (M06) + team-ai-pack content ready.\n// Caller `init.ts` vẫn gọi để giữ API stable cho khi re-enable, hiện trả [].\n//\n// Re-enable checklist:\n// 1. team-ai-pack có content thật (chương trình knowledge curation chạy)\n// 2. avatar scan implement xong + output structured knowledge JSON\n// 3. CLAUDE.md template re-add @-imports cho 5 file knowledge\n// 4. Update test/lib/project-tree-scaffolder.test.ts ngừng expect []\n//\n// Nếu sau v1.15 vẫn chưa wire → đánh giá lại có cần stub không, có thể xóa và\n// inline empty array tại caller để giảm noise.\nexport async function writeProjectKnowledgeFiles(\n _projectRoot: string,\n _vars: ScaffoldVariables,\n): Promise<string[]> {\n return [];\n}\n\n// Write root CLAUDE.md from template — this is the entry point Claude Code reads.\n// Returns backup path if a pre-existing CLAUDE.md was renamed.\nexport async function writeRootClaudeMd(\n projectRoot: string,\n vars: ScaffoldVariables,\n): Promise<string | null> {\n const content = await renderTemplateByName(\"CLAUDE.md\", vars);\n return await writeWithBackup(join(projectRoot, \"CLAUDE.md\"), content);\n}\n\n// Write .claude/settings.json from template. Returns backup path if existed.\nexport async function writeProjectSettings(\n projectRoot: string,\n vars: ScaffoldVariables,\n): Promise<string | null> {\n const content = await renderTemplateByName(\"settings.json\", vars);\n return await writeWithBackup(join(projectRoot, \".claude\", \"settings.json\"), content);\n}\n\n// v3: workspace root KHÔNG git → KHÔNG tạo .gitignore ở root (gây hiểu nhầm).\n// Thay bằng .claude/.gitignore nhỏ — bảo vệ settings.json (chứa API key) + state,\n// PHÒNG khi user/Claude lỡ git init bên trong .claude/ hoặc workspace.\n// Nội dung path tương đối từ .claude/ (không phải workspace root).\nconst CLAUDE_GITIGNORE_CONTENT = `# Avatar — bảo vệ file nhạy cảm trong .claude/.\n# Workspace root KHÔNG git; file này phòng trường hợp .claude/ bị track nhầm.\nsettings.json\nsettings.json.backup-*\n.env\n.env.*\n_pending/\n_backup/\nstate/session-*.json\n`;\n\nexport async function writeClaudeGitignore(projectRoot: string): Promise<void> {\n const path = join(projectRoot, \".claude\", \".gitignore\");\n await writeTextAtomic(path, CLAUDE_GITIGNORE_CONTENT);\n}\n\n// Install git hooks into <gitDir>/hooks/. `gitDir` lets caller point at\n// src/.git/hooks (client mode pre-push) or workspace .git/hooks (post-merge).\nexport async function installGitHook(\n gitDir: string,\n hookName: \"post-merge\" | \"pre-push\",\n): Promise<void> {\n const content = await loadHook(hookName);\n const hooksDir = join(gitDir, \"hooks\");\n await ensureDir(hooksDir);\n const dest = join(hooksDir, hookName);\n await writeTextAtomic(dest, content, 0o755);\n}\n","// workspace-repos-manifest-store.ts\n//\n// v3: workspace KHÔNG git → không có .gitmodules để biết \"workspace có repo nào\".\n// Thay bằng .claude/repos.json do Avatar quản — nguồn sự thật DUY NHẤT về các repo\n// code trong src/. Mỗi repo là git clone độc lập.\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const REPOS_MANIFEST_RELATIVE = \".claude/repos.json\";\n\nexport interface RepoEntry {\n name: string; // tên thư mục trong src/ (vd \"frontend\")\n path: string; // relative từ workspace root (vd \"src/frontend\")\n url: string; // git remote URL\n addedAt: string; // ISO timestamp\n}\n\ninterface ReposManifest {\n repos: RepoEntry[];\n}\n\nfunction manifestPath(workspaceRoot: string): string {\n return join(workspaceRoot, REPOS_MANIFEST_RELATIVE);\n}\n\nexport async function readReposManifest(workspaceRoot: string): Promise<RepoEntry[]> {\n const p = manifestPath(workspaceRoot);\n if (!(await pathExists(p))) return [];\n try {\n const data = await readJson<ReposManifest>(p);\n return Array.isArray(data.repos) ? data.repos : [];\n } catch {\n return [];\n }\n}\n\nexport async function addRepoToManifest(workspaceRoot: string, entry: RepoEntry): Promise<void> {\n const repos = await readReposManifest(workspaceRoot);\n // Replace nếu cùng name (idempotent), else append.\n const filtered = repos.filter((r) => r.name !== entry.name);\n filtered.push(entry);\n await writeJsonAtomic(manifestPath(workspaceRoot), { repos: filtered });\n}\n\nexport async function removeRepoFromManifest(workspaceRoot: string, name: string): Promise<void> {\n const repos = await readReposManifest(workspaceRoot);\n await writeJsonAtomic(manifestPath(workspaceRoot), {\n repos: repos.filter((r) => r.name !== name),\n });\n}\n\n// Check tên repo đã tồn tại trong manifest chưa (tránh trùng).\nexport async function repoNameExists(workspaceRoot: string, name: string): Promise<boolean> {\n const repos = await readReposManifest(workspaceRoot);\n return repos.some((r) => r.name === name);\n}\n","// clone-code-repo-into-src.ts\n//\n// v3: Clone 1 repo code vào src/<name> (KHÔNG submodule — workspace không git).\n// Mỗi repo là git clone độc lập, có remote riêng, push/pull bình thường.\n//\n// Cài pre-push hook cho repo sau clone (P6: hook cài Ở ĐÂY, không ở init).\nimport { join } from \"node:path\";\nimport { ensureDir, pathExists } from \"./filesystem-helpers.js\";\nimport { git } from \"./git-operations.js\";\nimport { installGitHook } from \"./project-tree-scaffolder.js\";\nimport { addRepoToManifest, repoNameExists } from \"./workspace-repos-manifest-store.js\";\n\n// Tên repo: kebab/alphanumeric, không path separator, không trùng \"pack\".\nexport function validateRepoName(name: string): string | null {\n if (!name || name.trim().length === 0) return \"Tên repo không được rỗng.\";\n if (/[/\\\\]/.test(name) || name.includes(\"..\")) return \"Tên repo không được chứa '/', '\\\\', '..'.\";\n if (name === \"pack\") return \"Tên 'pack' bị giữ cho team-ai-pack.\";\n return null;\n}\n\n// Suy tên repo từ git URL. \"git@github.com:org/frontend.git\" → \"frontend\".\nexport function inferRepoNameFromUrl(url: string): string {\n const trimmed = url.trim().replace(/\\/+$/, \"\");\n const last = trimmed.split(/[/:]/).pop() ?? \"\";\n return last.replace(/\\.git$/, \"\") || \"repo\";\n}\n\nexport interface CloneRepoResult {\n name: string;\n path: string; // absolute\n url: string;\n}\n\n// Clone repo từ remote URL vào src/<name> của workspace.\n// Validate tên + chống trùng. Cài pre-push hook. Ghi manifest.\nexport async function cloneCodeRepoIntoSrc(args: {\n workspaceRoot: string;\n url: string;\n name: string;\n}): Promise<CloneRepoResult> {\n const nameError = validateRepoName(args.name);\n if (nameError) throw new Error(nameError);\n\n if (await repoNameExists(args.workspaceRoot, args.name)) {\n throw new Error(`Repo \"${args.name}\" đã có trong workspace. Chọn tên khác hoặc gỡ trước.`);\n }\n\n const srcDir = join(args.workspaceRoot, \"src\");\n await ensureDir(srcDir);\n const destPath = join(srcDir, args.name);\n if (await pathExists(destPath)) {\n throw new Error(`Thư mục src/${args.name} đã tồn tại. Chọn tên khác.`);\n }\n\n // git clone <url> src/<name>\n await git(srcDir).clone(args.url, args.name);\n\n // Cài pre-push hook cho repo vừa clone (P6).\n const gitDir = join(destPath, \".git\");\n await installGitHook(gitDir, \"pre-push\").catch(() => {\n // Hook là tiện ích, fail không block add repo.\n });\n\n // Ghi manifest.\n await addRepoToManifest(args.workspaceRoot, {\n name: args.name,\n path: `src/${args.name}`,\n url: args.url,\n addedAt: new Date().toISOString(),\n });\n\n return { name: args.name, path: destPath, url: args.url };\n}\n\n// Check folder có uncommitted changes không (P9: tránh clone-from-remote mất data).\n// Trả porcelain output (rỗng = clean).\nexport async function folderDirtyStatus(folderPath: string): Promise<string> {\n try {\n const status = await git(folderPath).status();\n return status.isClean() ? \"\" : status.files.map((f) => `${f.path}`).join(\", \");\n } catch {\n return \"\";\n }\n}\n\n// Đọc remote origin URL của 1 folder git (cho flow \"thư mục đã có\").\nexport async function readFolderRemoteUrl(folderPath: string): Promise<string | null> {\n try {\n const remotes = await git(folderPath).getRemotes(true);\n const origin = remotes.find((r) => r.name === \"origin\");\n return origin?.refs.fetch ?? origin?.refs.push ?? null;\n } catch {\n return null;\n }\n}\n","// Terminal logging helpers — chalk for color, ora for spinners.\n// All Avatar CLI output should funnel through here for consistent UX.\nimport chalk from \"chalk\";\nimport ora, { type Ora } from \"ora\";\n\ntype LogFn = (message: string) => void;\n\nexport const log: {\n info: LogFn;\n success: LogFn;\n warn: LogFn;\n error: LogFn;\n dim: LogFn;\n plain: LogFn;\n} = {\n info: (m) => process.stdout.write(`${chalk.blue(\"ℹ\")} ${m}\\n`),\n success: (m) => process.stdout.write(`${chalk.green(\"✓\")} ${m}\\n`),\n warn: (m) => process.stdout.write(`${chalk.yellow(\"⚠\")} ${m}\\n`),\n error: (m) => process.stderr.write(`${chalk.red(\"✗\")} ${m}\\n`),\n dim: (m) => process.stdout.write(`${chalk.dim(m)}\\n`),\n plain: (m) => process.stdout.write(`${m}\\n`),\n};\n\n// Spinner wrapper — handles non-TTY by degrading to plain output.\nexport function spinner(text: string): Ora {\n return ora({\n text,\n spinner: \"dots\",\n isEnabled: process.stdout.isTTY ?? false,\n }).start();\n}\n\n// Long-running spinner với elapsed time — update text mỗi giây.\n// Trả về { stop } để caller clear interval + stop spinner đúng cách.\n// Format: \"⠋ <prefix> (1:23)\" — user biết tool alive + chạy bao lâu.\nexport function spinnerWithElapsed(prefix: string): {\n succeed: (text: string) => void;\n fail: (text: string) => void;\n stop: () => void;\n} {\n const startMs = Date.now();\n const sp = spinner(`${prefix} (0:00)`);\n const formatElapsed = (): string => {\n const sec = Math.floor((Date.now() - startMs) / 1000);\n const m = Math.floor(sec / 60);\n const s = sec % 60;\n return `${m}:${String(s).padStart(2, \"0\")}`;\n };\n const interval = setInterval(() => {\n sp.text = `${prefix} (${formatElapsed()})`;\n }, 1000);\n return {\n succeed: (text: string) => {\n clearInterval(interval);\n sp.succeed(`${text} (${formatElapsed()})`);\n },\n fail: (text: string) => {\n clearInterval(interval);\n sp.fail(`${text} (${formatElapsed()})`);\n },\n stop: () => {\n clearInterval(interval);\n sp.stop();\n },\n };\n}\n\n// Chalk re-export so commands don't all import chalk separately.\nexport { chalk };\n","// Wrapper around `gh repo create <org>/<name> --<vis> --source=<folder>\n// --remote=origin --push`. Stream stdio để user thấy gh prompt nếu có.\nimport { spawnSync } from \"node:child_process\";\nimport type { RepoVisibility } from \"./validate-repo-name-and-visibility.js\";\n\nexport class RepoAlreadyExistsError extends Error {\n constructor(fullName: string) {\n super(`Repo \"${fullName}\" đã tồn tại trên GitHub. Đổi tên hoặc xóa repo cũ.`);\n this.name = \"RepoAlreadyExistsError\";\n }\n}\n\nexport interface ExecuteGhRepoCreateInput {\n folder: string;\n org: string;\n name: string;\n visibility: RepoVisibility;\n}\n\nexport interface ExecuteGhRepoCreateOutput {\n sshUrl: string;\n httpsUrl: string;\n}\n\nexport function executeGhRepoCreate(input: ExecuteGhRepoCreateInput): ExecuteGhRepoCreateOutput {\n const fullName = `${input.org}/${input.name}`;\n const args = [\n \"repo\",\n \"create\",\n fullName,\n `--${input.visibility}`,\n \"--source\",\n input.folder,\n \"--remote\",\n \"origin\",\n \"--push\",\n ];\n const r = spawnSync(\"gh\", args, { stdio: \"inherit\" });\n if (r.status !== 0) {\n // gh thường in \"GraphQL: Name already exists\" cho duplicate. Không parse\n // stdout (vì inherit) — surface generic error, user sẽ thấy stderr của gh.\n if (r.status === 1) {\n throw new RepoAlreadyExistsError(fullName);\n }\n throw new Error(`gh repo create thất bại (exit ${r.status})`);\n }\n return {\n sshUrl: `git@github.com:${fullName}.git`,\n httpsUrl: `https://github.com/${fullName}.git`,\n };\n}\n","// Lấy GitHub login mặc định của user qua `gh api user --jq .login`.\n// Dùng làm default org khi user không truyền --repo-org.\nimport { spawnSync } from \"node:child_process\";\n\nexport function resolveGithubUsernameDefault(): string {\n const r = spawnSync(\"gh\", [\"api\", \"user\", \"--jq\", \".login\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.status !== 0) {\n throw new Error(`Không lấy được GitHub username: ${r.stderr?.trim()}`);\n }\n return r.stdout.trim();\n}\n","// Validate repo name + visibility trước khi gọi `gh repo create`. Fail-fast với\n// thông báo Vietnamese rõ ràng. GitHub repo name spec: alphanumeric, dash,\n// underscore, dot. 1-100 chars.\nconst REPO_NAME_REGEX = /^[a-zA-Z0-9._-]{1,100}$/;\n\nexport type RepoVisibility = \"private\" | \"public\";\n\nexport class InvalidRepoNameError extends Error {\n constructor(name: string) {\n super(\n `Tên repo \"${name}\" không hợp lệ. Chỉ dùng chữ/số/dấu chấm/gạch/underscore, dài 1-100 ký tự.`,\n );\n this.name = \"InvalidRepoNameError\";\n }\n}\n\nexport function validateRepoName(name: string): void {\n if (!REPO_NAME_REGEX.test(name)) {\n throw new InvalidRepoNameError(name);\n }\n}\n\nexport function validateRepoVisibility(v: string): asserts v is RepoVisibility {\n if (v !== \"private\" && v !== \"public\") {\n throw new Error(`Visibility phải là \"private\" hoặc \"public\", nhận: \"${v}\"`);\n }\n}\n","// Orchestrator phase 4: validate input → gọi gh repo create → return URLs.\n// Caller chịu trách nhiệm đảm bảo gh đã auth (gọi ensureGitHubReady trước).\nimport { type ExecuteGhRepoCreateOutput, executeGhRepoCreate } from \"./execute-gh-repo-create.js\";\nimport { resolveGithubUsernameDefault } from \"./resolve-github-username-default.js\";\nimport { log } from \"./terminal-logger.js\";\nimport {\n type RepoVisibility,\n validateRepoName,\n validateRepoVisibility,\n} from \"./validate-repo-name-and-visibility.js\";\n\nexport interface CreateGithubRemoteInput {\n folder: string;\n name: string;\n visibility: RepoVisibility;\n org?: string; // mặc định = GitHub login của user\n}\n\nexport function createGithubRemoteFromFolder(\n input: CreateGithubRemoteInput,\n): ExecuteGhRepoCreateOutput {\n validateRepoName(input.name);\n validateRepoVisibility(input.visibility);\n\n const org = input.org ?? resolveGithubUsernameDefault();\n log.info(`Tạo GitHub repo ${org}/${input.name} (${input.visibility})...`);\n\n const urls = executeGhRepoCreate({\n folder: input.folder,\n org,\n name: input.name,\n visibility: input.visibility,\n });\n\n log.success(`Đã tạo: ${urls.sshUrl}`);\n return urls;\n}\n","// create-empty-github-repo.ts\n//\n// v3: Tạo repo GitHub RỖNG (không cần folder source) cho flow \"avatar add repo →\n// dự án mới\". Khác executeGhRepoCreate (cần --source folder để push). Ở đây ta\n// tạo repo trống rồi caller clone về src/<name>.\nimport { spawnSync } from \"node:child_process\";\nimport { resolveGithubUsernameDefault } from \"./resolve-github-username-default.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Tạo repo rỗng trên GitHub, trả SSH clone URL.\nexport async function createEmptyGithubRepoAndGetUrl(\n name: string,\n visibility: \"private\" | \"public\",\n org?: string,\n): Promise<string> {\n const owner = org ?? resolveGithubUsernameDefault();\n const fullName = `${owner}/${name}`;\n log.info(`Tạo GitHub repo rỗng ${fullName} (${visibility})...`);\n\n // --add-readme để repo không rỗng hoàn toàn (clone được ngay, có HEAD).\n const r = spawnSync(\"gh\", [\"repo\", \"create\", fullName, `--${visibility}`, \"--add-readme\"], {\n stdio: \"inherit\",\n });\n if (r.status !== 0) {\n throw new Error(`gh repo create thất bại (exit ${r.status}). Repo có thể đã tồn tại.`);\n }\n\n log.success(`Đã tạo: git@github.com:${fullName}.git`);\n return `git@github.com:${fullName}.git`;\n}\n","// Google OAuth 2.0 Device Authorization Grant (RFC 8628) implementation.\n//\n// Why Device Flow: Avatar is a terminal CLI with no browser redirect URL.\n// The user logs in via google.com/device on a browser and the CLI polls\n// Google for the resulting token.\n//\n// Why the client secret is bundled in source: Device Flow does not treat the\n// secret as a security boundary — the human Allow click in the browser is.\n// Google's TV/Limited Input docs explicitly permit this.\n//\n// To rotate: Google Cloud Console → APIs & Services → Credentials → click the\n// OAuth client → Reset Secret. Replace GOOGLE_CLIENT_SECRET below.\n\nimport type { UserConfig } from \"../types/config-schema.js\";\n\n// ─────────────────────────────────────────────────────────────────────────────\n// OAuth client config (hardcoded — see file header for rationale).\n// To regenerate: Google Cloud Console → project \"avatar-cli\" → Clients.\n// Application type must be \"TV and Limited Input devices\".\n//\n// v1.13.0 SECURITY: Module-private (không `export`). Giảm attack surface bằng\n// cách khóa secret chỉ accessible từ chính file này.\n// Action item ngoài code (NAL ops):\n// 1. ROTATE secret hiện tại (đã ship trong dist/ npm v1.10.0+ → public)\n// 2. Set Cloud Console quota limits cho client này tránh abuse\n// 3. Monitor unusual auth traffic qua audit log Google Cloud\n// ─────────────────────────────────────────────────────────────────────────────\nconst GOOGLE_CLIENT_ID =\n \"1014766441755-i4jimivh5rd7vt8phuhmepmt45sovtph.apps.googleusercontent.com\";\nconst GOOGLE_CLIENT_SECRET = \"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\";\n\n// Restrict to the NAL Workspace domain. Enforced at TWO layers:\n// 1. ?hd=nal.vn appended to the verification URL — Google filters the picker\n// 2. Verify id_token.hd === HOSTED_DOMAIN after token exchange (defense-in-depth)\nexport const HOSTED_DOMAIN = \"nal.vn\";\n\nexport const SCOPES = [\"openid\", \"email\", \"profile\"];\n\nconst DEVICE_CODE_URL = \"https://oauth2.googleapis.com/device/code\";\nconst TOKEN_URL = \"https://oauth2.googleapis.com/token\";\nconst REVOKE_URL = \"https://oauth2.googleapis.com/revoke\";\n\nexport interface DeviceCodeResponse {\n device_code: string;\n user_code: string;\n verification_url: string;\n expires_in: number;\n interval: number;\n}\n\nexport interface TokenResponse {\n access_token: string;\n refresh_token: string;\n id_token: string;\n expires_in: number;\n token_type: string;\n scope: string;\n}\n\nexport interface IdTokenClaims {\n email: string;\n email_verified: boolean;\n name?: string;\n hd?: string;\n exp: number;\n iss: string;\n aud: string;\n}\n\n// ── Step 2 of Command 01 spec: request device + user codes.\nexport async function requestDeviceCode(): Promise<DeviceCodeResponse> {\n const body = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n scope: SCOPES.join(\" \"),\n });\n const res = await fetch(DEVICE_CODE_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Device code request failed (${res.status}): ${text}`);\n }\n return (await res.json()) as DeviceCodeResponse;\n}\n\n// ── Step 5: poll the token endpoint until user authorises or expiry.\n// Returns the token response on success, null while still pending.\n// Throws on hard errors (access_denied, expired_token).\nexport async function pollForToken(deviceCode: string): Promise<TokenResponse | null> {\n const body = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n device_code: deviceCode,\n grant_type: \"urn:ietf:params:oauth:grant-type:device_code\",\n });\n const res = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n\n if (res.ok) {\n return (await res.json()) as TokenResponse;\n }\n\n // Google returns 4xx with a JSON {error} field for both pending and fatal states.\n let errorCode = \"\";\n try {\n const data = (await res.json()) as { error?: string };\n errorCode = data.error ?? \"\";\n } catch {\n errorCode = \"\";\n }\n\n if (errorCode === \"authorization_pending\" || errorCode === \"slow_down\") {\n return null;\n }\n if (errorCode === \"access_denied\") {\n throw new Error(\"User từ chối quyền truy cập\");\n }\n if (errorCode === \"expired_token\") {\n throw new Error(\"Hết hạn chờ (5 phút). Chạy lại 'avatar login'.\");\n }\n throw new Error(`OAuth token endpoint trả lỗi: ${errorCode || res.status}`);\n}\n\n// Decode JWT payload WITHOUT verifying signature. Safe here because we receive\n// the token directly from Google over HTTPS — no MITM surface.\nexport function decodeIdToken(idToken: string): IdTokenClaims {\n const parts = idToken.split(\".\");\n if (parts.length !== 3) {\n throw new Error(\"id_token format không hợp lệ\");\n }\n const payload = parts[1];\n if (!payload) throw new Error(\"id_token thiếu payload\");\n // Convert base64url to base64.\n const base64 = payload.replace(/-/g, \"+\").replace(/_/g, \"/\");\n const json = Buffer.from(base64, \"base64\").toString(\"utf8\");\n return JSON.parse(json) as IdTokenClaims;\n}\n\n// ── Step 6: enforce hosted domain. Reject any non-@nal.vn account.\n// v1.14.0 SECURITY: Full JWT claims verify (iss + aud + exp + hd + email_verified).\n// Trước fix: chỉ check `hd` + `email_verified` → token forged với matching `hd`\n// nhưng issuer khác / audience khác / đã expired vẫn pass.\n// Sau fix: defense-in-depth — 5 invariants. Token Google sign nhưng signature\n// verify để skip (NAL không có private key Google). Claims-only check vẫn block\n// most attack vectors khi token đến trực tiếp từ Google endpoint qua HTTPS.\n\n// Issuer canonical Google. Accept cả 2 form: với https:// và không (theo OIDC spec).\nconst VALID_ISSUERS = new Set([\"https://accounts.google.com\", \"accounts.google.com\"]);\n// Clock skew tolerance — Google docs khuyến nghị 30-60s, dùng 60s.\nconst CLOCK_SKEW_SECONDS = 60;\n\nexport function verifyIdTokenClaims(claims: IdTokenClaims): void {\n // 1. Issuer phải là Google.\n if (!VALID_ISSUERS.has(claims.iss)) {\n throw new Error(`id_token issuer không hợp lệ: ${claims.iss} (expect: accounts.google.com)`);\n }\n // 2. Audience phải khớp client ID của Avatar.\n if (claims.aud !== GOOGLE_CLIENT_ID) {\n throw new Error(\n \"id_token audience không khớp client ID Avatar. Token có thể được sign cho app khác.\",\n );\n }\n // 3. Token chưa expire (với clock skew tolerance).\n const nowSec = Math.floor(Date.now() / 1000);\n if (claims.exp + CLOCK_SKEW_SECONDS < nowSec) {\n const ageSec = nowSec - claims.exp;\n throw new Error(`id_token đã hết hạn ${ageSec}s trước. Login lại để lấy token mới.`);\n }\n // 4. Hosted domain restriction NAL.\n if (claims.hd !== HOSTED_DOMAIN) {\n throw new Error(\n `Email không thuộc workspace NAL (yêu cầu @${HOSTED_DOMAIN}). Nhận: ${claims.email}`,\n );\n }\n // 5. Email Google verified.\n if (!claims.email_verified) {\n throw new Error(\"Email chưa được Google verify\");\n }\n}\n\n// Backward-compat alias — existing callers giữ tên cũ. Có thể deprecate sau.\nexport const verifyHostedDomain = verifyIdTokenClaims;\n\n// Convert OAuth token response + decoded claims into the on-disk UserConfig shape.\nexport function buildUserConfig(token: TokenResponse, claims: IdTokenClaims): UserConfig {\n const expiresAt = new Date(Date.now() + token.expires_in * 1000).toISOString();\n return {\n email: claims.email,\n name: claims.name ?? claims.email,\n access_token: token.access_token,\n refresh_token: token.refresh_token,\n expires_at: expiresAt,\n id_token: token.id_token,\n };\n}\n\n// Refresh flow: exchange refresh_token for a new access_token. Used by other\n// commands when they detect an expired token (see isTokenExpired).\nexport async function refreshAccessToken(refreshToken: string): Promise<{\n access_token: string;\n expires_in: number;\n id_token?: string;\n}> {\n const body = new URLSearchParams({\n client_id: GOOGLE_CLIENT_ID,\n client_secret: GOOGLE_CLIENT_SECRET,\n refresh_token: refreshToken,\n grant_type: \"refresh_token\",\n });\n const res = await fetch(TOKEN_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n });\n if (!res.ok) {\n const text = await res.text();\n throw new Error(`Refresh token failed (${res.status}): ${text}`);\n }\n return (await res.json()) as { access_token: string; expires_in: number; id_token?: string };\n}\n\n// Revoke a token (used when login is rejected post-hoc, e.g. wrong domain).\nexport async function revokeToken(token: string): Promise<void> {\n const body = new URLSearchParams({ token });\n await fetch(REVOKE_URL, {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/x-www-form-urlencoded\" },\n body,\n }).catch(() => {\n // Best-effort revoke — don't fail the caller if revoke itself errors.\n });\n}\n\n// Build the verification URL with hd hint so Google pre-filters the account picker.\nexport function buildVerificationUrl(response: DeviceCodeResponse): string {\n const url = new URL(response.verification_url);\n url.searchParams.set(\"user_code\", response.user_code);\n url.searchParams.set(\"hd\", HOSTED_DOMAIN);\n return url.toString();\n}\n","// Bootstrap: load commander, register all 15 subcommands, parse argv.\n// Each command lives in src/commands/<name>.ts as a function that accepts the\n// commander Command instance and registers itself.\nimport { Command } from \"commander\";\nimport { registerAddCommand } from \"./commands/add-repo.js\";\nimport { registerAiCommand } from \"./commands/ai.js\";\nimport { registerDoctorCommand } from \"./commands/doctor.js\";\nimport { registerGitnexusCommand } from \"./commands/gitnexus.js\";\nimport { registerInitCommand } from \"./commands/init.js\";\nimport { registerLoginCommand } from \"./commands/login.js\";\nimport { registerMcpRunCommand } from \"./commands/mcp-run.js\";\nimport { registerPackCommand } from \"./commands/pack-status-and-version-check.js\";\nimport { registerRestoreCommand } from \"./commands/restore.js\";\nimport { registerReviewCommand } from \"./commands/review.js\";\nimport { registerScanCommand } from \"./commands/scan.js\";\nimport { registerStatusCommand } from \"./commands/status.js\";\nimport { registerSyncCommand } from \"./commands/sync.js\";\nimport { registerToolsCommand } from \"./commands/tools.js\";\nimport { registerUninstallCommand } from \"./commands/uninstall.js\";\nimport { printAvatarBanner, renderAvatarBanner } from \"./lib/avatar-ascii-banner.js\";\nimport { readCliVersion } from \"./lib/read-cli-version-from-package-json.js\";\n\nconst CLI_VERSION = readCliVersion();\n\nconst program = new Command();\n\nprogram\n .name(\"avatar\")\n .description(\"AI harness CLI for NAL Vietnam engineering\")\n .version(CLI_VERSION, \"-v, --version\", \"Hiển thị phiên bản Avatar CLI\")\n // Override mặc định của commander để in banner kèm version. Commander cho phép\n // custom output qua configureOutput nhưng cách đơn giản nhất là intercept ở argv.\n .addHelpText(\n \"beforeAll\",\n () =>\n `\\n${renderAvatarBanner({ tagline: `v${CLI_VERSION} · AI harness CLI for NAL Vietnam` })}\\n\\n`,\n );\n\n// In banner khi user gọi `avatar -v` / `avatar --version`. Banner đã chứa\n// version trong tagline nên ta short-circuit luôn, không để commander in thêm\n// dòng \"1.0.1\" dư thừa.\nconst isVersionCall = process.argv.includes(\"-v\") || process.argv.includes(\"--version\");\nif (isVersionCall) {\n printAvatarBanner({ tagline: `v${CLI_VERSION} · AI harness CLI for NAL Vietnam` });\n process.exit(0);\n}\n\n// Register all commands. Order matches Chapter 09 spec in implementation doc.\nregisterLoginCommand(program);\nregisterInitCommand(program);\nregisterSyncCommand(program);\nregisterScanCommand(program);\nregisterReviewCommand(program);\nregisterStatusCommand(program);\nregisterDoctorCommand(program);\nregisterRestoreCommand(program);\nregisterToolsCommand(program);\nregisterMcpRunCommand(program);\nregisterAiCommand(program);\nregisterGitnexusCommand(program);\nregisterPackCommand(program);\nregisterAddCommand(program);\nregisterUninstallCommand(program);\n\nprogram.parseAsync(process.argv).catch((err: unknown) => {\n // Top-level error sink. Individual commands should handle their own errors;\n // anything reaching here is unexpected.\n const msg = err instanceof Error ? err.message : String(err);\n process.stderr.write(`✗ Lỗi không xử lý được: ${msg}\\n`);\n process.exit(1);\n});\n","import { resolve } from \"node:path\";\n// `avatar add repo` — v3. Cài repo code vào src/<name> bằng git clone.\n//\n// 3 nguồn: ① repo có sẵn (URL), ② thư mục đã có (clone từ remote của nó),\n// ③ dự án mới (tạo repo GitHub rồi clone). Sau clone: ghi repos.json, hỏi\n// GitNexus index per-repo + wiki. Loop add tiếp.\nimport { confirm, input, select } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport {\n folderDirtyStatus,\n inferRepoNameFromUrl,\n readFolderRemoteUrl,\n} from \"../lib/clone-code-repo-into-src.js\";\nimport { cloneRepoWithRecovery } from \"../lib/clone-repo-with-recovery-menu.js\";\nimport { detectGitnexusInstallation } from \"../lib/detect-gitnexus-installation.js\";\nimport { ensureGitHubReady } from \"../lib/git-auth-and-install-orchestrator.js\";\nimport { resolveAvatarWorkspaceRootFromCwd } from \"../lib/resolve-avatar-workspace-root-from-cwd.js\";\nimport { runGitnexusAnalyze } from \"../lib/run-gitnexus-setup-and-analyze.js\";\nimport { runGitnexusWikiConditional } from \"../lib/run-gitnexus-wiki-conditional.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\nexport function registerAddCommand(program: Command): void {\n const add = program.command(\"add\").description(\"Thêm tài nguyên vào workspace\");\n add\n .command(\"repo\")\n .description(\"Clone 1 repo code vào src/ (repo có sẵn / thư mục / dự án mới)\")\n .option(\"--url <url>\", \"URL git repo có sẵn (bỏ qua prompt nguồn)\")\n .option(\"--name <name>\", \"Tên thư mục trong src/ (mặc định suy từ URL)\")\n .option(\"--yes\", \"Auto-confirm prompt\")\n .action(async (opts: { url?: string; name?: string; yes?: boolean }) => {\n try {\n await runAddRepo(opts);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nfunction ensureWorkspace(): string {\n const root = resolveAvatarWorkspaceRootFromCwd(process.cwd());\n if (!root) {\n log.error(\n \"Không tìm thấy Avatar workspace từ thư mục hiện tại.\\n Cần .claude/ + CLAUDE.md + src/. Chạy 'avatar init' trước.\",\n );\n process.exit(1);\n }\n return root;\n}\n\nasync function runAddRepo(opts: { url?: string; name?: string; yes?: boolean }): Promise<void> {\n const workspaceRoot = ensureWorkspace();\n\n let addMore = true;\n let firstUrl = opts.url;\n let firstName = opts.name;\n\n while (addMore) {\n const url = await resolveRepoUrl(firstUrl);\n const name = firstName ?? (await promptRepoName(url));\n\n log.info(`Clone ${url} → src/${name} ...`);\n // Clone với recovery menu: repo not found / permission / network → menu\n // retry / nhập URL khác / switch account / bỏ qua (thay vì git văng lỗi thô).\n const { cloned, skipped } = await cloneRepoWithRecovery({ workspaceRoot, url, name });\n\n if (skipped || !cloned) {\n log.dim(`Bỏ qua repo ${name}.`);\n } else {\n log.success(`✓ Đã clone src/${cloned.name}`);\n await maybeIndexRepo(workspaceRoot, cloned.path, opts.yes);\n log.success(`✓ Done repo: ${cloned.name}`);\n }\n\n // Reset cho vòng sau + hỏi add tiếp.\n firstUrl = undefined;\n firstName = undefined;\n if (opts.yes) break;\n addMore = await confirm({ message: \"Add repo khác nữa?\", default: false });\n }\n}\n\n// Resolve URL: nếu có --url thì dùng; else hỏi nguồn (3 lựa chọn).\nasync function resolveRepoUrl(presetUrl?: string): Promise<string> {\n if (presetUrl) return presetUrl;\n\n const source = await select({\n message: \"Nguồn repo?\",\n choices: [\n { name: \"1. Repo có sẵn (điền URL git)\", value: \"url\" as const },\n { name: \"2. Thư mục đã có trên máy (clone từ remote của nó)\", value: \"folder\" as const },\n { name: \"3. Dự án mới hoàn toàn (tạo repo GitHub)\", value: \"new\" as const },\n ],\n });\n\n if (source === \"url\") {\n return await input({\n message: \"URL git repo:\",\n validate: (v) => (v.trim().length > 0 ? true : \"URL bắt buộc\"),\n });\n }\n\n if (source === \"folder\") {\n const folderPath = resolve(\n await input({\n message: \"Đường dẫn folder:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Path bắt buộc\"),\n }),\n );\n // P9: cảnh báo nếu folder có uncommitted changes (clone-from-remote sẽ KHÔNG có chúng).\n const dirty = await folderDirtyStatus(folderPath);\n if (dirty) {\n log.warn(`⚠ Folder có thay đổi chưa commit: ${dirty.slice(0, 120)}`);\n log.warn(\" Clone lấy bản REMOTE → các thay đổi local này sẽ KHÔNG có trong src/<name>.\");\n const proceed = await confirm({\n message: \"Tiếp tục clone từ remote (bỏ thay đổi local chưa push)?\",\n default: false,\n });\n if (!proceed) {\n throw new Error(\n \"Hủy. Commit + push thay đổi trong folder gốc rồi chạy lại 'avatar add repo'.\",\n );\n }\n }\n const remote = await readFolderRemoteUrl(folderPath);\n if (remote) return remote;\n\n // Folder không có remote → offer tạo remote GitHub + push, thay vì bắt user\n // tự làm ngoài flow. ensureFolderHasRemote tạo repo + push code lên + trả URL.\n return await ensureFolderHasRemote(folderPath);\n }\n\n // source === \"new\": tạo repo GitHub mới rồi clone.\n await ensureGitHubReady();\n const projectName = await input({\n message: \"Tên repo mới:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Tên bắt buộc\"),\n });\n const visibility = (await select({\n message: \"Visibility?\",\n choices: [\n { name: \"private (mặc định)\", value: \"private\" as const },\n { name: \"public\", value: \"public\" as const },\n ],\n })) as \"private\" | \"public\";\n\n // Tạo repo rỗng trên GitHub (không cần folder source — dùng gh repo create + clone).\n const { createEmptyGithubRepoAndGetUrl } = await import(\"../lib/create-empty-github-repo.js\");\n return await createEmptyGithubRepoAndGetUrl(projectName.trim(), visibility);\n}\n\n// Folder chưa có remote → offer tạo repo GitHub + push code lên, trả URL remote.\n// Thay vì bắt user tự `gh repo create` ngoài flow rồi quay lại.\nasync function ensureFolderHasRemote(folderPath: string): Promise<string> {\n const { basename } = await import(\"node:path\");\n log.warn(`Folder ${folderPath} chưa có remote origin.`);\n const create = await confirm({\n message: \"Tạo GitHub repo cho folder này + push code lên?\",\n default: true,\n });\n if (!create) {\n throw new Error(\n \"Hủy. Cần remote để add repo. Tự tạo remote (gh repo create) rồi chạy lại, hoặc chọn nguồn khác.\",\n );\n }\n\n await ensureGitHubReady();\n\n // Đảm bảo folder là git repo + có ít nhất 1 commit (gh repo create --source --push cần).\n const { git } = await import(\"../lib/git-operations.js\");\n const { pathExists } = await import(\"../lib/filesystem-helpers.js\");\n const g = git(folderPath);\n if (!(await pathExists(`${folderPath}/.git`))) {\n log.info(\"Folder chưa git init — đang init...\");\n await g.init();\n }\n // Stage + commit mọi thứ. Nếu nothing-to-commit (đã clean + đã có commit) → catch bỏ qua.\n log.info(\"Commit code hiện tại trong folder (baseline cho push)...\");\n await g.add(\".\");\n await g.commit(\"chore: initial commit (avatar add repo)\").catch(() => {\n // nothing to commit — folder đã clean + có commit từ trước.\n });\n\n const repoName = await input({\n message: \"Tên repo GitHub:\",\n default: basename(folderPath),\n });\n const visibility = (await select({\n message: \"Visibility?\",\n choices: [\n { name: \"private (mặc định)\", value: \"private\" as const },\n { name: \"public\", value: \"public\" as const },\n ],\n })) as \"private\" | \"public\";\n\n const { createGithubRemoteFromFolder } = await import(\n \"../lib/create-github-remote-from-folder.js\"\n );\n const urls = createGithubRemoteFromFolder({\n folder: folderPath,\n name: repoName.trim(),\n visibility,\n });\n log.success(`✓ Đã tạo remote + push: ${urls.sshUrl}`);\n return urls.sshUrl;\n}\n\nasync function promptRepoName(url: string): Promise<string> {\n const inferred = inferRepoNameFromUrl(url);\n return await input({ message: \"Tên thư mục trong src/:\", default: inferred });\n}\n\n// Hỏi GitNexus index repo này (per-repo, P7). Sau index, nếu có key AI thì hỏi\n// tạo wiki cho repo. Chỉ khi gitnexus đã cài.\n// workspaceRoot: đọc .claude/settings.json (credentials AI cho wiki)\n// repoPath: src/<name> — nơi analyze + chạy wiki\nasync function maybeIndexRepo(\n workspaceRoot: string,\n repoPath: string,\n autoYes?: boolean,\n): Promise<void> {\n const info = detectGitnexusInstallation();\n if (!info.installed) {\n log.dim(\"GitNexus chưa cài — skip index. Cài qua 'avatar gitnexus install'.\");\n return;\n }\n const shouldIndex = autoYes\n ? true\n : await confirm({ message: \"Index repo này bằng GitNexus?\", default: true });\n if (!shouldIndex) return;\n\n try {\n runGitnexusAnalyze(repoPath);\n log.success(` ✓ GitNexus indexed src/${repoPath.split(\"/\").pop()}`);\n } catch (err) {\n log.warn(\n ` ! GitNexus index fail (repo vẫn dùng được): ${err instanceof Error ? err.message : err}`,\n );\n return; // index fail → không tạo wiki được (wiki cần index).\n }\n\n // Sau index OK: nếu có key AI (settings.json) → hỏi tạo wiki cho repo.\n // runGitnexusWikiConditional tự detect provider + key, tự confirm (cost prompt),\n // tự skip nếu subscription mode (không key). Wiki tạo trong <repo>/.gitnexus/wiki/.\n try {\n // settings ở workspaceRoot, wiki chạy trong repoPath (src/<name>).\n const wiki = await runGitnexusWikiConditional(workspaceRoot, repoPath);\n if (wiki.ran) {\n log.success(` ✓ GitNexus wiki tạo cho src/${repoPath.split(\"/\").pop()}`);\n } else if (wiki.reason === \"subscription-mode\") {\n log.dim(\" (Subscription mode — không có API key cho wiki, skip.)\");\n }\n } catch (err) {\n log.warn(` ! Wiki fail (repo vẫn dùng được): ${err instanceof Error ? err.message : err}`);\n }\n}\n","import { spawnSync } from \"node:child_process\";\n// clone-repo-with-recovery-menu.ts\n//\n// v3: Wrap clone repo vào src/ với recovery menu khi gặp vấn đề (repo not found,\n// permission denied, network). Thay vì để git văng lỗi thô + crash, verify remote\n// TRƯỚC clone → classify lỗi → menu: retry / nhập URL khác / switch account / bỏ.\n//\n// Cũng dọn thư mục src/<name> dở nếu clone fail giữa chừng (tránh \"đã tồn tại\"\n// khi user retry).\nimport { join } from \"node:path\";\nimport { confirm, input, select } from \"@inquirer/prompts\";\nimport { type CloneRepoResult, cloneCodeRepoIntoSrc } from \"./clone-code-repo-into-src.js\";\nimport { removeRecursive } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { tryVerifyGitRemoteAccessible } from \"./verify-git-remote-accessible.js\";\n\n// Hướng dẫn theo từng loại lỗi remote.\nfunction explainRemoteFailure(url: string, reason: string, detail?: string): void {\n log.warn(`⛔ Không clone được ${url}`);\n switch (reason) {\n case \"not-found\":\n log.dim(\" → Repo không tồn tại (sai URL, hoặc repo chưa được tạo trên GitHub).\");\n break;\n case \"no-access\":\n log.dim(\n \" → Không có quyền truy cập (repo private + sai account), HOẶC repo không tồn tại.\\n\" +\n \" Kiểm tra: đang login đúng GitHub account chưa? (gh auth status)\",\n );\n break;\n case \"network\":\n log.dim(\" → Lỗi mạng (mất kết nối / DNS / timeout).\");\n break;\n default:\n log.dim(` → Lỗi không xác định${detail ? `: ${detail}` : \"\"}.`);\n }\n}\n\n// Trigger gh auth login interactive để switch account.\nfunction triggerGhAuthLogin(): void {\n log.info(\"Mở browser để switch GitHub account...\");\n const r = spawnSync(\"gh\", [\"auth\", \"login\", \"--web\"], { stdio: \"inherit\" });\n if (r.status !== 0) {\n log.warn(`gh auth login exit ${r.status}. Có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Menu recovery khi clone fail. Trả action user chọn.\nasync function promptCloneRecovery(\n reason: string,\n): Promise<\"retry\" | \"new-url\" | \"switch-account\" | \"skip\"> {\n const choices: Array<{ name: string; value: string }> = [\n { name: \"Thử lại (retry)\", value: \"retry\" },\n { name: \"Nhập URL repo khác\", value: \"new-url\" },\n ];\n // Chỉ offer switch account khi lỗi liên quan quyền truy cập.\n if (reason === \"no-access\") {\n choices.push({\n name: \"Switch GitHub account (gh auth login — mở browser)\",\n value: \"switch-account\",\n });\n }\n choices.push({ name: \"Bỏ qua repo này\", value: \"skip\" });\n\n return (await select({ message: \"Cách xử lý?\", choices })) as\n | \"retry\"\n | \"new-url\"\n | \"switch-account\"\n | \"skip\";\n}\n\nexport interface CloneWithRecoveryResult {\n cloned: CloneRepoResult | null; // null nếu user skip.\n skipped: boolean;\n}\n\n// Clone repo vào src/<name> với recovery menu. url/name có thể đổi qua menu.\n// Trả { cloned, skipped } — skipped=true nếu user chọn \"Bỏ qua repo này\".\nexport async function cloneRepoWithRecovery(args: {\n workspaceRoot: string;\n url: string;\n name: string;\n}): Promise<CloneWithRecoveryResult> {\n let url = args.url;\n let name = args.name;\n\n while (true) {\n // 1. Verify remote accessible TRƯỚC clone (tránh git văng lỗi thô + thư mục dở).\n const verify = tryVerifyGitRemoteAccessible(url);\n if (!verify.ok) {\n explainRemoteFailure(url, verify.reason ?? \"unknown\", verify.detail);\n const action = await promptCloneRecovery(verify.reason ?? \"unknown\");\n\n if (action === \"skip\") {\n log.dim(`Bỏ qua repo ${name}.`);\n return { cloned: null, skipped: true };\n }\n if (action === \"switch-account\") {\n triggerGhAuthLogin();\n continue; // re-verify với account mới.\n }\n if (action === \"new-url\") {\n url = await input({\n message: \"URL repo khác:\",\n validate: (v) => (v.trim().length > 0 ? true : \"URL bắt buộc\"),\n });\n // Hỏi đổi tên không (URL mới có thể tên khác).\n const renameToo = await confirm({\n message: `Đổi tên thư mục trong src/ theo URL mới? (hiện: ${name})`,\n default: false,\n });\n if (renameToo) {\n const { inferRepoNameFromUrl } = await import(\"./clone-code-repo-into-src.js\");\n name = await input({ message: \"Tên thư mục:\", default: inferRepoNameFromUrl(url) });\n }\n continue;\n }\n // action === \"retry\" → loop lại verify.\n continue;\n }\n\n // 2. Remote OK → clone. Vẫn try/catch phòng clone fail sau verify (race).\n try {\n const cloned = await cloneCodeRepoIntoSrc({ workspaceRoot: args.workspaceRoot, url, name });\n return { cloned, skipped: false };\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`Clone fail: ${message}`);\n // Dọn thư mục src/<name> dở (nếu git clone tạo ra rồi fail).\n await removeRecursive(join(args.workspaceRoot, \"src\", name)).catch(() => {});\n\n const action = await promptCloneRecovery(\"unknown\");\n if (action === \"skip\") return { cloned: null, skipped: true };\n if (action === \"switch-account\") {\n triggerGhAuthLogin();\n continue;\n }\n if (action === \"new-url\") {\n url = await input({\n message: \"URL repo khác:\",\n validate: (v) => (v.trim().length > 0 ? true : \"URL bắt buộc\"),\n });\n continue;\n }\n // retry → loop.\n }\n }\n}\n","// Verify một remote URL có accessible không bằng `git ls-remote <url> HEAD`\n// với timeout 5 giây. Tách ra để init flow check sớm — fail-fast nếu URL sai.\n//\n// v1.2.4: Capture stderr để caller diagnose root cause (private no access\n// vs URL wrong vs network). Thêm `tryVerifyGitRemoteAccessible` non-throwing\n// variant trả structured result cho UX recovery flow.\nimport { spawnSync } from \"node:child_process\";\n\nconst TIMEOUT_MS = 5_000;\n\nexport type RemoteFailReason =\n | \"timeout\"\n | \"no-access\" // 403/auth required (private repo + wrong user)\n | \"not-found\" // 404 URL wrong hoặc repo bị xóa\n | \"network\" // DNS / unreachable\n | \"unknown\";\n\nexport interface RemoteAccessResult {\n ok: boolean;\n reason?: RemoteFailReason;\n detail?: string; // stderr sample để log debug\n}\n\nexport class RemoteNotAccessibleError extends Error {\n reason: RemoteFailReason;\n detail?: string;\n url: string;\n constructor(url: string, reason: RemoteFailReason, detail?: string) {\n super(`Không truy cập được remote ${url}: ${reason}${detail ? ` (${detail})` : \"\"}`);\n this.name = \"RemoteNotAccessibleError\";\n this.reason = reason;\n this.detail = detail;\n this.url = url;\n }\n}\n\n// Classify git ls-remote stderr thành reason code chuẩn hóa.\n// Patterns dựa theo git output thực tế trên GitHub HTTPS auth-required scenarios.\nfunction classifyRemoteError(stderr: string): RemoteFailReason {\n const text = stderr.toLowerCase();\n if (\n text.includes(\"authentication\") ||\n text.includes(\"could not read username\") ||\n text.includes(\"permission denied\") ||\n text.includes(\"403\") ||\n text.includes(\"access denied\") ||\n text.includes(\"repository not found\") // GitHub trả \"not found\" cho cả private no-access (bảo mật)\n ) {\n return \"no-access\";\n }\n if (text.includes(\"404\") || text.includes(\"does not exist\")) {\n return \"not-found\";\n }\n if (\n text.includes(\"could not resolve host\") ||\n text.includes(\"network\") ||\n text.includes(\"connection refused\") ||\n text.includes(\"connection timed out\")\n ) {\n return \"network\";\n }\n return \"unknown\";\n}\n\n// Non-throwing variant — trả structured result cho caller decide flow.\n// Recommend dùng cho flow cần fallback recovery (vd account switch).\n//\n// v1.13.0 fix: Check `r.error` TRƯỚC khi classify. spawnSync trả error khi:\n// - Binary not found (ENOENT) — git chưa cài\n// - Permission denied (EACCES)\n// - Killed by signal khác SIGTERM\n// Trước fix: r.status=null + stderr=\"\" → classify(\"\") → \"unknown\" → user thấy\n// \"Lỗi không xác định\" thay vì \"git chưa cài\". Pattern này đã chuẩn ở\n// check-claude-code-subscription-and-quota.ts:38, sync hóa cho consistency.\nexport function tryVerifyGitRemoteAccessible(url: string): RemoteAccessResult {\n const r = spawnSync(\"git\", [\"ls-remote\", \"--exit-code\", url, \"HEAD\"], {\n encoding: \"utf8\",\n timeout: TIMEOUT_MS,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.error) {\n const err = r.error as NodeJS.ErrnoException;\n if (err.code === \"ENOENT\") {\n return {\n ok: false,\n reason: \"network\",\n detail: \"git binary không tìm thấy — cài git rồi retry\",\n };\n }\n if (err.code === \"ETIMEDOUT\") {\n return { ok: false, reason: \"timeout\", detail: `git ls-remote > ${TIMEOUT_MS / 1000}s` };\n }\n return { ok: false, reason: \"unknown\", detail: err.message.slice(0, 300) };\n }\n if (r.status === 0) return { ok: true };\n if (r.signal === \"SIGTERM\") {\n return { ok: false, reason: \"timeout\", detail: `git ls-remote > ${TIMEOUT_MS / 1000}s` };\n }\n const stderr = (r.stderr || \"\").trim();\n const reason = classifyRemoteError(stderr);\n return { ok: false, reason, detail: stderr.slice(0, 300) };\n}\n\n// Throwing variant — backward compat cho existing callers.\n// Caller catch RemoteNotAccessibleError nếu cần handle.\nexport function verifyGitRemoteAccessible(url: string): void {\n const result = tryVerifyGitRemoteAccessible(url);\n if (result.ok) return;\n throw new RemoteNotAccessibleError(url, result.reason ?? \"unknown\", result.detail);\n}\n","// Detect GitNexus CLI (\"gitnexus\" binary) trên PATH + parse SemVer version.\n// Trả structured info để các module sau (install / setup / analyze) decide flow.\n// Cross-platform: Unix dùng `which`, Windows dùng `where`.\n//\n// Pattern identical với detect-claude-code-installation.ts — reuse design.\nimport { spawnSync } from \"node:child_process\";\nimport { detectHostPlatform } from \"./detect-host-platform.js\";\n\nexport interface GitnexusInstallationInfo {\n installed: boolean;\n version: string | null; // SemVer string khi detect được, vd \"1.6.5\"\n path: string | null; // Absolute path đến binary nếu tìm thấy\n}\n\n// Timeout cho `gitnexus --version` — nếu binary hang thì abort tránh block init.\nconst VERSION_PROBE_TIMEOUT_MS = 5000;\n\n// Regex SemVer chấp nhận extra suffix vd \"1.6.5 (GitNexus)\" hoặc \"gitnexus 1.6.5\".\nconst SEMVER_REGEX = /(\\d+\\.\\d+\\.\\d+)/;\n\n// Probe binary location qua `which` (POSIX) hoặc `where` (Windows).\n// Cả 2 đều là external executable → KHÔNG cần shell:true → tránh DEP0190 Node 23+.\nfunction probeGitnexusBinaryPath(): string | null {\n const isWindows = detectHostPlatform() === \"win32\";\n const probeCmd = isWindows ? \"where\" : \"which\";\n const result = spawnSync(probeCmd, [\"gitnexus\"], { encoding: \"utf8\" });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n if (!out) return null;\n // `where` có thể list nhiều dòng — lấy dòng đầu.\n // out đã verified non-empty (line 30) → split[0] luôn tồn tại.\n return out.split(/\\r?\\n/)[0]!.trim();\n}\n\n// Parse `gitnexus --version` output → SemVer. Null nếu không match.\nfunction probeGitnexusVersion(): string | null {\n const result = spawnSync(\"gitnexus\", [\"--version\"], {\n encoding: \"utf8\",\n timeout: VERSION_PROBE_TIMEOUT_MS,\n });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n const match = SEMVER_REGEX.exec(out);\n return match?.[1] ?? null;\n}\n\n// Main API: detect GitNexus installation state.\n// Không throw — luôn return structured info để caller branch logic.\n//\n// v1.13.0 perf: Module-level cache. Init flow gọi 2-3 lần → save 500ms-1.5s.\n// Caller invalidate qua `invalidateGitnexusInstallationCache()` sau install.\nlet cachedInfo: GitnexusInstallationInfo | null = null;\n\nexport function detectGitnexusInstallation(): GitnexusInstallationInfo {\n if (cachedInfo !== null) return cachedInfo;\n const path = probeGitnexusBinaryPath();\n if (!path) {\n cachedInfo = { installed: false, version: null, path: null };\n return cachedInfo;\n }\n const version = probeGitnexusVersion();\n cachedInfo = { installed: true, version, path };\n return cachedInfo;\n}\n\nexport function invalidateGitnexusInstallationCache(): void {\n cachedInfo = null;\n}\n","// Wrapper mỏng quanh os.platform() để các module khác switch theo OS dễ hơn\n// và test dễ mock.\nimport { platform } from \"node:os\";\n\nexport type HostPlatform = \"darwin\" | \"linux\" | \"win32\" | \"unsupported\";\n\n// Trả về tên platform chuẩn hoá. Mọi giá trị khác 3 OS chính → \"unsupported\".\nexport function detectHostPlatform(): HostPlatform {\n const p = platform();\n if (p === \"darwin\" || p === \"linux\" || p === \"win32\") return p;\n return \"unsupported\";\n}\n","// Kiểm tra gh CLI đã login chưa. `gh auth status` exit 0 = OK.\n// Không parse stdout vì format thay đổi giữa các bản gh.\nimport { spawnSync } from \"node:child_process\";\n\nexport type GhAuthState = \"not-installed\" | \"not-authenticated\" | \"authenticated\";\n\nexport function checkGhCliAuthStatus(): GhAuthState {\n // Probe binary trước. spawnSync với gh không tồn tại trả ENOENT.\n const r = spawnSync(\"gh\", [\"auth\", \"status\"], { stdio: \"ignore\" });\n if (r.error && (r.error as NodeJS.ErrnoException).code === \"ENOENT\") {\n return \"not-installed\";\n }\n return r.status === 0 ? \"authenticated\" : \"not-authenticated\";\n}\n","// Detect package manager để cài binary hệ thống (gh CLI). Order matters:\n// brew (macOS preferred) → winget (Windows) → apt/dnf/pacman (Linux distros).\n// Returns null nếu không tìm được PM nào — caller phải fail-fast.\nimport { spawnSync } from \"node:child_process\";\nimport { detectHostPlatform } from \"./detect-host-platform.js\";\n\nexport type PackageManager = \"brew\" | \"apt\" | \"dnf\" | \"pacman\" | \"winget\";\n\n// Probe binary có trong PATH không. Dùng `command -v` (POSIX) hoặc `where` (Win).\nfunction hasBinary(name: string): boolean {\n const platform = detectHostPlatform();\n const probe = platform === \"win32\" ? \"where\" : \"command\";\n const args = platform === \"win32\" ? [name] : [\"-v\", name];\n // spawnSync với shell=true để `command -v` (builtin) hoạt động.\n const r = spawnSync(probe, args, {\n shell: platform !== \"win32\",\n stdio: \"ignore\",\n });\n return r.status === 0;\n}\n\n// Trả về PM đầu tiên có sẵn theo thứ tự ưu tiên phù hợp với OS.\nexport function detectPackageManager(): PackageManager | null {\n const platform = detectHostPlatform();\n const candidates: PackageManager[] =\n platform === \"darwin\"\n ? [\"brew\"]\n : platform === \"win32\"\n ? [\"winget\"]\n : platform === \"linux\"\n ? [\"apt\", \"dnf\", \"pacman\"]\n : [];\n for (const pm of candidates) {\n if (hasBinary(pm)) return pm;\n }\n return null;\n}\n","// Recovery handler khi verify remote fail — thay vì throw văng init, prompt user\n// xử lý phù hợp với reason code:\n// - no-access: có thể wrong gh account → offer switch (gh auth login)\n// - not-found: URL sai → user re-nhập URL đúng (v1.2.7)\n// - network: user check mạng/VPN, retry\n// - timeout: tương tự network\n//\n// Loop retry cho phép user thử nhiều lần (switch account, re-nhập URL, accept invite, ...).\n// Return { resolvedUrl } khi access OK (URL có thể khác input nếu user re-nhập).\n// Throw RemoteAccessAbortedError khi user abort.\n//\n// v1.2.7: Thêm option \"Nhập lại URL\" để user fix URL sai mà không phải abort init.\nimport { spawnSync } from \"node:child_process\";\nimport { input, select } from \"@inquirer/prompts\";\nimport { resetFolderGitAndCreateNewRemoteUnderCurrentUser } from \"./reset-folder-git-and-create-new-remote-under-current-user.js\";\nimport { log } from \"./terminal-logger.js\";\nimport {\n type RemoteFailReason,\n tryVerifyGitRemoteAccessible,\n} from \"./verify-git-remote-accessible.js\";\n\n// Custom error báo user abort recovery — caller catch để exit 0 graceful\n// (giống pattern TeamPackAccessAbortedError v1.2.3).\nexport class RemoteAccessAbortedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"RemoteAccessAbortedError\";\n }\n}\n\n// Resolve gh user hiện tại (nếu có) để show context cho user khi prompt.\nfunction getCurrentGhUser(): string | null {\n const r = spawnSync(\"gh\", [\"api\", \"user\", \"--jq\", \".login\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.status !== 0) return null;\n return r.stdout.trim() || null;\n}\n\n// Trigger `gh auth login` interactive — block đến khi user complete browser.\n// Sau khi switch, gh CLI dùng account mới cho mọi command sau.\nfunction triggerGhAuthLoginInteractive(): void {\n log.info(\"Mở browser để switch GitHub account...\");\n const r = spawnSync(\"gh\", [\"auth\", \"login\", \"--web\"], { stdio: \"inherit\" });\n if (r.status !== 0) {\n log.warn(`gh auth login exit ${r.status}. Bạn có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Hint theo reason code — giúp user biết phải làm gì cụ thể.\nfunction getReasonHint(reason: RemoteFailReason, url: string, ghUser: string | null): string {\n switch (reason) {\n case \"no-access\":\n return ghUser\n ? `Repo có thể private và account '${ghUser}' không có quyền access. Switch sang account có quyền, hoặc xin invite từ owner repo.`\n : \"Repo có thể private và gh CLI chưa login. Login trước rồi retry.\";\n case \"not-found\":\n return `URL có thể sai chính tả, hoặc repo đã bị xóa/rename. Nhập lại URL đúng. Check: ${url}`;\n case \"network\":\n return \"Không kết nối được GitHub. Check mạng / VPN / firewall.\";\n case \"timeout\":\n return \"Network chậm > 5s. Check mạng rồi retry.\";\n default:\n return \"Lỗi không xác định. URL có thể sai — nhập lại, hoặc check gh auth status.\";\n }\n}\n\n// Validate URL format git remote (http/https/ssh github).\nfunction isValidGitUrl(url: string): boolean {\n const trimmed = url.trim();\n if (!trimmed) return false;\n return (\n /^https?:\\/\\/[\\w.@/-]+$/.test(trimmed) ||\n /^git@[\\w.-]+:[\\w./-]+\\.git$/.test(trimmed) ||\n /^[\\w.-]+\\/[\\w.-]+$/.test(trimmed) // owner/repo shorthand\n );\n}\n\n// Show menu recovery options theo reason. Loop cho đến khi user pick abort\n// hoặc remote accessible. Return resolved URL (có thể khác input nếu user re-nhập).\n// Throw RemoteAccessAbortedError nếu user abort.\n//\n// v1.2.7: Thêm option \"Nhập lại URL\" để user fix URL sai mà không phải abort init.\n// v1.12.0: Thêm option \"Reset & make folder mine\" — chỉ enable khi caller pass\n// folderPath (tức flow existing-folder). Cho phép user clone folder team khác\n// xuống làm khởi đầu project mới của mình mà không bị block bởi remote không access.\nexport async function handleRemoteAccessFailureWithAccountSwitch(args: {\n url: string;\n initialReason: RemoteFailReason;\n initialDetail?: string;\n // v1.12.0: Khi caller pass folderPath (existing-folder flow), enable option\n // \"Reset folder & tạo repo mới\". Khi không có (existing-remote flow), folder\n // chưa tồn tại nên option này không áp dụng.\n folderPath?: string;\n // v1.12.0: Skip prompt khi caller đã có visibility từ --repo-visibility.\n defaultVisibility?: \"private\" | \"public\";\n}): Promise<{ resolvedUrl: string }> {\n let currentUrl = args.url;\n let reason = args.initialReason;\n let detail = args.initialDetail;\n\n while (true) {\n const ghUser = getCurrentGhUser();\n log.warn(`Không truy cập được ${currentUrl}`);\n log.dim(` Lý do: ${reason}${detail ? ` — ${detail.slice(0, 150)}` : \"\"}`);\n log.info(getReasonHint(reason, currentUrl, ghUser));\n if (ghUser) log.dim(` gh CLI hiện đang login: ${ghUser}`);\n\n // Choices base — 4 option có sẵn từ v1.2.7.\n const choices: Array<{\n name: string;\n value: \"re-input-url\" | \"switch\" | \"retry\" | \"reset-folder\" | \"abort\";\n }> = [\n {\n name: \"Nhập lại URL đúng (recommended khi URL sai chính tả)\",\n value: \"re-input-url\",\n },\n {\n name: \"Switch GitHub account (gh auth login — mở browser)\",\n value: \"switch\",\n },\n {\n name: \"Tôi vừa fix (accept invite / sửa permission) — retry verify\",\n value: \"retry\",\n },\n ];\n\n // v1.12.0: Conditional option — chỉ show khi caller pass folderPath.\n // Common case: clone folder team khác xuống, không có quyền push lên repo gốc,\n // muốn dùng làm khởi đầu project riêng.\n if (args.folderPath) {\n choices.push({\n name: \"Reset folder & tạo repo MỚI dưới account của tôi (backup .git cũ)\",\n value: \"reset-folder\",\n });\n }\n\n choices.push({\n name: \"Tạm ngưng init — chạy lại 'avatar init' sau\",\n value: \"abort\",\n });\n\n const action = await select({ message: \"Cách xử lý?\", choices });\n\n if (action === \"abort\") {\n throw new RemoteAccessAbortedError(\n `User chọn tạm ngưng. Fix access ${currentUrl} rồi chạy lại 'avatar init'.`,\n );\n }\n\n if (action === \"reset-folder\") {\n // Defensive: folderPath required cho branch này — đã guard ở choices push,\n // nhưng TS không narrow qua select() callback.\n if (!args.folderPath) {\n log.warn(\"Reset folder cần folderPath — internal error.\");\n continue;\n }\n try {\n const reset = await resetFolderGitAndCreateNewRemoteUnderCurrentUser({\n folderPath: args.folderPath,\n visibility: args.defaultVisibility,\n });\n log.success(`Folder đã reset. Backup tại: ${reset.backupPath}`);\n log.success(`Remote mới: ${reset.newRemoteUrl}`);\n return { resolvedUrl: reset.newRemoteUrl };\n } catch (err) {\n log.warn(`Reset folder thất bại: ${(err as Error).message}`);\n // Loop tiếp — user có thể chọn option khác.\n continue;\n }\n }\n\n if (action === \"re-input-url\") {\n const newUrl = await input({\n message:\n \"URL git remote (https://github.com/owner/repo.git hoặc git@github.com:owner/repo.git):\",\n default: currentUrl,\n validate: (v) => isValidGitUrl(v) || \"URL không đúng format git remote\",\n });\n currentUrl = newUrl.trim();\n }\n\n if (action === \"switch\") {\n triggerGhAuthLoginInteractive();\n }\n\n // Re-verify (cả 3 case re-input-url + switch + retry).\n log.info(`Verify remote lại: ${currentUrl}...`);\n const result = tryVerifyGitRemoteAccessible(currentUrl);\n if (result.ok) {\n log.success(`Remote accessible: ${currentUrl}`);\n return { resolvedUrl: currentUrl };\n }\n // Fail tiếp — update reason + loop.\n reason = result.reason ?? \"unknown\";\n detail = result.detail;\n }\n}\n","// Recovery action cho scenario \"folder code clone từ team khác, user không có\n// quyền push lên repo gốc nhưng muốn dùng folder làm khởi đầu cho project mới\n// của mình\".\n//\n// Flow:\n// 1. Backup .git → .git.backup-{YYMMDD-HHMM} (safe undo nếu nhầm)\n// 2. git init mới trên folder\n// 3. Optional: gh repo create dưới owner mới (current gh user hoặc org)\n// 4. Trả URL mới để caller dùng cho submodule add\n//\n// User trigger qua menu trong handleRemoteAccessFailureWithAccountSwitch khi\n// detect \"no-access\" trên existing-folder flow.\n\nimport { spawnSync } from \"node:child_process\";\nimport { promises as fs } from \"node:fs\";\nimport { basename, join } from \"node:path\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport { createGithubRemoteFromFolder } from \"./create-github-remote-from-folder.js\";\nimport { pathExists } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\nimport type { RepoVisibility } from \"./validate-repo-name-and-visibility.js\";\n\n// Timestamp YYMMDD-HHMM cho backup folder — readable + sortable.\nfunction backupTimestamp(): string {\n const d = new Date();\n return (\n `${d.getFullYear().toString().slice(-2)}` +\n `${String(d.getMonth() + 1).padStart(2, \"0\")}` +\n `${String(d.getDate()).padStart(2, \"0\")}-` +\n `${String(d.getHours()).padStart(2, \"0\")}` +\n `${String(d.getMinutes()).padStart(2, \"0\")}`\n );\n}\n\nexport interface ResetFolderResult {\n newRemoteUrl: string; // HTTPS URL của repo mới\n backupPath: string; // Path tới .git.backup-{stamp}\n}\n\n// Backup .git → .git.backup-{stamp} thay vì xóa hẳn.\n// User restore được nếu nhầm: `mv .git.backup-XXX .git`.\nasync function backupExistingDotGit(folderPath: string): Promise<string> {\n const gitDir = join(folderPath, \".git\");\n if (!(await pathExists(gitDir))) {\n throw new Error(`.git không tồn tại ở ${folderPath} — không cần reset.`);\n }\n const backupName = `.git.backup-${backupTimestamp()}`;\n const backupPath = join(folderPath, backupName);\n await fs.rename(gitDir, backupPath);\n log.success(`Backup .git → ${backupName}`);\n return backupPath;\n}\n\n// git init + add all + initial commit để folder ready cho gh repo create.\nfunction reinitGitInFolder(folderPath: string): void {\n const r1 = spawnSync(\"git\", [\"-C\", folderPath, \"init\", \"-b\", \"main\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r1.status !== 0) {\n throw new Error(`git init thất bại: ${r1.stderr || r1.stdout}`);\n }\n log.success(\"Git init mới (branch main)\");\n\n // Stage all + commit để gh repo create có thể push.\n spawnSync(\"git\", [\"-C\", folderPath, \"add\", \"-A\"], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n const r3 = spawnSync(\n \"git\",\n [\"-C\", folderPath, \"commit\", \"-m\", \"chore: initial commit from existing folder\"],\n { encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"pipe\"] },\n );\n if (r3.status !== 0) {\n // Commit fail OK nếu folder rỗng — log warn không throw.\n log.warn(`git commit thất bại (folder có thể rỗng): ${(r3.stderr || \"\").slice(0, 200)}`);\n } else {\n log.success(\"Initial commit\");\n }\n}\n\n// Main entry: backup .git + reinit + tạo remote mới + return URL.\n//\n// repoName: default = basename(folderPath). Caller có thể override qua opts.\n// autoYes=true: skip mọi confirm (CI mode) — visibility default \"private\".\nexport async function resetFolderGitAndCreateNewRemoteUnderCurrentUser(opts: {\n folderPath: string;\n repoName?: string;\n visibility?: RepoVisibility;\n org?: string; // default = current gh user\n autoYes?: boolean;\n}): Promise<ResetFolderResult> {\n const folderName = basename(opts.folderPath);\n const repoName = opts.repoName ?? folderName;\n\n // Confirm trước khi destructive op — clear UX về backup behavior.\n if (!opts.autoYes) {\n const confirmed = await confirm({\n message: `Folder '${folderName}' sẽ được reset:\\n 1. Backup .git → .git.backup-{timestamp}\\n 2. Git init mới (branch main, initial commit)\\n 3. Tạo GitHub repo mới '${repoName}' dưới account của bạn\\nTiếp tục?`,\n default: true,\n });\n if (!confirmed) {\n throw new Error(\"User abort reset folder.\");\n }\n }\n\n const visibility =\n opts.visibility ??\n (opts.autoYes\n ? \"private\"\n : ((await select({\n message: \"Visibility cho repo mới?\",\n choices: [\n { name: \"private (mặc định)\", value: \"private\" as const },\n { name: \"public\", value: \"public\" as const },\n ],\n })) as RepoVisibility));\n\n // Step 1: Backup .git.\n const backupPath = await backupExistingDotGit(opts.folderPath);\n\n // Step 2: Reinit + commit.\n reinitGitInFolder(opts.folderPath);\n\n // Step 3: Tạo remote qua gh + push initial commit.\n const urls = createGithubRemoteFromFolder({\n folder: opts.folderPath,\n name: repoName,\n visibility,\n org: opts.org,\n });\n\n return {\n newRemoteUrl: urls.httpsUrl,\n backupPath,\n };\n}\n","// Cài `gh` CLI qua package manager đã detect. Stream stdio để user thấy progress.\n// Throws nếu cài fail hoặc PM trả non-zero.\nimport { spawnSync } from \"node:child_process\";\nimport type { PackageManager } from \"./detect-package-manager.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Map PM → command + args để cài gh. apt/dnf cần sudo; brew/pacman/winget thì không.\nconst INSTALL_COMMANDS: Record<PackageManager, { cmd: string; args: string[] }> = {\n brew: { cmd: \"brew\", args: [\"install\", \"gh\"] },\n apt: { cmd: \"sudo\", args: [\"apt-get\", \"install\", \"-y\", \"gh\"] },\n dnf: { cmd: \"sudo\", args: [\"dnf\", \"install\", \"-y\", \"gh\"] },\n pacman: { cmd: \"sudo\", args: [\"pacman\", \"-S\", \"--noconfirm\", \"github-cli\"] },\n winget: { cmd: \"winget\", args: [\"install\", \"--id\", \"GitHub.cli\", \"-e\", \"--silent\"] },\n};\n\nexport function installGhCliViaPackageManager(pm: PackageManager): void {\n const spec = INSTALL_COMMANDS[pm];\n log.info(`Đang cài gh CLI qua ${pm}...`);\n const r = spawnSync(spec.cmd, spec.args, { stdio: \"inherit\" });\n if (r.status !== 0) {\n throw new Error(`Cài gh CLI thất bại qua ${pm} (exit ${r.status}). Cài tay rồi chạy lại.`);\n }\n log.success(\"Đã cài gh CLI\");\n}\n","// Generic recovery prompt cho các bước có thể recover trong init flow.\n// Dùng pattern này thay vì throw thẳng để user luôn có choice (retry/skip/abort).\n//\n// 3 mode tùy task:\n// - retry-only: task không thể skip (vd login fail) → \"Try again\" / \"Abort\"\n// - retry-skip: task có thể bỏ qua (vd workspace remote fail) → \"Retry\" / \"Skip & continue\" / \"Abort\"\n// - manual-fix: task cần user tự fix tay (vd workspace path conflict) → \"Provide value\" / \"Abort\"\n//\n// Loop retry — caller gọi trong while loop, user chỉ exit khi pick abort hoặc success.\nimport { input, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\n// User chọn abort recovery — caller catch để exit 0 graceful.\nexport class UserAbortedRecoveryError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"UserAbortedRecoveryError\";\n }\n}\n\nexport type RecoveryAction = \"retry\" | \"skip\" | \"abort\";\n\ninterface PromptRetryOrSkipArgs {\n // Tiêu đề task fail (vd \"Cài đặt gh CLI\").\n taskName: string;\n // Lý do fail (extracted từ error message hoặc custom).\n reason: string;\n // Có cho phép skip task này không? false = chỉ Retry / Abort.\n allowSkip?: boolean;\n // Custom hint hiển thị trước menu (vd \"Check mạng, VPN\").\n hint?: string;\n}\n\n// Show menu Retry/Skip/Abort. Trả về action user chọn.\n// Caller responsibility: implement retry logic + handle skip semantic theo task.\nexport async function promptRetryOrSkip(args: PromptRetryOrSkipArgs): Promise<RecoveryAction> {\n log.warn(`${args.taskName} thất bại: ${args.reason}`);\n if (args.hint) log.info(args.hint);\n\n const choices: Array<{ name: string; value: RecoveryAction }> = [\n { name: \"Thử lại (Retry)\", value: \"retry\" },\n ];\n if (args.allowSkip) {\n choices.push({ name: \"Bỏ qua bước này và tiếp tục (Skip)\", value: \"skip\" });\n }\n choices.push({ name: \"Tạm ngưng init — chạy lại sau (Abort)\", value: \"abort\" });\n\n return (await select({\n message: \"Cách xử lý?\",\n choices,\n })) as RecoveryAction;\n}\n\n// Prompt user input manual value khi automated detection fail.\n// Vd: workspace name conflict → user tự nhập tên khác.\n// Trả về giá trị user nhập, hoặc throw UserAbortedRecoveryError nếu abort.\nexport async function promptManualValueOrAbort(args: {\n message: string;\n validate?: (v: string) => boolean | string;\n abortMessage?: string;\n}): Promise<string> {\n // Allow user nhập empty để abort (Ctrl+C vẫn work).\n // Hoặc nhập value hợp lệ.\n const value = await input({\n message: args.message,\n validate: args.validate ?? ((v) => v.trim().length > 0 || \"Giá trị không được rỗng\"),\n });\n if (!value.trim()) {\n throw new UserAbortedRecoveryError(\n args.abortMessage ?? \"User để rỗng giá trị — abort recovery.\",\n );\n }\n return value.trim();\n}\n","// Config git credential helper dùng gh token. Cần thiết để `git ls-remote`,\n// `git clone`, `git push` qua HTTPS hoạt động khi gh đã auth nhưng git chưa\n// biết về token đó. Idempotent — chạy nhiều lần OK.\nimport { spawnSync } from \"node:child_process\";\nimport { log } from \"./terminal-logger.js\";\n\nexport function setupGitCredentialViaGh(): void {\n const r = spawnSync(\"gh\", [\"auth\", \"setup-git\"], { stdio: \"ignore\" });\n if (r.status !== 0) {\n // Không throw — nếu setup-git fail, git operation sau có thể vẫn work\n // (vd user đã có credential helper khác). Chỉ warn.\n log.warn(\"gh auth setup-git fail (non-fatal). Nếu git clone lỗi 128 → chạy thủ công.\");\n return;\n }\n log.dim(\"Git credential helper đã link với gh token.\");\n}\n","// Orchestrator phase 2: đảm bảo gh CLI có và đã auth. Tự cài + tự login nếu cần.\n//\n// v1.2.4: Khi verify remote fail → KHÔNG throw văng. Call recovery handler\n// prompt user switch account / retry / abort.\n//\n// v1.2.5: gh CLI install fail + gh auth login fail giờ cũng có recovery prompt\n// (loop retry / skip / abort) thay vì throw thẳng.\nimport { checkGhCliAuthStatus } from \"./check-gh-cli-auth-status.js\";\nimport { detectPackageManager } from \"./detect-package-manager.js\";\nimport { handleRemoteAccessFailureWithAccountSwitch } from \"./handle-remote-access-failure-with-account-switch.js\";\nimport { installGhCliViaPackageManager } from \"./install-gh-cli-via-package-manager.js\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.js\";\nimport { setupGitCredentialViaGh } from \"./setup-git-credential-via-gh.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { triggerGhCliAuthLogin } from \"./trigger-gh-cli-auth-login.js\";\nimport { tryVerifyGitRemoteAccessible } from \"./verify-git-remote-accessible.js\";\n\n// Gọi trước mọi flow cần GitHub (init nhánh 1, nhánh 2-create-remote, nhánh 3).\n// remoteUrl optional — nếu truyền sẽ verify access cụ thể.\n// v1.2.5: Loop retry cho install + auth steps — user luôn có choice.\n// v1.2.7: Return resolvedRemoteUrl — user có thể đã re-nhập URL khác trong recovery menu.\n// Caller dùng URL này thay vì URL gốc cho các bước tiếp theo.\nexport async function ensureGitHubReady(\n remoteUrl?: string,\n): Promise<{ resolvedRemoteUrl?: string }> {\n // === Step 1: Ensure gh CLI installed (loop retry) ===\n while (checkGhCliAuthStatus() === \"not-installed\") {\n log.warn(\"gh CLI chưa cài. Avatar sẽ tự cài.\");\n const pm = detectPackageManager();\n if (!pm) {\n // Không có PM → user phải cài tay. Loop prompt cho retry sau khi cài.\n const action = await promptRetryOrSkip({\n taskName: \"Phát hiện package manager\",\n reason: \"Không tìm thấy brew/apt/dnf/pacman/winget trên máy.\",\n allowSkip: false,\n hint: \"Cài gh CLI tay (https://cli.github.com) rồi chọn Retry.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước cài gh CLI.\");\n }\n continue; // Retry → re-check status loop\n }\n\n try {\n installGhCliViaPackageManager(pm);\n } catch (err) {\n const action = await promptRetryOrSkip({\n taskName: `Cài gh CLI qua ${pm}`,\n reason: (err as Error).message,\n allowSkip: false,\n hint: \"Cài tay (https://cli.github.com) rồi chọn Retry, hoặc Abort.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước cài gh CLI.\");\n }\n // Retry → loop tiếp re-check installed.\n }\n }\n\n // === Step 2: Ensure gh CLI authenticated (loop retry) ===\n while (checkGhCliAuthStatus() === \"not-authenticated\") {\n log.warn(\"Chưa đăng nhập GitHub.\");\n try {\n triggerGhCliAuthLogin();\n } catch (err) {\n const action = await promptRetryOrSkip({\n taskName: \"Đăng nhập GitHub qua gh\",\n reason: (err as Error).message,\n allowSkip: false,\n hint: \"Thử lại — chọn cách login khác (browser vs token) khi gh prompt.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước gh auth login.\");\n }\n continue;\n }\n // Re-check sau attempt — nếu vẫn fail, prompt retry.\n if (checkGhCliAuthStatus() !== \"authenticated\") {\n const action = await promptRetryOrSkip({\n taskName: \"Verify gh auth\",\n reason: \"Sau gh auth login vẫn báo not-authenticated.\",\n allowSkip: false,\n hint: \"Có thể user cancel browser. Thử lại hoặc abort.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước verify gh auth.\");\n }\n }\n }\n\n log.success(\"gh CLI sẵn sàng\");\n\n // Đảm bảo git CLI dùng gh token cho HTTPS operations. Idempotent — chạy\n // mỗi lần init OK. Fix lỗi `git ls-remote exit 128` khi gh đã auth nhưng\n // git chưa biết.\n setupGitCredentialViaGh();\n\n if (remoteUrl) {\n const result = tryVerifyGitRemoteAccessible(remoteUrl);\n if (result.ok) {\n log.success(`Remote accessible: ${remoteUrl}`);\n return { resolvedRemoteUrl: remoteUrl };\n }\n // Recovery handler — loop menu cho user re-input URL / switch account / retry / abort.\n // Throws RemoteAccessAbortedError nếu user abort (init.ts catch exit 0).\n const recovered = await handleRemoteAccessFailureWithAccountSwitch({\n url: remoteUrl,\n initialReason: result.reason ?? \"unknown\",\n initialDetail: result.detail,\n });\n return { resolvedRemoteUrl: recovered.resolvedUrl };\n }\n return {};\n}\n","// Spawn `gh auth login --hostname github.com --web` interactive.\n// User sẽ thấy device-code prompt và browser tự mở. Block đến khi xong.\nimport { spawnSync } from \"node:child_process\";\nimport { log } from \"./terminal-logger.js\";\n\nexport function triggerGhCliAuthLogin(): void {\n log.info(\"Khởi động đăng nhập GitHub qua gh CLI (browser sẽ mở)...\");\n const r = spawnSync(\n \"gh\",\n [\"auth\", \"login\", \"--hostname\", \"github.com\", \"--web\", \"--git-protocol\", \"ssh\"],\n { stdio: \"inherit\" },\n );\n if (r.status !== 0) {\n throw new Error(`gh auth login thất bại (exit ${r.status}). Thử 'gh auth login' tay.`);\n }\n log.success(\"Đã đăng nhập GitHub\");\n}\n","// Resolve Avatar workspace root từ current cwd qua walk-up search.\n// Avatar workspace marker = có CẢ 3:\n// - .claude/ directory (Claude Code config)\n// - CLAUDE.md file (Avatar-scaffolded marker)\n// - .gitmodules với entry src/ HOẶC src/ directory\n//\n// Phân biệt với folder random có `.claude/` (vd Claude Code config generic\n// ở random dir hoặc parent của project). Tránh user chạy `avatar ai` ở\n// parent dir vô tình ghi config sai chỗ — bug user gặp v1.3.0.\n//\n// Walk up max 5 levels từ cwd (đủ cho mọi case practical).\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\n\nconst MAX_WALKUP_LEVELS = 5;\n\n// Check thư mục có phải Avatar workspace không.\n// v3: workspace KHÔNG git → marker = .claude/ + CLAUDE.md + src/ folder.\n// (Bỏ check .gitmodules — workspace không còn dùng submodule.)\nfunction isAvatarWorkspace(dir: string): boolean {\n const hasClaudeDir = existsSync(join(dir, \".claude\"));\n const hasClaudeMd = existsSync(join(dir, \"CLAUDE.md\"));\n const hasSrcDir = existsSync(join(dir, \"src\"));\n return hasClaudeDir && hasClaudeMd && hasSrcDir;\n}\n\n// Walk-up từ startDir tìm Avatar workspace root.\n// Trả về path workspace nếu tìm thấy, null nếu không.\nexport function resolveAvatarWorkspaceRootFromCwd(startDir: string): string | null {\n let current = startDir;\n for (let i = 0; i < MAX_WALKUP_LEVELS; i++) {\n if (isAvatarWorkspace(current)) return current;\n const parent = dirname(current);\n if (parent === current) return null; // Reached filesystem root.\n current = parent;\n }\n return null;\n}\n","// Step 2 + 3 của Phase 10 GitNexus integration:\n// - runGitnexusSetup() : `gitnexus setup` → install 4-6 core skills vào ~/.claude/skills/gitnexus-*\n// - runGitnexusAnalyze() : `gitnexus analyze .` ở workspace root → tạo .gitnexus/ với index + meta.json\n//\n// Cả 2 stream stdio để user thấy progress (long-running commands).\n// Throws GitnexusOperationError với operation + reason để caller (orchestrator) recovery menu.\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { spinnerWithElapsed } from \"./terminal-logger.js\";\n\n// Timeouts theo doc GitNexus performance estimate.\nconst SETUP_TIMEOUT_MS = 2 * 60 * 1000; // 2 phút — gitnexus setup nhanh (chỉ copy skill files)\nconst ANALYZE_TIMEOUT_MS = 5 * 60 * 1000; // 5 phút — analyze repo medium\n\nexport type GitnexusOperation = \"setup\" | \"analyze\";\nexport type GitnexusOperationReason =\n | \"binary-not-found\" // gitnexus không trong PATH (sanity check)\n | \"timeout\" // SIGTERM\n | \"permission\" // EACCES khi write file\n | \"non-zero-exit\" // generic failure\n | \"missing-output\"; // analyze xong nhưng .gitnexus/meta.json không tạo\n\nexport class GitnexusOperationError extends Error {\n operation: GitnexusOperation;\n reason: GitnexusOperationReason;\n exitCode: number | null;\n stderr?: string;\n constructor(\n operation: GitnexusOperation,\n reason: GitnexusOperationReason,\n message: string,\n exitCode: number | null = null,\n stderr?: string,\n ) {\n super(message);\n this.name = \"GitnexusOperationError\";\n this.operation = operation;\n this.reason = reason;\n this.exitCode = exitCode;\n this.stderr = stderr;\n }\n}\n\n// Classify exit code + stderr thành reason chuẩn hóa cho recovery menu.\nfunction classifyOperationFailure(\n operation: GitnexusOperation,\n exitCode: number | null,\n signal: string | null,\n stderrSample: string,\n): GitnexusOperationError {\n if (signal === \"SIGTERM\") {\n return new GitnexusOperationError(\n operation,\n \"timeout\",\n `gitnexus ${operation} timeout. Check mạng / repo size.`,\n null,\n stderrSample,\n );\n }\n const stderr = stderrSample.toLowerCase();\n if (stderr.includes(\"eacces\") || stderr.includes(\"permission denied\")) {\n return new GitnexusOperationError(\n operation,\n \"permission\",\n `gitnexus ${operation} fail (permission). Check write access ~/.claude hoặc cwd.`,\n exitCode,\n stderrSample,\n );\n }\n return new GitnexusOperationError(\n operation,\n \"non-zero-exit\",\n `gitnexus ${operation} exit ${exitCode ?? \"null\"}. Xem log phía trên.`,\n exitCode,\n stderrSample,\n );\n}\n\n// Tail N dòng cuối — dùng khi fail để debug mà không spam terminal.\nfunction tailLines(text: string, n: number): string {\n const lines = text.split(\"\\n\");\n return lines.slice(-n).join(\"\\n\");\n}\n\n// Step 2 — Run `gitnexus setup` để install global skills (~/.claude/skills/gitnexus-*).\n// Idempotent: chạy lần 2 OK, GitNexus tự skip nếu skills đã có.\n// Spinner với elapsed time + buffer stdio (không inherit) để spinner mượt.\nexport function runGitnexusSetup(): void {\n const sp = spinnerWithElapsed(\"Setup GitNexus global skills (~/.claude/skills/gitnexus-*)\");\n const result = spawnSync(\"gitnexus\", [\"setup\"], {\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: SETUP_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || result.signal === \"SIGTERM\") {\n sp.fail(\"GitNexus setup failed\");\n const stderr = (result.stderr || \"\").trim();\n const stdout = (result.stdout || \"\").trim();\n if (stderr) process.stderr.write(`${tailLines(stderr, 30)}\\n`);\n else if (stdout) process.stderr.write(`${tailLines(stdout, 30)}\\n`);\n throw classifyOperationFailure(\"setup\", result.status, result.signal, stderr);\n }\n sp.succeed(\"GitNexus setup OK (global skills installed)\");\n}\n\n// Step 3 — Run `gitnexus analyze .` ở workspace root.\n// KHÔNG pass --skills flag (Q5 confirmed bỏ qua repo-specific generated skills).\n// Verify .gitnexus/meta.json tạo ra sau khi xong.\n// Spinner với elapsed time + buffer stdio.\nexport function runGitnexusAnalyze(workspacePath: string): void {\n const sp = spinnerWithElapsed(`Analyze workspace ${workspacePath} (1-3 phút)`);\n const result = spawnSync(\"gitnexus\", [\"analyze\", \".\"], {\n cwd: workspacePath,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: ANALYZE_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || result.signal === \"SIGTERM\") {\n sp.fail(\"Analyze failed\");\n const stderr = (result.stderr || \"\").trim();\n const stdout = (result.stdout || \"\").trim();\n if (stderr) process.stderr.write(`${tailLines(stderr, 30)}\\n`);\n else if (stdout) process.stderr.write(`${tailLines(stdout, 30)}\\n`);\n throw classifyOperationFailure(\"analyze\", result.status, result.signal, stderr);\n }\n\n // Verify output tồn tại — analyze có thể exit 0 nhưng không tạo meta.json (edge case).\n const metaPath = join(workspacePath, \".gitnexus\", \"meta.json\");\n if (!existsSync(metaPath)) {\n sp.fail(\"Analyze exit 0 nhưng không thấy meta.json\");\n throw new GitnexusOperationError(\n \"analyze\",\n \"missing-output\",\n `gitnexus analyze xong nhưng không thấy ${metaPath}. Repo có thể empty hoặc gitnexus fail silent.`,\n );\n }\n sp.succeed(`Analyze OK (index tại ${join(workspacePath, \".gitnexus\")})`);\n}\n","// Step 4 của Phase 10 GitNexus integration — chạy `gitnexus wiki` conditional theo AI provider:\n// - LLMLite mode (env.ANTHROPIC_AUTH_TOKEN + ANTHROPIC_BASE_URL) → run wiki via NAL gateway\n// - Anthropic Direct (env.ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL) → run wiki via Anthropic OpenAI-compat layer (v1.6.1)\n// - Subscription mode (no API key env) → SKIP wiki, log warning (OAuth không expose key cho GitNexus)\n//\n// GitNexus dùng OpenAI-compatible HTTP client (POST /v1/chat/completions).\n// Anthropic API có compatibility layer tại https://api.anthropic.com/v1/ (Bearer auth) — work với\n// GitNexus default --provider openai. Cần normalize base URL có /v1/ suffix cho Anthropic.\n//\n// Confirm prompt cost trước khi chạy (~$0.50 cho repo 100 modules, 2-5 phút).\n// Verify .gitnexus/wiki/index.html tạo ra sau khi xong.\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { confirm } from \"@inquirer/prompts\";\nimport { isReasoningModel } from \"./detect-reasoning-model-from-name.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\nimport { log, spinnerWithElapsed } from \"./terminal-logger.js\";\nimport { writeGitnexusConfigForWikiRun } from \"./write-gitnexus-config-to-avoid-cli-arg-key-leak.js\";\n\n// Wiki gen full lần đầu có thể lâu — 15 phút timeout an toàn cho repo lớn.\nconst WIKI_TIMEOUT_MS = 15 * 60 * 1000;\n\n// Fallback model cho LLMLite (last-resort nếu settings.json không có top-level \"model\").\n// nal-claude là default an toàn cho LLMLite NAL. Thường KHÔNG dùng đến vì avatar ai setup\n// luôn ghi model user đã chọn vào settings.json.model — fallback chỉ chạy khi user manual edit.\n// GitNexus default = minimax/minimax-m2.5 → LLMLite key reject → MUST override.\nconst FALLBACK_LLMLITE_MODEL = \"nal-claude\";\n\n// Fallback model cho Anthropic Direct (last-resort).\nconst FALLBACK_ANTHROPIC_MODEL = \"claude-sonnet-4-5\";\n\nexport type WikiProvider = \"llmlite\" | \"anthropic\";\nexport type WikiSkipReason = \"subscription-mode\" | \"user-declined\" | \"fail\";\n\nexport interface GitnexusWikiResult {\n ran: boolean;\n skipped: boolean;\n reason?: WikiSkipReason;\n wikiPath?: string;\n detail?: string;\n}\n\ninterface WikiCredentials {\n provider: WikiProvider;\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\n// Anthropic OpenAI-compat layer REQUIRE `/v1/` suffix trong base URL — nếu user lưu\n// \"https://api.anthropic.com\" (no slash) → append \"/v1/\" để GitNexus call đúng path.\n// LLMLite endpoint base URL không cần /v1/ vì GitNexus tự append.\nfunction normalizeAnthropicBaseUrl(rawBaseUrl: string): string {\n const cleaned = rawBaseUrl.replace(/\\/+$/, \"\"); // strip trailing slash\n if (cleaned.endsWith(\"/v1\")) return `${cleaned}/`;\n if (cleaned.endsWith(\"/v1/\")) return cleaned;\n return `${cleaned}/v1/`;\n}\n\n// Đọc settings.json detect provider + credentials + model.\n// Order check: Anthropic Direct (ANTHROPIC_API_KEY) > LLMLite (ANTHROPIC_AUTH_TOKEN) > null (Subscription).\n//\n// v1.20.0 fix: Keys giờ sống ở .envrc (direnv) thay vì settings.json. Đọc 2 nguồn:\n// 1. settings.json.env (legacy, hoặc subscription-clear path)\n// 2. process.env (direnv đã load .envrc vào shell env)\n// Order: process.env wins if both exist (latest source of truth).\n//\n// v1.6.3: Model đọc từ TOP-LEVEL `settings.json.model` (Claude Code spec).\n// Trả null nếu thật sự không tìm thấy key nào (Subscription mode hoặc direnv\n// chưa load — caller skip wiki).\nasync function readSettingsForWikiCredentials(\n workspacePath: string,\n): Promise<WikiCredentials | null> {\n const settingsPath = join(workspacePath, \".claude\", \"settings.json\");\n if (!(await pathExists(settingsPath))) return null;\n\n try {\n const settings = await readJson<{\n env?: Record<string, unknown>;\n model?: unknown;\n avatarProvider?: unknown;\n }>(settingsPath);\n const env = settings.env || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : null;\n if (!baseUrl) return null;\n\n // Model do `avatar ai setup` lưu ở top-level (Claude Code schema).\n const topLevelModel = typeof settings.model === \"string\" ? settings.model : \"\";\n const envModel = typeof env.ANTHROPIC_MODEL === \"string\" ? env.ANTHROPIC_MODEL : \"\";\n const userModel = topLevelModel.length > 0 ? topLevelModel : envModel;\n\n // v3: key sống trong settings.json.env (bỏ direnv/.envrc). Đọc 2 nguồn:\n // 1. process.env — nếu user/CI export thủ công.\n // 2. settings.json.env — nơi `avatar ai setup` ghi key (chmod 600, gitignored).\n const resolveKey = (envVarName: string): string | null => {\n const fromProcess = process.env[envVarName];\n if (typeof fromProcess === \"string\" && fromProcess.length > 0) {\n return fromProcess;\n }\n const fromSettings = env[envVarName];\n if (typeof fromSettings === \"string\" && fromSettings.length > 0) {\n return fromSettings;\n }\n return null;\n };\n\n // v1.20.1: Provider detection — 2-step:\n // Step 1: avatarProvider field (v1.20.0 informational marker) — explicit.\n // Step 2: baseUrl heuristic — Anthropic official endpoint vs NAL gateway.\n //\n // Tránh process.env.ANTHROPIC_API_KEY leak (vd user export key cho project A,\n // chạy `avatar` trong project B với LLMLite config — không nên dùng key project A).\n const providerHint =\n typeof settings.avatarProvider === \"string\" ? settings.avatarProvider : null;\n // Match hostname `api.anthropic.com` trong URL — preceded bởi protocol slash\n // hoặc domain dot. Sử dụng URL parse cho accuracy.\n let baseUrlSuggestsAnthropic = false;\n try {\n const parsed = new URL(baseUrl);\n baseUrlSuggestsAnthropic = parsed.hostname === \"api.anthropic.com\";\n } catch {\n // Invalid URL — treat as non-Anthropic (will fall to LLMLite path)\n baseUrlSuggestsAnthropic = false;\n }\n const effectiveProvider: \"anthropic\" | \"llmlite\" =\n providerHint === \"anthropic\"\n ? \"anthropic\"\n : providerHint === \"llmlite\"\n ? \"llmlite\"\n : baseUrlSuggestsAnthropic\n ? \"anthropic\"\n : \"llmlite\";\n\n if (effectiveProvider === \"anthropic\") {\n const anthropicKey = resolveKey(\"ANTHROPIC_API_KEY\");\n if (anthropicKey) {\n return {\n provider: \"anthropic\",\n apiKey: anthropicKey,\n baseUrl: normalizeAnthropicBaseUrl(baseUrl),\n model: userModel.length > 0 ? userModel : FALLBACK_ANTHROPIC_MODEL,\n };\n }\n } else {\n const llmliteToken = resolveKey(\"ANTHROPIC_AUTH_TOKEN\");\n if (llmliteToken) {\n return {\n provider: \"llmlite\",\n apiKey: llmliteToken,\n baseUrl,\n model: userModel.length > 0 ? userModel : FALLBACK_LLMLITE_MODEL,\n };\n }\n }\n\n // No key anywhere → Subscription mode hoặc direnv chưa load.\n return null;\n } catch {\n return null;\n }\n}\n\n// Confirm prompt UX — user biết cost + thời gian + model trước khi commit.\nasync function confirmWikiGeneration(baseUrl: string, model: string): Promise<boolean> {\n return await confirm({\n message: `Generate wiki cho workspace? (~$0.50 qua ${baseUrl} model=${model}, 2-5 phút). Continue?`,\n default: true,\n });\n}\n\n// Tail N dòng cuối — dùng khi fail để debug mà không spam terminal.\nfunction tailLines(text: string, n: number): string {\n const lines = text.split(\"\\n\");\n return lines.slice(-n).join(\"\\n\");\n}\n\n// Main entry — branch theo provider mode + run wiki nếu LLMLite/Anthropic + user confirm.\n// workspacePath: nơi đọc .claude/settings.json (credentials).\n// runDir: nơi chạy `gitnexus wiki .` (mặc định = workspacePath). v3 multi-repo:\n// settings ở workspace root, wiki chạy trong src/<repo> → 2 path khác nhau.\nexport async function runGitnexusWikiConditional(\n workspacePath: string,\n runDir: string = workspacePath,\n): Promise<GitnexusWikiResult> {\n // Branch 1: detect provider mode.\n const creds = await readSettingsForWikiCredentials(workspacePath);\n if (!creds) {\n log.warn(\n \"Không resolve được API key cho wiki gen.\\n\" +\n \" Đã kiểm tra: process.env → .envrc Avatar block → settings.json.env\\n\" +\n \" Nguyên nhân có thể:\\n\" +\n \" • Subscription mode (OAuth, không có key)\\n\" +\n \" • Key chưa setup — chạy 'avatar ai setup' hoặc 'avatar secrets set ANTHROPIC_API_KEY'\\n\" +\n \" • settings.json.env.ANTHROPIC_BASE_URL bị thiếu (provider chưa configured)\",\n );\n log.dim(\n \"Để gen wiki sau khi đã có key, chạy manual:\\n\" +\n \" gitnexus wiki . --api-key <key> --base-url <url> --model <model>\",\n );\n return { ran: false, skipped: true, reason: \"subscription-mode\" };\n }\n\n // Branch 2: confirm prompt cost.\n const proceed = await confirmWikiGeneration(creds.baseUrl, creds.model);\n if (!proceed) {\n log.dim(\n \"User decline wiki gen — workspace OK không có wiki. Chạy `gitnexus wiki` manual sau khi cần.\",\n );\n return { ran: false, skipped: true, reason: \"user-declined\" };\n }\n\n // Branch 3: run wiki với spinner + buffer stdio.\n // CRITICAL: GitNexus default --model = minimax/minimax-m2.5 → key sẽ reject. MUST override --model.\n // Provider naming: Anthropic Direct dùng OpenAI-compat layer của Anthropic API (https://api.anthropic.com/v1/)\n // → GitNexus --provider openai vẫn work với base URL đó (Anthropic ship compat endpoint).\n //\n // v1.6.4: Auto-detect reasoning models (Claude 4+, OpenAI o-series) → add --reasoning-model\n // để strip `temperature` param (API reject \"temperature is deprecated for this model\").\n const reasoningMode = isReasoningModel(creds.model);\n\n // v1.13.0 SECURITY FIX: Pre-write `~/.gitnexus/config.json` để gitnexus binary\n // tự đọc credentials, KHÔNG pass `--api-key` qua CLI args (leak qua `ps aux`).\n // Gitnexus support đọc config từ file khi vắng flag (xác minh `gitnexus wiki --help`).\n await writeGitnexusConfigForWikiRun({\n apiKey: creds.apiKey,\n baseUrl: creds.baseUrl,\n model: creds.model,\n isReasoningModel: reasoningMode,\n });\n\n // Args giờ KHÔNG còn --api-key. Gitnexus đọc từ ~/.gitnexus/config.json (chmod 600).\n const args = [\"wiki\", \".\", \"--base-url\", creds.baseUrl, \"--model\", creds.model];\n if (reasoningMode) {\n args.push(\"--reasoning-model\");\n }\n\n const sp = spinnerWithElapsed(\n `Generating wiki via ${creds.baseUrl} (${creds.provider}) model=${creds.model}${reasoningMode ? \" [reasoning]\" : \"\"}`,\n );\n const result = spawnSync(\"gitnexus\", args, {\n cwd: runDir,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n timeout: WIKI_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.status !== 0 || result.signal === \"SIGTERM\") {\n const reason = result.signal === \"SIGTERM\" ? \"timeout\" : \"non-zero-exit\";\n sp.fail(`Wiki gen ${reason} (exit ${result.status ?? \"null\"})`);\n // Dump tail 30 dòng output để debug, không spam full.\n const stderr = (result.stderr || \"\").trim();\n const stdout = (result.stdout || \"\").trim();\n if (stderr) process.stderr.write(`${tailLines(stderr, 30)}\\n`);\n else if (stdout) process.stderr.write(`${tailLines(stdout, 30)}\\n`);\n return {\n ran: false,\n skipped: true,\n reason: \"fail\",\n detail: `Wiki gen ${reason} (exit ${result.status ?? \"null\"})`,\n };\n }\n\n // Verify output tồn tại (wiki tạo trong runDir, không phải workspacePath).\n const wikiPath = join(runDir, \".gitnexus\", \"wiki\", \"index.html\");\n if (!existsSync(wikiPath)) {\n sp.fail(\"Wiki exit 0 nhưng không thấy index.html\");\n return {\n ran: false,\n skipped: true,\n reason: \"fail\",\n detail: `Wiki exit 0 nhưng không thấy ${wikiPath}`,\n };\n }\n\n sp.succeed(`Wiki ready: ${wikiPath}`);\n return { ran: true, skipped: false, wikiPath };\n}\n","// Detect xem model name có phải reasoning model không (cần strip temperature, dùng\n// max_completion_tokens thay max_tokens).\n//\n// Background:\n// - Reasoning models (Anthropic Claude 4+ extended thinking, OpenAI o-series)\n// KHÔNG accept `temperature` param → API 400 invalid_request_error.\n// - GitNexus có flag --reasoning-model strip param này. Avatar cần auto-pass khi\n// detect model thuộc nhóm reasoning.\n// - Code khác Avatar call LLM (vd avatar ai test /v1/messages) cũng cần biết để\n// skip temperature param.\n//\n// Pattern match (theo Anthropic + OpenAI docs):\n// - Anthropic: claude-opus-4-*, claude-sonnet-4-* (thinking từ Claude 4)\n// LLMLite alias mapping: nal-claude-opus-* nếu user map tới opus-4\n// - OpenAI: o1, o1-mini, o1-preview, o3, o3-mini, o4-mini\n//\n// Maintainable: thêm pattern vào REASONING_PATTERNS array khi Anthropic/OpenAI ship\n// model mới với reasoning.\n\n// Regex patterns — case-insensitive match. Order không quan trọng (any match → true).\nconst REASONING_PATTERNS: RegExp[] = [\n // Anthropic Claude 4+ với extended thinking.\n // Match: claude-opus-4-7, claude-opus-4-8, claude-sonnet-4-5, claude-opus-4-1, ...\n /^claude-(opus|sonnet)-4/i,\n\n // Anthropic Claude 5+ (future-proof — assume reasoning theo trend).\n /^claude-(opus|sonnet|haiku)-[5-9]/i,\n\n // OpenAI o-series (o1, o3, o4).\n /^o1(-|$)/i,\n /^o3(-|$)/i,\n /^o4(-|$)/i,\n\n // LLMLite NAL alias mapping — phổ biến nal-claude-opus-* trỏ tới opus-4+.\n // (Conservative: chỉ match nếu name có chứa opus/sonnet + version số.)\n /nal-claude-(opus|sonnet)-?4/i,\n];\n\n// Main entry. Trả true nếu model name khớp 1 pattern reasoning.\n// Empty string / null → false (an toàn — fallback model thường dùng temperature OK).\nexport function isReasoningModel(modelName: string | null | undefined): boolean {\n if (!modelName) return false;\n return REASONING_PATTERNS.some((pattern) => pattern.test(modelName));\n}\n","// Pre-write `~/.gitnexus/config.json` để chạy `gitnexus wiki` mà KHÔNG phải pass\n// `--api-key` qua CLI argument list. Lý do: argv leak qua `ps aux` / `/proc/<pid>/cmdline`\n// cho mọi process cùng user trong suốt wiki run (timeout 5 phút) — secret exposure cao.\n//\n// Gitnexus binary tự đọc config từ `~/.gitnexus/config.json` khi không có `--api-key`\n// (xác minh qua `gitnexus wiki --help`: \"--api-key ... saved to ~/.gitnexus/config.json\").\n//\n// Approach: idempotent merge — read existing config (preserve user fields khác), update\n// `apiKey/baseUrl/model/isReasoningModel`, write back với chmod 600.\n//\n// v1.13.0 security fix CVE-internal: gitnexus CLI arg API key leak.\n\nimport { promises as fs } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\n\nconst GITNEXUS_CONFIG_DIR = join(homedir(), \".gitnexus\");\nconst GITNEXUS_CONFIG_PATH = join(GITNEXUS_CONFIG_DIR, \"config.json\");\n\nexport interface GitnexusConfigPayload {\n apiKey: string;\n baseUrl: string;\n model: string;\n isReasoningModel?: boolean;\n}\n\n// Read existing config nếu có. Lỗi parse / file missing → trả empty object để merge.\nasync function readExistingConfig(): Promise<Record<string, unknown>> {\n try {\n const raw = await fs.readFile(GITNEXUS_CONFIG_PATH, \"utf8\");\n const parsed = JSON.parse(raw);\n return typeof parsed === \"object\" && parsed !== null ? (parsed as Record<string, unknown>) : {};\n } catch {\n return {};\n }\n}\n\n// Atomic write: tmp file + rename để tránh corrupt config nếu process crash giữa chừng.\nasync function writeConfigAtomic(content: string): Promise<void> {\n await fs.mkdir(dirname(GITNEXUS_CONFIG_PATH), { recursive: true });\n const tmpPath = `${GITNEXUS_CONFIG_PATH}.tmp-${process.pid}`;\n await fs.writeFile(tmpPath, content, { mode: 0o600 });\n await fs.rename(tmpPath, GITNEXUS_CONFIG_PATH);\n}\n\n// Main API: merge payload vào existing config, write atomic, chmod 600.\n// Caller (run-gitnexus-wiki-conditional.ts) gọi function này TRƯỚC khi spawn\n// `gitnexus wiki` mà không kèm `--api-key`.\nexport async function writeGitnexusConfigForWikiRun(payload: GitnexusConfigPayload): Promise<void> {\n const existing = await readExistingConfig();\n const merged = {\n ...existing,\n apiKey: payload.apiKey,\n baseUrl: payload.baseUrl,\n model: payload.model,\n isReasoningModel: payload.isReasoningModel ?? false,\n };\n await writeConfigAtomic(JSON.stringify(merged, null, 2));\n // Defensive: ensure 600 dù tmp file đã chmod (some FS lose mode on rename).\n await fs.chmod(GITNEXUS_CONFIG_PATH, 0o600).catch(() => {\n // Best-effort — Windows / WSL ignore chmod, mode đã set ở writeFile.\n });\n}\n","// `avatar ai` — Quản lý AI provider config per-workspace (M12).\n// 4 subcommand:\n// - setup : Wizard re-config (reuse runAiSetupPhase từ phase 07)\n// - status : Show provider/model/baseUrl/token-mask hiện tại\n// - test : Cheap prompt verify provider work\n// - reset : Strip env.ANTHROPIC_* khỏi settings.json (về dùng default subscription)\n//\n// v1.3.1 fix: walk-up resolve Avatar workspace root (strict 3-marker check)\n// thay vì chỉ check `.claude/` ở cwd. Tránh user chạy ở parent dir (vd\n// /Users/Luke/Downloads/Programming/ có .claude/ của Claude Code generic)\n// → ghi nhầm config vào sai chỗ.\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { confirm } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport { pathExists, readJson, writeJsonAtomic } from \"../lib/filesystem-helpers.js\";\nimport { resolveAvatarWorkspaceRootFromCwd } from \"../lib/resolve-avatar-workspace-root-from-cwd.js\";\nimport { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport { maskApiKey } from \"../lib/setup-llmlite-api-key-and-model.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { testAiProviderByDetectedMode } from \"../lib/test-ai-provider-by-detected-mode.js\";\n\n// Resolve Avatar workspace root từ cwd. Walk-up tìm dir có cả .claude/ +\n// CLAUDE.md + (src/ hoặc .gitmodules với src/) — phân biệt với folder random\n// có .claude/. Exit 1 với hint rõ nếu không tìm thấy.\nasync function ensureWorkspaceCwd(): Promise<string> {\n const cwd = process.cwd();\n const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);\n if (!workspaceRoot) {\n log.error(\n `Không tìm thấy Avatar workspace từ thư mục hiện tại.\n Avatar workspace cần có: .claude/ + CLAUDE.md + src/ (hoặc .gitmodules).\n Bạn đang ở: ${cwd}\n Cd vào workspace dir (vd /path/to/<project>-workspace) rồi chạy lại.`,\n );\n process.exit(1);\n }\n if (workspaceRoot !== cwd) {\n log.dim(`Detected workspace root: ${workspaceRoot}`);\n }\n return workspaceRoot;\n}\n\n// Read settings.json hiện có. Trả {} nếu chưa có file (subscription default state).\nasync function readWorkspaceSettings(workspacePath: string): Promise<Record<string, unknown>> {\n const settingsPath = join(workspacePath, \".claude\", \"settings.json\");\n if (!(await pathExists(settingsPath))) return {};\n try {\n return await readJson<Record<string, unknown>>(settingsPath);\n } catch {\n return {};\n }\n}\n\n// === setup ===\nasync function runAiSetup(): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n await runAiSetupPhase({ workspacePath });\n}\n\n// === status ===\n// v1.6.0: Detect 4 provider modes — LLMLite (BASE_URL + AUTH_TOKEN), Anthropic Direct\n// (BASE_URL + API_KEY), Custom (chỉ AUTH_TOKEN), Subscription (no env).\nasync function runAiStatus(): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n const settings = await readWorkspaceSettings(workspacePath);\n const env = (settings.env as Record<string, unknown> | undefined) || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : undefined;\n const token = typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" ? env.ANTHROPIC_AUTH_TOKEN : undefined;\n const apiKey = typeof env.ANTHROPIC_API_KEY === \"string\" ? env.ANTHROPIC_API_KEY : undefined;\n const model = typeof settings.model === \"string\" ? settings.model : undefined;\n\n // Distinguish Anthropic Direct (sk-ant- + api.anthropic.com) vs LLMLite (other gateway).\n let provider: string;\n let credentialDisplay: string;\n if (apiKey) {\n provider =\n baseUrl?.includes(\"api.anthropic.com\") || apiKey.startsWith(\"sk-ant-\")\n ? \"Anthropic Direct\"\n : \"Custom (API key)\";\n credentialDisplay = maskApiKey(apiKey);\n } else if (baseUrl && token) {\n provider = \"LLMLite\";\n credentialDisplay = maskApiKey(token);\n } else if (token) {\n provider = \"Custom\";\n credentialDisplay = maskApiKey(token);\n } else {\n provider = \"Subscription (default)\";\n credentialDisplay = \"(không set — dùng subscription auth)\";\n }\n\n log.info(`Project: ${workspacePath}`);\n log.info(`Provider: ${provider}${baseUrl ? ` (${baseUrl})` : \"\"}`);\n log.info(`Model: ${model ?? \"(default — Claude Code chọn)\"}`);\n log.info(`Token: ${credentialDisplay}`);\n}\n\n// === test ===\n// v1.2.3: Detect provider mode từ settings.json, test đúng path (LLMLite hoặc Subscription).\nasync function runAiTest(): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n const settings = await readWorkspaceSettings(workspacePath);\n try {\n const result = await testAiProviderByDetectedMode(settings);\n log.success(`✓ ${result.message}`);\n } catch (err) {\n log.error(`Test fail: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\n// === reset ===\nasync function runAiReset(opts: { yes?: boolean }): Promise<void> {\n const workspacePath = await ensureWorkspaceCwd();\n const settingsPath = join(workspacePath, \".claude\", \"settings.json\");\n const settings = await readWorkspaceSettings(workspacePath);\n\n if (!opts.yes) {\n const ok = await confirm({\n message: \"Xóa AI config (về dùng Claude Code Subscription default)?\",\n default: false,\n });\n if (!ok) {\n log.dim(\"Đã hủy.\");\n return;\n }\n }\n\n // Rebuild settings strip MỌI provider env keys (LLMLite token + Anthropic direct key + baseUrl).\n // v1.6.0: include ANTHROPIC_API_KEY (Anthropic direct provider key).\n const { env: existingEnv, ...rest } = settings as { env?: Record<string, unknown> };\n const reset: Record<string, unknown> = { ...rest };\n if (existingEnv) {\n const {\n ANTHROPIC_BASE_URL: _b,\n ANTHROPIC_AUTH_TOKEN: _t,\n ANTHROPIC_API_KEY: _k,\n ...envRest\n } = existingEnv;\n if (Object.keys(envRest).length > 0) {\n reset.env = envRest;\n }\n }\n\n // Nếu sau strip object rỗng → xóa file luôn (clean state).\n if (Object.keys(reset).length === 0) {\n await fs.unlink(settingsPath).catch(() => {});\n log.success(\"Đã xóa .claude/settings.json (clean state)\");\n } else {\n await writeJsonAtomic(settingsPath, reset, 0o600);\n log.success(\"Đã reset env block trong .claude/settings.json\");\n }\n}\n\nexport function registerAiCommand(program: Command): void {\n const ai = program.command(\"ai\").description(\"Quản lý AI provider config (M12)\");\n\n ai.command(\"setup\")\n .description(\"Wizard setup/re-config AI provider cho workspace hiện tại\")\n .action(async () => {\n await runAiSetup();\n });\n\n ai.command(\"status\")\n .description(\"Show AI config hiện tại (mask token)\")\n .action(async () => {\n await runAiStatus();\n });\n\n ai.command(\"test\")\n .description(\"Verify AI provider qua cheap prompt\")\n .action(async () => {\n await runAiTest();\n });\n\n ai.command(\"reset\")\n .description(\"Xóa env.ANTHROPIC_* khỏi settings.json (về Subscription default)\")\n .option(\"--yes\", \"Skip confirm\")\n .action(async (opts: { yes?: boolean }) => {\n await runAiReset(opts);\n });\n}\n","// Append-only audit log at ~/.avatar/audit.log. Records sensitive actions\n// (secrets set/rm, tool install/remove) WITHOUT logging secret values.\n// Used by `avatar doctor` to surface recent activity.\nimport { promises as fs } from \"node:fs\";\nimport { AUDIT_LOG_PATH, ensureAvatarHome } from \"./user-config-store.js\";\n\n// Union khai báo theo thực tế caller (verified qua grep `appendAuditEntry(\"...\")`).\n// Thêm action mới: vừa update union vừa update caller — TypeScript sẽ enforce.\n// \"logout\", \"secret_*\", \"tool_*\", \"sync\", \"restore\" reserved cho future commands\n// (đã được spec ở blueprint nhưng caller chưa wire — giữ trong union để command\n// mới chỉ cần thêm caller, không cần đổi type).\nexport type AuditAction =\n | \"login\"\n | \"login_reset\"\n | \"logout\"\n | \"secret_set\"\n | \"secret_rm\"\n | \"tool_install\"\n | \"tool_remove\"\n | \"init\"\n | \"sync\"\n | \"restore\"\n | \"uninstall\"\n | \"ai_setup\"\n | \"gitnexus_setup\"\n | \"bootstrap\";\n\nexport interface AuditEntry {\n timestamp: string;\n action: AuditAction;\n detail?: string;\n}\n\n// v1.14.0 SECURITY: chmod 600 audit.log. Default `fs.appendFile` không set mode\n// → file world-readable (umask thông thường 644). Audit entries có thể chứa email,\n// path tuyệt đối từ error messages → defense-in-depth chmod 600 (owner-only).\n// Best-effort: Windows/WSL ignore chmod nhưng không throw → safe.\nasync function ensureAuditLogPermissions(): Promise<void> {\n try {\n await fs.chmod(AUDIT_LOG_PATH, 0o600);\n } catch {\n // File chưa tồn tại lần đầu (appendFile sẽ tạo) hoặc Windows skip chmod.\n }\n}\n\nexport async function appendAuditEntry(action: AuditAction, detail?: string): Promise<void> {\n await ensureAvatarHome();\n const entry: AuditEntry = {\n timestamp: new Date().toISOString(),\n action,\n ...(detail ? { detail } : {}),\n };\n const line = `${JSON.stringify(entry)}\\n`;\n await fs.appendFile(AUDIT_LOG_PATH, line, { encoding: \"utf8\", mode: 0o600 });\n // Re-enforce chmod 600 vì appendFile mode chỉ apply khi tạo file mới — file\n // pre-exist với mode khác sẽ không bị thay đổi. Idempotent + cheap.\n await ensureAuditLogPermissions();\n}\n","// Read/write ~/.avatar/config.json (OAuth credentials) and ~/.avatar/state.json\n// (non-credential user state). Validates with Zod, enforces chmod 600 on config.\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport {\n type UserConfig,\n type UserState,\n userConfigSchema,\n userStateSchema,\n} from \"../types/config-schema.js\";\nimport { ensureDir, pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const AVATAR_HOME = join(homedir(), \".avatar\");\nexport const USER_CONFIG_PATH = join(AVATAR_HOME, \"config.json\");\nexport const USER_STATE_PATH = join(AVATAR_HOME, \"state.json\");\nexport const AUDIT_LOG_PATH = join(AVATAR_HOME, \"audit.log\");\nexport const BACKUPS_DIR = join(AVATAR_HOME, \"backups\");\n\n// chmod 600 — owner-only read/write. Matches spec for credential files.\nconst SECRET_FILE_MODE = 0o600;\n\nexport async function ensureAvatarHome(): Promise<void> {\n await ensureDir(AVATAR_HOME);\n}\n\nexport async function readUserConfig(): Promise<UserConfig | null> {\n if (!(await pathExists(USER_CONFIG_PATH))) return null;\n const raw = await readJson<unknown>(USER_CONFIG_PATH);\n const parsed = userConfigSchema.safeParse(raw);\n if (!parsed.success) return null;\n return parsed.data;\n}\n\nexport async function writeUserConfig(config: UserConfig): Promise<void> {\n await ensureAvatarHome();\n await writeJsonAtomic(USER_CONFIG_PATH, config, SECRET_FILE_MODE);\n}\n\nexport async function clearUserConfig(): Promise<void> {\n if (await pathExists(USER_CONFIG_PATH)) {\n const { promises: fs } = await import(\"node:fs\");\n await fs.unlink(USER_CONFIG_PATH);\n }\n}\n\nexport async function readUserState(): Promise<UserState> {\n if (!(await pathExists(USER_STATE_PATH))) {\n return userStateSchema.parse({});\n }\n const raw = await readJson<unknown>(USER_STATE_PATH);\n const parsed = userStateSchema.safeParse(raw);\n if (!parsed.success) return userStateSchema.parse({});\n return parsed.data;\n}\n\nexport async function writeUserState(state: UserState): Promise<void> {\n await ensureAvatarHome();\n await writeJsonAtomic(USER_STATE_PATH, state);\n}\n\n// Token is considered expired if it would expire in the next 60 seconds.\n// 60s buffer accounts for slow network round-trip on the upcoming request.\nexport function isTokenExpired(config: UserConfig): boolean {\n const expiresAt = Date.parse(config.expires_at);\n return Number.isNaN(expiresAt) || expiresAt - Date.now() < 60_000;\n}\n\n// Lỗi báo caller cần chạy `avatar login` lại (chưa login HOẶC refresh fail).\nexport class NoValidTokenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"NoValidTokenError\";\n }\n}\n\n// Decode JWT exp của id_token (giây). Trả null nếu không parse được.\n// id_token có lifetime RIÊNG, có thể hết hạn dù access_token (expires_at) còn —\n// nên phải check exp của chính id_token, không chỉ dựa expires_at (S2).\nasync function idTokenIsExpired(idToken: string): Promise<boolean> {\n const { decodeIdToken } = await import(\"./google-oauth-device-flow.js\");\n try {\n const claims = decodeIdToken(idToken);\n const nowSec = Math.floor(Date.now() / 1000);\n // 60s buffer cho network round-trip của request sắp gọi.\n return claims.exp - 60 < nowSec;\n } catch {\n return true; // không decode được = coi như không hợp lệ.\n }\n}\n\n// v3: Lấy id_token CÒN HẠN + ĐÃ VERIFY để gọi Supabase get-pack. Tự refresh nếu hết hạn.\n//\n// SECURITY (S1+S2): MỌI nhánh đều decode + verifyIdTokenClaims id_token cuối cùng\n// trước khi return (issuer/audience/hd=nal.vn/email_verified/exp). Không nhánh nào\n// trả token chưa-verify hoặc đã-hết-hạn xuống downloader.\n//\n// - chưa login → NoValidTokenError\n// - id_token còn hạn (theo JWT exp) → verify → trả\n// - hết hạn → refresh → nếu Google KHÔNG trả id_token mới (hợp lệ theo OAuth khi\n// thiếu scope) thì id_token cũ vẫn hết hạn → throw, KHÔNG fallback im lặng\n// - refresh fail (refresh_token revoked) → NoValidTokenError (prompt login)\nexport async function getValidIdToken(): Promise<string> {\n const config = await readUserConfig();\n if (!config) {\n throw new NoValidTokenError(\"Chưa đăng nhập. Chạy 'avatar login' trước.\");\n }\n\n const { refreshAccessToken, decodeIdToken, verifyIdTokenClaims } = await import(\n \"./google-oauth-device-flow.js\"\n );\n\n // Fast path: id_token hiện tại còn hạn (theo chính JWT exp của nó, không phải expires_at).\n if (!(await idTokenIsExpired(config.id_token))) {\n verifyIdTokenClaims(decodeIdToken(config.id_token));\n return config.id_token;\n }\n\n // id_token hết hạn → refresh.\n let refreshed: { access_token: string; expires_in: number; id_token?: string };\n try {\n refreshed = await refreshAccessToken(config.refresh_token);\n } catch (err) {\n throw new NoValidTokenError(\n `Token hết hạn và refresh thất bại (${err instanceof Error ? err.message : err}). Chạy 'avatar login' lại.`,\n );\n }\n\n // S1: Google có thể KHÔNG trả id_token mới (OAuth spec, khi request thiếu scope).\n // KHÔNG fallback id_token cũ (đã hết hạn) — đó là lỗ hổng. Yêu cầu id_token mới.\n if (!refreshed.id_token) {\n throw new NoValidTokenError(\n \"Refresh không trả id_token mới (id_token cũ đã hết hạn). Chạy 'avatar login' lại.\",\n );\n }\n\n // S1: verify claims id_token MỚI trước khi tin (defense-in-depth, gồm exp).\n verifyIdTokenClaims(decodeIdToken(refreshed.id_token));\n\n const updated: UserConfig = {\n ...config,\n access_token: refreshed.access_token,\n id_token: refreshed.id_token,\n expires_at: new Date(Date.now() + refreshed.expires_in * 1000).toISOString(),\n };\n await writeUserConfig(updated);\n return refreshed.id_token;\n}\n","// Zod schemas + inferred TypeScript types for all on-disk Avatar config files.\n// Centralised so commands import from one place and validation matches storage.\nimport { z } from \"zod\";\n\n// ~/.avatar/config.json — OAuth credentials after `avatar login`.\n// chmod 600 enforced at write site.\nexport const userConfigSchema = z.object({\n email: z.string().email(),\n name: z.string(),\n access_token: z.string().min(1),\n refresh_token: z.string().min(1),\n expires_at: z.string().datetime(),\n id_token: z.string().min(1),\n});\nexport type UserConfig = z.infer<typeof userConfigSchema>;\n\n// ~/.avatar/state.json — non-credential user state (tool install records,\n// allowed-paths chosen for filesystem MCP, etc).\nexport const userStateSchema = z.object({\n installed_tools: z\n .record(\n z.string(),\n z.object({\n version: z.string().optional(),\n installed_at: z.string().datetime(),\n install_method: z.string(),\n }),\n )\n .default({}),\n tool_inputs: z.record(z.string(), z.unknown()).default({}),\n});\nexport type UserState = z.infer<typeof userStateSchema>;\n\n// .claude/settings.json — per-project Claude Code settings emitted by `avatar init`.\n// Schema migrated v1.5.0 from legacy `allowedTools` to Claude Code modern schema\n// (permissions.allow/deny + hooks per-event). Pack template merging targets this shape.\nexport const projectSettingsSchema = z.object({\n $schema: z.string().optional(),\n includeCoAuthoredBy: z.boolean().optional(),\n env: z.record(z.string(), z.string()).default({}),\n permissions: z\n .object({\n allow: z.array(z.string()).default([]),\n deny: z.array(z.string()).default([]),\n })\n .partial()\n .optional(),\n // Hooks shape per Claude Code spec: each event key maps to array of matcher entries.\n // We accept unknown body since pack/template control concrete schema; Zod just guards\n // top-level structure to avoid corrupting user file on merge.\n hooks: z.record(z.string(), z.array(z.unknown())).optional(),\n statusLine: z\n .object({\n type: z.string(),\n command: z.string(),\n padding: z.number().optional(),\n })\n .optional(),\n});\nexport type ProjectSettings = z.infer<typeof projectSettingsSchema>;\n\n// Init mode — determines workspace topology (Chapter 02 + Chapter 07 spec).\nexport const initModeSchema = z.enum([\"internal\", \"client\", \"library\"]);\nexport type InitMode = z.infer<typeof initModeSchema>;\n","// Subscription path (Anthropic Pro/Team/Max plan) — 3 step verify:\n// 1) checkClaudeCodeSubscriptionAuth() — `claude auth status` exit code + parse JSON loggedIn\n// 2) triggerClaudeCodeAuthLogin() — `claude auth login` interactive (browser)\n// 3) verifyClaudeCodeQuota() — cheap prompt detect quota/auth issues\n//\n// Mục đích phase 03: đảm bảo claude binary có thể chạy thật trước khi ghi settings.\n// Quan trọng: `auth status loggedIn:true` KHÔNG đảm bảo token còn valid — có thể stale/revoked.\n// verifyClaudeCodeQuota phát hiện token expired qua 401 và trả reason=auth-expired.\nimport { spawnSync } from \"node:child_process\";\nimport { log } from \"./terminal-logger.js\";\n\nexport type ClaudeCodeAuthState = \"authenticated\" | \"not-authenticated\";\n\n// v1.12.2: Rich auth info để skip verify quota khi auth status đã đủ tin cậy.\n// Cost: 0.5s `claude auth status` thay vì 30-60s `claude --print ok`.\nexport interface ClaudeCodeAuthInfo {\n state: ClaudeCodeAuthState;\n email?: string;\n subscriptionType?: string; // \"max\" | \"pro\" | \"team\" | ...\n apiProvider?: string; // \"firstParty\" | ...\n}\n\nexport interface ClaudeCodeQuotaResult {\n ok: boolean;\n // Reason code chuẩn hóa để caller branch:\n // \"credit_balance_too_low\" | \"insufficient_quota\" | \"invalid_api_key\"\n // | \"auth-expired\" | \"rate_limit\" | \"timeout\" | \"unknown\"\n reason?: string;\n // Raw stderr/stdout sample để debug (truncated 500 chars).\n detail?: string;\n}\n\n// Quota verify timeout — claude --print API call thông thường 5-15s, nhưng:\n// - Mạng VN chậm + Anthropic API có thể spike 30s+\n// - First-call sau auth có thể slow vì load model cache\n// 60s threshold safe cho most cases, vẫn không quá lâu để user wait UX.\nconst QUOTA_VERIFY_TIMEOUT_MS = 60_000;\n\n// Cheap prompt: nội dung tối giản. Cost ~$0.0001 trên Sonnet.\nconst QUOTA_VERIFY_PROMPT = \"ok\";\n\n// Step 1: check `claude auth status` exit code + parse JSON output.\n// v2.1.x output JSON `{ loggedIn: bool, ... }`. Trust BOTH exit code AND loggedIn flag.\n// Nếu exit 0 nhưng loggedIn:false → treat as not-authenticated.\nexport function checkClaudeCodeSubscriptionAuth(): ClaudeCodeAuthState {\n return readClaudeCodeAuthInfo().state;\n}\n\n// v1.12.2: Rich variant — trả về cả email, subscriptionType, apiProvider.\n// Dùng để skip verify quota khi auth status đủ tin cậy (subscriptionType set =\n// account active có plan, không cần probe thêm `claude --print` 30-60s).\nexport function readClaudeCodeAuthInfo(): ClaudeCodeAuthInfo {\n const result = spawnSync(\"claude\", [\"auth\", \"status\"], { encoding: \"utf8\" });\n if (result.error || result.status !== 0) return { state: \"not-authenticated\" };\n\n const stdout = (result.stdout || \"\").trim();\n if (!stdout.startsWith(\"{\")) {\n // Older versions plain text output → trust exit code only.\n return { state: \"authenticated\" };\n }\n\n try {\n const parsed = JSON.parse(stdout) as {\n loggedIn?: boolean;\n email?: string;\n subscriptionType?: string;\n apiProvider?: string;\n };\n if (parsed.loggedIn !== true) return { state: \"not-authenticated\" };\n return {\n state: \"authenticated\",\n email: parsed.email,\n subscriptionType: parsed.subscriptionType,\n apiProvider: parsed.apiProvider,\n };\n } catch {\n // JSON parse fail → trust exit code.\n return { state: \"authenticated\" };\n }\n}\n\n// Step 2: trigger `claude auth login` interactive — block đến khi user complete trên browser.\n// Throws nếu spawn fail / user cancel (exit non-zero).\nexport function triggerClaudeCodeAuthLogin(): void {\n log.info(\"Khởi động đăng nhập Claude Code (browser sẽ mở)...\");\n const result = spawnSync(\"claude\", [\"auth\", \"login\"], { stdio: \"inherit\" });\n if (result.status !== 0) {\n throw new Error(\n `claude auth login thất bại (exit ${result.status}). Thử 'claude auth login' tay rồi chạy lại.`,\n );\n }\n log.success(\"Đã đăng nhập Claude Code\");\n}\n\n// Map error keyword → reason code chuẩn hóa.\n// Order quan trọng: check specific trước generic.\n// Expanded coverage cho stderr message dạng mới (vd \"401 Invalid authentication credentials\").\nfunction classifyQuotaError(combinedOutput: string): string {\n const text = combinedOutput.toLowerCase();\n\n // Credit / quota family.\n if (text.includes(\"credit_balance_too_low\") || text.includes(\"credit balance too low\")) {\n return \"credit_balance_too_low\";\n }\n if (text.includes(\"insufficient_quota\") || text.includes(\"insufficient quota\")) {\n return \"insufficient_quota\";\n }\n if (\n text.includes(\"quota_exceeded\") ||\n text.includes(\"quota exceeded\") ||\n text.includes(\"usage limit\") ||\n text.includes(\"you've used all\")\n ) {\n return \"insufficient_quota\";\n }\n\n // Auth family — token expired / 401 / invalid credentials.\n // Common Anthropic messages: \"401 Invalid authentication credentials\", \"authentication failed\",\n // \"failed to authenticate\", \"unauthorized\".\n if (\n text.includes(\"401\") ||\n text.includes(\"invalid authentication\") ||\n text.includes(\"authentication credentials\") ||\n text.includes(\"failed to authenticate\") ||\n text.includes(\"authentication failed\") ||\n text.includes(\"unauthorized\")\n ) {\n return \"auth-expired\";\n }\n if (text.includes(\"invalid_api_key\") || text.includes(\"invalid api key\")) {\n return \"invalid_api_key\";\n }\n\n // Rate limit family.\n if (text.includes(\"rate_limit\") || text.includes(\"rate limit\") || text.includes(\"429\")) {\n return \"rate_limit\";\n }\n\n return \"unknown\";\n}\n\n// User-facing hint string theo reason code — giúp message cuối cùng actionable.\n// Caller (run-ai-setup-phase) sẽ append hint này vào log warn.\nexport function getQuotaErrorHint(reason: string): string {\n switch (reason) {\n case \"auth-expired\":\n return \"Token Claude Code đã hết hạn/bị revoke. Chạy: `claude auth logout && claude auth login`.\";\n case \"credit_balance_too_low\":\n case \"insufficient_quota\":\n return \"Hết quota subscription. Upgrade plan hoặc dùng LLMLite (avatar ai setup → chọn LLMLite).\";\n case \"invalid_api_key\":\n return \"API key invalid. Re-login: `claude auth login`.\";\n case \"rate_limit\":\n return \"Bị rate limit tạm thời. Chờ vài phút rồi chạy `avatar ai setup`.\";\n case \"timeout\":\n return \"Timeout 60s: (1) mạng VN chậm — thử VPN, (2) Anthropic API spike — retry vài phút, (3) token vẫn auth nhưng quota hết âm thầm — check tại claude.ai/settings/usage.\";\n default:\n return \"Lỗi chưa biết. Xem stderr ở trên + chạy `claude --print ok` tay để debug.\";\n }\n}\n\n// Step 3: verify quota qua cheap prompt. Trả structured result.\n// Không throw — caller cần ok/reason để decide branch (warn + suggest LLMLite).\n// Khi reason=\"unknown\" — log full stderr+stdout để user/dev biết Anthropic đổi message gì.\n//\n// v1.12 fix: stdin \"ignore\" thay vì default pipe — tránh race nếu parent đang đợi\n// inquirer prompt (stdin TTY buffer leak gây claude --print block/fail intermittent).\n// v1.12 fix: dump cả stdout (không chỉ stderr) khi unknown — observed case status≠0\n// nhưng stdout có response (Anthropic message qua stdout, exit code lệch).\nexport function verifyClaudeCodeQuota(): ClaudeCodeQuotaResult {\n const result = spawnSync(\"claude\", [\"--print\", QUOTA_VERIFY_PROMPT], {\n encoding: \"utf8\",\n timeout: QUOTA_VERIFY_TIMEOUT_MS,\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n\n // Detect timeout — cross-platform Node.js quirk:\n // - macOS thường: signal=\"SIGTERM\", status=null\n // - Một số env: signal=null, status=143 (128+15 = SIGTERM exit code convention)\n // - Cũng có thể: error.code=\"ETIMEDOUT\"\n // Cover cả 3 case để không classify nhầm thành \"unknown\".\n const isTimeout =\n result.signal === \"SIGTERM\" ||\n result.status === 143 ||\n (result.error as NodeJS.ErrnoException | undefined)?.code === \"ETIMEDOUT\";\n if (isTimeout) {\n return {\n ok: false,\n reason: \"timeout\",\n detail: `claude --print > ${QUOTA_VERIFY_TIMEOUT_MS / 1000}s (mạng chậm / API rate limit / token revoked không return error)`,\n };\n }\n\n const stderr = result.stderr || \"\";\n const stdout = result.stdout || \"\";\n\n if (result.status === 0) {\n // Claude trả response — quota OK.\n return { ok: true };\n }\n\n // Defensive fallback: exit non-zero nhưng stdout có response thật (>20 chars,\n // không có keyword lỗi) → likely false-negative (claude binary quirk).\n // Trust stdout content thay vì exit code.\n const stdoutTrimmed = stdout.trim();\n const stderrLower = stderr.toLowerCase();\n if (\n stdoutTrimmed.length > 20 &&\n !stderrLower.includes(\"error\") &&\n !stderrLower.includes(\"limit\") &&\n !stderrLower.includes(\"quota\") &&\n !stderrLower.includes(\"401\")\n ) {\n log.warn(\n `claude --print exit=${result.status} nhưng có response (${stdoutTrimmed.length} chars). Accept với caution.`,\n );\n return { ok: true };\n }\n\n // Exit non-zero — parse stderr+stdout để classify.\n const reason = classifyQuotaError(`${stderr}\\n${stdout}`);\n\n // Khi unknown → dump cả stderr + stdout (truncated 500) qua log.warn (không phải\n // log.dim) để user dev thấy ngay. log.dim quá mờ trên terminal nhiều màu.\n if (reason === \"unknown\") {\n log.warn(`[debug] claude --print exit=${result.status} signal=${result.signal ?? \"none\"}`);\n if (stderr.trim()) log.warn(`[debug] stderr: ${stderr.slice(0, 500)}`);\n if (stdout.trim()) log.warn(`[debug] stdout: ${stdout.slice(0, 300)}`);\n }\n\n return { ok: false, reason, detail: stderr.slice(0, 500) || stdout.slice(0, 500) };\n}\n","// Detect Claude Code CLI (\"claude\" binary) trên PATH + parse SemVer version.\n// Trả structured info để các module sau (install / auth-check) decide flow.\n// Cross-platform: Unix dùng `which`, Windows dùng `where`.\n// Lưu ý: TRÁNH `command -v` (shell builtin) + `shell:true` — Node ≥23 emit DEP0190.\nimport { spawnSync } from \"node:child_process\";\nimport { detectHostPlatform } from \"./detect-host-platform.js\";\n\nexport interface ClaudeCodeInstallationInfo {\n installed: boolean;\n version: string | null; // SemVer string khi detect được, vd \"1.2.3\"\n path: string | null; // Absolute path đến binary nếu tìm thấy\n}\n\n// Timeout cho `claude --version` — nếu binary hang thì abort tránh block init.\nconst VERSION_PROBE_TIMEOUT_MS = 5000;\n\n// Regex SemVer chấp nhận extra suffix vd \"1.2.3 (Claude Code)\" hoặc \"claude 1.2.3\".\nconst SEMVER_REGEX = /(\\d+\\.\\d+\\.\\d+)/;\n\n// Probe binary location qua `which` (POSIX) hoặc `where` (Windows).\n// Cả 2 đều là external executable → KHÔNG cần shell:true → tránh DEP0190 trên Node 23+.\nfunction probeClaudeBinaryPath(): string | null {\n const isWindows = detectHostPlatform() === \"win32\";\n const probeCmd = isWindows ? \"where\" : \"which\";\n const result = spawnSync(probeCmd, [\"claude\"], { encoding: \"utf8\" });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n if (!out) return null;\n // `where` có thể list nhiều dòng — lấy dòng đầu.\n // out đã verified non-empty (line 29) → split[0] luôn tồn tại.\n return out.split(/\\r?\\n/)[0]!.trim();\n}\n\n// Parse `claude --version` output → SemVer. Null nếu không match.\nfunction probeClaudeVersion(): string | null {\n const result = spawnSync(\"claude\", [\"--version\"], {\n encoding: \"utf8\",\n timeout: VERSION_PROBE_TIMEOUT_MS,\n });\n\n if (result.error || result.status !== 0) return null;\n const out = (result.stdout || \"\").trim();\n const match = SEMVER_REGEX.exec(out);\n return match?.[1] ?? null;\n}\n\n// Main API: detect Claude Code installation state.\n// Không throw — luôn return structured info để caller branch logic.\n//\n// v1.13.0 perf: Module-level cache để tránh spawnSync `which claude` + `claude --version`\n// mỗi lần caller gọi. Init flow gọi function này 3+ lần → save 1-3s. Caller có thể\n// invalidate qua `invalidateClaudeCodeInstallationCache()` sau khi install/uninstall.\nlet cachedInfo: ClaudeCodeInstallationInfo | null = null;\n\nexport function detectClaudeCodeInstallation(): ClaudeCodeInstallationInfo {\n if (cachedInfo !== null) return cachedInfo;\n const path = probeClaudeBinaryPath();\n if (!path) {\n cachedInfo = { installed: false, version: null, path: null };\n return cachedInfo;\n }\n const version = probeClaudeVersion();\n cachedInfo = { installed: true, version, path };\n return cachedInfo;\n}\n\n// Caller gọi sau khi install/uninstall để re-probe state.\nexport function invalidateClaudeCodeInstallationCache(): void {\n cachedInfo = null;\n}\n","// Auto-install Claude Code CLI qua `npm install -g @anthropic-ai/claude-code`.\n// Stream stdio để user thấy npm progress. Sau khi cài xong → re-detect verify PATH.\n// Throws InstallClaudeCodeError với hint cụ thể cho EACCES / ENOSPC / generic.\nimport { spawnSync } from \"node:child_process\";\nimport {\n detectClaudeCodeInstallation,\n invalidateClaudeCodeInstallationCache,\n} from \"./detect-claude-code-installation.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// 5 phút — npm global install có thể chậm trên mạng yếu (registry resolve + download).\nconst NPM_INSTALL_TIMEOUT_MS = 5 * 60 * 1000;\n\n// Package name chính thức của Anthropic Claude Code CLI.\nconst CLAUDE_CODE_PACKAGE = \"@anthropic-ai/claude-code\";\n\nexport type InstallClaudeCodeReason =\n | \"permission-denied\" // EACCES → cần sudo hoặc fix npm prefix\n | \"disk-full\" // ENOSPC\n | \"timeout\" // > 5 phút\n | \"binary-not-in-path\" // install OK nhưng PATH chưa refresh\n | \"generic\"; // exit non-zero khác\n\nexport class InstallClaudeCodeError extends Error {\n reason: InstallClaudeCodeReason;\n exitCode: number | null;\n constructor(reason: InstallClaudeCodeReason, message: string, exitCode: number | null = null) {\n super(message);\n this.name = \"InstallClaudeCodeError\";\n this.reason = reason;\n this.exitCode = exitCode;\n }\n}\n\n// Phân loại exit từ npm install thành reason cụ thể để caller branch hint.\n// Hint hiển thị qua message, không log trực tiếp ở đây (separation of concerns).\nfunction classifyNpmFailure(exitCode: number | null, stderrSample: string): InstallClaudeCodeError {\n const stderr = stderrSample.toLowerCase();\n if (stderr.includes(\"eacces\") || stderr.includes(\"permission denied\")) {\n return new InstallClaudeCodeError(\n \"permission-denied\",\n `npm install -g cần quyền. Thử: sudo npm install -g ${CLAUDE_CODE_PACKAGE} hoặc fix npm prefix (npm config set prefix ~/.npm-global).`,\n exitCode,\n );\n }\n if (stderr.includes(\"enospc\") || stderr.includes(\"no space\")) {\n return new InstallClaudeCodeError(\n \"disk-full\",\n \"Đĩa đầy. Free disk space rồi thử lại.\",\n exitCode,\n );\n }\n return new InstallClaudeCodeError(\n \"generic\",\n `npm install thất bại (exit ${exitCode ?? \"null\"}). Xem log npm phía trên.`,\n exitCode,\n );\n}\n\n// Main API: cài Claude Code + verify. Throws InstallClaudeCodeError nếu fail.\n// Return version đã detect (luôn non-null nếu success vì re-detect verify SemVer).\nexport function installClaudeCodeViaNpm(): { version: string | null; path: string } {\n log.info(\"Đang cài Claude Code qua npm (có thể mất 1-2 phút)...\");\n\n // Pipe stderr để classify error sau, nhưng inherit stdout cho user thấy progress.\n // Note: stdio mixed array — [stdin, stdout, stderr].\n const result = spawnSync(\"npm\", [\"install\", \"-g\", CLAUDE_CODE_PACKAGE], {\n stdio: [\"inherit\", \"inherit\", \"pipe\"],\n timeout: NPM_INSTALL_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n // Timeout case — Node set signal SIGTERM khi timeout.\n if (result.signal === \"SIGTERM\") {\n throw new InstallClaudeCodeError(\n \"timeout\",\n `npm install timeout sau ${NPM_INSTALL_TIMEOUT_MS / 1000}s. Check mạng rồi thử lại.`,\n null,\n );\n }\n\n if (result.status !== 0) {\n // In stderr ra để user thấy lỗi gốc trước khi throw.\n if (result.stderr) process.stderr.write(result.stderr);\n throw classifyNpmFailure(result.status, result.stderr || \"\");\n }\n\n // v1.14.1 BUG FIX: Invalidate memoize cache TRƯỚC re-detect.\n // Cùng bug như install-gitnexus-via-npm: cache pre-install giữ {installed: false}\n // → re-detect trả false dù binary đã thực sự install.\n invalidateClaudeCodeInstallationCache();\n\n // Re-detect — npm install OK không đồng nghĩa binary đã trong PATH (PATH cache).\n const probe = detectClaudeCodeInstallation();\n if (!probe.installed || !probe.path) {\n throw new InstallClaudeCodeError(\n \"binary-not-in-path\",\n \"npm cài xong nhưng `claude` không trong PATH. Reload shell (source ~/.zshrc) hoặc thêm npm global bin vào PATH.\",\n null,\n );\n }\n\n log.success(`Đã cài Claude Code${probe.version ? ` v${probe.version}` : \"\"} tại ${probe.path}`);\n return { version: probe.version, path: probe.path };\n}\n","// Wizard chọn AI provider cho workspace — hybrid logic:\n// 1) Nếu detect được ~/.claude/settings.json global có ANTHROPIC_BASE_URL + token\n// → hỏi user \"Dùng global hay setup riêng?\"\n// 2) Nếu user setup riêng (hoặc không có global) → hỏi \"Subscription / LLMLite?\"\n// Output: discriminated union — caller branch theo provider tiếp tục phase 03 (sub) hoặc 05 (llmlite).\nimport { readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { select } from \"@inquirer/prompts\";\n\n// AI provider options:\n// - subscription : Claude Code Pro/Max OAuth (no API key — Anthropic billing qua subscription)\n// - llmlite : NAL gateway ai.nal.vn (key sk-pvd... bypass billing direct)\n// - anthropic : Anthropic API direct (key sk-ant-... pay-as-you-go credit) (v1.6.0)\n// - use-global : Copy ~/.claude/settings.json (đã có config sẵn từ Claude Code global)\n//\n// v1.20.3: Codex/Gemini providers removed — Claude Code native không support\n// OpenAI/Gemini. Để dùng OPENAI_API_KEY / GEMINI_API_KEY cho skills khác\n// (ai-multimodal, vv) → 'avatar secrets set OPENAI_API_KEY' / 'GEMINI_API_KEY'.\nexport type AiProviderChoice = \"use-global\" | \"subscription\" | \"llmlite\" | \"anthropic\";\n\n// Snapshot info global config — KHÔNG return raw token (security).\n// Token presence chỉ check, value không expose ra UI / log / return.\nexport interface GlobalClaudeSettingsInfo {\n exists: boolean;\n hasBaseUrl: boolean;\n baseUrl?: string; // OK expose vì base URL là endpoint public (vd https://ai.nal.vn)\n hasToken: boolean;\n model?: string;\n rawSettings?: Record<string, unknown>; // Để caller (use-global path) copy\n}\n\n// Path mặc định Claude Code đọc settings — không config khác.\nfunction getGlobalSettingsPath(): string {\n return join(homedir(), \".claude\", \"settings.json\");\n}\n\n// Read + validate ~/.claude/settings.json an toàn. Không throw — graceful null-ish.\nexport function detectGlobalClaudeSettings(): GlobalClaudeSettingsInfo {\n const path = getGlobalSettingsPath();\n let raw: string;\n try {\n raw = readFileSync(path, \"utf8\");\n } catch {\n return { exists: false, hasBaseUrl: false, hasToken: false };\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n // JSON corrupted — treat as no global config (an toàn hơn dùng config rác).\n return { exists: true, hasBaseUrl: false, hasToken: false };\n }\n\n const env = (parsed.env as Record<string, unknown> | undefined) || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : undefined;\n const hasToken =\n typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" && env.ANTHROPIC_AUTH_TOKEN.length > 0;\n const model = typeof parsed.model === \"string\" ? parsed.model : undefined;\n\n return {\n exists: true,\n hasBaseUrl: !!baseUrl,\n baseUrl,\n hasToken,\n model,\n rawSettings: parsed,\n };\n}\n\n// Main wizard. Trả enum choice — caller branch theo phase 03 / 05 / use-global path.\n// `globalInfo` injectable cho test (default = detect thật).\nexport async function promptAiProviderChoice(\n globalInfo: GlobalClaudeSettingsInfo = detectGlobalClaudeSettings(),\n): Promise<AiProviderChoice> {\n // Hybrid path: chỉ offer use-global khi global có ĐỦ baseUrl + token (config full).\n if (globalInfo.exists && globalInfo.hasBaseUrl && globalInfo.hasToken) {\n const choice = (await select({\n message: `Phát hiện AI config global (base URL: ${globalInfo.baseUrl}). Dùng cho project này?`,\n choices: [\n {\n name: \"a. Yes — copy config global vào .claude/settings.json (per-project)\",\n value: \"use-global\" as const,\n },\n {\n name: \"b. No — setup riêng (chọn provider khác)\",\n value: \"setup-fresh\" as const,\n },\n ],\n })) as \"use-global\" | \"setup-fresh\";\n\n if (choice === \"use-global\") return \"use-global\";\n // Fall-through to provider picker.\n }\n\n // Provider picker — 3 paths (Subscription / LLMLite / Anthropic Direct).\n return (await select({\n message: \"Chọn provider cho AI tools:\",\n choices: [\n {\n name: \"1. Claude Code Subscription (dùng quota cá nhân Anthropic, OAuth login)\",\n value: \"subscription\" as const,\n },\n {\n name: \"2. LLM key NAL (ai.nal.vn — gateway nội bộ, key sk-...)\",\n value: \"llmlite\" as const,\n },\n {\n name: \"3. Anthropic API key trực tiếp (console.anthropic.com, key sk-ant-...)\",\n value: \"anthropic\" as const,\n },\n ],\n })) as \"subscription\" | \"llmlite\" | \"anthropic\";\n}\n","// Anthropic direct API setup wizard — flow:\n// 1) Prompt API key ẩn (password input, format sk-ant-api03-...)\n// 2) Verify GET https://api.anthropic.com/v1/models với x-api-key header\n// 3) Smart picker: auto-pick nếu chỉ 1 claude model, prompt nếu nhiều\n//\n// Output: { apiKey, baseUrl, model } cho phase 06 write settings.json.\n//\n// KHÁC LLMLite:\n// - Header auth: x-api-key (Anthropic standard) thay vì Authorization: Bearer\n// - Bắt buộc anthropic-version: 2023-06-01 (API version header required)\n// - Base URL hardcode https://api.anthropic.com (Anthropic 1 endpoint chính thức)\n// - Settings.json output dùng ANTHROPIC_API_KEY (KHÔNG phải ANTHROPIC_AUTH_TOKEN)\nimport { password, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface AnthropicConfig {\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\n// Anthropic official endpoint. KHÔNG cho user override (decision Q1: keep simple).\n// User cần proxy đặc biệt → edit settings.json tay sau init.\nconst ANTHROPIC_BASE_URL = \"https://api.anthropic.com\";\n\n// API version bắt buộc theo Anthropic spec — endpoint reject nếu thiếu.\n// 2023-06-01 là version stable hiện tại (sẽ phải update nếu Anthropic bump).\nconst ANTHROPIC_API_VERSION = \"2023-06-01\";\n\n// 10s timeout fetch — đủ cho list models (response < 2KB).\nconst FETCH_TIMEOUT_MS = 10_000;\n\n// Trả \"sk-ant...XXXX\" để log không expose full token nhưng vẫn distinguishable.\nexport function maskAnthropicKey(key: string): string {\n if (key.length <= 12) return \"sk-ant-***\";\n return `${key.slice(0, 7)}...${key.slice(-4)}`;\n}\n\n// Anthropic key format: sk-ant-api03-... (108 ký tự). Soft validate prefix\n// để catch typo nhưng không strict length (Anthropic có thể bump format).\nfunction validateAnthropicKeyFormat(key: string): true | string {\n const trimmed = key.trim();\n if (trimmed.length === 0) return \"API key bắt buộc\";\n if (!trimmed.startsWith(\"sk-ant-\")) {\n return \"Anthropic API key thường bắt đầu bằng 'sk-ant-' (lấy từ console.anthropic.com).\";\n }\n return true;\n}\n\n// Prompt ẩn — `password` không echo input, hiển thị `*`.\nasync function promptAnthropicKeyHidden(): Promise<string> {\n return await password({\n message: \"Anthropic API key (sk-ant-..., ẩn input):\",\n mask: \"*\",\n validate: validateAnthropicKeyFormat,\n });\n}\n\n// Shape response /v1/models theo Anthropic spec — chỉ trust field `id`.\ninterface AnthropicModelsResponse {\n data?: Array<{ id?: unknown; type?: unknown }>;\n}\n\n// Fetch danh sách model từ Anthropic /v1/models.\n// Headers: x-api-key + anthropic-version (KHÔNG phải Authorization: Bearer).\nexport async function fetchAnthropicModels(apiKey: string): Promise<string[]> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(`${ANTHROPIC_BASE_URL}/v1/models`, {\n method: \"GET\",\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n Accept: \"application/json\",\n },\n signal: controller.signal,\n });\n\n if (res.status === 401) {\n throw new Error(\"API key invalid (HTTP 401). Check key trên console.anthropic.com.\");\n }\n if (res.status === 403) {\n throw new Error(\n \"API key bị reject (HTTP 403). Key có thể đã bị revoke hoặc thiếu permission.\",\n );\n }\n if (res.status === 429) {\n throw new Error(\"Rate limit (HTTP 429). Chờ vài giây rồi thử lại.\");\n }\n if (!res.ok) {\n throw new Error(`Fetch models thất bại (HTTP ${res.status}).`);\n }\n\n const json = (await res.json()) as AnthropicModelsResponse;\n const models = (json.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n\n if (models.length === 0) {\n throw new Error(\"Anthropic trả về list rỗng. Liên hệ support hoặc check account.\");\n }\n return models;\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new Error(`Connect ${ANTHROPIC_BASE_URL} timeout sau ${FETCH_TIMEOUT_MS / 1000}s.`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Smart picker: auto-pick nếu chỉ 1 model, prompt nếu nhiều.\n// Anthropic /v1/models trả tất cả claude-* models account access được — không cần\n// filter substring như LLMLite (LLMLite list có cả minimax, gpt, gemini...).\nexport async function promptAnthropicModelChoice(models: string[]): Promise<string> {\n if (models.length === 1) {\n // length === 1 đã guard → models[0] non-null.\n const only = models[0]!;\n log.info(`Auto-pick model: ${only} (chỉ 1 model available)`);\n return only;\n }\n\n // Sort: Sonnet preferred, sau đó Opus, Haiku. Default top option.\n const sorted = [...models].sort((a, b) => {\n const score = (m: string) => {\n const lower = m.toLowerCase();\n if (lower.includes(\"sonnet\")) return 0;\n if (lower.includes(\"opus\")) return 1;\n if (lower.includes(\"haiku\")) return 2;\n return 3;\n };\n return score(a) - score(b);\n });\n\n return await select({\n message: \"Chọn model mặc định cho project:\",\n choices: sorted.map((m) => ({ name: m, value: m })),\n });\n}\n\n// Main wizard. Orchestrate 3 step (KHÔNG có baseUrl prompt — hardcode endpoint).\nexport async function setupAnthropicApiKeyAndModel(): Promise<AnthropicConfig> {\n const apiKey = await promptAnthropicKeyHidden();\n\n log.info(`Verify key (${maskAnthropicKey(apiKey)}) qua ${ANTHROPIC_BASE_URL}/v1/models...`);\n const models = await fetchAnthropicModels(apiKey);\n log.success(`Endpoint OK — ${models.length} models available`);\n\n const model = await promptAnthropicModelChoice(models);\n return { apiKey, baseUrl: ANTHROPIC_BASE_URL, model };\n}\n","// LLMLite setup wizard — flow:\n// 1) Prompt API key ẩn (password input, không echo)\n// 2) Prompt base URL (default https://ai.nal.vn)\n// 3) Fetch GET /v1/models verify key + lấy danh sách model\n// 4) Smart picker: auto-pick nếu chỉ 1 claude-alias, prompt nếu nhiều, fallback toàn list\n// Output: { apiKey, baseUrl, model } cho phase 06 write settings.json.\nimport { input, password, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface LLMLiteConfig {\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\n// Endpoint mặc định NAL host. User có thể override (vd self-hosted dev).\nconst DEFAULT_BASE_URL = \"https://ai.nal.vn\";\n\n// 10s timeout fetch — VN mạng có thể chậm nhưng 10s đủ cho list models (response < 1KB).\nconst FETCH_TIMEOUT_MS = 10_000;\n\n// Trả \"sk-...XXXX\" để log không expose full token nhưng vẫn distinguishable.\nexport function maskApiKey(key: string): string {\n if (key.length <= 8) return \"sk-***\";\n return `${key.slice(0, 3)}...${key.slice(-4)}`;\n}\n\n// Prompt ẩn — `password` không echo input, hiển thị `*`.\nasync function promptApiKeyHidden(): Promise<string> {\n return await password({\n message: \"LLMLite API key (ẩn input):\",\n mask: \"*\",\n validate: (v) => (v.trim().length > 0 ? true : \"API key bắt buộc\"),\n });\n}\n\n// Prompt base URL với default cho user enter-to-accept nhanh.\nasync function promptBaseUrl(defaultUrl: string = DEFAULT_BASE_URL): Promise<string> {\n const value = await input({\n message: \"LLMLite base URL:\",\n default: defaultUrl,\n validate: (v) => (/^https?:\\/\\//.test(v) ? true : \"Phải là URL hợp lệ (http/https)\"),\n });\n // Strip trailing slash để concat /v1/... predictable.\n return value.replace(/\\/+$/, \"\");\n}\n\n// Shape response /v1/models theo OpenAI spec — chỉ trust field `id`.\ninterface ModelsResponse {\n data?: Array<{ id?: unknown }>;\n}\n\n// Fetch danh sách model từ endpoint. Throws có message rõ để caller hiển thị.\nexport async function fetchAvailableModels(baseUrl: string, apiKey: string): Promise<string[]> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const res = await fetch(`${baseUrl}/v1/models`, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n signal: controller.signal,\n });\n\n if (res.status === 401 || res.status === 403) {\n throw new Error(`API key invalid (HTTP ${res.status}).`);\n }\n if (res.status === 404) {\n throw new Error(`Endpoint /v1/models không tồn tại trên ${baseUrl}.`);\n }\n if (!res.ok) {\n throw new Error(`Fetch models thất bại (HTTP ${res.status}).`);\n }\n\n const json = (await res.json()) as ModelsResponse;\n const models = (json.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n\n if (models.length === 0) {\n throw new Error(\"LLMLite trả về list rỗng. Liên hệ admin NAL.\");\n }\n return models;\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n // v1.14.2 UX: NAL gateway (ai.nal.vn) là internal endpoint — chỉ accessible qua VPN NAL.\n // Common case: user quên bật VPN → timeout. Hint actionable thay vì chỉ \"timeout\".\n const isNalGateway = baseUrl.includes(\"nal.vn\") || baseUrl.includes(\"nal-vn\");\n const vpnHint = isNalGateway\n ? \"\\n Hint: ai.nal.vn là internal endpoint NAL — kiểm tra VPN NAL đã bật chưa, hoặc tcp test bằng `curl -v https://ai.nal.vn`.\"\n : \"\\n Hint: check mạng / firewall / VPN, hoặc base URL có đúng không.\";\n throw new Error(`Connect ${baseUrl} timeout sau ${FETCH_TIMEOUT_MS / 1000}s.${vpnHint}`);\n }\n // Network error khác (DNS fail, refused, ...) — log raw error.\n const errMsg = (err as Error).message || String(err);\n if (errMsg.toLowerCase().includes(\"fetch failed\") || errMsg.includes(\"ENOTFOUND\")) {\n const isNalGateway = baseUrl.includes(\"nal.vn\");\n const vpnHint = isNalGateway ? \" (ai.nal.vn cần VPN NAL — kiểm tra VPN đã bật chưa)\" : \"\";\n throw new Error(`Network error khi connect ${baseUrl}: ${errMsg}${vpnHint}`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Smart picker: auto-pick nếu chỉ 1 claude-alias, prompt nếu nhiều, fallback all.\n// `claude` substring match cover: nal-claude, claude-sonnet-4-5, claude-3-5-sonnet, ...\nexport async function promptModelChoice(models: string[]): Promise<string> {\n const claudeAliases = models.filter((m) => m.toLowerCase().includes(\"claude\"));\n\n if (claudeAliases.length === 1) {\n // length === 1 đã guard → claudeAliases[0] non-null.\n const only = claudeAliases[0]!;\n log.info(`Auto-pick model: ${only} (chỉ 1 claude alias trên endpoint)`);\n return only;\n }\n\n const choiceList = claudeAliases.length > 0 ? claudeAliases : models;\n return await select({\n message: \"Chọn model mặc định cho project:\",\n choices: choiceList.map((m) => ({ name: m, value: m })),\n });\n}\n\n// Main wizard. Orchestrate 4 step.\nexport async function setupLLMLiteApiKeyAndModel(): Promise<LLMLiteConfig> {\n const apiKey = await promptApiKeyHidden();\n const baseUrl = await promptBaseUrl();\n\n log.info(`Verify key (${maskApiKey(apiKey)}) qua ${baseUrl}/v1/models...`);\n const models = await fetchAvailableModels(baseUrl, apiKey);\n log.success(`Endpoint OK — ${models.length} models available`);\n\n const model = await promptModelChoice(models);\n return { apiKey, baseUrl, model };\n}\n","// Orchestrator AI Setup — compose phase 01-06 thành 1 flow fail-soft.\n// Gọi sau `maybeCreateWorkspaceRemote` trong `finalizeWorkspaceScaffold`.\n// Try/catch toàn bộ: success → log + audit ok. Fail → log warn + audit failed, KHÔNG throw —\n// workspace vẫn dùng được, user setup AI lại qua `avatar ai setup`.\nimport { appendAuditEntry } from \"./audit-log-appender.js\";\nimport {\n getQuotaErrorHint,\n readClaudeCodeAuthInfo,\n triggerClaudeCodeAuthLogin,\n verifyClaudeCodeQuota,\n} from \"./check-claude-code-subscription-and-quota.js\";\nimport {\n detectClaudeCodeInstallation,\n invalidateClaudeCodeInstallationCache,\n} from \"./detect-claude-code-installation.js\";\nimport { installClaudeCodeViaNpm } from \"./install-claude-code-via-npm.js\";\nimport {\n type AiProviderChoice,\n detectGlobalClaudeSettings,\n promptAiProviderChoice,\n} from \"./prompt-ai-provider-choice.js\";\nimport { setupAnthropicApiKeyAndModel } from \"./setup-anthropic-api-key-and-model.js\";\nimport { setupLLMLiteApiKeyAndModel } from \"./setup-llmlite-api-key-and-model.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { writeClaudeSettings } from \"./write-claude-settings-json-per-project.js\";\n\n// Default model alias cho Subscription path — Claude Code expect string vd \"sonnet\".\n// User có thể edit settings.json sau nếu muốn dùng model khác.\nconst SUBSCRIPTION_DEFAULT_MODEL = \"sonnet\";\n\n// v1.20.0: warnAboutPlaintextSecret removed — keys giờ lưu vào .envrc qua direnv,\n// không còn plaintext trong settings.json.\n\nexport interface AiSetupArgs {\n workspacePath: string;\n // CI / test mode: skip mọi interactive prompt — caller phải đảm bảo không vào path cần input.\n // Hiện tại không dùng vì AI setup interactive — reserve cho future enhancement.\n autoYes?: boolean;\n}\n\nexport type AiSetupResult =\n | { ok: true; provider: AiProviderChoice; model?: string }\n | { ok: false; reason: string; phase?: string };\n\n// Main orchestrator. Fail-soft — không throw.\nexport async function runAiSetupPhase(args: AiSetupArgs): Promise<AiSetupResult> {\n try {\n log.info(\"Setup AI provider cho workspace...\");\n\n // Phase 01: detect Claude Code. Phase 02: install nếu thiếu.\n let info = detectClaudeCodeInstallation();\n if (!info.installed) {\n log.info(\"Chưa có Claude Code — sẽ tự cài qua npm.\");\n installClaudeCodeViaNpm();\n // v1.13.0: Invalidate cache sau install để re-probe thấy binary mới.\n invalidateClaudeCodeInstallationCache();\n info = detectClaudeCodeInstallation();\n if (!info.installed) {\n throw new Error(\"Cài Claude Code xong nhưng vẫn không detect được binary.\");\n }\n } else {\n log.success(`Claude Code đã có${info.version ? ` v${info.version}` : \"\"}`);\n }\n\n // Phase 04: prompt provider choice (hybrid global detect).\n const globalInfo = detectGlobalClaudeSettings();\n const choice = await promptAiProviderChoice(globalInfo);\n\n // Branch theo choice → call phase 03 hoặc 05 → call phase 06 (write settings).\n switch (choice) {\n case \"subscription\": {\n // Phase 03: auth check + optional quota verify.\n //\n // v1.12.2: Đổi strategy verify quota từ MANDATORY → OPTIONAL.\n // Cũ: luôn chạy `claude --print ok` (30-60s) để verify token còn valid.\n // Vấn đề: trên mạng VN, prompt \"ok\" mất 30s+ → timeout 60s vẫn fail\n // thường xuyên dù subscription thực sự OK. UX cực tệ.\n // Mới: trust `claude auth status` JSON output. Nếu có\n // `subscriptionType` (max/pro/team) → trust ngay. Skip 60s probe.\n // Token revoked sẽ phát hiện lazy khi user run claude command thật,\n // không block setup wizard.\n let authInfo = readClaudeCodeAuthInfo();\n if (authInfo.state !== \"authenticated\") {\n triggerClaudeCodeAuthLogin();\n authInfo = readClaudeCodeAuthInfo();\n }\n\n // Trust path: auth status JSON có subscriptionType → active plan, skip probe.\n if (authInfo.state === \"authenticated\" && authInfo.subscriptionType) {\n await writeClaudeSettings(args.workspacePath, {\n provider: \"subscription\",\n model: SUBSCRIPTION_DEFAULT_MODEL,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=subscription,result=ok,plan=${authInfo.subscriptionType},probe=skipped`,\n );\n log.success(\n `AI ready · Subscription (${authInfo.subscriptionType}) · model=${SUBSCRIPTION_DEFAULT_MODEL}`,\n );\n return { ok: true, provider: \"subscription\", model: SUBSCRIPTION_DEFAULT_MODEL };\n }\n\n // Fallback: auth OK nhưng không có subscriptionType field (older claude version,\n // hoặc edge case JSON shape khác) → fallback verify quota như cũ.\n log.dim(\"Auth status không trả subscriptionType — verify quota (30-60s)...\");\n let quota = verifyClaudeCodeQuota();\n if (!quota.ok && quota.reason === \"auth-expired\") {\n log.warn(\"Token Claude Code đã hết hạn. Tự động re-login...\");\n triggerClaudeCodeAuthLogin();\n quota = verifyClaudeCodeQuota();\n }\n\n // Soft-pass: timeout/unknown KHÔNG block init. Auth status đã pass — trust.\n // Setup vẫn ghi settings.json, user dùng được. Token revoked sẽ fail lazy\n // ở first real call, user re-login manual qua `claude auth login`.\n if (!quota.ok && (quota.reason === \"timeout\" || quota.reason === \"unknown\")) {\n log.warn(`Probe verify ${quota.reason} — accept trust auth status. Tiếp tục.`);\n if (quota.detail?.trim()) log.warn(` Chi tiết: ${quota.detail.slice(0, 200)}`);\n await writeClaudeSettings(args.workspacePath, {\n provider: \"subscription\",\n model: SUBSCRIPTION_DEFAULT_MODEL,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=subscription,result=ok,probe=${quota.reason}-soft-pass`,\n );\n log.success(\n `AI ready · Subscription (probe ${quota.reason}, soft-pass) · model=${SUBSCRIPTION_DEFAULT_MODEL}`,\n );\n return { ok: true, provider: \"subscription\", model: SUBSCRIPTION_DEFAULT_MODEL };\n }\n\n // Hard fail: quota actually exhausted / invalid key / rate limit — block và suggest LLMLite.\n if (!quota.ok) {\n const reason = quota.reason ?? \"unknown\";\n await appendAuditEntry(\n \"ai_setup\",\n `provider=subscription,result=no-quota,reason=${reason}`,\n );\n log.warn(`Subscription verify thất bại (${reason}).`);\n if (quota.detail?.trim()) log.warn(` Chi tiết: ${quota.detail.slice(0, 200)}`);\n log.warn(` → ${getQuotaErrorHint(reason)}`);\n return { ok: false, reason: `subscription-${reason}`, phase: \"quota\" };\n }\n\n await writeClaudeSettings(args.workspacePath, {\n provider: \"subscription\",\n model: SUBSCRIPTION_DEFAULT_MODEL,\n });\n await appendAuditEntry(\"ai_setup\", \"provider=subscription,result=ok\");\n log.success(`AI ready · Subscription · model=${SUBSCRIPTION_DEFAULT_MODEL}`);\n return { ok: true, provider: \"subscription\", model: SUBSCRIPTION_DEFAULT_MODEL };\n }\n\n case \"llmlite\": {\n // Phase 05: API key + model picker + verify.\n const llmConfig = await setupLLMLiteApiKeyAndModel();\n // v3: bỏ direnv — key ghi thẳng vào settings.json (chmod 600 + .claude/.gitignore\n // bảo vệ). Claude Code đọc trực tiếp env field. skipApiKey=false.\n await writeClaudeSettings(args.workspacePath, {\n provider: \"llmlite\",\n apiKey: llmConfig.apiKey,\n baseUrl: llmConfig.baseUrl,\n model: llmConfig.model,\n skipApiKey: false,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=llmlite,result=ok,model=${llmConfig.model},base=${llmConfig.baseUrl},storage=settings.json`,\n );\n log.success(`AI ready · LLMLite (NAL) · model=${llmConfig.model} · ${llmConfig.baseUrl}`);\n return { ok: true, provider: \"llmlite\", model: llmConfig.model };\n }\n\n case \"anthropic\": {\n // v1.6.0: Anthropic API direct (key sk-ant-..., x-api-key auth).\n // v3: key ghi vào settings.json (chmod 600 + gitignored).\n const anthropicConfig = await setupAnthropicApiKeyAndModel();\n await writeClaudeSettings(args.workspacePath, {\n provider: \"anthropic\",\n apiKey: anthropicConfig.apiKey,\n baseUrl: anthropicConfig.baseUrl,\n model: anthropicConfig.model,\n skipApiKey: false,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=anthropic,result=ok,model=${anthropicConfig.model},storage=settings.json`,\n );\n log.success(\n `AI ready · Anthropic Direct · model=${anthropicConfig.model} · ${anthropicConfig.baseUrl}`,\n );\n return { ok: true, provider: \"anthropic\", model: anthropicConfig.model };\n }\n\n case \"use-global\": {\n if (!globalInfo.rawSettings) {\n throw new Error(\"use-global chọn nhưng không đọc được global settings.\");\n }\n await writeClaudeSettings(args.workspacePath, {\n provider: \"use-global\",\n sourceSettings: globalInfo.rawSettings,\n });\n await appendAuditEntry(\"ai_setup\", \"provider=use-global,result=ok\");\n log.success(`AI ready · Copy từ global config (${globalInfo.baseUrl ?? \"subscription\"})`);\n return { ok: true, provider: \"use-global\", model: globalInfo.model };\n }\n }\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`AI setup thất bại: ${message}`);\n log.dim(\"Workspace vẫn sẵn sàng. Setup AI sau qua: avatar ai setup\");\n await appendAuditEntry(\"ai_setup\", `result=failed,error=${message.slice(0, 200)}`);\n return { ok: false, reason: message };\n }\n}\n","// Ghi <workspace>/.claude/settings.json — schema Claude Code expect.\n// 3 branch input: subscription / llmlite / use-global.\n// Merge-safe: preserve key user thêm thủ công (vd \"theme\", \"permissions\"),\n// chỉ override env.ANTHROPIC_BASE_URL / env.ANTHROPIC_AUTH_TOKEN / model.\n// Atomic write qua tmp-rename + chmod 600 (file chứa secret token).\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\n// chmod 600 = owner read/write only. Bắt buộc cho file chứa raw token.\nconst SECRET_FILE_MODE = 0o600;\n\n// 4 branch input — discriminated union để TS narrow chính xác.\n// - subscription : OAuth Pro/Max, không cần key\n// - llmlite : NAL gateway (ai.nal.vn), dùng ANTHROPIC_AUTH_TOKEN (Bearer)\n// - anthropic : Anthropic direct, dùng ANTHROPIC_API_KEY (x-api-key) (v1.6.0)\n// - use-global : copy raw từ ~/.claude/settings.json\n//\n// v1.20.0: skipApiKey=true → keys lưu ở .envrc thay vì settings.json plaintext.\n// settings.json chỉ giữ non-secret config (provider label, baseUrl, model).\n// direnv tự load env var từ .envrc khi cd vào workspace.\n//\n// v1.20.3: Codex/Gemini providers removed — Claude Code native không support.\n// Để lưu OPENAI_API_KEY / GEMINI_API_KEY cho skills khác → 'avatar secrets set'.\nexport type ClaudeSettingsInput =\n | { provider: \"subscription\"; model: string }\n | { provider: \"llmlite\"; apiKey: string; baseUrl: string; model: string; skipApiKey?: boolean }\n | { provider: \"anthropic\"; apiKey: string; baseUrl: string; model: string; skipApiKey?: boolean }\n | { provider: \"use-global\"; sourceSettings: Record<string, unknown> };\n\n// Shape minimal của Claude Code settings.json — chỉ key Avatar đụng tới.\n// Index signature cho phép user thêm key custom (permissions, theme...) mà không lỗi type.\ninterface ClaudeSettings {\n env?: {\n ANTHROPIC_BASE_URL?: string;\n ANTHROPIC_AUTH_TOKEN?: string; // LLMLite gateway (Bearer auth)\n ANTHROPIC_API_KEY?: string; // Anthropic direct (x-api-key auth) — v1.6.0\n [k: string]: unknown;\n };\n model?: string;\n // v1.20.0: provider label (informational only, Claude Code không đọc).\n avatarProvider?: string;\n [k: string]: unknown;\n}\n\n// Đường dẫn settings target trong workspace.\nexport function getClaudeSettingsPath(workspacePath: string): string {\n return join(workspacePath, \".claude\", \"settings.json\");\n}\n\n// Đọc settings hiện có nếu file tồn tại. Throw nếu JSON corrupted để caller báo user\n// (an toàn hơn merge với object rỗng — có thể nuốt config user đã có).\nasync function readExistingSettings(path: string): Promise<ClaudeSettings> {\n if (!(await pathExists(path))) return {};\n try {\n return await readJson<ClaudeSettings>(path);\n } catch (err) {\n throw new Error(\n `Không parse được ${path} (JSON lỗi): ${(err as Error).message}. Backup file rồi xóa để Avatar tạo lại.`,\n );\n }\n}\n\n// Subscription path: clear MỌI provider env keys (LLMLite token + Anthropic direct key\n// nếu có từ config cũ), set model. OAuth dùng quota cá nhân, không cần env var.\nfunction applySubscription(existing: ClaudeSettings, model: string): ClaudeSettings {\n const { env: existingEnv, ...rest } = existing;\n const merged: ClaudeSettings = { ...rest, model };\n if (existingEnv) {\n const {\n ANTHROPIC_BASE_URL: _b,\n ANTHROPIC_AUTH_TOKEN: _t,\n ANTHROPIC_API_KEY: _k,\n ...envRest\n } = existingEnv;\n if (Object.keys(envRest).length > 0) {\n merged.env = envRest as ClaudeSettings[\"env\"];\n }\n }\n return merged;\n}\n\n// Drop all provider-specific keys (env vars that might conflict between providers).\n// Per-provider apply functions add back the ones THIS provider needs.\nfunction stripAllProviderKeys(env: ClaudeSettings[\"env\"]): ClaudeSettings[\"env\"] {\n if (!env) return env;\n const { ANTHROPIC_AUTH_TOKEN: _t, ANTHROPIC_API_KEY: _k, ...rest } = env;\n return rest as ClaudeSettings[\"env\"];\n}\n\n// LLMLite path: ANTHROPIC_AUTH_TOKEN (Bearer auth) + baseUrl gateway + model.\n// v1.20.0: if skipApiKey=true, omit token field (direnv .envrc handles it).\nfunction applyLLMLite(\n existing: ClaudeSettings,\n apiKey: string,\n baseUrl: string,\n model: string,\n skipApiKey: boolean,\n): ClaudeSettings {\n const envRest = stripAllProviderKeys(existing.env);\n const env: ClaudeSettings[\"env\"] = {\n ...envRest,\n ANTHROPIC_BASE_URL: baseUrl,\n };\n if (!skipApiKey) env.ANTHROPIC_AUTH_TOKEN = apiKey;\n return { ...existing, env, model, avatarProvider: \"llmlite\" };\n}\n\nfunction applyAnthropic(\n existing: ClaudeSettings,\n apiKey: string,\n baseUrl: string,\n model: string,\n skipApiKey: boolean,\n): ClaudeSettings {\n const envRest = stripAllProviderKeys(existing.env);\n const env: ClaudeSettings[\"env\"] = {\n ...envRest,\n ANTHROPIC_BASE_URL: baseUrl,\n };\n if (!skipApiKey) env.ANTHROPIC_API_KEY = apiKey;\n return { ...existing, env, model, avatarProvider: \"anthropic\" };\n}\n\n// Use-global path: copy block env + model từ ~/.claude/settings.json, giữ key local khác.\nfunction applyUseGlobal(existing: ClaudeSettings, source: Record<string, unknown>): ClaudeSettings {\n const sourceEnv = (source.env as Record<string, unknown> | undefined) || {};\n const sourceModel = typeof source.model === \"string\" ? source.model : undefined;\n return {\n ...existing,\n env: {\n ...(existing.env || {}),\n ...sourceEnv,\n },\n ...(sourceModel ? { model: sourceModel } : {}),\n };\n}\n\n// Main API. Ghi settings + chmod 600. Throws nếu file corrupted hoặc IO fail.\nexport async function writeClaudeSettings(\n workspacePath: string,\n input: ClaudeSettingsInput,\n): Promise<{ path: string; mode: number }> {\n const path = getClaudeSettingsPath(workspacePath);\n const existing = await readExistingSettings(path);\n\n let merged: ClaudeSettings;\n switch (input.provider) {\n case \"subscription\":\n merged = applySubscription(existing, input.model);\n break;\n case \"llmlite\":\n merged = applyLLMLite(\n existing,\n input.apiKey,\n input.baseUrl,\n input.model,\n input.skipApiKey === true,\n );\n break;\n case \"anthropic\":\n merged = applyAnthropic(\n existing,\n input.apiKey,\n input.baseUrl,\n input.model,\n input.skipApiKey === true,\n );\n break;\n case \"use-global\":\n merged = applyUseGlobal(existing, input.sourceSettings);\n break;\n }\n\n await writeJsonAtomic(path, merged, SECRET_FILE_MODE);\n\n // Re-chmod sau write — writeJsonAtomic đã apply mode trên tmp, rename giữ mode trên POSIX.\n // Trên Windows chmod no-op, không lỗi.\n try {\n await fs.chmod(path, SECRET_FILE_MODE);\n } catch {\n // Windows hoặc filesystem không support — bỏ qua, không fail flow.\n }\n\n return { path, mode: SECRET_FILE_MODE };\n}\n","// Test AI provider hoạt động — detect mode từ settings.json, test đúng path:\n// - LLMLite mode (có env.ANTHROPIC_BASE_URL + token): fetch /v1/models + /v1/chat/completions\n// - Subscription mode (no env): spawn `claude --print \"say ok\"`\n//\n// v1.2.3 fix: trước đây luôn dùng `claude --print` ngay cả khi LLMLite mode →\n// test sai (validate SDK auth chứ không phải LLMLite key). Giờ detect đúng provider.\nimport { spawnSync } from \"node:child_process\";\nimport { maskApiKey } from \"./setup-llmlite-api-key-and-model.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Timeout cho HTTP fetch LLMLite — 10s đủ cho list models + chat completion.\nconst FETCH_TIMEOUT_MS = 10_000;\n\n// Timeout cho subprocess claude --print — 30s an toàn cho cold start.\nconst CLAUDE_PRINT_TIMEOUT_MS = 30_000;\n\n// Cheap chat completion — max_tokens nhỏ giảm cost (~$0.0001 trên Sonnet).\nconst TEST_CHAT_MAX_TOKENS = 5;\nconst TEST_CHAT_PROMPT = \"say ok\";\n\nexport interface TestResult {\n ok: boolean;\n provider: \"llmlite\" | \"subscription\" | \"anthropic\";\n message: string;\n}\n\n// Anthropic API version bắt buộc theo spec (giống setup-anthropic module).\nconst ANTHROPIC_API_VERSION = \"2023-06-01\";\n\ninterface ModelsResponse {\n data?: Array<{ id?: unknown }>;\n}\n\ninterface ChatResponse {\n choices?: Array<{ message?: { content?: unknown } }>;\n usage?: { total_tokens?: number };\n}\n\n// LLMLite test path: /v1/models (verify connectivity + key) + /v1/chat/completions (verify model work).\n// Throws với message rõ nếu fail bất kỳ step nào.\nasync function testLLMLiteProvider(baseUrl: string, token: string, model: string): Promise<void> {\n log.info(`Testing LLMLite provider: ${baseUrl} (key: ${maskApiKey(token)})`);\n\n // Step 1: List models — verify connectivity + key valid.\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const modelsRes = await fetch(`${baseUrl}/v1/models`, {\n headers: { Authorization: `Bearer ${token}` },\n signal: controller.signal,\n });\n\n if (modelsRes.status === 401 || modelsRes.status === 403) {\n throw new Error(`API key invalid (HTTP ${modelsRes.status}). Re-run: avatar ai setup`);\n }\n if (!modelsRes.ok) {\n throw new Error(`Endpoint /v1/models lỗi (HTTP ${modelsRes.status}).`);\n }\n\n const modelsJson = (await modelsRes.json()) as ModelsResponse;\n const models = (modelsJson.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n\n log.success(`Connectivity OK · ${models.length} models available`);\n if (models.length > 0) {\n const preview = models.slice(0, 5).join(\", \");\n const more = models.length > 5 ? ` ...+${models.length - 5} more` : \"\";\n log.dim(` Models: ${preview}${more}`);\n }\n\n // Step 2: Cheap chat completion — verify model thật sự work end-to-end.\n log.info(`Testing chat completion với model \"${model}\"...`);\n const chatRes = await fetch(`${baseUrl}/v1/chat/completions`, {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${token}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model,\n messages: [{ role: \"user\", content: TEST_CHAT_PROMPT }],\n max_tokens: TEST_CHAT_MAX_TOKENS,\n }),\n signal: controller.signal,\n });\n\n if (!chatRes.ok) {\n const errBody = (await chatRes.text()).slice(0, 200);\n throw new Error(`Chat completion fail (HTTP ${chatRes.status}). ${errBody}`);\n }\n\n const chatJson = (await chatRes.json()) as ChatResponse;\n const reply =\n typeof chatJson.choices?.[0]?.message?.content === \"string\"\n ? chatJson.choices[0].message.content\n : \"(empty response)\";\n const tokens = chatJson.usage?.total_tokens ?? \"?\";\n\n log.success(`Response: \"${String(reply).trim().slice(0, 100)}\"`);\n log.dim(` Tokens used: ${tokens}`);\n } catch (err) {\n if ((err as Error).name === \"AbortError\") {\n throw new Error(`Timeout ${FETCH_TIMEOUT_MS / 1000}s. Check mạng / endpoint ${baseUrl}.`);\n }\n throw err;\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Subscription test path: chạy `claude --print` — verify SDK auth + quota.\n// Hint cụ thể khi 401 (token stale — pattern từ v1.2.1).\nfunction testSubscriptionProvider(): void {\n log.info(\"Testing Subscription provider qua `claude --print`...\");\n const result = spawnSync(\"claude\", [\"--print\", TEST_CHAT_PROMPT], {\n encoding: \"utf8\",\n timeout: CLAUDE_PRINT_TIMEOUT_MS,\n });\n\n if (result.signal === \"SIGTERM\") {\n throw new Error(`Timeout ${CLAUDE_PRINT_TIMEOUT_MS / 1000}s. Check mạng / endpoint.`);\n }\n if (result.status !== 0) {\n const stderr = (result.stderr || \"\").toLowerCase();\n if (\n stderr.includes(\"401\") ||\n stderr.includes(\"invalid authentication\") ||\n stderr.includes(\"unauthorized\")\n ) {\n throw new Error(\n \"Token Claude Code stale (401). Fix: `claude auth logout && claude auth login`.\",\n );\n }\n throw new Error(\n `Test fail (exit ${result.status}). Stderr: ${(result.stderr || \"\").slice(0, 200)}`,\n );\n }\n log.success(`Response: \"${(result.stdout || \"\").trim().slice(0, 100)}\"`);\n}\n\n// Anthropic Direct test path: /v1/models (verify key) + /v1/messages (verify model work).\n// KHÁC LLMLite: header x-api-key + anthropic-version, body schema khác (system/messages).\nasync function testAnthropicProvider(\n baseUrl: string,\n apiKey: string,\n model: string,\n): Promise<void> {\n log.info(`Testing Anthropic Direct provider: ${baseUrl} (key: ${maskApiKey(apiKey)})`);\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n // Step 1: List models — verify connectivity + key valid.\n const modelsRes = await fetch(`${baseUrl}/v1/models`, {\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n },\n signal: controller.signal,\n });\n\n if (modelsRes.status === 401 || modelsRes.status === 403) {\n throw new Error(`API key invalid (HTTP ${modelsRes.status}). Re-run: avatar ai setup`);\n }\n if (!modelsRes.ok) {\n throw new Error(`Endpoint /v1/models lỗi (HTTP ${modelsRes.status}).`);\n }\n\n const modelsJson = (await modelsRes.json()) as ModelsResponse;\n const models = (modelsJson.data || [])\n .map((m) => (typeof m.id === \"string\" ? m.id : null))\n .filter((id): id is string => id !== null);\n log.success(`Connectivity OK · ${models.length} models available`);\n\n // Step 2: Cheap message — verify model work end-to-end via /v1/messages.\n log.info(`Testing message với model \"${model}\"...`);\n const msgRes = await fetch(`${baseUrl}/v1/messages`, {\n method: \"POST\",\n headers: {\n \"x-api-key\": apiKey,\n \"anthropic-version\": ANTHROPIC_API_VERSION,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify({\n model,\n max_tokens: TEST_CHAT_MAX_TOKENS,\n messages: [{ role: \"user\", content: TEST_CHAT_PROMPT }],\n }),\n signal: controller.signal,\n });\n\n if (!msgRes.ok) {\n const errText = (await msgRes.text()).slice(0, 200);\n throw new Error(`Message endpoint fail (HTTP ${msgRes.status}): ${errText}`);\n }\n const msgJson = (await msgRes.json()) as {\n content?: Array<{ text?: unknown }>;\n usage?: { input_tokens?: number; output_tokens?: number };\n };\n const text = (msgJson.content || [])\n .map((c) => (typeof c.text === \"string\" ? c.text : \"\"))\n .join(\"\")\n .trim()\n .slice(0, 100);\n log.success(`Response: \"${text}\"`);\n } finally {\n clearTimeout(timer);\n }\n}\n\n// Main entry — detect provider từ settings, run đúng test path.\n// Throws với message actionable nếu fail. Caller (avatar ai test) catch + log.error.\n//\n// v1.6.0: Add Anthropic Direct branch (ANTHROPIC_API_KEY + ANTHROPIC_BASE_URL).\n// Order check: anthropic > llmlite > subscription (more specific provider first).\nexport async function testAiProviderByDetectedMode(\n settings: Record<string, unknown>,\n): Promise<TestResult> {\n const env = (settings.env as Record<string, unknown> | undefined) || {};\n const baseUrl = typeof env.ANTHROPIC_BASE_URL === \"string\" ? env.ANTHROPIC_BASE_URL : undefined;\n const token = typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" ? env.ANTHROPIC_AUTH_TOKEN : undefined;\n const apiKey = typeof env.ANTHROPIC_API_KEY === \"string\" ? env.ANTHROPIC_API_KEY : undefined;\n const model = typeof settings.model === \"string\" ? settings.model : \"default\";\n\n if (apiKey && baseUrl) {\n await testAnthropicProvider(baseUrl, apiKey, model);\n return { ok: true, provider: \"anthropic\", message: \"Anthropic Direct provider working\" };\n }\n\n if (baseUrl && token) {\n await testLLMLiteProvider(baseUrl, token, model);\n return { ok: true, provider: \"llmlite\", message: \"LLMLite provider working\" };\n }\n\n testSubscriptionProvider();\n return { ok: true, provider: \"subscription\", message: \"Subscription provider working\" };\n}\n","import { spawnSync } from \"node:child_process\";\n// `avatar doctor [--fix]` — Command 07 spec.\n// Run a series of health checks and surface ✓/✗ for each. --fix attempts\n// auto-fixes for the ones safe to fix automatically.\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { checkEnabledToolsHealth } from \"../lib/check-enabled-tools-health.js\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\nimport { isTokenExpired, readUserConfig } from \"../lib/user-config-store.js\";\n\ninterface CheckResult {\n name: string;\n status: \"ok\" | \"warn\" | \"fail\";\n detail: string;\n fixable: boolean;\n fix?: () => Promise<void>;\n}\n\nexport function registerDoctorCommand(program: Command): void {\n program\n .command(\"doctor\")\n .description(\"Chẩn đoán cài đặt Avatar: hooks, MCP, login, submodule, ...\")\n .option(\"--fix\", \"Tự động fix các issue có thể fix tự động\")\n .action(async (opts: { fix?: boolean }) => {\n try {\n const checks = await runChecks(process.cwd());\n renderChecks(checks);\n if (opts.fix) await applyFixes(checks);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nasync function runChecks(cwd: string): Promise<CheckResult[]> {\n const checks: CheckResult[] = [];\n\n // 1. Node.js version >= 18.17\n const nodeVer = process.versions.node;\n const [major, minor] = nodeVer.split(\".\").map((n) => Number.parseInt(n, 10));\n const nodeOk = (major ?? 0) > 18 || ((major ?? 0) === 18 && (minor ?? 0) >= 17);\n checks.push({\n name: \"Node.js version\",\n status: nodeOk ? \"ok\" : \"fail\",\n detail: `v${nodeVer}${nodeOk ? \"\" : \" (cần >= 18.17)\"}`,\n fixable: false,\n });\n\n // 2. Login.\n const config = await readUserConfig();\n if (!config) {\n checks.push({\n name: \"Login status\",\n status: \"fail\",\n detail: \"Chưa đăng nhập — chạy 'avatar login'\",\n fixable: false,\n });\n } else if (isTokenExpired(config)) {\n checks.push({\n name: \"Login status\",\n status: \"warn\",\n detail: `Token hết hạn (${config.email}) — chạy 'avatar login'`,\n fixable: false,\n });\n } else {\n checks.push({\n name: \"Login status\",\n status: \"ok\",\n detail: `Logged in: ${config.email}`,\n fixable: false,\n });\n }\n\n // v1.14.0 perf: Batch 1 — 4 probes I/O độc lập chạy parallel qua Promise.all.\n // Save ~80-150ms (4 stat calls × ~30ms mỗi cái → ~30ms total thay vì 120ms).\n // v3: workspace KHÔNG git (pack tarball, repos clone độc lập trong src/).\n // Bỏ check \"Git repository\" + post-merge hook. Avatar-init = có .claude/pack/ + CLAUDE.md.\n const packPath = join(cwd, \".claude\", \"pack\");\n const claudeMdPath = join(cwd, \"CLAUDE.md\");\n const [hasPack, hasClaudeMd] = await Promise.all([\n pathExists(packPath),\n pathExists(claudeMdPath),\n ]);\n\n // 4. .claude/pack/ installation (tarball).\n checks.push({\n name: \"team-ai-pack installation\",\n status: hasPack ? \"ok\" : \"warn\",\n detail: hasPack ? packPath : \"Avatar chưa init — chạy 'avatar init'\",\n fixable: false,\n });\n\n // 5. CLAUDE.md present.\n checks.push({\n name: \"CLAUDE.md\",\n status: hasClaudeMd ? \"ok\" : \"warn\",\n detail: hasClaudeMd ? \"tồn tại ở project root\" : \"thiếu — chạy 'avatar init'\",\n fixable: false,\n });\n\n // v3: post-merge hook + workspace .gitignore BỎ (workspace không git).\n // Secrets (API key) sống trong .claude/settings.json, bảo vệ bằng .claude/.gitignore\n // (phòng .claude/ bị git track nhầm). Check file đó có + bảo vệ settings.json.\n if (hasPack) {\n const claudeGitignorePath = join(cwd, \".claude\", \".gitignore\");\n let settingsProtected = false;\n if (await pathExists(claudeGitignorePath)) {\n const content = await fs.readFile(claudeGitignorePath, \"utf8\");\n settingsProtected = content.includes(\"settings.json\");\n }\n checks.push({\n name: \"🔒 settings.json gitignored (.claude/.gitignore)\",\n status: settingsProtected ? \"ok\" : \"fail\",\n detail: settingsProtected\n ? \"an toàn — settings.json không commit nhầm\"\n : \"CRITICAL: settings.json chứa API key — chạy 'avatar doctor --fix' để bảo vệ\",\n fixable: !settingsProtected,\n fix: settingsProtected\n ? undefined\n : async () => {\n const { writeClaudeGitignore } = await import(\"../lib/project-tree-scaffolder.js\");\n await writeClaudeGitignore(cwd);\n },\n });\n }\n\n // v1.7.0 — 7c. python vs python3 (pack scripts thường hardcode python).\n // Modern macOS chỉ có python3 → pack script fail. Check + suggest alias.\n const pythonCheck = spawnSync(\"which\", [\"python\"]);\n const python3Check = spawnSync(\"which\", [\"python3\"]);\n const hasPython = pythonCheck.status === 0;\n const hasPython3 = python3Check.status === 0;\n if (hasPython3 && !hasPython) {\n checks.push({\n name: \"Python binary alias\",\n status: \"warn\",\n detail: `Chỉ có python3 (modern macOS). Pack scripts thường ref 'python' → suggest: ln -s ${python3Check.stdout.toString().trim()} ~/.local/bin/python`,\n fixable: false,\n });\n } else if (hasPython) {\n checks.push({\n name: \"Python binary\",\n status: \"ok\",\n detail: `python: ${pythonCheck.stdout.toString().trim()}`,\n fixable: false,\n });\n } else if (hasPython3) {\n checks.push({\n name: \"Python binary\",\n status: \"ok\",\n detail: `python3: ${python3Check.stdout.toString().trim()}`,\n fixable: false,\n });\n }\n\n // v1.7.0 — 7d. statusLine path in settings.json — verify file ref tồn tại.\n // Pack template thường ref `node .claude/statusline.cjs` nhưng pack KHÔNG ship file.\n // Avatar v1.7.0 merger đã skip merge nếu file missing, nhưng workspace cũ có thể đã commit settings này.\n const settingsPath = join(cwd, \".claude\", \"settings.json\");\n if (await pathExists(settingsPath)) {\n try {\n const settingsRaw = await fs.readFile(settingsPath, \"utf8\");\n const settings = JSON.parse(settingsRaw) as {\n statusLine?: { command?: string };\n };\n if (settings.statusLine?.command) {\n const cmd = settings.statusLine.command.trim();\n const match = cmd.match(/^(node|python|python3|bash|sh)\\s+([^\\s]+)/);\n if (match?.[2]) {\n const refFile = match[2];\n const fullPath = refFile.startsWith(\"/\") ? refFile : join(cwd, refFile);\n const fileExists = await pathExists(fullPath);\n checks.push({\n name: \"statusLine command\",\n status: fileExists ? \"ok\" : \"fail\",\n detail: fileExists\n ? `ref OK: ${refFile}`\n : `BROKEN: settings.json ref '${refFile}' nhưng file không tồn tại. Strip field statusLine hoặc fix path.`,\n fixable: false,\n });\n }\n }\n } catch {\n // settings.json corrupted — đã có check khác handle.\n }\n }\n\n // 8. Claude Code CLI installed (best-effort `which claude`).\n const which = spawnSync(\"which\", [\"claude\"]);\n const hasClaudeCli = which.status === 0;\n checks.push({\n name: \"Claude Code CLI\",\n status: hasClaudeCli ? \"ok\" : \"warn\",\n detail: hasClaudeCli ? which.stdout.toString().trim() : \"không tìm thấy 'claude' trên PATH\",\n fixable: false,\n });\n\n // 9. Tool toggle health (prompt-scoring, ...). Mỗi tool enabled trong state:\n // manifest còn? hook còn trong settings.json? (skew/orphan → warn; mất hook → fail+fix).\n if (hasPack) {\n const toolChecks = await checkEnabledToolsHealth(cwd);\n checks.push(...toolChecks);\n }\n\n return checks;\n}\n\nfunction renderChecks(checks: CheckResult[]): void {\n const lines = [chalk.bold(\"Avatar Doctor\"), \"─\".repeat(48)];\n let passed = 0;\n let issues = 0;\n let fixable = 0;\n for (const c of checks) {\n const icon =\n c.status === \"ok\"\n ? chalk.green(\"✓\")\n : c.status === \"warn\"\n ? chalk.yellow(\"⚠\")\n : chalk.red(\"✗\");\n lines.push(`${icon} ${c.name.padEnd(28)} ${chalk.dim(c.detail)}`);\n if (c.status === \"ok\") passed += 1;\n else {\n issues += 1;\n if (c.fixable) fixable += 1;\n }\n }\n lines.push(\"─\".repeat(48));\n lines.push(\n `${passed} checks passed, ${issues} issue${issues === 1 ? \"\" : \"s\"}${fixable > 0 ? ` (${fixable} fixable — chạy 'avatar doctor --fix')` : \"\"}`,\n );\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n\nasync function applyFixes(checks: CheckResult[]): Promise<void> {\n let count = 0;\n for (const c of checks) {\n if (c.fixable && c.fix) {\n try {\n await c.fix();\n log.success(`Fixed: ${c.name}`);\n count += 1;\n } catch (err) {\n log.error(`Failed to fix ${c.name}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n if (count === 0) log.dim(\"Không có gì để fix tự động.\");\n}\n","// check-enabled-tools-health.ts\n//\n// Doctor check: với mỗi tool enabled=true trong state, xác minh\n// (a) manifest còn trong pack (phát hiện version skew / orphan state)\n// (b) hook của tool THỰC SỰ có trong settings.json (phát hiện \"state nói bật\n// nhưng settings bị ghi đè/mất hook\" — fixable bằng re-enable)\n//\n// Trả về list check shape khớp doctor CheckResult (name/status/detail/fixable/fix).\n\nimport { join } from \"node:path\";\nimport {\n type ToolManifest,\n enableTool,\n readToolManifest,\n} from \"./apply-tool-manifest-to-settings.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\nimport { listEnabledTools } from \"./tool-state-store.js\";\n\nexport interface ToolCheck {\n name: string;\n status: \"ok\" | \"warn\" | \"fail\";\n detail: string;\n fixable: boolean;\n fix?: () => Promise<void>;\n}\n\ninterface SettingsShape {\n hooks?: Record<string, Array<{ matcher?: string; hooks?: Array<{ command?: string }> }>>;\n}\n\n// Mọi command của tool đã có mặt trong settings.json chưa?\nfunction manifestHooksPresent(settings: SettingsShape, manifest: ToolManifest): boolean {\n const mHooks = manifest.settings.hooks ?? {};\n const sHooks = settings.hooks ?? {};\n for (const [event, featEntries] of Object.entries(mHooks)) {\n const present = sHooks[event] ?? [];\n const presentCommands = new Set(\n present.flatMap((e) => (e.hooks ?? []).map((h) => h.command ?? \"\")),\n );\n for (const entry of featEntries) {\n for (const h of entry.hooks ?? []) {\n if (h.command && !presentCommands.has(h.command)) {\n return false;\n }\n }\n }\n }\n return true;\n}\n\nexport async function checkEnabledToolsHealth(cwd: string): Promise<ToolCheck[]> {\n const enabled = await listEnabledTools(cwd);\n if (enabled.length === 0) {\n return [];\n }\n\n const settingsPath = join(cwd, \".claude\", \"settings.json\");\n let settings: SettingsShape = {};\n if (await pathExists(settingsPath)) {\n try {\n settings = await readJson<SettingsShape>(settingsPath);\n } catch {\n settings = {};\n }\n }\n\n const checks: ToolCheck[] = [];\n for (const name of enabled) {\n const manifest = await readToolManifest(cwd, name);\n if (!manifest) {\n checks.push({\n name: `Tool: ${name}`,\n status: \"warn\",\n detail: \"state=enabled nhưng pack thiếu manifest (version skew). Chạy 'avatar sync'.\",\n fixable: false,\n });\n continue;\n }\n if (manifestHooksPresent(settings, manifest)) {\n checks.push({\n name: `Tool: ${name}`,\n status: \"ok\",\n detail: `enabled, hook active (v${manifest.version})`,\n fixable: false,\n });\n } else {\n checks.push({\n name: `Tool: ${name}`,\n status: \"fail\",\n detail: \"state=enabled nhưng hook thiếu trong settings.json — fixable (re-apply)\",\n fixable: true,\n fix: async () => {\n await enableTool(cwd, manifest);\n },\n });\n }\n }\n return checks;\n}\n","// apply-tool-manifest-to-settings.ts\n//\n// Áp / rút manifest của 1 tool toggle (vd prompt-scoring) vào project\n// settings.json. Đối xứng: enable = merge, disable = remove ĐÚNG những gì manifest\n// khai. Nguồn sự thật là tool.json (KHÔNG nhúng marker `_feature` vào settings\n// → settings giữ schema chuẩn 100%).\n//\n// Manifest sống ở: <project>/.claude/pack/tools/<name>/tool.json\n// (submodule mount tại .claude/pack/). Manifest.settings chỉ chứa subset cần cho\n// tool: hooks (per-event) + permissions.deny.\n//\n// Khác mergePackSettingsIntoProjectSettings (chỉ áp template + có statusLine/env/\n// model): file này CHỈ lo hooks + deny của 1 tool, và biết RÚT ngược lại.\n//\n// Safety: backup settings.json trước mọi thay đổi; atomic write; idempotent\n// (enable lại không nhân đôi; disable tool chưa bật → no-op).\n\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\nimport {\n type PackSettingsTemplate,\n backupFilename,\n unionDedupe,\n} from \"./merge-pack-settings-into-project-settings.js\";\n\n// Một hook entry trong settings: { matcher?, hooks: [{type, command}] }.\ninterface HookEntry {\n matcher?: string;\n hooks?: Array<{ type?: string; command?: string }>;\n}\n\nexport interface ToolManifest {\n name: string;\n version: string;\n description?: string;\n requires?: { runtime?: string };\n settings: {\n hooks?: Record<string, HookEntry[]>;\n permissions?: { deny?: string[]; allow?: string[] };\n };\n}\n\nexport interface ApplyToolResult {\n action: \"enabled\" | \"disabled\" | \"no-change\" | \"no-manifest\";\n backupPath?: string;\n changes: string[];\n}\n\nexport function featureManifestPath(workspacePath: string, name: string): string {\n return join(workspacePath, \".claude\", \"pack\", \"tools\", name, \"tool.json\");\n}\n\n// Đọc manifest tool. Thiếu → null (caller fail mềm: \"pack chưa hỗ trợ tool\").\nexport async function readToolManifest(\n workspacePath: string,\n name: string,\n): Promise<ToolManifest | null> {\n const path = featureManifestPath(workspacePath, name);\n if (!(await pathExists(path))) {\n return null;\n }\n return await readJson<ToolManifest>(path);\n}\n\nconst PROJECT_SETTINGS_REL = [\".claude\", \"settings.json\"];\n\nfunction projectSettingsPath(workspacePath: string): string {\n return join(workspacePath, ...PROJECT_SETTINGS_REL);\n}\n\n// So khớp 2 hook entry theo (matcher + danh sách command) — định danh dùng cho\n// cả dedupe (enable) lẫn remove (disable).\nfunction hookEntryKey(entry: HookEntry): string {\n const commands = (entry.hooks ?? [])\n .map((h) => h.command ?? \"\")\n .sort()\n .join(\"\u0000\");\n return `${entry.matcher ?? \"\"}\u0001${commands}`;\n}\n\nasync function loadProjectSettings(\n workspacePath: string,\n): Promise<{ settings: PackSettingsTemplate; existed: boolean }> {\n const path = projectSettingsPath(workspacePath);\n if (!(await pathExists(path))) {\n return { settings: {}, existed: false };\n }\n try {\n return { settings: await readJson<PackSettingsTemplate>(path), existed: true };\n } catch (err) {\n throw new Error(\n `Project settings.json không parse được: ${(err as Error).message}. Manual fix trước khi enable/disable tool.`,\n );\n }\n}\n\nasync function backupAndWrite(\n workspacePath: string,\n settings: PackSettingsTemplate,\n existed: boolean,\n): Promise<string | undefined> {\n const path = projectSettingsPath(workspacePath);\n let backupPath: string | undefined;\n if (existed) {\n backupPath = backupFilename(path);\n await fs.copyFile(path, backupPath);\n }\n await writeJsonAtomic(path, settings);\n return backupPath;\n}\n\n// v1.18.0: Normalize command để detect stale relative-path entries cần migrate.\n// Strip ${CLAUDE_PROJECT_DIR}/ prefix → so sánh logical-equivalent giữa old + new.\nfunction normalizeHookEntryForMigration(entry: HookEntry): string {\n const commands = (entry.hooks ?? [])\n .map((h) => (h.command ?? \"\").replace(/\\$\\{CLAUDE_PROJECT_DIR\\}\\//g, \"\").trim())\n .sort()\n .join(\"|\");\n return `${entry.matcher ?? \"\"}::${commands}`;\n}\n\nfunction entryUsesProjectDirVar(entry: HookEntry): boolean {\n return (entry.hooks ?? []).some((h) => (h.command ?? \"\").includes(\"${CLAUDE_PROJECT_DIR}\"));\n}\n\n// v1.18.0 MIGRATION: Trong cùng event, nếu user settings có entry old (relative\n// path) VÀ tool manifest mang entry new (${CLAUDE_PROJECT_DIR}/) logical-tương-đương,\n// drop entry old để chỉ giữ new sau merge. Fix cho upgrade từ pack pre-v0.7.1.\nfunction migrateStaleEntries(\n userEntries: HookEntry[],\n featureEntries: HookEntry[],\n): { migratedUser: HookEntry[]; droppedCount: number } {\n // Build set of normalized keys mà tool đang ship (đang là new version)\n const featureNormalized = new Set<string>();\n for (const fe of featureEntries) {\n if (entryUsesProjectDirVar(fe)) {\n featureNormalized.add(normalizeHookEntryForMigration(fe));\n }\n }\n if (featureNormalized.size === 0) {\n return { migratedUser: userEntries, droppedCount: 0 };\n }\n let dropped = 0;\n const migratedUser = userEntries.filter((ue) => {\n if (entryUsesProjectDirVar(ue)) return true; // keep new versions\n // Old version — drop nếu tool đang ship new logical-equivalent\n if (featureNormalized.has(normalizeHookEntryForMigration(ue))) {\n dropped++;\n return false;\n }\n return true;\n });\n return { migratedUser, droppedCount: dropped };\n}\n\n// ENABLE: merge hooks (append + dedupe theo matcher+command) + union deny.\n// v1.18.0: Auto-migrate stale relative-path entries của cùng tool trong user\n// settings — drop old keep new. Trigger khi user upgrade pack từ pre-v0.7.1.\nexport async function enableTool(\n workspacePath: string,\n manifest: ToolManifest,\n): Promise<ApplyToolResult> {\n const { settings, existed } = await loadProjectSettings(workspacePath);\n const merged: PackSettingsTemplate = { ...settings };\n const changes: string[] = [];\n\n // Hooks per-event: migrate stale + append entries tool, dedupe theo key.\n const mHooks = manifest.settings.hooks ?? {};\n if (Object.keys(mHooks).length > 0) {\n const userHooks = (settings.hooks ?? {}) as Record<string, HookEntry[]>;\n const outHooks: Record<string, HookEntry[]> = { ...userHooks };\n const touched: string[] = [];\n let totalMigrated = 0;\n for (const [event, featEntries] of Object.entries(mHooks)) {\n const existing = userHooks[event] ?? [];\n // v1.18.0: Drop stale relative-path versions của entries tool đang re-ship.\n const { migratedUser, droppedCount } = migrateStaleEntries(existing, featEntries);\n if (droppedCount > 0) {\n totalMigrated += droppedCount;\n }\n const seen = new Set(migratedUser.map(hookEntryKey));\n const toAdd = featEntries.filter((e) => !seen.has(hookEntryKey(e)));\n if (toAdd.length > 0 || droppedCount > 0) {\n outHooks[event] = [...migratedUser, ...toAdd];\n touched.push(event);\n }\n }\n if (touched.length > 0) {\n merged.hooks = outHooks as Record<string, unknown[]>;\n changes.push(`hooks added: ${touched.join(\", \")}`);\n if (totalMigrated > 0) {\n changes.push(\n `migrated ${totalMigrated} stale relative-path entries → \\\\${\"{\"}CLAUDE_PROJECT_DIR\\\\${\"}\"} version`,\n );\n }\n }\n }\n\n // permissions.deny: union + dedupe.\n const featDeny = manifest.settings.permissions?.deny ?? [];\n if (featDeny.length > 0) {\n const userDeny = settings.permissions?.deny ?? [];\n const unionDeny = unionDedupe(userDeny, featDeny);\n if (unionDeny.length !== userDeny.length) {\n merged.permissions = { ...settings.permissions, deny: unionDeny };\n changes.push(`deny +${unionDeny.length - userDeny.length}`);\n }\n }\n\n if (changes.length === 0) {\n return { action: \"no-change\", changes: [] };\n }\n const backupPath = await backupAndWrite(workspacePath, merged, existed);\n return { action: \"enabled\", backupPath, changes };\n}\n\n// DISABLE: rút ĐÚNG hook entries + deny patterns mà manifest khai. Giữ nguyên mọi\n// thứ khác (scout-block, deny user tự thêm). Dọn mảng rỗng sau khi rút.\nexport async function disableTool(\n workspacePath: string,\n manifest: ToolManifest,\n): Promise<ApplyToolResult> {\n const { settings, existed } = await loadProjectSettings(workspacePath);\n if (!existed) {\n return { action: \"no-change\", changes: [] };\n }\n // Build merged bằng cách OMIT key (không dùng `delete` — theo convention codebase\n // + lint noDelete). Bắt đầu từ bản sao không gồm hooks/permissions; gán lại nếu\n // còn nội dung sau khi rút.\n const { hooks: _h, permissions: _p, ...rest } = settings;\n const merged: PackSettingsTemplate = { ...rest };\n const changes: string[] = [];\n\n // Hooks: xoá entry khớp key của tool, GIỮ entry khác (scout-block...).\n const mHooks = manifest.settings.hooks ?? {};\n const userHooks = (settings.hooks ?? {}) as Record<string, HookEntry[]>;\n const outHooks: Record<string, HookEntry[]> = {};\n const removed: string[] = [];\n for (const [event, entries] of Object.entries(userHooks)) {\n const featEntries = mHooks[event];\n if (!featEntries) {\n outHooks[event] = entries; // event không thuộc tool → giữ nguyên.\n continue;\n }\n const featKeys = new Set(featEntries.map(hookEntryKey));\n const kept = entries.filter((e) => !featKeys.has(hookEntryKey(e)));\n if (kept.length !== entries.length) {\n removed.push(event);\n }\n if (kept.length > 0) {\n outHooks[event] = kept; // còn entry khác → giữ; rỗng → bỏ key.\n }\n }\n if (Object.keys(outHooks).length > 0) {\n merged.hooks = outHooks as Record<string, unknown[]>;\n }\n if (removed.length > 0) {\n changes.push(`hooks removed: ${removed.join(\", \")}`);\n }\n\n // Deny: chỉ xoá đúng patterns manifest đã thêm. KHÔNG xoá deny user tự thêm.\n const featDeny = new Set(manifest.settings.permissions?.deny ?? []);\n const userDeny = settings.permissions?.deny ?? [];\n const keptDeny = featDeny.size > 0 ? userDeny.filter((d) => !featDeny.has(d)) : userDeny;\n // Tái dựng permissions: giữ field khác (allow...), set deny nếu còn, OMIT nếu rỗng.\n if (settings.permissions) {\n const { deny: _d, ...permRest } = settings.permissions;\n const newPerms = { ...permRest, ...(keptDeny.length > 0 ? { deny: keptDeny } : {}) };\n if (Object.keys(newPerms).length > 0) {\n merged.permissions = newPerms;\n }\n }\n if (keptDeny.length !== userDeny.length) {\n changes.push(`deny -${userDeny.length - keptDeny.length}`);\n }\n\n if (changes.length === 0) {\n return { action: \"no-change\", changes: [] };\n }\n const backupPath = await backupAndWrite(workspacePath, merged, existed);\n return { action: \"disabled\", backupPath, changes };\n}\n","// Merge pack template settings.json vào project settings.json.\n//\n// Background:\n// - Pack ship templates/settings.json.tpl với `hooks`, `statusLine`, `permissions`\n// declare workflow team-shared (dev-rules-reminder, scout-block, ...).\n// - Avatar `init` ghi base settings.json với env Avatar vars + permissions baseline.\n// - Sau khi `sync` symlink farm xong, hooks pack KHÔNG active vì project\n// settings.json chưa khai báo hook entries → Claude Code không trigger.\n//\n// Strategy (Q1 confirmed: \"Pack base + user override\"):\n// - Pack template = baseline cho hooks + statusLine + permissions.\n// - User customization (env, model, existing permissions extras, existing hook entries\n// dùng matcher khác) → preserve.\n// - Deep merge field-level:\n// env → user wins (giữ API key)\n// statusLine → pack wins nếu user chưa set\n// permissions → union (pack + user, dedupe)\n// hooks → merge per-event, dedupe theo (matcher + command)\n// model → user wins nếu set; pack default fallback\n// includeCoAuthoredBy → pack value áp dụng nếu user chưa set\n//\n// Safety:\n// - Backup .claude/settings.json → .claude/settings.json.backup-<timestamp> trước merge\n// - Atomic write file mới\n// - Return result để caller log từng action\n\nimport { promises as fs } from \"node:fs\";\nimport path, { join } from \"node:path\";\nimport { pathExists, readJson, readText, writeJsonAtomic } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface PackSettingsTemplate {\n $schema?: string;\n includeCoAuthoredBy?: boolean;\n model?: string;\n env?: Record<string, string>;\n permissions?: { allow?: string[]; deny?: string[] };\n hooks?: Record<string, unknown[]>;\n statusLine?: { type: string; command: string; padding?: number };\n}\n\nexport interface MergeSettingsResult {\n action: \"merged\" | \"no-change\" | \"no-pack-template\";\n backupPath?: string;\n changes: string[]; // human-readable list of fields touched\n}\n\n// v1.7.0: Verify statusLine command file ref có thật trong workspace.\n// Pack template thường ref `node .claude/statusline.cjs` hoặc shell command absolute.\n// Parse simple: nếu là `node <path>` hoặc `python3 <path>` → check path tồn tại.\n// Nếu là shell command khác (vd `echo`, `date`) → assume OK (không Avatar's concern).\nasync function isStatusLineCommandResolvable(\n workspacePath: string,\n command: string,\n): Promise<boolean> {\n const trimmed = command.trim();\n // Pattern: <interpreter> <relative-or-absolute-path> [args...]\n const match = trimmed.match(/^(node|python|python3|bash|sh)\\s+([^\\s]+)/);\n if (!match?.[2]) {\n // Shell command (echo, date, ...) hoặc regex miss capture group — không verify, assume OK.\n return true;\n }\n const filePath = match[2];\n // v1.14.0 SECURITY: Reject absolute paths từ pack template (supply chain attack vector).\n // Pack compromise có thể inject hook trỏ tới `/etc/passwd` / `/root/.ssh/...`\n // (pathExists pass cho mọi file readable trên system → leak hint).\n // Pack hook PHẢI relative tới workspace — không vượt khỏi `.claude/`.\n if (filePath.startsWith(\"/\")) {\n log.warn(\n `Pack hook reject: absolute path \"${filePath}\" — chỉ accept relative path tới workspace.`,\n );\n return false;\n }\n // Resolve relative path từ workspace root (.claude/statusline.cjs → <ws>/.claude/statusline.cjs).\n // Defensive: assert resolved path vẫn nằm trong workspace (block `../etc/...` escape).\n const fullPath = join(workspacePath, filePath);\n const wsResolved = path.resolve(workspacePath);\n const targetResolved = path.resolve(fullPath);\n if (!targetResolved.startsWith(`${wsResolved}/`) && targetResolved !== wsResolved) {\n log.warn(`Pack hook reject: path \"${filePath}\" resolve ra ngoài workspace (path traversal).`);\n return false;\n }\n return await pathExists(fullPath);\n}\n\n// Generate backup filename với timestamp YYMMDD-HHMM để debug + restore.\n// Exported: tái dùng cho apply-tool-manifest (enable/disable cũng backup trước).\nexport function backupFilename(originalPath: string): string {\n const d = new Date();\n const stamp = `${\n d.getFullYear().toString().slice(-2) +\n String(d.getMonth() + 1).padStart(2, \"0\") +\n String(d.getDate()).padStart(2, \"0\")\n }-${String(d.getHours()).padStart(2, \"0\")}${String(d.getMinutes()).padStart(2, \"0\")}`;\n return `${originalPath}.backup-${stamp}`;\n}\n\n// Dedupe + union 2 arrays giữ thứ tự (first occurrence wins).\n// Exported: tái dùng cho apply-tool-manifest (union deny + dedupe hooks).\nexport function unionDedupe<T>(a: T[], b: T[]): T[] {\n const seen = new Set<string>();\n const out: T[] = [];\n for (const item of [...a, ...b]) {\n const key = typeof item === \"string\" ? item : JSON.stringify(item);\n if (!seen.has(key)) {\n seen.add(key);\n out.push(item);\n }\n }\n return out;\n}\n\n// Normalize command để so sánh \"logically same hook\" giữa old relative-path\n// và new ${CLAUDE_PROJECT_DIR}-path. Strip prefix để hai version cùng hook\n// có chung normalized form.\n//\n// Examples:\n// \"node .claude/hooks/foo.cjs\" → \"node .claude/hooks/foo.cjs\"\n// \"node ${CLAUDE_PROJECT_DIR}/.claude/hooks/foo.cjs\" → \"node .claude/hooks/foo.cjs\"\n// → cùng normalized form → coi là duplicate.\nfunction normalizeCommandForMigration(command: string): string {\n return command.replace(/\\$\\{CLAUDE_PROJECT_DIR\\}\\//g, \"\").trim();\n}\n\n// Extract command string từ hook entry (best-effort — return all commands joined\n// nếu entry có nhiều hooks; mỗi entry thường chỉ có 1 hook).\nfunction extractCommandsFromEntry(entry: unknown): string[] {\n if (typeof entry !== \"object\" || entry === null) return [];\n const obj = entry as Record<string, unknown>;\n const hooks = Array.isArray(obj.hooks) ? obj.hooks : [];\n const cmds: string[] = [];\n for (const h of hooks) {\n if (typeof h === \"object\" && h !== null) {\n const cmd = (h as Record<string, unknown>).command;\n if (typeof cmd === \"string\") cmds.push(cmd);\n }\n }\n return cmds;\n}\n\n// v1.18.0 MIGRATION: Loại bỏ stale relative-path entries khi pack template/tool\n// manifest đã chuyển sang ${CLAUDE_PROJECT_DIR}/. Background: v0.7.0 pack ship\n// relative path \"node .claude/hooks/foo.cjs\"; Bash tool cd vào subdir làm cwd\n// kẹt → hook fail ENOENT. v0.7.1 pack đổi sang \"node ${CLAUDE_PROJECT_DIR}/...\".\n// Nhưng user settings.json đã có entry cũ — merge mới chỉ APPEND new, không xóa\n// old → cả 2 entries cùng tồn tại, old fail noisy mỗi prompt.\n//\n// Migration logic:\n// - Trong mỗi event, group entries theo normalized command.\n// - Nếu group có cả old (no ${CLAUDE_PROJECT_DIR}) + new (with) → drop old, keep new.\n// - Group chỉ có 1 version → giữ nguyên (không opinion về user's custom hooks).\n//\n// Returns: { migrated entries, count of dropped stale entries per event }\nfunction migrateStaleRelativePathHooks(entries: unknown[]): {\n entries: unknown[];\n droppedCount: number;\n} {\n // Group by normalized command form\n type IndexedEntry = { index: number; entry: unknown; commands: string[]; hasVar: boolean };\n const indexed: IndexedEntry[] = entries.map((entry, index) => {\n const commands = extractCommandsFromEntry(entry);\n const hasVar = commands.some((c) => c.includes(\"${CLAUDE_PROJECT_DIR}\"));\n return { index, entry, commands, hasVar };\n });\n\n // Build normalized-command → list of indexed entries\n const byNormalized = new Map<string, IndexedEntry[]>();\n for (const ie of indexed) {\n const key = ie.commands.map(normalizeCommandForMigration).sort().join(\"|\");\n if (!key) continue; // skip entries with no commands\n const list = byNormalized.get(key) ?? [];\n list.push(ie);\n byNormalized.set(key, list);\n }\n\n // Find indices to drop: groups with both old + new → drop old.\n const toDrop = new Set<number>();\n for (const [, items] of byNormalized) {\n if (items.length < 2) continue;\n const hasAnyNew = items.some((it) => it.hasVar);\n if (!hasAnyNew) continue;\n for (const it of items) {\n if (!it.hasVar) toDrop.add(it.index);\n }\n }\n\n if (toDrop.size === 0) {\n return { entries, droppedCount: 0 };\n }\n\n const filtered = entries.filter((_, i) => !toDrop.has(i));\n return { entries: filtered, droppedCount: toDrop.size };\n}\n\n// Merge hooks per-event: pack entries + user entries, dedupe theo (matcher + commands).\n// Schema Claude Code: { EventName: [ { matcher?: string, hooks: [{type, command}] } ] }\n//\n// v1.18.0: Sau khi union dedupe, chạy migrateStaleRelativePathHooks để xóa entries\n// relative-path cũ nếu đã có version ${CLAUDE_PROJECT_DIR} mới — fix cho upgrade\n// từ pack pre-v0.7.1 lên v0.7.1+ (settings.json không tự cleanup khi tool.json\n// đổi command string).\nfunction mergeHooksPerEvent(\n packHooks: Record<string, unknown[]>,\n userHooks: Record<string, unknown[]>,\n): { merged: Record<string, unknown[]>; touchedEvents: string[]; migratedCount: number } {\n const touched: string[] = [];\n const merged: Record<string, unknown[]> = { ...userHooks };\n let totalMigrated = 0;\n\n // First migrate existing user entries (handle case where user has stale entries\n // not from pack — eg. legacy install before sync).\n for (const event of Object.keys(merged)) {\n const userEntries = (merged[event] as unknown[]) || [];\n const { entries: migrated, droppedCount } = migrateStaleRelativePathHooks(userEntries);\n if (droppedCount > 0) {\n merged[event] = migrated;\n totalMigrated += droppedCount;\n if (!touched.includes(event)) touched.push(event);\n }\n }\n\n // Then union with pack entries + dedupe (existing behavior).\n for (const [event, packEntries] of Object.entries(packHooks)) {\n const userEntries = (merged[event] as unknown[]) || [];\n const union = unionDedupe(userEntries, packEntries);\n // Run migration again after union (in case pack new + user old now coexist).\n const { entries: postMigrate, droppedCount: postDropped } =\n migrateStaleRelativePathHooks(union);\n if (postDropped > 0) {\n totalMigrated += postDropped;\n }\n if (postMigrate.length !== userEntries.length) {\n if (!touched.includes(event)) touched.push(event);\n }\n merged[event] = postMigrate;\n }\n return { merged, touchedEvents: touched, migratedCount: totalMigrated };\n}\n\n// Main entry. Đọc pack template + user settings, merge, backup + write atomic.\n// Returns result để caller report.\nexport async function mergePackSettingsIntoProjectSettings(\n workspacePath: string,\n): Promise<MergeSettingsResult> {\n const packTemplatePath = join(workspacePath, \".claude\", \"pack\", \"templates\", \"settings.json.tpl\");\n const projectSettingsPath = join(workspacePath, \".claude\", \"settings.json\");\n\n // Pack chưa sync hoặc không có template → no-op.\n if (!(await pathExists(packTemplatePath))) {\n return { action: \"no-pack-template\", changes: [] };\n }\n\n // Pack template có thể chứa placeholders {{...}}; chúng ta merge nguyên trạng\n // (placeholder sẽ giữ literal trong project settings nếu user chưa thay).\n // Tuy nhiên hooks + statusLine + permissions không dùng placeholder → safe.\n let packTemplate: PackSettingsTemplate;\n try {\n const raw = await readText(packTemplatePath);\n packTemplate = JSON.parse(raw) as PackSettingsTemplate;\n } catch (err) {\n throw new Error(\n `Pack settings template không parse được JSON: ${(err as Error).message}. Path: ${packTemplatePath}`,\n );\n }\n\n // Project settings — nếu chưa có (vd init bị skip) thì coi như empty base.\n let userSettings: PackSettingsTemplate = {};\n let projectHasSettings = false;\n if (await pathExists(projectSettingsPath)) {\n projectHasSettings = true;\n try {\n userSettings = await readJson<PackSettingsTemplate>(projectSettingsPath);\n } catch (err) {\n throw new Error(\n `Project settings.json không parse được: ${(err as Error).message}. Manual fix trước khi sync.`,\n );\n }\n }\n\n const changes: string[] = [];\n const merged: PackSettingsTemplate = { ...userSettings };\n\n // 1. statusLine — pack wins nếu user chưa set, NHƯNG verify file ref tồn tại.\n // v1.7.0 fix: trước đây merge blindly → settings ref `node .claude/statusline.cjs`\n // nhưng pack KHÔNG ship file đó → Claude Code fail im lặng render statusbar.\n // Giờ check: command pack ref tồn tại trong workspace? Nếu không → skip + warn.\n if (packTemplate.statusLine && !userSettings.statusLine) {\n const statusLineOk = await isStatusLineCommandResolvable(\n workspacePath,\n packTemplate.statusLine.command,\n );\n if (statusLineOk) {\n merged.statusLine = packTemplate.statusLine;\n changes.push(\"statusLine added\");\n } else {\n changes.push(\n `statusLine SKIPPED (file ref '${packTemplate.statusLine.command}' không tồn tại)`,\n );\n }\n }\n\n // 2. includeCoAuthoredBy — pack wins nếu user chưa set.\n if (\n typeof packTemplate.includeCoAuthoredBy === \"boolean\" &&\n typeof userSettings.includeCoAuthoredBy !== \"boolean\"\n ) {\n merged.includeCoAuthoredBy = packTemplate.includeCoAuthoredBy;\n changes.push(\"includeCoAuthoredBy added\");\n }\n\n // 3. model — user wins; chỉ add nếu pack có và user chưa set.\n if (packTemplate.model && !userSettings.model) {\n merged.model = packTemplate.model;\n changes.push(\"model added\");\n }\n\n // 4. env — user wins entirely (giữ API key). Chỉ add var pack có mà user chưa có.\n // v1.20.0: Auto-rewrite legacy domain llm.nal.vn → ai.nal.vn nếu user có\n // settings.json từ pre-v1.20.0 (NAL gateway đổi domain). Silent migration,\n // không cần user confirm — cùng infra rename.\n if (packTemplate.env || userSettings.env) {\n const mergedEnv: Record<string, string> = { ...(userSettings.env || {}) };\n let envChanged = false;\n // Domain rewrite trên mọi string value\n for (const [k, v] of Object.entries(mergedEnv)) {\n if (typeof v === \"string\" && v.includes(\"llm.nal.vn\")) {\n mergedEnv[k] = v.replace(/llm\\.nal\\.vn/g, \"ai.nal.vn\");\n envChanged = true;\n }\n }\n if (envChanged) {\n changes.push(\"rewrote legacy llm.nal.vn → ai.nal.vn in env vars\");\n }\n // Pack env vars add nếu user chưa có\n if (packTemplate.env) {\n for (const [k, v] of Object.entries(packTemplate.env)) {\n if (!(k in mergedEnv)) {\n mergedEnv[k] = v;\n envChanged = true;\n }\n }\n }\n if (envChanged) {\n merged.env = mergedEnv;\n if (!changes.some((c) => c.includes(\"env vars\"))) {\n changes.push(\"env vars added from pack\");\n }\n }\n }\n\n // 5. permissions — union allow + union deny (dedupe).\n if (packTemplate.permissions) {\n const userAllow = userSettings.permissions?.allow || [];\n const userDeny = userSettings.permissions?.deny || [];\n const packAllow = packTemplate.permissions.allow || [];\n const packDeny = packTemplate.permissions.deny || [];\n const mergedAllow = unionDedupe(userAllow, packAllow);\n const mergedDeny = unionDedupe(userDeny, packDeny);\n if (mergedAllow.length !== userAllow.length || mergedDeny.length !== userDeny.length) {\n merged.permissions = { allow: mergedAllow, deny: mergedDeny };\n changes.push(\n `permissions union (+${mergedAllow.length - userAllow.length} allow, +${mergedDeny.length - userDeny.length} deny)`,\n );\n }\n }\n\n // 6. hooks — merge per-event với dedupe + migrate stale relative-path entries.\n if (packTemplate.hooks) {\n const userHooks = userSettings.hooks || {};\n const {\n merged: mergedHooks,\n touchedEvents,\n migratedCount,\n } = mergeHooksPerEvent(packTemplate.hooks, userHooks);\n if (touchedEvents.length > 0 || migratedCount > 0) {\n merged.hooks = mergedHooks;\n if (touchedEvents.length > 0) {\n changes.push(`hooks added for events: ${touchedEvents.join(\", \")}`);\n }\n if (migratedCount > 0) {\n changes.push(\n `migrated ${migratedCount} stale relative-path hook entries to use \\${CLAUDE_PROJECT_DIR} version (fixes hook failure when shell cwd changes)`,\n );\n }\n }\n }\n\n // Không có thay đổi gì → skip write.\n if (changes.length === 0) {\n return { action: \"no-change\", changes: [] };\n }\n\n // Backup + write.\n let backupPath: string | undefined;\n if (projectHasSettings) {\n backupPath = backupFilename(projectSettingsPath);\n await fs.copyFile(projectSettingsPath, backupPath);\n }\n await writeJsonAtomic(projectSettingsPath, merged);\n return { action: \"merged\", backupPath, changes };\n}\n","// tool-state-store.ts\n//\n// Đọc/ghi <project>/.claude/avatar-tools.json — state file project-level ghi\n// nhận tool nào ĐANG bật (ý định Avatar, tách khỏi settings.json của Claude Code).\n//\n// Vì sao project-level (không phải ~/.avatar): bật/tắt tool là quyết định CỦA\n// DỰ ÁN — cả team nên giống nhau → file commit được. `sync` chạy trong project\n// context nên đọc tự nhiên để re-apply.\n//\n// Schema:\n// {\n// \"tools\": {\n// \"prompt-scoring\": { \"enabled\": true, \"version\": \"0.1.0\", \"appliedAt\": \"ISO\" }\n// }\n// }\n//\n// Safety: atomic write (qua filesystem-helpers). Parse lỗi → coi như empty state\n// (fail-soft, không crash CLI vì 1 file state hỏng).\n\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const TOOL_STATE_RELATIVE_PATH = \".claude/avatar-tools.json\";\n\nexport interface ToolStateEntry {\n enabled: boolean;\n version: string;\n appliedAt: string; // ISO timestamp lần enable/disable gần nhất\n}\n\nexport interface ToolState {\n tools: Record<string, ToolStateEntry>;\n}\n\nfunction emptyState(): ToolState {\n return { tools: {} };\n}\n\nfunction stateFilePath(workspacePath: string): string {\n return join(workspacePath, TOOL_STATE_RELATIVE_PATH);\n}\n\n// Đọc state. File thiếu hoặc parse lỗi → empty state (fail-soft).\nexport async function readToolState(workspacePath: string): Promise<ToolState> {\n const path = stateFilePath(workspacePath);\n if (!(await pathExists(path))) {\n return emptyState();\n }\n try {\n const parsed = await readJson<Partial<ToolState>>(path);\n // Defensive: file tồn tại nhưng thiếu `tools` → normalize.\n return { tools: parsed.tools ?? {} };\n } catch {\n return emptyState();\n }\n}\n\n// Ghi state atomic. Giữ nguyên các field không quản lý nếu sau này mở rộng.\nexport async function writeToolState(workspacePath: string, state: ToolState): Promise<void> {\n await writeJsonAtomic(stateFilePath(workspacePath), state);\n}\n\n// Set 1 tool enabled/disabled + stamp version & appliedAt. Trả về state mới.\nexport async function setToolState(\n workspacePath: string,\n name: string,\n entry: { enabled: boolean; version: string },\n): Promise<ToolState> {\n const state = await readToolState(workspacePath);\n state.tools[name] = {\n enabled: entry.enabled,\n version: entry.version,\n appliedAt: new Date().toISOString(),\n };\n await writeToolState(workspacePath, state);\n return state;\n}\n\n// Tiện ích: danh sách tên tool đang enabled=true (dùng cho sync re-apply).\nexport async function listEnabledTools(workspacePath: string): Promise<string[]> {\n const state = await readToolState(workspacePath);\n return Object.entries(state.tools)\n .filter(([, entry]) => entry.enabled)\n .map(([name]) => name);\n}\n","// `avatar gitnexus` — Standalone command quản lý GitNexus integration (M10).\n// 3 subcommands:\n// - install : Run full setup phase (install + setup + analyze + wiki + MCP)\n// - status : Show index info (last commit, staleness, wiki age)\n// - analyze : Re-run analyze only (no wiki, không cost API)\n//\n// Cần chạy trong workspace dir — resolve qua resolveAvatarWorkspaceRootFromCwd\n// để strict detect Avatar workspace (không phải folder random có .claude/).\nimport { spawnSync } from \"node:child_process\";\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport type { Command } from \"commander\";\nimport { pathExists, readJson } from \"../lib/filesystem-helpers.js\";\nimport { resolveAvatarWorkspaceRootFromCwd } from \"../lib/resolve-avatar-workspace-root-from-cwd.js\";\nimport { runGitnexusAnalyze } from \"../lib/run-gitnexus-setup-and-analyze.js\";\nimport { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\n// Resolve Avatar workspace từ cwd (walk-up + strict marker check).\n// Exit 1 với hint clear nếu không tìm thấy.\nfunction ensureWorkspaceCwd(): string {\n const cwd = process.cwd();\n const workspaceRoot = resolveAvatarWorkspaceRootFromCwd(cwd);\n if (!workspaceRoot) {\n log.error(\n `Không tìm thấy Avatar workspace từ thư mục hiện tại.\n Avatar workspace cần có: .claude/ + CLAUDE.md + src/ (hoặc .gitmodules).\n Bạn đang ở: ${cwd}\n Cd vào workspace dir rồi chạy lại.`,\n );\n process.exit(1);\n }\n if (workspaceRoot !== cwd) {\n log.dim(`Detected workspace root: ${workspaceRoot}`);\n }\n return workspaceRoot;\n}\n\n// === install ===\n// Full setup phase — reuse orchestrator từ Phase 06.\nasync function runGitnexusInstall(): Promise<void> {\n const workspacePath = ensureWorkspaceCwd();\n const result = await runGitnexusSetupPhase({ workspacePath });\n if (result.ok) {\n log.success(\"GitNexus setup complete\");\n log.dim(\"Update CLAUDE.md để re-render section GitNexus: re-run avatar init hoặc chỉnh tay.\");\n } else {\n log.warn(`Setup không complete: ${result.reason ?? \"unknown\"}`);\n }\n}\n\n// === status ===\n// Show GitNexus index info + staleness warning + wiki age.\nasync function runGitnexusStatus(): Promise<void> {\n const workspacePath = ensureWorkspaceCwd();\n const metaPath = join(workspacePath, \".gitnexus\", \"meta.json\");\n\n if (!(await pathExists(metaPath))) {\n log.warn(`Chưa có ${metaPath}. Chạy: avatar gitnexus install`);\n return;\n }\n\n try {\n const meta = await readJson<{\n lastCommit?: string;\n indexedAt?: string;\n stats?: { files?: number; nodes?: number; edges?: number };\n }>(metaPath);\n\n log.info(`Project: ${workspacePath}`);\n log.info(`Last commit: ${meta.lastCommit?.slice(0, 7) ?? \"(unknown)\"}`);\n log.info(`Indexed at: ${meta.indexedAt ?? \"(unknown)\"}`);\n if (meta.stats) {\n log.info(\n `Stats: ${meta.stats.files ?? \"?\"} files · ${meta.stats.nodes ?? \"?\"} nodes · ${meta.stats.edges ?? \"?\"} edges`,\n );\n }\n\n // Staleness check qua git rev-parse HEAD vs meta.lastCommit.\n if (meta.lastCommit) {\n const headResult = spawnSync(\"git\", [\"rev-parse\", \"HEAD\"], {\n cwd: workspacePath,\n encoding: \"utf8\",\n });\n if (headResult.status === 0) {\n const currentHead = headResult.stdout.trim();\n if (currentHead !== meta.lastCommit) {\n log.warn(\"⚠ Index stale — HEAD đã tiến từ lastCommit. Chạy: avatar gitnexus analyze\");\n } else {\n log.dim(\"Index fresh (HEAD === lastCommit)\");\n }\n }\n }\n\n // Wiki age check.\n const wikiPath = join(workspacePath, \".gitnexus\", \"wiki\", \"index.html\");\n if (await pathExists(wikiPath)) {\n const stat = await fs.stat(wikiPath);\n log.info(`Wiki: ${stat.mtime.toISOString()}`);\n } else {\n log.dim(\"Wiki: chưa generate (chạy gitnexus wiki manual)\");\n }\n } catch (err) {\n log.error(`Read meta.json fail: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\n// === analyze ===\n// Re-run analyze refresh index (no wiki — fast + zero API cost).\nasync function runGitnexusAnalyzeCommand(): Promise<void> {\n const workspacePath = ensureWorkspaceCwd();\n try {\n runGitnexusAnalyze(workspacePath);\n log.success(\"Index refreshed. Chạy `avatar gitnexus status` xem chi tiết.\");\n } catch (err) {\n log.error(`Analyze fail: ${(err as Error).message}`);\n process.exit(1);\n }\n}\n\nexport function registerGitnexusCommand(program: Command): void {\n const gx = program.command(\"gitnexus\").description(\"Quản lý GitNexus code intelligence (M10)\");\n\n gx.command(\"install\")\n .description(\"Cài + setup GitNexus cho workspace hiện tại\")\n .action(async () => {\n await runGitnexusInstall();\n });\n\n gx.command(\"status\")\n .description(\"Show index info + staleness warning\")\n .action(async () => {\n await runGitnexusStatus();\n });\n\n gx.command(\"analyze\")\n .description(\"Re-run analyze refresh index (no wiki)\")\n .action(async () => {\n await runGitnexusAnalyzeCommand();\n });\n}\n","// Orchestrator Phase 10 GitNexus Setup — compose phase 01-05 thành fail-soft flow.\n// Gọi sau `runAiSetupPhase` trong `finalizeWorkspaceScaffold`.\n//\n// Try/catch toàn bộ — KHÔNG throw. Workspace OK dù GitNexus fail:\n// - User decline install → return { ok: false, reason: \"user-declined\" }\n// - Install fail → recovery menu (retry/skip/abort) — abort throw UserAbortedRecoveryError\n// - Setup/analyze fail → recovery menu\n// - Wiki fail → log warn nhưng continue (wiki không critical)\n// - MCP register fail → log warn nhưng continue (user thấy workspace work)\n//\n// Audit log: gitnexus_setup,result=ok/skipped/failed,reason=...\nimport { confirm } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport { appendAuditEntry } from \"./audit-log-appender.js\";\nimport {\n detectGitnexusInstallation,\n invalidateGitnexusInstallationCache,\n} from \"./detect-gitnexus-installation.js\";\nimport { InstallGitnexusError, installGitnexusViaNpm } from \"./install-gitnexus-via-npm.js\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.js\";\nimport { registerGitnexusMcpServer } from \"./register-gitnexus-mcp-server.js\";\nimport {\n GitnexusOperationError,\n runGitnexusAnalyze,\n runGitnexusSetup,\n} from \"./run-gitnexus-setup-and-analyze.js\";\nimport { runGitnexusWikiConditional } from \"./run-gitnexus-wiki-conditional.js\";\nimport { chalk, log } from \"./terminal-logger.js\";\n\nexport interface GitnexusSetupArgs {\n workspacePath: string;\n // v3: init KHÔNG analyze (workspace src/ rỗng → index vô nghĩa). Chỉ install +\n // setup skills + MCP register. Analyze chạy per-repo khi `avatar add repo`.\n skipAnalyze?: boolean;\n}\n\nexport interface GitnexusSetupResult {\n ok: boolean;\n installed: boolean; // gitnexus binary có hay chưa\n analyzed: boolean; // .gitnexus/ created\n wikiGenerated: boolean; // wiki/index.html created\n mcpRegistered: boolean;\n reason?: string;\n}\n\n// Boxen prompt giải thích GitNexus + ask install. UX explicit consent.\nasync function promptInstallGitnexus(): Promise<boolean> {\n const lines = [\n chalk.bold(\"🧠 GitNexus chưa cài\"),\n \"\",\n \"GitNexus = code intelligence layer cho Claude Code:\",\n \" • Architectural awareness (impact analysis)\",\n \" • Call chain debug + blast radius trước refactor\",\n \" • Wiki HTML tự gen mô tả codebase\",\n \"\",\n `Sẽ cài: ${chalk.cyan(\"npm install -g gitnexus\")} (global)`,\n ];\n process.stdout.write(\n `${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\", borderColor: \"cyan\" })}\\n`,\n );\n return await confirm({ message: \"Cài GitNexus global?\", default: true });\n}\n\n// Helper — wrap install call với recovery menu (retry/skip/abort).\n// Trả true nếu cài thành công (sau retry), false nếu user skip.\nasync function installWithRecovery(): Promise<boolean> {\n while (true) {\n try {\n installGitnexusViaNpm();\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint =\n err instanceof InstallGitnexusError && err.reason === \"permission-denied\"\n ? \"Thử lại với sudo, hoặc fix npm prefix: npm config set prefix ~/.npm-global\"\n : \"Check log npm phía trên + thử lại.\";\n const action = await promptRetryOrSkip({\n taskName: \"Cài GitNexus qua npm\",\n reason: message,\n allowSkip: true,\n hint,\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước cài GitNexus.\");\n }\n if (action === \"skip\") return false;\n // retry → loop tiếp\n }\n }\n}\n\n// Helper — wrap analyze call với recovery menu.\nasync function analyzeWithRecovery(workspacePath: string): Promise<boolean> {\n while (true) {\n try {\n runGitnexusAnalyze(workspacePath);\n return true;\n } catch (err) {\n const message = err instanceof Error ? err.message : String(err);\n const hint =\n err instanceof GitnexusOperationError && err.reason === \"missing-output\"\n ? \"Repo có thể empty hoặc gitnexus version mismatch. Check `gitnexus --version`.\"\n : \"Network glitch? Retry thường work.\";\n const action = await promptRetryOrSkip({\n taskName: \"GitNexus analyze workspace\",\n reason: message,\n allowSkip: true,\n hint,\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước GitNexus analyze.\");\n }\n if (action === \"skip\") return false;\n }\n }\n}\n\n// Main orchestrator. Fail-soft semantic — return result, không throw (trừ user abort).\nexport async function runGitnexusSetupPhase(args: GitnexusSetupArgs): Promise<GitnexusSetupResult> {\n const result: GitnexusSetupResult = {\n ok: false,\n installed: false,\n analyzed: false,\n wikiGenerated: false,\n mcpRegistered: false,\n };\n\n try {\n log.info(\"=== Phase 10: GitNexus Setup ===\");\n\n // Step 1 — Detect + install nếu cần\n let info = detectGitnexusInstallation();\n if (!info.installed) {\n const shouldInstall = await promptInstallGitnexus();\n if (!shouldInstall) {\n await appendAuditEntry(\"gitnexus_setup\", \"result=skipped,reason=user-declined\");\n log.dim(\"Skip GitNexus. Cài sau qua `avatar gitnexus install`.\");\n result.reason = \"user-declined\";\n return result;\n }\n const installed = await installWithRecovery();\n if (!installed) {\n await appendAuditEntry(\"gitnexus_setup\", \"result=skipped,reason=install-skipped\");\n log.dim(\"Skip GitNexus install. Workspace OK không có codebase intelligence.\");\n result.reason = \"install-skipped\";\n return result;\n }\n // v1.13.0: Invalidate cache sau install để re-probe thấy binary mới.\n invalidateGitnexusInstallationCache();\n info = detectGitnexusInstallation();\n if (!info.installed) {\n throw new Error(\"Cài xong nhưng không detect được binary (PATH issue).\");\n }\n }\n result.installed = true;\n log.success(`GitNexus available${info.version ? ` v${info.version}` : \"\"}`);\n\n // Step 2 — `gitnexus setup` (global skills)\n // Wrap try/catch — setup fail không critical (skills không phải core), continue.\n try {\n runGitnexusSetup();\n } catch (err) {\n log.warn(`gitnexus setup fail: ${(err as Error).message}`);\n log.dim(\"Skip global skills install. Workspace vẫn dùng được.\");\n }\n\n // v3: skipAnalyze (init mỏng) → bỏ Step 3 (analyze) + Step 4 (wiki). Chỉ\n // install + skills + MCP. Analyze + wiki chạy per-repo khi `avatar add repo`.\n if (args.skipAnalyze) {\n log.dim(\"GitNexus: cài + MCP xong. Index sẽ chạy per-repo khi `avatar add repo`.\");\n } else {\n // Step 3 — `gitnexus analyze .` (workspace root)\n const analyzed = await analyzeWithRecovery(args.workspacePath);\n if (!analyzed) {\n await appendAuditEntry(\"gitnexus_setup\", \"result=skipped,reason=analyze-skipped\");\n log.dim(\"Skip analyze. GitNexus installed nhưng chưa index.\");\n result.reason = \"analyze-skipped\";\n return result;\n }\n result.analyzed = true;\n\n // Step 4 — Wiki conditional (LLMLite mode only)\n const wikiResult = await runGitnexusWikiConditional(args.workspacePath);\n result.wikiGenerated = wikiResult.ran;\n if (wikiResult.skipped && wikiResult.reason === \"fail\") {\n log.warn(`Wiki gen fail (workspace vẫn OK): ${wikiResult.detail ?? \"unknown\"}`);\n }\n }\n\n // Step 5 — Register MCP server\n try {\n const mcpResult = await registerGitnexusMcpServer();\n result.mcpRegistered = true;\n if (!mcpResult.wasUpdated) {\n log.dim(\"MCP server gitnexus đã registered trước đó.\");\n }\n } catch (err) {\n log.warn(`MCP server register fail: ${(err as Error).message}`);\n log.dim(\n \"Workspace OK nhưng Claude Code không tự attach MCP server. Manual add vào ~/.claude/mcp_servers.json.\",\n );\n }\n\n result.ok = true;\n await appendAuditEntry(\n \"gitnexus_setup\",\n `result=ok,analyzed=${result.analyzed},wiki=${result.wikiGenerated},mcp=${result.mcpRegistered}`,\n );\n log.success(\"GitNexus ready\");\n return result;\n } catch (err) {\n // User abort → re-throw để init.ts catch + exit 0 graceful (consistent với pattern).\n if (err instanceof UserAbortedRecoveryError) throw err;\n\n const message = err instanceof Error ? err.message : String(err);\n log.warn(`GitNexus setup thất bại: ${message}`);\n log.dim(\"Workspace vẫn sẵn sàng. Setup sau qua `avatar gitnexus install`.\");\n await appendAuditEntry(\"gitnexus_setup\", `result=failed,error=${message.slice(0, 200)}`);\n result.reason = message;\n return result;\n }\n}\n","// Auto-install GitNexus CLI qua `npm install -g gitnexus`.\n// Stream stdio để user thấy npm progress. Sau khi cài xong → re-detect verify PATH.\n// Throws InstallGitnexusError với hint cụ thể cho EACCES / ENOSPC / timeout / generic.\n//\n// Pattern identical với install-claude-code-via-npm.ts.\nimport { spawnSync } from \"node:child_process\";\nimport {\n detectGitnexusInstallation,\n invalidateGitnexusInstallationCache,\n} from \"./detect-gitnexus-installation.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// 5 phút — npm global install có thể chậm trên mạng yếu.\nconst NPM_INSTALL_TIMEOUT_MS = 5 * 60 * 1000;\n\n// Package name chính thức của GitNexus trên npm.\nconst GITNEXUS_PACKAGE = \"gitnexus\";\n\nexport type InstallGitnexusReason =\n | \"permission-denied\" // EACCES → cần sudo hoặc fix npm prefix\n | \"disk-full\" // ENOSPC\n | \"timeout\" // > 5 phút\n | \"binary-not-in-path\" // install OK nhưng PATH chưa refresh\n | \"generic\"; // exit non-zero khác\n\nexport class InstallGitnexusError extends Error {\n reason: InstallGitnexusReason;\n exitCode: number | null;\n constructor(reason: InstallGitnexusReason, message: string, exitCode: number | null = null) {\n super(message);\n this.name = \"InstallGitnexusError\";\n this.reason = reason;\n this.exitCode = exitCode;\n }\n}\n\n// Phân loại exit từ npm install thành reason cụ thể để caller branch hint.\nfunction classifyNpmFailure(exitCode: number | null, stderrSample: string): InstallGitnexusError {\n const stderr = stderrSample.toLowerCase();\n if (stderr.includes(\"eacces\") || stderr.includes(\"permission denied\")) {\n return new InstallGitnexusError(\n \"permission-denied\",\n `npm install -g cần quyền. Thử: sudo npm install -g ${GITNEXUS_PACKAGE} hoặc fix npm prefix (npm config set prefix ~/.npm-global).`,\n exitCode,\n );\n }\n if (stderr.includes(\"enospc\") || stderr.includes(\"no space\")) {\n return new InstallGitnexusError(\"disk-full\", \"Đĩa đầy. Free disk space rồi thử lại.\", exitCode);\n }\n return new InstallGitnexusError(\n \"generic\",\n `npm install thất bại (exit ${exitCode ?? \"null\"}). Xem log npm phía trên.`,\n exitCode,\n );\n}\n\n// Main API: cài GitNexus global + verify. Throws InstallGitnexusError nếu fail.\n// Return version + path detect được (luôn non-null nếu success).\nexport function installGitnexusViaNpm(): { version: string | null; path: string } {\n log.info(\"Đang cài GitNexus qua npm (có thể mất 1-2 phút)...\");\n\n const result = spawnSync(\"npm\", [\"install\", \"-g\", GITNEXUS_PACKAGE], {\n stdio: [\"inherit\", \"inherit\", \"pipe\"],\n timeout: NPM_INSTALL_TIMEOUT_MS,\n encoding: \"utf8\",\n });\n\n if (result.signal === \"SIGTERM\") {\n throw new InstallGitnexusError(\n \"timeout\",\n `npm install timeout sau ${NPM_INSTALL_TIMEOUT_MS / 1000}s. Check mạng rồi thử lại.`,\n null,\n );\n }\n\n if (result.status !== 0) {\n if (result.stderr) process.stderr.write(result.stderr);\n throw classifyNpmFailure(result.status, result.stderr || \"\");\n }\n\n // v1.14.1 BUG FIX: Invalidate memoize cache TRƯỚC re-detect.\n // Trước fix: cache giữ `{installed: false}` từ probe pre-install →\n // re-detect sau install trả false → throw \"binary-not-in-path\" SAI dù\n // binary đã thực sự cài vào /opt/homebrew/bin/gitnexus.\n invalidateGitnexusInstallationCache();\n\n // Re-detect — npm install OK không đồng nghĩa binary đã trong PATH (PATH cache).\n const probe = detectGitnexusInstallation();\n if (!probe.installed || !probe.path) {\n throw new InstallGitnexusError(\n \"binary-not-in-path\",\n \"npm cài xong nhưng `gitnexus` không trong PATH. Reload shell (source ~/.zshrc) hoặc thêm npm global bin vào PATH.\",\n null,\n );\n }\n\n log.success(`Đã cài GitNexus${probe.version ? ` v${probe.version}` : \"\"} tại ${probe.path}`);\n return { version: probe.version, path: probe.path };\n}\n","// Step 5 của Phase 10 — modify ~/.claude/mcp_servers.json add entry GitNexus\n// để Claude Code attach MCP server lúc start.\n//\n// Behavior:\n// - File chưa tồn tại → tạo mới với entry gitnexus duy nhất\n// - File có entries khác (vd tools, secrets) → merge giữ\n// - File có entry gitnexus đúng → no-op (idempotent)\n// - File có entry gitnexus khác → override (Q4 decision) + backup file cũ\n//\n// Atomic write qua tmp + rename + chmod 600 (consistent với pattern Avatar settings).\nimport { promises as fs } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// chmod 600 — consistent với .claude/settings.json pattern (chứa OAuth-like data).\nconst MCP_FILE_MODE = 0o600;\n\n// Entry expected mà Avatar muốn enforce — match GitNexus official MCP server spec.\nconst EXPECTED_GITNEXUS_ENTRY = {\n command: \"gitnexus\",\n args: [\"mcp\"],\n} as const;\n\nexport interface RegisterMcpResult {\n path: string; // ~/.claude/mcp_servers.json\n wasUpdated: boolean; // false nếu entry đã đúng (no-op)\n backup?: string; // path backup nếu file existed + cần modify\n}\n\n// Shape minimal của mcp_servers.json — index signature giữ key user khác.\ninterface McpServersConfig {\n mcp_servers?: Record<string, unknown>;\n [k: string]: unknown;\n}\n\n// Get path ~/.claude/mcp_servers.json cross-platform.\nfunction getMcpServersPath(): string {\n return join(homedir(), \".claude\", \"mcp_servers.json\");\n}\n\n// Deep equal cho 2 plain objects (đủ dùng cho entry compare, không cần full lodash).\nfunction isEntryEqual(a: unknown, b: unknown): boolean {\n if (a === b) return true;\n if (typeof a !== \"object\" || typeof b !== \"object\" || a === null || b === null) return false;\n return JSON.stringify(a) === JSON.stringify(b);\n}\n\n// Backup file existing với timestamp suffix (idempotent backup name).\nasync function backupExistingFile(path: string): Promise<string> {\n const ts = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupPath = `${path}.avatar-backup-${ts}`;\n await fs.copyFile(path, backupPath);\n return backupPath;\n}\n\n// Main API — register GitNexus MCP server entry idempotent.\n// Trả structured result để caller (orchestrator) log đúng action đã làm.\nexport async function registerGitnexusMcpServer(): Promise<RegisterMcpResult> {\n const path = getMcpServersPath();\n\n // Read existing (default {} nếu chưa tồn tại).\n let existing: McpServersConfig = {};\n let fileExisted = false;\n if (await pathExists(path)) {\n fileExisted = true;\n try {\n existing = await readJson<McpServersConfig>(path);\n } catch (err) {\n throw new Error(\n `MCP config corrupted (${path}): ${(err as Error).message}. Backup + xóa file để Avatar tạo lại.`,\n );\n }\n }\n\n // Idempotent check — nếu entry đã đúng thì no-op.\n const existingEntry = existing.mcp_servers?.gitnexus;\n if (existingEntry && isEntryEqual(existingEntry, EXPECTED_GITNEXUS_ENTRY)) {\n log.dim(`MCP entry gitnexus đã đúng tại ${path} (no-op)`);\n return { path, wasUpdated: false };\n }\n\n // Backup file cũ trước khi modify.\n let backup: string | undefined;\n if (fileExisted) {\n backup = await backupExistingFile(path);\n log.dim(`Backup ${path} → ${backup}`);\n }\n\n // Merge giữ entries khác user đã có.\n const merged: McpServersConfig = {\n ...existing,\n mcp_servers: {\n ...(existing.mcp_servers || {}),\n gitnexus: EXPECTED_GITNEXUS_ENTRY,\n },\n };\n\n await writeJsonAtomic(path, merged, MCP_FILE_MODE);\n // Re-chmod sau write — pattern consistent với write-claude-settings-json-per-project.\n try {\n await fs.chmod(path, MCP_FILE_MODE);\n } catch {\n // Windows / fs không support — bỏ qua.\n }\n\n log.success(`Registered MCP server: gitnexus → ${path}`);\n return { path, wasUpdated: true, backup };\n}\n","// `avatar init <workspace-name>` — v3 THIN INIT.\n//\n// 1 flow duy nhất (bỏ internal/client/library + existing-remote/folder/new-project).\n// Init chỉ dựng MÔI TRƯỜNG: login NAL → tạo workspace folder (KHÔNG git) → cài\n// team-ai-pack tarball → symlink + settings → GitNexus(install+MCP) → AI setup.\n//\n// Repo code cài SAU bằng `avatar add repo` (src/ rỗng sau init).\nimport { resolve } from \"node:path\";\nimport { input } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport { printAvatarBanner } from \"../lib/avatar-ascii-banner.js\";\nimport { RemoteAccessAbortedError } from \"../lib/handle-remote-access-failure-with-account-switch.js\";\nimport { UserAbortedRecoveryError } from \"../lib/prompt-recovery-action-on-failure.js\";\nimport { InitAbortedByUserError } from \"../lib/safe-bootstrap-for-dirty-folder.js\";\nimport { TeamPackAccessAbortedError } from \"../lib/team-pack-submodule-manager.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { isTokenExpired, readUserConfig } from \"../lib/user-config-store.js\";\nimport { promptTeamOwner, resolveWorkspacePath } from \"./init-success-rendering-and-helpers.js\";\nimport { runLogin } from \"./login.js\";\nimport { scaffoldThinWorkspace } from \"./workspace-scaffold-and-finalize-orchestrator.js\";\n\nexport interface InitOptions {\n workspaceParent?: string;\n packVersion?: string;\n teamOwner?: string;\n description?: string;\n skipTeamPack?: boolean;\n force?: boolean;\n yes?: boolean;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n}\n\nexport function registerInitCommand(program: Command): void {\n program\n .command(\"init\")\n .description(\"Khởi tạo Avatar workspace (môi trường) — repo cài sau bằng 'avatar add repo'\")\n .argument(\"[workspace-name]\", \"Tên workspace (bắt buộc, hỏi nếu thiếu)\")\n .option(\"--workspace-parent <path>\", \"Thư mục cha tạo workspace (mặc định . — CWD)\")\n .option(\"--pack-version <tag>\", \"Pin team-ai-pack vào tag cụ thể (vd v0.2.0-beta.0)\")\n .option(\"--team-owner <email>\", \"Email team owner (bỏ qua prompt)\")\n .option(\"--description <text>\", \"Mô tả 1 dòng của workspace\")\n .option(\"--skip-team-pack\", \"Bỏ qua tải team-ai-pack (test mode)\")\n .option(\"--force\", \"Bỏ qua prompt khi workspace path đã tồn tại\")\n .option(\"--yes\", \"Auto-confirm tất cả prompt\")\n .option(\"--ai-skip\", \"Bỏ qua phase AI setup (chạy 'avatar ai setup' sau)\")\n .option(\"--gitnexus-skip\", \"Bỏ qua phase GitNexus setup\")\n .action(async (workspaceName: string | undefined, opts: InitOptions) => {\n try {\n await runInit(workspaceName, opts);\n } catch (err) {\n if (\n err instanceof InitAbortedByUserError ||\n err instanceof TeamPackAccessAbortedError ||\n err instanceof RemoteAccessAbortedError ||\n err instanceof UserAbortedRecoveryError\n ) {\n log.dim(err.message);\n process.exit(0);\n }\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nasync function runInit(workspaceName: string | undefined, opts: InitOptions): Promise<void> {\n if (!opts.yes) printAvatarBanner({ tagline: \"Khởi tạo Avatar workspace\" });\n\n // Login NAL — bắt buộc (cần token để tải pack từ Supabase).\n let userConfig = await readUserConfig();\n while (!userConfig || isTokenExpired(userConfig)) {\n log.info(\"Chưa đăng nhập (hoặc token hết hạn) — chạy login trước...\");\n try {\n await runLogin({});\n } catch (err) {\n log.warn(`Login fail: ${(err as Error).message}`);\n }\n userConfig = await readUserConfig();\n if (userConfig && !isTokenExpired(userConfig)) break;\n throw new UserAbortedRecoveryError(\n \"Cần đăng nhập @nal.vn để tải team-ai-pack. Chạy 'avatar login' rồi 'avatar init' lại.\",\n );\n }\n\n // Tên workspace bắt buộc — hỏi nếu thiếu.\n const name =\n workspaceName ??\n (await input({\n message: \"Tên workspace:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Tên không được rỗng\"),\n }));\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(userConfig.email));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, name.trim(), opts.force);\n\n await scaffoldThinWorkspace({\n workspacePath,\n workspaceName: name.trim(),\n teamOwner,\n description: opts.description ?? `Avatar workspace: ${name.trim()}`,\n packVersion: opts.packVersion,\n autoYes: opts.yes,\n skipTeamPack: opts.skipTeamPack,\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n });\n}\n","// Avatar ASCII banner — gradient màu cam → tím để in ở các entry point chính:\n// `avatar --version`, `avatar init`, `avatar login`.\n//\n// Style: ANSI Shadow block characters (Unicode box-drawing).\n// Tự fallback sang plain text khi không phải TTY (CI, pipe ra file).\nimport chalk from \"chalk\";\n\n// 6 dòng chính của logo, mỗi dòng giữ độ rộng đồng nhất 50 ký tự để gradient đẹp.\nconst BANNER_LINES: readonly string[] = [\n \" █████╗ ██╗ ██╗ █████╗ ████████╗ █████╗ ██████╗ \",\n \"██╔══██╗██║ ██║██╔══██╗╚══██╔══╝██╔══██╗██╔══██╗\",\n \"███████║██║ ██║███████║ ██║ ███████║██████╔╝\",\n \"██╔══██║╚██╗ ██╔╝██╔══██║ ██║ ██╔══██║██╔══██╗\",\n \"██║ ██║ ╚████╔╝ ██║ ██║ ██║ ██║ ██║██║ ██║\",\n \"╚═╝ ╚═╝ ╚═══╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝\",\n] as const;\n\n// Gradient stops cam → tím (RGB triples). Mỗi dòng nội suy theo % chiều cao.\nconst GRADIENT_STOPS: ReadonlyArray<readonly [number, number, number]> = [\n [217, 79, 30], // cam-cháy (#d94f1e)\n [200, 70, 80], // cam-hồng\n [170, 70, 140], // hồng-tím\n [125, 88, 217], // tím (#7d58d9)\n];\n\n// Nội suy tuyến tính 1 kênh màu giữa 2 stop.\nfunction lerpChannel(a: number, b: number, t: number): number {\n return Math.round(a + (b - a) * t);\n}\n\n// Lấy màu RGB tại vị trí [0,1] trên gradient.\nfunction gradientAt(t: number): readonly [number, number, number] {\n const clamped = Math.max(0, Math.min(1, t));\n const scaled = clamped * (GRADIENT_STOPS.length - 1);\n const lo = Math.floor(scaled);\n const hi = Math.min(GRADIENT_STOPS.length - 1, lo + 1);\n const localT = scaled - lo;\n // Non-null safe: lo ∈ [0, len-1] qua Math.floor + clamp, hi ∈ [0, len-1] qua Math.min.\n // TS strict mode không narrow tuple index → assert.\n const a = GRADIENT_STOPS[lo]!;\n const b = GRADIENT_STOPS[hi]!;\n return [\n lerpChannel(a[0], b[0], localT),\n lerpChannel(a[1], b[1], localT),\n lerpChannel(a[2], b[2], localT),\n ];\n}\n\n// Build banner string đã tô màu sẵn. Trả empty khi terminal không hỗ trợ màu.\nexport function renderAvatarBanner(opts?: { tagline?: string }): string {\n const isTty = process.stdout.isTTY ?? false;\n const supportsColor = isTty && chalk.level > 0;\n\n // Plain fallback cho CI hoặc pipe.\n if (!supportsColor) {\n return [...BANNER_LINES, ...(opts?.tagline ? [\"\", opts.tagline] : [])].join(\"\\n\");\n }\n\n const colored = BANNER_LINES.map((line, idx) => {\n const t = BANNER_LINES.length === 1 ? 0 : idx / (BANNER_LINES.length - 1);\n const [r, g, b] = gradientAt(t);\n return chalk.rgb(r, g, b).bold(line);\n });\n\n if (opts?.tagline) {\n colored.push(\"\");\n colored.push(chalk.dim(opts.tagline));\n }\n\n return colored.join(\"\\n\");\n}\n\n// Tiện ích: in banner ra stdout với newline phía trước/sau cho thoáng.\nexport function printAvatarBanner(opts?: { tagline?: string }): void {\n process.stdout.write(`\\n${renderAvatarBanner(opts)}\\n\\n`);\n}\n","// Safe Bootstrap (Tier A) — bảo vệ folder user khỏi mất changes uncommitted khi\n// `avatar init` modify .gitignore + tạo initial commit.\n//\n// Flow:\n// 1. detectFolderGitState() → \"empty\" | \"untracked-only\" | \"clean\" | \"dirty\"\n// 2. empty/clean → bootstrap thẳng (không có rủi ro)\n// 3. dirty/untracked-only → promptBootstrapStrategy() → 4 lựa chọn\n// 4. executeBootstrapWithStrategy() → branch xử lý từng strategy\n//\n// 4 strategies:\n// - stash (DEFAULT highlight): git stash -u → bootstrap → stash pop\n// - commit-all (legacy v1.1.6): git add . && commit, all changes bundled\n// - skip: throw InitAbortedByUserError — user tự commit rồi chạy lại\n// - branch: checkout -b avatar/init → bootstrap → checkout back\n//\n// CLI flags:\n// --bootstrap-strategy <s> : preset, skip prompt\n// --preserve-uncommitted : alias cho --bootstrap-strategy=stash\n// --yes : CI mode → default stash (safest)\nimport { readdirSync } from \"node:fs\";\nimport { select } from \"@inquirer/prompts\";\nimport { type SimpleGit, simpleGit } from \"simple-git\";\nimport { appendAuditEntry } from \"./audit-log-appender.js\";\nimport { checkFolderHasGit } from \"./check-folder-has-git.js\";\nimport { createInitialGitCommit } from \"./create-initial-git-commit.js\";\nimport { detectFolderTechStack } from \"./detect-folder-tech-stack.js\";\nimport { composeGitignoreContent } from \"./gitignore-template-loader.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { writeOrMergeGitignore } from \"./write-or-merge-gitignore.js\";\n\nexport type GitState = \"empty\" | \"untracked-only\" | \"clean\" | \"dirty\";\n\nexport type BootstrapStrategy = \"stash\" | \"commit-all\" | \"skip\" | \"branch\";\n\nexport interface SafeBootstrapOptions {\n presetStrategy?: BootstrapStrategy;\n autoYes?: boolean;\n}\n\n// User chọn skip giữa wizard → caller catch để abort init.ts gracefully.\nexport class InitAbortedByUserError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InitAbortedByUserError\";\n }\n}\n\n// === Step 1: detect git state ===\nexport async function detectFolderGitState(folderPath: string): Promise<GitState> {\n const hasGit = checkFolderHasGit(folderPath);\n if (!hasGit) {\n // Filter `.git` (rare gitlink file edge case) + dotfiles không count cho empty check.\n const entries = readdirSync(folderPath).filter((e) => e !== \".git\");\n return entries.length === 0 ? \"empty\" : \"untracked-only\";\n }\n const g = simpleGit({ baseDir: folderPath });\n const status = await g.status();\n return status.isClean() ? \"clean\" : \"dirty\";\n}\n\n// === Step 2: prompt strategy ===\nexport async function promptBootstrapStrategy(\n state: GitState,\n opts: SafeBootstrapOptions,\n): Promise<BootstrapStrategy> {\n // Preset flag → skip prompt hoàn toàn.\n if (opts.presetStrategy) return opts.presetStrategy;\n\n // CI mode (--yes) → safe default. Stash là an toàn nhất (reversible).\n if (opts.autoYes) return \"stash\";\n\n // Empty/clean state không gọi prompt (caller skip). Defensive return.\n if (state === \"empty\" || state === \"clean\") return \"commit-all\";\n\n return (await select({\n message:\n state === \"dirty\"\n ? \"Folder có changes chưa commit. Cách xử lý:\"\n : \"Folder có file chưa version. Cách xử lý:\",\n choices: [\n {\n value: \"stash\" as const,\n name: \"1. Stash changes → bootstrap → restore (KHUYẾN NGHỊ)\",\n },\n {\n value: \"commit-all\" as const,\n name: \"2. Commit toàn bộ vào initial commit (legacy v1.1.6)\",\n },\n {\n value: \"skip\" as const,\n name: \"3. Skip — tôi commit thủ công rồi chạy lại\",\n },\n {\n value: \"branch\" as const,\n name: \"4. Commit vào branch riêng `avatar/init` (main giữ sạch)\",\n },\n ],\n default: \"stash\",\n })) as BootstrapStrategy;\n}\n\n// === Helpers: stash + branch ===\n\n// Stash user changes (kể cả untracked) với name rõ ràng để track. Trả true nếu stash thật sự.\nasync function stashUserChanges(g: SimpleGit, stashName: string): Promise<boolean> {\n const status = await g.status();\n if (status.isClean() && status.not_added.length === 0) return false;\n await g.stash([\"push\", \"--include-untracked\", \"-m\", stashName]);\n log.info(`Stashed changes: ${stashName}`);\n return true;\n}\n\n// Restore stash. Nếu conflict → log warning + leave markers (KHÔNG tự revert).\nasync function restoreStash(g: SimpleGit, stashName: string): Promise<void> {\n try {\n await g.stash([\"pop\"]);\n log.success(`Restored stash: ${stashName}`);\n } catch (err) {\n log.warn(\n \"Restore stash conflict — files có Avatar tạo và stash của user xung đột. Stash giữ tại ref stash@{0}.\",\n );\n log.warn(\"Resolve: git stash show -p stash@{0} → fix conflict → git stash drop\");\n log.dim(`Detail: ${(err as Error).message}`);\n }\n}\n\n// Lấy branch hiện tại (HEAD). Fallback \"main\" nếu detached HEAD.\nasync function getCurrentBranch(g: SimpleGit): Promise<string> {\n try {\n const result = await g.revparse([\"--abbrev-ref\", \"HEAD\"]);\n const branch = result.trim();\n return branch === \"HEAD\" ? \"main\" : branch;\n } catch {\n return \"main\";\n }\n}\n\n// === Step 3: execute strategy ===\nasync function writeAvatarGitignore(folderPath: string): Promise<void> {\n const stacks = detectFolderTechStack(folderPath);\n log.info(`Tech stack: ${stacks.join(\", \")}`);\n writeOrMergeGitignore(folderPath, composeGitignoreContent(stacks));\n log.success(\".gitignore đã ghi (Avatar block)\");\n}\n\nexport async function executeBootstrapWithStrategy(\n folderPath: string,\n strategy: BootstrapStrategy,\n): Promise<void> {\n const g = simpleGit({ baseDir: folderPath });\n\n switch (strategy) {\n case \"skip\":\n throw new InitAbortedByUserError(\n \"Init aborted. Commit thủ công changes hiện tại rồi chạy lại `avatar init`.\",\n );\n\n case \"stash\": {\n const stashName = `avatar-init-backup-${Date.now()}`;\n // Stash cần initial commit để work. Nếu folder chưa có .git hoặc chưa có commit\n // → init + empty commit baseline trước, sau đó mới stash user changes.\n const hadGit = checkFolderHasGit(folderPath);\n if (!hadGit) {\n await g.init();\n await g.branch([\"-M\", \"main\"]).catch(() => undefined);\n }\n const hasCommit = (await g.raw([\"rev-list\", \"-n\", \"1\", \"--all\"]).catch(() => \"\")).trim();\n if (!hasCommit) {\n await g.commit(\"chore: avatar baseline (pre-stash)\", undefined, { \"--allow-empty\": null });\n }\n const stashed = await stashUserChanges(g, stashName);\n try {\n await writeAvatarGitignore(folderPath);\n await createInitialGitCommit(folderPath);\n } finally {\n // Always restore stash dù bootstrap fail giữa chừng.\n if (stashed) await restoreStash(g, stashName);\n }\n break;\n }\n\n case \"commit-all\": {\n // Legacy v1.1.6 behavior — KHÔNG dùng cho default mới nhưng giữ cho backward compat.\n await writeAvatarGitignore(folderPath);\n await createInitialGitCommit(folderPath);\n break;\n }\n\n case \"branch\": {\n // Cần repo trước. Nếu chưa có git thì init + commit baseline ở main đầu tiên.\n const hadGit = checkFolderHasGit(folderPath);\n if (!hadGit) {\n await g.init();\n await g.branch([\"-M\", \"main\"]);\n }\n const originalBranch = await getCurrentBranch(g);\n // checkoutLocalBranch fail nếu branch đã tồn tại — fallback checkout existing.\n try {\n await g.checkoutLocalBranch(\"avatar/init\");\n } catch {\n await g.checkout(\"avatar/init\");\n }\n await writeAvatarGitignore(folderPath);\n await createInitialGitCommit(folderPath);\n // Switch back. Nếu fail (vd no commits ở branch gốc) → ở lại avatar/init.\n try {\n await g.checkout(originalBranch);\n log.info(\n `Avatar init committed ở branch 'avatar/init'. Switch back về '${originalBranch}'. Merge khi sẵn sàng: git merge avatar/init`,\n );\n } catch {\n log.warn(\n `Không switch về '${originalBranch}' được — ở lại branch 'avatar/init'. Switch tay sau.`,\n );\n }\n break;\n }\n }\n}\n\n// === Main orchestrator (thay bootstrapGitInFolder cho dirty/untracked case) ===\nexport async function safeBootstrapGitInFolder(\n folderPath: string,\n opts: SafeBootstrapOptions = {},\n): Promise<void> {\n const state = await detectFolderGitState(folderPath);\n log.info(`Folder state: ${state}`);\n\n // Empty/clean → bootstrap thẳng, không prompt.\n if (state === \"empty\" || state === \"clean\") {\n await writeAvatarGitignore(folderPath);\n if (state === \"empty\") {\n await createInitialGitCommit(folderPath);\n }\n await appendAuditEntry(\"bootstrap\", `state=${state},strategy=auto`);\n return;\n }\n\n // Dirty/untracked → prompt + execute.\n const strategy = await promptBootstrapStrategy(state, opts);\n await executeBootstrapWithStrategy(folderPath, strategy);\n await appendAuditEntry(\"bootstrap\", `state=${state},strategy=${strategy}`);\n}\n","// Kiểm tra folder đã là git repo chưa. Đơn giản: check tồn tại của \".git\"\n// (folder hoặc file gitlink — cả hai đều hợp lệ với submodule).\nimport { existsSync, statSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport function checkFolderHasGit(folderPath: string): boolean {\n const gitPath = join(folderPath, \".git\");\n if (!existsSync(gitPath)) return false;\n // .git có thể là directory (repo bình thường) hoặc file (submodule gitlink).\n // Cả 2 đều xem là \"đã có git\".\n const stat = statSync(gitPath);\n return stat.isDirectory() || stat.isFile();\n}\n","// Init git repo trong folder + tạo initial commit. Idempotent: skip nếu đã\n// có commit. Default branch \"main\".\nimport { simpleGit } from \"simple-git\";\n\nconst INITIAL_COMMIT_MESSAGE = \"chore: initial commit\";\n\nexport async function createInitialGitCommit(folderPath: string): Promise<void> {\n const g = simpleGit({ baseDir: folderPath });\n\n // Init nếu chưa có .git.\n const isRepo = await g.checkIsRepo().catch(() => false);\n if (!isRepo) {\n await g.init();\n }\n\n // Đảm bảo branch hiện tại là main (đổi master → main nếu cần).\n // Một số git config user có init.defaultBranch=master.\n try {\n await g.branch([\"-M\", \"main\"]);\n } catch {\n // Repo trống chưa có commit nào — branch -M sẽ fail. Bỏ qua, commit\n // đầu tiên dưới đây sẽ tạo branch main qua HEAD ref.\n }\n\n // Stage all + commit. Nếu không có file (folder rỗng) thì commit empty để\n // submodule add có HEAD reference dùng.\n await g.add(\".\");\n const status = await g.status();\n const hasCommits = (await g.raw([\"rev-list\", \"-n\", \"1\", \"--all\"]).catch(() => \"\")).trim();\n if (hasCommits) return; // Đã có commit, skip.\n\n if (status.files.length === 0) {\n await g.commit(INITIAL_COMMIT_MESSAGE, undefined, { \"--allow-empty\": null });\n } else {\n await g.commit(INITIAL_COMMIT_MESSAGE);\n }\n}\n","// Detect tech stack của folder qua signature file ở root. Trả về tất cả stack\n// match được (folder polyglot là chuyện thường — vd monorepo Node + Python).\n// Nếu không match gì → [\"generic\"].\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport type TechStack = \"node\" | \"python\" | \"go\" | \"rust\" | \"java\" | \"ruby\" | \"generic\";\n\n// Bảng signature: stack → các file đủ điều kiện claim stack đó.\nconst SIGNATURES: Record<Exclude<TechStack, \"generic\">, string[]> = {\n node: [\"package.json\"],\n python: [\"pyproject.toml\", \"requirements.txt\", \"setup.py\", \"Pipfile\"],\n go: [\"go.mod\"],\n rust: [\"Cargo.toml\"],\n java: [\"pom.xml\", \"build.gradle\", \"build.gradle.kts\"],\n ruby: [\"Gemfile\"],\n};\n\nexport function detectFolderTechStack(folderPath: string): TechStack[] {\n const matched: TechStack[] = [];\n for (const [stack, files] of Object.entries(SIGNATURES) as [\n Exclude<TechStack, \"generic\">,\n string[],\n ][]) {\n if (files.some((f) => existsSync(join(folderPath, f)))) {\n matched.push(stack);\n }\n }\n return matched.length > 0 ? matched : [\"generic\"];\n}\n","// Load template gitignore từ src/templates/gitignore/<stack>.txt và compose\n// nội dung tổng hợp cho 1+ stack. Generic luôn được prepend.\nimport { readFileSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type { TechStack } from \"./detect-folder-tech-stack.js\";\n\n// Resolve template dir tương đối với file này, không phải CWD.\n// v1.2.9 fix: tsup bundle thành `dist/index.js` (single file) → __dirname = `dist/`,\n// KHÔNG phải `dist/lib/`. Phải search cả 2 layout:\n// - Bundled (dist/index.js): __dirname/templates/gitignore (= dist/templates/gitignore)\n// - Dev (src/lib/*.ts): __dirname/../../src/templates/gitignore\n// - npm-installed src: package_root/src/templates/gitignore (qua files field)\nconst __dirname = dirname(fileURLToPath(import.meta.url));\n\nconst CANDIDATE_DIRS = [\n // Bundled production: dist/index.js → __dirname = .../dist/, sibling dist/templates\n join(__dirname, \"templates\", \"gitignore\"),\n // Legacy bundled: nếu file là dist/lib/*.js (sub-bundle), templates ở dist/templates\n join(__dirname, \"..\", \"templates\", \"gitignore\"),\n // Dev mode (vitest/tsx run src/ trực tiếp): __dirname = src/lib/\n join(__dirname, \"..\", \"..\", \"src\", \"templates\", \"gitignore\"),\n // npm-installed alt: __dirname = .../dist/ → package_root/src/templates\n join(__dirname, \"..\", \"src\", \"templates\", \"gitignore\"),\n];\n\nconst AVATAR_MARKER_START = \"# === avatar ===\";\nconst AVATAR_MARKER_END = \"# === /avatar ===\";\n\nfunction readTemplate(stack: TechStack): string {\n for (const dir of CANDIDATE_DIRS) {\n try {\n return readFileSync(join(dir, `${stack}.txt`), \"utf8\");\n } catch {\n // continue\n }\n }\n throw new Error(`Không tìm thấy template gitignore cho stack \"${stack}\"`);\n}\n\n// Compose: generic luôn ở đầu, sau đó các stack khác theo thứ tự detect.\n// Wrap trong marker để uninstall biết range gỡ chính xác.\nexport function composeGitignoreContent(stacks: TechStack[]): string {\n const all: TechStack[] = [\"generic\", ...stacks.filter((s) => s !== \"generic\")];\n const sections = all.map((s) => `# --- ${s} ---\\n${readTemplate(s).trim()}`);\n return [AVATAR_MARKER_START, ...sections, AVATAR_MARKER_END, \"\"].join(\"\\n\");\n}\n\nexport { AVATAR_MARKER_START, AVATAR_MARKER_END };\n","// Ghi .gitignore: tạo mới nếu chưa có, merge content nếu đã có. Merge logic\n// dùng marker `# === avatar === ... # === /avatar ===`: replace block đó nếu\n// tồn tại, append nếu chưa.\nimport { existsSync, readFileSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { AVATAR_MARKER_END, AVATAR_MARKER_START } from \"./gitignore-template-loader.js\";\n\nexport function writeOrMergeGitignore(folderPath: string, avatarBlock: string): void {\n const path = join(folderPath, \".gitignore\");\n\n if (!existsSync(path)) {\n writeFileSync(path, avatarBlock, \"utf8\");\n return;\n }\n\n const existing = readFileSync(path, \"utf8\");\n const startIdx = existing.indexOf(AVATAR_MARKER_START);\n const endIdx = existing.indexOf(AVATAR_MARKER_END);\n\n // Đã có marker → replace block giữa marker (giữ content trước/sau).\n if (startIdx !== -1 && endIdx !== -1 && endIdx > startIdx) {\n const before = existing.slice(0, startIdx);\n const after = existing.slice(endIdx + AVATAR_MARKER_END.length);\n writeFileSync(path, `${before.trimEnd()}\\n\\n${avatarBlock}${after.trimStart()}`, \"utf8\");\n return;\n }\n\n // Chưa có marker → append phía cuối, thêm newline phân cách.\n writeFileSync(path, `${existing.trimEnd()}\\n\\n${avatarBlock}`, \"utf8\");\n}\n","// team-pack-submodule-manager.ts\n//\n// v3: team-ai-pack KHÔNG còn là git submodule. Pack tải qua tarball từ Supabase\n// Edge Function get-pack (xem supabase-pack-downloader.ts). File này giữ tên +\n// các export cũ (6+ file import gián tiếp) nhưng ruột đổi sang tarball.\n//\n// Lịch sử: trước v3 dùng git submodule + GitHub access check. Cắt hẳn ở v3 —\n// auth chuyển sang Supabase id_token (login NAL), user không cần GitHub access pack.\nimport { join } from \"node:path\";\nimport { readPackVersionFromFile, writePackManifest } from \"./pack-manifest-store.js\";\nimport { downloadAndExtractPack, fetchSignedPackUrl } from \"./supabase-pack-downloader.js\";\nimport { getValidIdToken } from \"./user-config-store.js\";\n\n// GIỮ export — 6+ file import path này (init-success, scaffold, init-flow, sync, status, pack-status).\nexport const TEAM_PACK_RELATIVE_PATH = \".claude/pack\";\n\n// GIỮ export — init.ts catch để exit 0 graceful khi user abort.\nexport class TeamPackAccessAbortedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TeamPackAccessAbortedError\";\n }\n}\n\n// Install team-ai-pack vào project qua tarball Supabase.\n// Tên giữ \"addTeamPackSubmodule\" vì caller cũ import; ruột là tải tarball.\n//\n// version undefined → server trả stable mới nhất (stable/latest.txt).\n// version cụ thể (vd v0.2.0-beta.0) → pin bản đó.\n// Trả { pinnedTag } = version đã cài (cho caller in ra).\nexport async function addTeamPackSubmodule(\n projectRoot: string,\n version?: string,\n): Promise<{ pinnedTag: string | null }> {\n const idToken = await getValidIdToken();\n const info = await fetchSignedPackUrl(idToken, version);\n\n const packDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n await downloadAndExtractPack(info.url, packDir);\n\n // version cài được = từ object name (pack-vX.Y.Z.tar.gz) hoặc version pin.\n const installedVersion = version ?? objectToVersion(info.object);\n await writePackManifest(packDir, {\n version: installedVersion,\n downloadedObject: info.object,\n extractedAt: new Date().toISOString(),\n });\n\n return { pinnedTag: installedVersion };\n}\n\n// \"pack-v0.2.0.tar.gz\" → \"v0.2.0\"\nfunction objectToVersion(object: string): string {\n return object.replace(/^pack-/, \"\").replace(/\\.tar\\.gz$/, \"\");\n}\n\n// Đọc version pack đang cài. v3: đọc từ manifest (.avatar-pack-manifest.json) thay\n// vì git tag. GIỮ tên export — status.ts + pack-status-and-version-check.ts import.\nexport async function readPinnedPackVersion(projectRoot: string): Promise<string> {\n const packDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n const version = await readPackVersionFromFile(packDir);\n return version ?? \"(unknown)\";\n}\n","// pack-manifest-store.ts\n//\n// v3: Pack tải qua tarball Supabase (không còn git submodule) → metadata version\n// không đọc từ git tag được nữa. Thay bằng file manifest ghi sau khi extract.\n//\n// Manifest sống tại `.claude/pack/.avatar-pack-manifest.json`:\n// { version, downloadedObject, sha256?, extractedAt }\n//\n// Đọc version ưu tiên manifest; fallback `.pack-version` (file kèm trong tarball\n// do script release ghi) nếu manifest thiếu (vd pack cài tay).\nimport { join } from \"node:path\";\nimport { pathExists, readJson, readText, writeJsonAtomic } from \"./filesystem-helpers.js\";\n\nexport const PACK_MANIFEST_FILENAME = \".avatar-pack-manifest.json\";\nexport const PACK_VERSION_FILENAME = \".pack-version\";\n\nexport interface PackManifest {\n version: string; // vd \"v0.2.0\"\n downloadedObject: string; // vd \"pack-v0.2.0.tar.gz\"\n sha256?: string; // hash tarball đã tải (nếu Supabase cung cấp)\n extractedAt: string; // ISO timestamp\n}\n\nfunction manifestPath(packDir: string): string {\n return join(packDir, PACK_MANIFEST_FILENAME);\n}\n\nexport async function writePackManifest(packDir: string, data: PackManifest): Promise<void> {\n await writeJsonAtomic(manifestPath(packDir), data);\n}\n\nexport async function readPackManifest(packDir: string): Promise<PackManifest | null> {\n const p = manifestPath(packDir);\n if (!(await pathExists(p))) return null;\n try {\n return await readJson<PackManifest>(p);\n } catch {\n return null;\n }\n}\n\n// Đọc version pack đang cài. Ưu tiên manifest → fallback .pack-version → null.\nexport async function readPackVersionFromFile(packDir: string): Promise<string | null> {\n const manifest = await readPackManifest(packDir);\n if (manifest?.version) return manifest.version;\n\n const versionFile = join(packDir, PACK_VERSION_FILENAME);\n if (await pathExists(versionFile)) {\n const v = (await readText(versionFile)).trim();\n if (v) return v;\n }\n return null;\n}\n","import { promises as fs } from \"node:fs\";\n// supabase-pack-downloader.ts\n//\n// v3: Tải team-ai-pack qua Supabase Edge Function thay git submodule.\n//\n// Flow:\n// 1. getValidIdToken() — id_token NAL còn hạn (tự refresh)\n// 2. fetchSignedPackUrl(idToken, version?) — POST get-pack → signed URL (300s)\n// 3. downloadAndExtractPack(signedUrl, packDir) — tải tarball vào temp TRONG\n// .claude/ (cùng filesystem để rename atomic) → giải nén temp → swap vào packDir\n// 4. caller ghi manifest (pack-manifest-store)\n//\n// Endpoint hardcode — hạ tầng NAL cố định (giống ORG_DEFAULT git URL cũ).\n// Override qua env AVATAR_GET_PACK_ENDPOINT cho test/staging.\nimport { join } from \"node:path\";\nimport {\n downloadFile,\n ensureDir,\n extractTarballToDir,\n pathExists,\n removeRecursive,\n} from \"./filesystem-helpers.js\";\n\nconst DEFAULT_GET_PACK_ENDPOINT = \"https://qpdbkewtpkkbcrptnlos.supabase.co/functions/v1/get-pack\";\n\nexport function getPackEndpoint(): string {\n return process.env.AVATAR_GET_PACK_ENDPOINT ?? DEFAULT_GET_PACK_ENDPOINT;\n}\n\n// ─── Error types — caller phân biệt để recovery đúng ─────────────────────────\n\n// id_token sai / hết hạn / email không @nal.vn → KHÔNG retry, prompt avatar login.\nexport class InvalidIdTokenError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"InvalidIdTokenError\";\n }\n}\n\n// Network: timeout / 5xx / DNS / signed URL hết hạn → CÓ thể retry.\nexport class PackNetworkError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PackNetworkError\";\n }\n}\n\n// Response lạ / tarball hỏng / sha mismatch → không retry mù.\nexport class PackParseError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"PackParseError\";\n }\n}\n\nexport interface SignedPackInfo {\n url: string; // signed URL tải tarball (hết hạn ~300s)\n object: string; // vd \"pack-v0.2.0.tar.gz\"\n expiresIn: number;\n requestedBy: string; // email NAL đã verify\n}\n\n// Gọi get-pack lấy signed URL. version undefined → server trả stable mới nhất.\nexport async function fetchSignedPackUrl(\n idToken: string,\n version?: string,\n): Promise<SignedPackInfo> {\n let res: Response;\n try {\n res = await fetch(getPackEndpoint(), {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ id_token: idToken, version }),\n });\n } catch (err) {\n throw new PackNetworkError(\n `Không kết nối được Supabase get-pack: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n // 403 = token/domain reject → InvalidIdToken. 5xx = server → Network.\n if (res.status === 401 || res.status === 403) {\n const detail = await safeErrorText(res);\n throw new InvalidIdTokenError(\n `Supabase từ chối (${res.status}): ${detail}. Chạy 'avatar login' với tài khoản @nal.vn.`,\n );\n }\n if (res.status >= 500) {\n throw new PackNetworkError(`Supabase get-pack lỗi server (${res.status}). Thử lại sau.`);\n }\n if (!res.ok) {\n const detail = await safeErrorText(res);\n throw new PackParseError(`get-pack trả ${res.status}: ${detail}`);\n }\n\n let body: {\n url?: string;\n object?: string;\n expires_in?: number;\n requested_by?: string;\n error?: string;\n };\n try {\n body = (await res.json()) as typeof body;\n } catch {\n throw new PackParseError(\"get-pack trả response không phải JSON.\");\n }\n if (body.error) {\n throw new PackParseError(`get-pack báo lỗi: ${body.error}`);\n }\n if (!body.url || !body.object) {\n throw new PackParseError(\"get-pack thiếu url/object trong response.\");\n }\n return {\n url: body.url,\n object: body.object,\n expiresIn: body.expires_in ?? 0,\n requestedBy: body.requested_by ?? \"\",\n };\n}\n\n// Tải tarball + giải nén vào packDir, atomic swap để không hỏng pack đang có.\n//\n// Đặt temp TRONG thư mục cha của packDir (cùng filesystem) để rename atomic.\n// Bước: tải tarball → giải nén ra <packDir>.new → xóa packDir cũ → rename .new → packDir.\nexport async function downloadAndExtractPack(signedUrl: string, packDir: string): Promise<void> {\n const parent = join(packDir, \"..\");\n await ensureDir(parent);\n\n const tmpTarball = join(parent, `.pack-download-${process.pid}.tar.gz`);\n const tmpExtractDir = `${packDir}.new-${process.pid}`;\n\n try {\n // 1. Tải tarball.\n try {\n await downloadFile(signedUrl, tmpTarball);\n } catch (err) {\n throw new PackNetworkError(\n `Tải tarball thất bại: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n // 2. Giải nén vào temp dir.\n await removeRecursive(tmpExtractDir);\n try {\n await extractTarballToDir(tmpTarball, tmpExtractDir);\n } catch (err) {\n throw new PackParseError(\n `Giải nén tarball thất bại: ${err instanceof Error ? err.message : err}`,\n );\n }\n\n // 3. Swap an toàn: GIỮ pack cũ tới khi pack mới vào xong, tránh mất pack\n // nếu rename fail giữa chừng. Đổi tên cũ → backup, rename mới vào, rồi mới xóa backup.\n const backupDir = `${packDir}.old-${process.pid}`;\n const hadOld = await pathExists(packDir);\n if (hadOld) {\n await fs.rename(packDir, backupDir); // pack cũ → backup (cùng fs, atomic)\n }\n try {\n await fs.rename(tmpExtractDir, packDir); // pack mới vào\n } catch (err) {\n // Rename mới fail → khôi phục pack cũ để workspace không mất pack.\n if (hadOld) {\n await fs.rename(backupDir, packDir).catch(() => {});\n }\n throw err;\n }\n // Mới vào OK → xóa backup.\n if (hadOld) {\n await removeRecursive(backupDir).catch(() => {});\n }\n } finally {\n // Dọn temp (tarball + extract dir nếu rename fail).\n await fs.rm(tmpTarball, { force: true }).catch(() => {});\n await removeRecursive(tmpExtractDir).catch(() => {});\n }\n}\n\nasync function safeErrorText(res: Response): Promise<string> {\n try {\n const body = await res.json();\n return (body as { error?: string }).error ?? JSON.stringify(body).slice(0, 200);\n } catch {\n return (await res.text().catch(() => \"\")).slice(0, 200);\n }\n}\n","// init-success-rendering-and-helpers.ts\n//\n// Render success box sau khi init hoàn thành + các helper nhỏ dùng chung:\n// - resolveWorkspacePath (exported): resolve + handle conflict tên workspace\n// - promptTeamOwner: prompt email team owner với default từ SSO\n// - maybeCommitWorkspace: commit baseline workspace (trừ khi --no-commit)\n// - formatAiStatusLine: format dòng trạng thái AI setup trong success box\n// - formatGitnexusStatusLine: format dòng trạng thái GitNexus trong success box\n// - printInitSuccessBox: in box thành công + cheatsheet commands\n//\n// Tách ra để workspace-scaffold-and-finalize-orchestrator.ts + init-flow-handlers\n// có thể import mà không tạo circular dependency với init.ts (entry point).\n\nimport { join, relative, resolve } from \"node:path\";\nimport { input, select } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport { ensureDir, pathExists } from \"../lib/filesystem-helpers.js\";\nimport { formatPackCommandsCheatsheetBox } from \"../lib/format-pack-commands-cheatsheet-box.js\";\nimport { UserAbortedRecoveryError } from \"../lib/prompt-recovery-action-on-failure.js\";\nimport type { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport type { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { TEAM_PACK_RELATIVE_PATH } from \"../lib/team-pack-submodule-manager.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\nimport {\n findAlternativeWorkspaceName,\n isEmptyOrMissing,\n} from \"./init-conflict-detection-helpers.js\";\n\n// ─── shared utilities ───────\n\n// v1.14.0 SECURITY: Path traversal guard. Workspace name từ user input (CLI flag\n// `--workspace-name` hoặc inquirer prompt) → nếu chứa `..` / `/` / `\\` → có thể\n// resolve ngoài parent dir, ghi settings.json/gitignore vào path attacker-chosen.\n// Block: name có separator hoặc resolve không bắt đầu bằng parent.\nfunction assertWorkspaceNameSafe(parent: string, name: string): void {\n // Reject ngay name chứa path separator hoặc relative parts.\n // Whitelist khuyến nghị: alphanumeric, dash, underscore, dot — không có / \\ ..\n if (/[\\/\\\\]/.test(name) || name === \".\" || name === \"..\" || name.includes(\"..\")) {\n throw new Error(\n `Tên workspace không an toàn (chứa path separator hoặc '..'): \"${name}\".\\nYêu cầu: chỉ chữ/số/dash/underscore/dot, không '/', '\\\\', '..'.`,\n );\n }\n // Defensive: resolve thực tế và assert vẫn nằm trong parent.\n const resolved = resolve(join(parent, name));\n const resolvedParent = resolve(parent);\n // Path resolution phải có root parent là prefix (đảm bảo không escape qua symlink).\n if (!resolved.startsWith(`${resolvedParent}/`) && resolved !== resolvedParent) {\n throw new Error(`Tên workspace \"${name}\" resolve ra ngoài parent dir (path traversal). Block.`);\n }\n}\n\nexport async function resolveWorkspacePath(\n parent: string,\n desiredName: string,\n force?: boolean,\n): Promise<string> {\n // v1.14.0 SECURITY: assert ngay khi entry với name từ CLI flag.\n assertWorkspaceNameSafe(parent, desiredName);\n const desired = join(parent, desiredName);\n if (await isEmptyOrMissing(desired)) return desired;\n\n log.warn(`Workspace path \"${desired}\" đã có nội dung.`);\n\n // Loop cho user chọn alt name / nhập tay / abort.\n // v1.2.5: không throw thẳng khi findAlternative fail hoặc user decline alt.\n while (true) {\n const alternative = await findAlternativeWorkspaceName(parent, desiredName);\n\n if (force && alternative) {\n log.info(`--force: dùng ${alternative}`);\n return alternative;\n }\n\n const choices: Array<{ name: string; value: string }> = [];\n if (alternative) {\n choices.push({ name: `Dùng \"${alternative}\" (suggest)`, value: \"use-alt\" });\n }\n choices.push({ name: \"Nhập tên workspace khác (manual)\", value: \"manual\" });\n choices.push({ name: \"Tạm ngưng init\", value: \"abort\" });\n\n const action = await select({\n message: \"Cách xử lý workspace path conflict?\",\n choices,\n });\n\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\n \"User abort tại bước resolve workspace path. Chạy lại với --workspace-name khác.\",\n );\n }\n if (action === \"use-alt\" && alternative) {\n return alternative;\n }\n // action === \"manual\" → prompt input tên mới + loop verify.\n // v1.14.0 SECURITY: inquirer validate reject path separator + .. ngay tại prompt.\n const newName = await input({\n message: \"Tên workspace mới:\",\n validate: (v) => {\n const trimmed = v.trim();\n if (trimmed.length === 0) return \"Tên không được rỗng\";\n if (\n /[\\/\\\\]/.test(trimmed) ||\n trimmed === \".\" ||\n trimmed === \"..\" ||\n trimmed.includes(\"..\")\n ) {\n return \"Tên không được chứa '/', '\\\\', '..' (path traversal)\";\n }\n return true;\n },\n });\n // Defensive recheck — inquirer validate đã pass nhưng assert lần nữa cho consistency.\n assertWorkspaceNameSafe(parent, newName.trim());\n const newPath = join(parent, newName.trim());\n if (await isEmptyOrMissing(newPath)) return newPath;\n log.warn(`\"${newPath}\" cũng đã có nội dung. Thử tên khác.`);\n // Loop tiếp.\n }\n}\n\nexport async function promptTeamOwner(currentUserEmail: string): Promise<string> {\n return await input({ message: \"Team owner email:\", default: currentUserEmail });\n}\n\n// v3: workspace KHÔNG git → maybeCommitWorkspace BỎ. Tạo workspace folder + báo lỗi\n// rõ ràng nếu không tạo được (trước đây git().init() ngầm gánh việc này).\nexport async function ensureWorkspaceFolder(workspacePath: string): Promise<void> {\n try {\n await ensureDir(workspacePath);\n } catch (err) {\n const e = err as NodeJS.ErrnoException;\n if (e.code === \"EACCES\" || e.code === \"EPERM\") {\n throw new Error(`Không tạo được workspace \"${workspacePath}\": không có quyền ghi.`);\n }\n if (e.code === \"ENAMETOOLONG\") {\n throw new Error(`Đường dẫn workspace quá dài: \"${workspacePath}\".`);\n }\n if (e.code === \"ENOSPC\") {\n throw new Error(\"Hết dung lượng đĩa, không tạo được workspace.\");\n }\n throw new Error(`Không tạo được workspace \"${workspacePath}\": ${e.message}`);\n }\n}\n\n// AI status line tùy theo kết quả runAiSetupPhase (hoặc null nếu --ai-skip).\nexport function formatAiStatusLine(\n aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null,\n): string {\n if (aiResult === null) {\n return ` ${chalk.yellow(\"AI:\")} skipped · ${chalk.cyan(\"avatar ai setup\")} để config sau`;\n }\n if (aiResult.ok) {\n const modelPart = aiResult.model ? ` · model=${aiResult.model}` : \"\";\n return ` ${chalk.green(\"AI:\")} ready · ${aiResult.provider}${modelPart}`;\n }\n return ` ${chalk.yellow(\"AI:\")} failed (${aiResult.reason.slice(0, 60)}) · thử ${chalk.cyan(\"avatar ai setup\")}`;\n}\n\n// v1.4.0: GitNexus status line tương tự pattern formatAiStatusLine.\nexport function formatGitnexusStatusLine(\n result: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null,\n): string {\n if (result === null) {\n return ` ${chalk.yellow(\"GitNexus:\")} skipped · ${chalk.cyan(\"avatar gitnexus install\")} để setup sau`;\n }\n if (result.ok) {\n const parts: string[] = [\"ready\"];\n if (result.analyzed) parts.push(\"indexed\");\n if (result.wikiGenerated) parts.push(\"wiki\");\n if (result.mcpRegistered) parts.push(\"mcp\");\n return ` ${chalk.green(\"GitNexus:\")} ${parts.join(\" · \")}`;\n }\n return ` ${chalk.yellow(\"GitNexus:\")} skipped (${(result.reason ?? \"unknown\").slice(0, 40)}) · thử ${chalk.cyan(\"avatar gitnexus install\")}`;\n}\n\nexport async function printInitSuccessBox(\n rootPath: string,\n aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null = null,\n gitnexusResult: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null = null,\n): Promise<void> {\n const lines: string[] = [\n `${chalk.green(\"✓\")} Workspace sẵn sàng: ${relative(process.cwd(), rootPath) || rootPath}`,\n formatAiStatusLine(aiResult),\n formatGitnexusStatusLine(gitnexusResult),\n \"\",\n ` ${chalk.cyan(`cd ${rootPath}`)}`,\n ` ${chalk.cyan(\"avatar add repo\")} Thêm repo code vào src/`,\n ` ${chalk.cyan(\"claude\")} Mở Claude Code ở workspace root`,\n \"\",\n ` ${chalk.cyan(\"avatar sync\")} Cập nhật team-ai-pack mới`,\n ` ${chalk.cyan(\"avatar uninstall\")} Gỡ Avatar (giữ code)`,\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n\n // v1.9.0: Hiển thị cheatsheet 13 commands cốt lõi từ pack — chỉ khi pack có.\n // Giúp user mới biết ngay những lệnh có thể gõ trong Claude Code session.\n const packDir = join(rootPath, TEAM_PACK_RELATIVE_PATH);\n if (await pathExists(packDir)) {\n process.stdout.write(`\\n${formatPackCommandsCheatsheetBox()}\\n`);\n }\n}\n","// Format cheatsheet box hiển thị 13 slash commands của team-ai-pack sau khi\n// `avatar init` thành công. Mục đích: user mới biết ngay những lệnh cốt lõi\n// có thể gõ trong Claude Code để dùng pack.\n//\n// Descriptions extracted từ team-ai-pack/commands/avatar/*.md frontmatter\n// (v0.4.0 snapshot). Nếu pack đổi description → cheatsheet không tự update.\n// Trade-off: hardcode đơn giản, không phải parse runtime — KISS.\n//\n// Khi nào hiển thị:\n// - Sau printInitSuccessBox khi pack đã sync (có .claude/pack/)\n// - Skip nếu --skip-team-pack (workspace không có pack content)\nimport boxen from \"boxen\";\nimport { chalk } from \"./terminal-logger.js\";\n\n// Cheatsheet entries — mỗi entry = { command, vietnameseDesc }.\n// Order theo workflow tự nhiên: planning → coding → debug → status.\nconst PACK_COMMAND_CHEATSHEET: Array<{ cmd: string; desc: string }> = [\n { cmd: \"/avatar:help\", desc: \"Hướng dẫn sử dụng Avatar — chỉ cần gõ tự nhiên\" },\n { cmd: \"/avatar:brainstorm\", desc: \"Brainstorm ý tưởng cho một tool\" },\n { cmd: \"/avatar:plan\", desc: \"Tạo plan thông minh với prompt enhancement\" },\n { cmd: \"/avatar:scout\", desc: \"Tìm file liên quan trong codebase, tiết kiệm token\" },\n { cmd: \"/avatar:implement\", desc: \"Bắt đầu code & test theo plan có sẵn\" },\n { cmd: \"/avatar:build-full-flow\", desc: \"Implement một tool từng bước (end-to-end)\" },\n { cmd: \"/avatar:fix\", desc: \"Phân tích và fix vấn đề tự điều hướng\" },\n { cmd: \"/avatar:debug\", desc: \"Debug vấn đề kỹ thuật + đưa ra giải pháp\" },\n { cmd: \"/avatar:test\", desc: \"Chạy test trên máy + phân tích báo cáo tổng hợp\" },\n { cmd: \"/avatar:design:good\", desc: \"Tạo thiết kế chỉn chu, sống động\" },\n { cmd: \"/avatar:docs:init\", desc: \"Phân tích codebase + tạo tài liệu khởi đầu\" },\n { cmd: \"/avatar:status\", desc: \"Xem lại thay đổi gần đây + tổng kết công việc\" },\n { cmd: \"/avatar:journal\", desc: \"Ghi nhật ký session\" },\n];\n\n// Build cheatsheet box. Width auto theo command dài nhất.\nexport function formatPackCommandsCheatsheetBox(): string {\n const maxCmdWidth = Math.max(...PACK_COMMAND_CHEATSHEET.map((e) => e.cmd.length));\n\n const header = chalk.bold(\"🎯 Slash commands từ team-ai-pack\");\n const subheader = chalk.dim(\"Gõ trong Claude Code session để gọi capability của pack:\");\n\n const lines = PACK_COMMAND_CHEATSHEET.map((e) => {\n const cmdPadded = chalk.cyan(e.cmd.padEnd(maxCmdWidth));\n return ` ${cmdPadded} ${chalk.dim(e.desc)}`;\n });\n\n const footer = chalk.dim(\n \"Catalog đầy đủ 46 commands: cat .claude/pack/scripts/commands_data.yaml\",\n );\n\n const content = [header, subheader, \"\", ...lines, \"\", footer].join(\"\\n\");\n\n return boxen(content, {\n padding: 1,\n borderStyle: \"round\",\n borderColor: \"cyan\",\n });\n}\n","// Pure helpers for `avatar init` conflict detection and workspace path\n// resolution. Extracted from init.ts so they're unit-testable without\n// triggering inquirer prompts or git operations.\n\nimport { readdir } from \"node:fs/promises\";\nimport { join } from \"node:path\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { AVATAR_MANAGED_PATHS } from \"../lib/project-tree-scaffolder.js\";\n\n// True if path doesn't exist OR is an empty directory (ignoring dotfiles and\n// Windows Thumbs.db noise). Safe to scaffold into. macOS .DS_Store and\n// .localized are already filtered by the dotfile rule.\nexport async function isEmptyOrMissing(path: string): Promise<boolean> {\n if (!(await pathExists(path))) return true;\n try {\n const entries = await readdir(path);\n const meaningful = entries.filter((e) => !e.startsWith(\".\") && e !== \"Thumbs.db\");\n return meaningful.length === 0;\n } catch {\n return false;\n }\n}\n\n// Return Avatar-managed top-level paths that already exist in projectRoot.\n// Caller decides whether to abort, prompt, or auto-backup.\nexport async function detectAvatarConflicts(projectRoot: string): Promise<string[]> {\n const found: string[] = [];\n for (const rel of AVATAR_MANAGED_PATHS) {\n if (await pathExists(join(projectRoot, rel))) found.push(rel);\n }\n return found;\n}\n\n// Find first numbered alternative path (e.g. \"foo-2\", \"foo-3\", ...) under\n// `parent` that is empty or missing. Returns null if exhausted.\n// maxAttempts defaults to 10; if a user has 9+ workspaces with the same\n// base name, something else is wrong.\nexport async function findAlternativeWorkspaceName(\n parent: string,\n desiredName: string,\n maxAttempts = 10,\n): Promise<string | null> {\n for (let i = 2; i < maxAttempts; i++) {\n const candidate = join(parent, `${desiredName}-${i}`);\n if (await isEmptyOrMissing(candidate)) return candidate;\n }\n return null;\n}\n","import boxen from \"boxen\";\n// `avatar login [--reset]` — Command 01 spec.\n// Implements the user-facing flow: announce verification URL, open browser,\n// poll Google until token returned, validate domain, persist credentials.\nimport type { Command } from \"commander\";\nimport open from \"open\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { printAvatarBanner } from \"../lib/avatar-ascii-banner.js\";\nimport {\n buildUserConfig,\n buildVerificationUrl,\n decodeIdToken,\n pollForToken,\n requestDeviceCode,\n revokeToken,\n verifyHostedDomain,\n} from \"../lib/google-oauth-device-flow.js\";\nimport { chalk, log, spinner } from \"../lib/terminal-logger.js\";\nimport {\n USER_CONFIG_PATH,\n clearUserConfig,\n isTokenExpired,\n readUserConfig,\n writeUserConfig,\n} from \"../lib/user-config-store.js\";\n\nexport function registerLoginCommand(program: Command): void {\n program\n .command(\"login\")\n .description(\"Đăng nhập Google SSO (workspace @nal.vn)\")\n .option(\"--reset\", \"Xóa credential cũ và đăng nhập lại\")\n .action(async (opts: { reset?: boolean }) => {\n try {\n await runLogin(opts);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\n// Exported để các command khác (vd init) reuse login flow khi user chưa login.\n// Throw nếu login fail, hoặc return void khi success (user config đã write xong).\nexport async function runLogin(opts: { reset?: boolean }): Promise<void> {\n // Banner trước khi vào device-code flow để user nhận diện thương hiệu.\n printAvatarBanner({ tagline: \"Đăng nhập Google SSO · workspace @nal.vn\" });\n\n // Step 1 of spec: short-circuit if already logged in and token is still good.\n if (opts.reset) {\n await clearUserConfig();\n await appendAuditEntry(\"login_reset\");\n } else {\n const existing = await readUserConfig();\n if (existing && !isTokenExpired(existing)) {\n log.success(`Đã đăng nhập: ${existing.email}`);\n return;\n }\n }\n\n // Step 2: request device + user code.\n const deviceSpinner = spinner(\"Đang yêu cầu device code từ Google...\");\n let deviceCode: Awaited<ReturnType<typeof requestDeviceCode>>;\n try {\n deviceCode = await requestDeviceCode();\n deviceSpinner.succeed(\"Nhận device code\");\n } catch (err) {\n deviceSpinner.fail(\"Không kết nối được Google\");\n throw err;\n }\n\n // Step 3: display instructions to user.\n const verificationUrl = buildVerificationUrl(deviceCode);\n const instructions = [\n `1. Truy cập: ${chalk.cyan(deviceCode.verification_url)}`,\n `2. Nhập code: ${chalk.bold.yellow(deviceCode.user_code)}`,\n \"\",\n `Hoặc Avatar tự mở browser, click ${chalk.green(\"Allow\")}...`,\n ].join(\"\\n\");\n process.stdout.write(`${boxen(instructions, { padding: 1, borderStyle: \"round\" })}\\n`);\n\n // Step 4: open browser. Failure here is non-fatal — user can copy URL manually.\n void open(verificationUrl).catch(() => {\n log.dim(\"(Không mở được browser tự động — copy URL ở trên)\");\n });\n\n // Step 5: poll token endpoint until success or expiry.\n const waitSpinner = spinner(\"Đang chờ xác nhận trong browser...\");\n const intervalMs = deviceCode.interval * 1000;\n const deadline = Date.now() + deviceCode.expires_in * 1000;\n\n let token = null;\n while (Date.now() < deadline) {\n await sleep(intervalMs);\n try {\n token = await pollForToken(deviceCode.device_code);\n if (token) break;\n } catch (err) {\n waitSpinner.fail(\"Xác thực thất bại\");\n throw err;\n }\n }\n if (!token) {\n waitSpinner.fail(\"Hết hạn chờ (5 phút). Chạy lại 'avatar login'.\");\n process.exit(1);\n }\n waitSpinner.succeed(\"Đã nhận token từ Google\");\n\n // Step 6: verify hosted domain. Revoke token if claim is wrong.\n const claims = decodeIdToken(token.id_token);\n try {\n verifyHostedDomain(claims);\n } catch (err) {\n await revokeToken(token.access_token);\n throw err;\n }\n\n // Step 7: persist credentials with chmod 600.\n const userConfig = buildUserConfig(token, claims);\n await writeUserConfig(userConfig);\n await appendAuditEntry(\"login\", userConfig.email);\n\n log.success(`Xác thực thành công: ${userConfig.email}`);\n log.success(`Verify hosted domain: ${claims.hd} ✓`);\n log.success(`Lưu credential vào ${USER_CONFIG_PATH} (chmod 600)`);\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// workspace-scaffold-and-finalize-orchestrator.ts\n//\n// v3 THIN INIT: workspace KHÔNG git. Scaffold = tạo folder + cài pack tarball +\n// symlink + settings + AI setup + GitNexus(install+MCP, KHÔNG analyze — src/ rỗng).\n//\n// Bỏ HẾT so với v2: git().init(), submodule add src/, installGitHook (post-merge/\n// pre-push), maybeCommitWorkspace, maybeCreateWorkspaceRemote. Repo code cài sau\n// bằng `avatar add repo` (Phase 05).\nimport { join } from \"node:path\";\nimport { addTeamPackSubmoduleWithRetryOnNetworkFail } from \"../lib/add-team-pack-submodule-with-retry-on-network-fail.js\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { ensureDir, pathExists } from \"../lib/filesystem-helpers.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport {\n createClaudeDirTree,\n writeClaudeGitignore,\n writeProjectKnowledgeFiles,\n writeProjectSettings,\n writeRootClaudeMd,\n} from \"../lib/project-tree-scaffolder.js\";\nimport { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { setupDefaultToolsOnInit } from \"../lib/setup-default-tools-on-init.js\";\nimport { syncAllMountDirs } from \"../lib/symlink-farm-for-team-pack-mount-dirs.js\";\nimport { TEAM_PACK_RELATIVE_PATH } from \"../lib/team-pack-submodule-manager.js\";\nimport { log, spinner } from \"../lib/terminal-logger.js\";\nimport { buildScaffoldVariables } from \"./init-scaffold-variable-builders.js\";\nimport {\n ensureWorkspaceFolder,\n printInitSuccessBox,\n} from \"./init-success-rendering-and-helpers.js\";\n\n// Scaffold workspace v3 (1 flow duy nhất). Tạo folder rỗng + cài pack + môi trường.\nexport async function scaffoldThinWorkspace(args: {\n workspacePath: string;\n workspaceName: string;\n teamOwner: string;\n description: string;\n packVersion?: string;\n autoYes?: boolean;\n skipTeamPack?: boolean;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n}): Promise<void> {\n // 1. Tạo workspace folder + src/ rỗng + notes/ (KHÔNG git init).\n await ensureWorkspaceFolder(args.workspacePath);\n await ensureDir(join(args.workspacePath, \"src\"));\n await ensureDir(join(args.workspacePath, \"notes\"));\n\n // 2. Cài team-ai-pack tarball.\n let pinnedTag = \"(skipped)\";\n if (!args.skipTeamPack) {\n const sp = spinner(\"Tải team-ai-pack từ Supabase...\");\n sp.stop(); // dừng spinner để menu recovery (nếu cần) không bị đè.\n const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(\n args.workspacePath,\n args.packVersion,\n );\n pinnedTag = result.pinnedTag ?? \"(skipped)\";\n if (!result.skipped) log.success(`team-ai-pack: ${pinnedTag}`);\n } else {\n log.dim(\"Skip team-ai-pack (--skip-team-pack).\");\n }\n\n // 3. Scaffold files (CLAUDE.md, settings, knowledge, gitignore).\n const vars = buildScaffoldVariables({\n projectName: args.workspaceName,\n projectDescription: args.description,\n teamOwner: args.teamOwner,\n packVersion: pinnedTag,\n });\n await createClaudeDirTree(args.workspacePath);\n await writeProjectKnowledgeFiles(args.workspacePath, vars);\n await writeRootClaudeMd(args.workspacePath, vars);\n await writeProjectSettings(args.workspacePath, vars);\n await writeClaudeGitignore(args.workspacePath);\n\n // 4. Auto-sync pack content → symlink farm + settings merge + default tools.\n await autoSyncPackOnInit(args.workspacePath, args.autoYes);\n\n await appendAuditEntry(\"init\", `workspace=${args.workspaceName}`);\n\n // 5. AI setup phase.\n let aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null = null;\n if (args.aiSkip) {\n log.dim(\"Bỏ qua AI setup (--ai-skip). Setup sau: avatar ai setup\");\n } else {\n aiResult = await runAiSetupPhase({ workspacePath: args.workspacePath });\n }\n\n // 6. GitNexus: install + MCP register THÔI. KHÔNG analyze (src/ rỗng — index per-repo\n // chạy khi `avatar add repo`). Auto-skip nếu --ai-skip (wiki cần API key từ AI phase).\n let gitnexusResult: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null = null;\n const skipGitnexus = args.aiSkip || args.gitnexusSkip;\n if (skipGitnexus) {\n log.dim(\n args.gitnexusSkip\n ? \"Bỏ qua GitNexus (--gitnexus-skip). Setup sau: avatar gitnexus install\"\n : \"Bỏ qua GitNexus (auto-skip do --ai-skip).\",\n );\n } else {\n gitnexusResult = await runGitnexusSetupPhase({\n workspacePath: args.workspacePath,\n skipAnalyze: true, // v3: init không analyze workspace rỗng.\n });\n }\n\n await printInitSuccessBox(args.workspacePath, aiResult, gitnexusResult);\n}\n\n// Auto-sync pack content vào convention paths Claude Code scan (symlink + settings).\nexport async function autoSyncPackOnInit(workspacePath: string, autoYes?: boolean): Promise<void> {\n const packDir = join(workspacePath, TEAM_PACK_RELATIVE_PATH);\n if (!(await pathExists(packDir))) {\n log.dim(\"Pack chưa cài (skip auto-sync). Chạy `avatar sync` sau.\");\n return;\n }\n\n const claudeDir = join(workspacePath, \".claude\");\n log.info(\"Auto-sync pack content vào .claude/ (symlinks + settings)...\");\n\n try {\n const results = await syncAllMountDirs(packDir, claudeDir, false);\n const created = results.filter((r) => r.action === \"created\" || r.action === \"updated\").length;\n const missing = results.filter((r) => r.action === \"source-missing\").length;\n log.success(\n ` ✓ Symlinks: ${created} created${missing > 0 ? `, ${missing} source-missing` : \"\"}`,\n );\n\n const mergeResult = await mergePackSettingsIntoProjectSettings(workspacePath);\n if (mergeResult.action === \"merged\") {\n log.success(` ✓ settings.json merged (${mergeResult.changes.join(\"; \")})`);\n }\n\n await setupDefaultToolsOnInit(workspacePath, { autoYes });\n } catch (err) {\n log.warn(\n `Auto-sync pack fail: ${err instanceof Error ? err.message : err}. Chạy \\`avatar sync\\` để retry.`,\n );\n }\n}\n","// add-team-pack-submodule-with-retry-on-network-fail.ts\n//\n// v3: Wrapper retry cho addTeamPackSubmodule (giờ tải tarball, không git submodule).\n// Phân loại lỗi từ downloader:\n// - InvalidIdTokenError → token sai/hết hạn/sai domain → KHÔNG retry, prompt avatar login\n// - PackNetworkError → mạng/Supabase down/signed URL hết hạn → retry backoff\n// - PackParseError → tarball hỏng → menu retry/skip/abort\n//\n// GIỮ tên file + chữ ký export (init-flow-handlers + scaffold-orchestrator import).\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.js\";\nimport { InvalidIdTokenError, PackNetworkError } from \"./supabase-pack-downloader.js\";\nimport { TeamPackAccessAbortedError, addTeamPackSubmodule } from \"./team-pack-submodule-manager.js\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface AddTeamPackResult {\n pinnedTag: string | null;\n skipped: boolean;\n}\n\nconst MAX_NETWORK_RETRIES = 3;\n\n// Wrap addTeamPackSubmodule với retry. Chữ ký giữ nguyên cho caller cũ.\n// (ssoEmail + latest từ v2 không còn dùng — giữ param để không vỡ call site, ignore.)\nexport async function addTeamPackSubmoduleWithRetryOnNetworkFail(\n projectRoot: string,\n version?: string,\n _ssoEmail?: string,\n _latest = false,\n): Promise<AddTeamPackResult> {\n let networkAttempt = 0;\n\n while (true) {\n try {\n const result = await addTeamPackSubmodule(projectRoot, version);\n return { pinnedTag: result.pinnedTag, skipped: false };\n } catch (err) {\n if (err instanceof TeamPackAccessAbortedError) throw err;\n\n // Token sai/hết hạn → KHÔNG retry (retry vô ích, cần login lại).\n if (err instanceof InvalidIdTokenError) {\n log.error(err.message);\n const action = await promptRetryOrSkip({\n taskName: \"Tải team-ai-pack (xác thực NAL)\",\n reason: err.message,\n allowSkip: true,\n hint: \"Chạy 'avatar login' với tài khoản @nal.vn rồi thử lại. Hoặc skip + 'avatar sync' sau.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước xác thực tải pack.\");\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng chưa có pack. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n continue; // retry-same: user đã login lại → thử lại.\n }\n\n // Mạng → retry tự động backoff vài lần, sau đó hỏi user.\n if (err instanceof PackNetworkError && networkAttempt < MAX_NETWORK_RETRIES) {\n networkAttempt += 1;\n log.warn(\n `Tải pack lỗi mạng (lần ${networkAttempt}/${MAX_NETWORK_RETRIES}): ${err.message}`,\n );\n await sleep(1000 * networkAttempt); // backoff tuyến tính 1s/2s/3s.\n continue;\n }\n\n // Hết retry mạng HOẶC lỗi khác (parse) → menu retry/skip/abort.\n const message = err instanceof Error ? err.message : String(err);\n const action = await promptRetryOrSkip({\n taskName: \"Tải team-ai-pack tarball\",\n reason: message,\n allowSkip: true,\n hint: \"Network glitch? Retry thường work. Skip → dùng `avatar sync` sau.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước tải team-ai-pack.\");\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng chưa có pack. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n networkAttempt = 0; // reset đếm sau khi user chủ động retry.\n }\n }\n}\n\nfunction sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n}\n","// setup-default-tools-on-init.ts\n//\n// Bước init: hỏi user có bật các default tool (đọc từ pack tools/defaults.json)\n// không, rồi enable cái được đồng ý. Mặc định YES.\n//\n// - Interactive: hỏi \"Do you want to setup <tool> (y/n)\" default yes mỗi tool.\n// - Non-interactive (autoYes / CI): tự động yes (giữ default bật), không hỏi.\n// - Fail-mềm: pack chưa có tools/ → defaults rỗng → no-op im lặng.\n\nimport { confirm } from \"@inquirer/prompts\";\nimport { readDefaultTools } from \"./discover-pack-tools-and-defaults.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { enableToolByName } from \"./tool-enable-disable-orchestrator.js\";\n\n// Tên hiển thị thân thiện cho prompt (fallback = tên tool).\nconst DISPLAY_NAME: Record<string, string> = {\n \"prompt-scoring\": \"prompt scoring\",\n};\n\nexport async function setupDefaultToolsOnInit(\n workspacePath: string,\n opts: { autoYes?: boolean } = {},\n): Promise<void> {\n const defaults = await readDefaultTools(workspacePath);\n if (defaults.length === 0) {\n return; // pack chưa có tools/defaults.json → no-op.\n }\n\n for (const name of defaults) {\n const display = DISPLAY_NAME[name] ?? name;\n let wantEnable: boolean;\n if (opts.autoYes) {\n wantEnable = true; // CI / --yes: giữ default bật, không hỏi.\n } else {\n wantEnable = await confirm({\n message: `Do you want to setup ${display} (y/n)`,\n default: true,\n });\n }\n if (!wantEnable) {\n log.dim(` Skip tool '${name}' (user opt-out).`);\n continue;\n }\n // silent: tool default có thể chưa có trong pack cũ → không spam warning.\n await enableToolByName(workspacePath, name, { silent: true });\n }\n}\n","// discover-pack-tools-and-defaults.ts\n//\n// Quét pack/tools/ để biết tool nào CÓ SẴN + đọc danh sách tool init bật\n// mặc định (tools/defaults.json). Đọc default từ pack (không hardcode trong CLI)\n// → đổi default không cần release CLI mới.\n\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\nimport { listEnabledTools } from \"./tool-state-store.js\";\n\nconst PACK_FEATURES_REL = [\".claude\", \"pack\", \"tools\"];\n\nfunction packToolsDir(workspacePath: string): string {\n return join(workspacePath, ...PACK_FEATURES_REL);\n}\n\n// Liệt kê tool có manifest trong pack (mỗi dir con có tool.json).\nexport async function listAvailableTools(workspacePath: string): Promise<string[]> {\n const dir = packToolsDir(workspacePath);\n if (!(await pathExists(dir))) {\n return [];\n }\n const entries = await fs.readdir(dir, { withFileTypes: true });\n const names: string[] = [];\n for (const entry of entries) {\n if (!entry.isDirectory()) continue;\n if (await pathExists(join(dir, entry.name, \"tool.json\"))) {\n names.push(entry.name);\n }\n }\n return names.sort();\n}\n\ninterface DefaultsFile {\n defaultTools?: string[];\n}\n\n// Đọc tools/defaults.json → danh sách tool init bật mặc định. Thiếu → [].\nexport async function readDefaultTools(workspacePath: string): Promise<string[]> {\n const path = join(packToolsDir(workspacePath), \"defaults.json\");\n if (!(await pathExists(path))) {\n return [];\n }\n try {\n const parsed = await readJson<DefaultsFile>(path);\n return parsed.defaultTools ?? [];\n } catch {\n return [];\n }\n}\n\nexport interface ToolsOverview {\n available: string[]; // có manifest trong pack\n enabled: string[]; // enabled=true trong state\n}\n\n// Tổng hợp cho status/doctor: tool có sẵn (pack) + đang bật (state).\nexport async function discoverEnabledAndAvailableTools(\n workspacePath: string,\n): Promise<ToolsOverview> {\n const [available, enabled] = await Promise.all([\n listAvailableTools(workspacePath),\n listEnabledTools(workspacePath),\n ]);\n return { available, enabled };\n}\n","// tool-enable-disable-orchestrator.ts\n//\n// Orchestrate 1 lần enable/disable tool: đọc manifest → (kiểm runtime) → apply\n// vào settings.json → cập nhật avatar-tools.json → log. Tách khỏi command để\n// init/sync tái dùng đúng path enable (DRY).\n//\n// Fail-mềm: pack chưa có tools/ (version skew) → log warn + return false,\n// KHÔNG throw (init không vỡ vì pack cũ).\n\nimport { spawnSync } from \"node:child_process\";\nimport {\n type ApplyToolResult,\n disableTool,\n enableTool,\n readToolManifest,\n} from \"./apply-tool-manifest-to-settings.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { readToolState, setToolState } from \"./tool-state-store.js\";\n\n// Kiểm runtime manifest.requires (vd python3) có trên PATH. Chỉ cảnh báo, không chặn.\nfunction warnIfRuntimeMissing(runtime?: string): void {\n if (!runtime) return;\n const probe = spawnSync(runtime, [\"--version\"], { stdio: \"ignore\" });\n if (probe.status !== 0 || probe.error) {\n log.warn(\n `Tool yêu cầu runtime '${runtime}' nhưng không tìm thấy trên PATH. ` +\n `Hook có thể fail khi chạy. Cài '${runtime}' rồi thử lại.`,\n );\n }\n}\n\nfunction logApplyResult(name: string, result: ApplyToolResult): void {\n switch (result.action) {\n case \"enabled\":\n log.success(`Tool '${name}' enabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"disabled\":\n log.success(`Tool '${name}' disabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"no-change\":\n log.dim(`Tool '${name}': settings.json đã đúng trạng thái, không thay đổi.`);\n break;\n case \"no-manifest\":\n break; // handled by caller\n }\n}\n\n// Enable 1 tool. Trả về true nếu manifest tồn tại (đã xử lý), false nếu pack\n// chưa hỗ trợ (fail-mềm). `silent` = không in dòng \"not supported\" (dùng cho init\n// khi tool default có thể chưa có trong pack cũ).\nexport async function enableToolByName(\n workspacePath: string,\n name: string,\n opts: { silent?: boolean } = {},\n): Promise<boolean> {\n const manifest = await readToolManifest(workspacePath, name);\n if (!manifest) {\n if (!opts.silent) {\n log.warn(\n `Pack version hiện tại chưa hỗ trợ tool '${name}' (thiếu .claude/pack/tools/${name}/tool.json). Chạy 'avatar sync' để pull pack mới, hoặc kiểm tên tool.`,\n );\n }\n return false;\n }\n warnIfRuntimeMissing(manifest.requires?.runtime);\n const result = await enableTool(workspacePath, manifest);\n logApplyResult(name, result);\n await setToolState(workspacePath, name, { enabled: true, version: manifest.version });\n return true;\n}\n\n// Disable 1 tool. Đọc manifest để biết CHÍNH XÁC entry nào của tool.\nexport async function disableToolByName(workspacePath: string, name: string): Promise<boolean> {\n const manifest = await readToolManifest(workspacePath, name);\n if (!manifest) {\n log.warn(\n `Không tìm thấy manifest tool '${name}' để biết entry nào cần rút. Nếu pack đã đổi, state vẫn được set disabled nhưng settings.json có thể còn sót entry.`,\n );\n // Vẫn set state disabled để phản ánh ý định; settings không đổi (không có manifest).\n const state = await readToolState(workspacePath);\n const prevVersion = state.tools[name]?.version ?? \"unknown\";\n await setToolState(workspacePath, name, { enabled: false, version: prevVersion });\n return false;\n }\n const result = await disableTool(workspacePath, manifest);\n logApplyResult(name, result);\n await setToolState(workspacePath, name, { enabled: false, version: manifest.version });\n return true;\n}\n","// Create symlink farm for team-ai-pack mount dirs.\n//\n// Links project/.claude/<dir> → project/.claude/pack/<dir> for each of:\n// skills, agents, commands, hooks, workflows, scripts.\n//\n// Why symlinks vs copy: Claude Code reads .claude/{skills,agents,...} by\n// convention. Submodule lives at .claude/pack/. Symlinks let Claude see the\n// pack content at expected paths without duplication. When pack updates, all\n// projects pick up changes via the symlink.\n//\n// Conflict handling: if .claude/<dir> exists as a real directory (not symlink),\n// caller must pass force=true to backup and replace. Otherwise we return a\n// conflict report and let CLI prompt the user.\nimport { promises as fs } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\n\nimport { backupDirBeforeReplace } from \"./backup-existing-dir-before-symlink-override.js\";\nimport { pathExists } from \"./filesystem-helpers.js\";\n\n// Mount dirs: Claude Code convention paths + Avatar-specific extras.\n// - skills/agents/commands/hooks → Claude Code scan tự động\n// - workflows/scripts → Avatar workflow refs + hook companion scripts\n// - knowledge → docs team-shared (coding standards, security policy...) — Claude Code\n// không auto-scan nhưng CLAUDE.md + agents reference được qua .claude/knowledge/\nexport const TEAM_PACK_MOUNT_DIRS = [\n \"skills\",\n \"agents\",\n \"commands\",\n \"hooks\",\n \"workflows\",\n \"scripts\",\n \"knowledge\",\n] as const;\n\nexport interface SyncMountedDirResult {\n dir: string;\n action: \"created\" | \"updated\" | \"skipped-conflict\" | \"backed-up-and-linked\" | \"source-missing\";\n backupPath?: string;\n}\n\n// Check whether a path is an existing symlink. fs.lstat doesn't follow symlinks.\nasync function isSymbolicLink(path: string): Promise<boolean> {\n try {\n const st = await fs.lstat(path);\n return st.isSymbolicLink();\n } catch {\n return false;\n }\n}\n\n// Sync one mount dir: pack/<dir> → .claude/<dir> (relative symlink).\n//\n// Cases:\n// 1. source doesn't exist → action: source-missing\n// 2. dest doesn't exist → create symlink, action: created\n// 3. dest is existing symlink → recreate (idempotent), action: updated\n// 4. dest is real dir, !force → action: skipped-conflict (caller prompts user)\n// 5. dest is real dir, force → backup + create symlink, action: backed-up-and-linked\nexport async function syncMountedDir(\n source: string,\n dest: string,\n force: boolean,\n): Promise<SyncMountedDirResult> {\n const dir = relative(dirname(dest), dest) || dest;\n\n if (!(await pathExists(source))) {\n return { dir, action: \"source-missing\" };\n }\n\n if (await pathExists(dest)) {\n if (await isSymbolicLink(dest)) {\n await fs.unlink(dest);\n } else if (force) {\n const backupPath = await backupDirBeforeReplace(dest);\n const relativeSource = relative(dirname(dest), source);\n await fs.symlink(relativeSource, dest);\n return { dir, action: \"backed-up-and-linked\", backupPath };\n } else {\n return { dir, action: \"skipped-conflict\" };\n }\n }\n\n const relativeSource = relative(dirname(dest), source);\n await fs.symlink(relativeSource, dest);\n return { dir, action: \"created\" };\n}\n\n// Sync all configured mount dirs. Returns aggregated results.\nexport async function syncAllMountDirs(\n packDir: string,\n claudeDir: string,\n force: boolean,\n): Promise<SyncMountedDirResult[]> {\n const results: SyncMountedDirResult[] = [];\n for (const dir of TEAM_PACK_MOUNT_DIRS) {\n const source = join(packDir, dir);\n const dest = join(claudeDir, dir);\n results.push(await syncMountedDir(source, dest, force));\n }\n return results;\n}\n","// Backup an existing real directory before replacing it with a symlink.\n//\n// Use case: avatar sync --force needs to override .claude/skills/ (which user\n// may have manually populated) with a symlink to .claude/pack/skills/. We must\n// NOT silently delete user content — instead rename to a timestamped backup so\n// they can restore.\n//\n// Backup naming: <dir>.backup-YYYY-MM-DD-HHMM\nimport { promises as fs } from \"node:fs\";\n\n// Build timestamp suffix in local time. Format: YYYY-MM-DD-HHMM\nfunction timestamp(): string {\n const d = new Date();\n const pad = (n: number) => n.toString().padStart(2, \"0\");\n return (\n `${d.getFullYear()}-${pad(d.getMonth() + 1)}-${pad(d.getDate())}` +\n `-${pad(d.getHours())}${pad(d.getMinutes())}`\n );\n}\n\n// Rename targetPath to \"<targetPath>.backup-<timestamp>\". Returns backup path.\n// Caller is responsible for any subsequent symlink/dir creation at targetPath.\nexport async function backupDirBeforeReplace(targetPath: string): Promise<string> {\n const backupPath = `${targetPath}.backup-${timestamp()}`;\n await fs.rename(targetPath, backupPath);\n return backupPath;\n}\n","// Single source of truth cho CLI version — đọc runtime từ package.json.\n//\n// Lý do: trước đây version hardcode ở nhiều file (index.ts, status.ts,\n// init-scaffold-variable-builders.ts, print-welcome-screen.ts) → drift bug khi\n// bump version chỉ ở 1 chỗ, các chỗ khác in version cũ.\n//\n// Layout runtime:\n// bin/avatar.js → dist/index.js (bundled) → package.json ở \"../package.json\"\n// từ dist/. Dev mode (vitest) → src/lib/*.ts → package.json ở \"../../package.json\".\n// Caller dùng helper này không cần biết path, chỉ gọi readCliVersion().\n\nimport { readFileSync } from \"node:fs\";\nimport { dirname, resolve } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n// Cache để tránh đọc disk mỗi lần gọi (CLI có thể gọi nhiều lần trong 1 process).\nlet cachedVersion: string | null = null;\n\nexport function readCliVersion(): string {\n if (cachedVersion !== null) return cachedVersion;\n\n // Tìm package.json bằng cách đi ngược lên từ vị trí file này.\n // Production (bundled): dist/index.js → ../package.json\n // Dev (vitest): src/lib/this-file.ts → ../../package.json\n // Walk up tối đa 5 level để robust với cả 2 layout:\n // - Bundled (production): dist/index.js → ../package.json (i=1)\n // - Dev (vitest/tsx): src/lib/*.ts → ../../package.json (i=2)\n // i=0 (cùng dir) cover trường hợp file move ngang sang root tương lai.\n const here = dirname(fileURLToPath(import.meta.url));\n for (let i = 0; i < 5; i++) {\n const candidate = resolve(here, ...Array(i).fill(\"..\"), \"package.json\");\n try {\n const raw = readFileSync(candidate, \"utf8\");\n const pkg = JSON.parse(raw) as { name?: string; version?: string };\n // Verify đúng package (tránh nhặt nhầm package.json của repo cha).\n if (pkg.name === \"@nalvietnam/avatar-cli\" && typeof pkg.version === \"string\") {\n cachedVersion = pkg.version;\n return cachedVersion;\n }\n } catch {\n // File không tồn tại hoặc JSON invalid → thử level tiếp theo.\n }\n }\n\n // Fallback: nếu không tìm thấy (case edge — symlink lạ, sandbox), trả \"unknown\"\n // thay vì throw. Status command vẫn hoạt động, chỉ in \"unknown\" thay vì version.\n cachedVersion = \"unknown\";\n return cachedVersion;\n}\n","// Pure transformation helpers used by `avatar init` to derive project names,\n// workspace names from git URLs, and build the ScaffoldVariables struct for\n// template rendering. Extracted from init.ts for testability — no IO,\n// no prompts, no git calls.\n\nimport type { ScaffoldVariables } from \"../lib/project-tree-scaffolder.js\";\nimport { readCliVersion } from \"../lib/read-cli-version-from-package-json.js\";\n\n// v1.4.0: Render GitNexus section content cho CLAUDE.md template.\n// Trả empty string nếu user skip GitNexus → template không có section dangling.\n// Strong instruction Q4 confirmed: \"soft\" wording — Claude tự action khi thấy\n// staleness hint từ MCP resource.\nexport function buildGitnexusSection(gitnexusReady: boolean): string {\n if (!gitnexusReady) return \"\";\n return `\n### 🧠 CODEBASE INTELLIGENCE — GitNexus\n\nWorkspace có GitNexus index tại \\`.gitnexus/\\` cung cấp architectural awareness\ncho Claude Code (impact analysis, call chains, blast radius).\n\n**Hướng dẫn cho Claude:**\n\n- Khi cần hiểu codebase architecture (vd: trước refactor, debug call chain) →\n query GitNexus MCP resource \\`gitnexus://repo/<name>/context\\`\n- Khi resource trả staleness hint \"⚠️ Index is N commit(s) behind HEAD\" →\n TỰ chạy \\`gitnexus analyze .\\` (trong workspace root) trước khi tiếp tục task\n- Sau khi \\`gitnexus analyze\\` xong: **KHÔNG tự chạy \\`gitnexus wiki\\`**\n (cost cao, có thể block session 2-5 phút) — user sẽ chạy manual khi cần\n\n**Folders Claude scan auto cho skills:**\n\n- \\`~/.claude/skills/gitnexus-*/\\` — GitNexus global skills (exploring, debugging, ...)\n- \\`.claude/pack/skills/\\` — NAL team-shared skills (qua team-ai-pack)\n- Cả 2 đều được scan, không xung đột (different naming prefix)\n\n**Manual wiki update:**\n\nKhi user cần regenerate wiki sau refactor lớn — chạy:\n\n\\`\\`\\`bash\ngitnexus wiki . --api-key <key> --base-url <url>\n\\`\\`\\`\n`;\n}\n\n// Build the template-rendering variable bag. lastScan stamps \"now\" — tests\n// that need a deterministic value should freeze time via vi.useFakeTimers().\n// v1.4.0: gitnexusReady flag controls render GitNexus section vào CLAUDE.md.\n// v3: bỏ `mode` (InitMode internal/client/library dead sau gộp 1 flow).\nexport function buildScaffoldVariables(args: {\n projectName: string;\n projectDescription: string;\n teamOwner: string;\n packVersion: string;\n gitnexusReady?: boolean;\n}): ScaffoldVariables {\n return {\n projectName: args.projectName,\n projectDescription: args.projectDescription,\n teamOwner: args.teamOwner,\n avatarVersion: readCliVersion(),\n packVersion: args.packVersion,\n lastScan: new Date().toISOString(),\n gitnexusSection: buildGitnexusSection(args.gitnexusReady ?? false),\n };\n}\n","// Shared stub action for commands wired into commander but not yet implemented.\n// Prints a consistent message + exit code so users know it's coming.\nimport { chalk } from \"./terminal-logger.js\";\n\nexport function notImplementedYet(commandName: string, milestone?: string): () => void {\n return () => {\n process.stdout.write(\n `${chalk.yellow(\"⏳\")} ${chalk.bold(`avatar ${commandName}`)} — chưa implement ở milestone hiện tại.\\n`,\n );\n if (milestone) {\n process.stdout.write(` Dự kiến: ${chalk.cyan(milestone)}\\n`);\n }\n process.stdout.write(\" Spec đã có trong avatar-cli-implementation_4.html.\\n\");\n process.exit(0);\n };\n}\n","// `avatar mcp-run <tool-id>` — hidden command from Chapter 13 roadmap.\n// Wrapper used by ~/.claude.json entries: Avatar injects secrets from keychain\n// then spawns the underlying MCP process with stdio piped to Claude Code.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerMcpRunCommand(program: Command): void {\n program\n .command(\"mcp-run <tool-id>\", { hidden: true })\n .description(\"[internal] Spawn MCP với secrets injected (M09)\")\n .action(notImplementedYet(\"mcp-run\", \"Milestone 09\"));\n}\n","// `avatar pack status` — hiển thị version team-ai-pack đang cài.\n//\n// v3: pack là tarball (không git). Không còn so sánh tag remote (chưa có endpoint\n// list-versions). Chỉ show version từ manifest + gợi ý `avatar sync` để cập nhật.\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { readPinnedPackVersion } from \"../lib/team-pack-submodule-manager.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\n\nconst PACK_RELATIVE_PATH = \".claude/pack\";\n\nexport function registerPackCommand(program: Command): void {\n const pack = program.command(\"pack\").description(\"Quản lý team-ai-pack (status, ...)\");\n\n pack\n .command(\"status\")\n .description(\"Hiển thị version pack đang cài\")\n .option(\"--json\", \"Output JSON cho script\")\n .action(async (opts: { json?: boolean }) => {\n try {\n const snap = await gatherPackStatus(process.cwd());\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(snap, null, 2)}\\n`);\n } else {\n renderPackStatus(snap);\n }\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\ninterface PackStatus {\n installed: boolean;\n currentVersion: string | null; // null khi chưa cài\n}\n\nasync function gatherPackStatus(cwd: string): Promise<PackStatus> {\n const packDir = join(cwd, PACK_RELATIVE_PATH);\n if (!(await pathExists(packDir))) {\n return { installed: false, currentVersion: null };\n }\n const currentVersion = await readPinnedPackVersion(cwd).catch(() => null);\n return { installed: true, currentVersion };\n}\n\nfunction renderPackStatus(s: PackStatus): void {\n if (!s.installed) {\n const lines = [\n `${chalk.bold(\"team-ai-pack\")} · ${chalk.yellow(\"chưa cài\")}`,\n \"─\".repeat(48),\n chalk.dim(\"Chạy `avatar init` hoặc `avatar sync` để cài pack.\"),\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n return;\n }\n\n const current = s.currentVersion ?? chalk.yellow(\"(unknown)\");\n const lines = [\n `${chalk.bold(\"team-ai-pack status\")}`,\n \"─\".repeat(48),\n `${chalk.dim(\"Version hiện tại:\")} ${current}`,\n \"\",\n chalk.dim(\"Cập nhật mới nhất: avatar sync\"),\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n","// `avatar restore [--backup <name>] [--list]` — Command 08 spec.\n// Restore .claude/pack/ from a previous backup snapshot.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerRestoreCommand(program: Command): void {\n program\n .command(\"restore\")\n .description(\"Khôi phục .claude/pack/ từ backup (M08)\")\n .option(\"--backup <name>\", \"Tên backup folder trong .claude/_backup/\")\n .option(\"--list\", \"Liệt kê các backup hiện có\")\n .action(notImplementedYet(\"restore\", \"Milestone 08\"));\n}\n","// `avatar review [--accept-all|--reject-all]` — Command 05 spec.\n// Interactive review of .claude/_pending/*.diff.md proposals.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerReviewCommand(program: Command): void {\n program\n .command(\"review\")\n .description(\"Review pending proposals từ avatar scan (M08)\")\n .option(\"--accept-all\", \"Approve mọi pending không hỏi (CI mode)\")\n .option(\"--reject-all\", \"Xóa mọi pending không hỏi\")\n .action(notImplementedYet(\"review\", \"Milestone 08\"));\n}\n","// `avatar scan [--incremental|--full] [--scanners <list>]` — Command 04 spec.\n// Runs 5 project scanners and writes proposals to .claude/_pending/.\n// Implementation deferred — scanner files in src/scanners/ are stubbed.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerScanCommand(program: Command): void {\n program\n .command(\"scan\")\n .description(\"Chạy project scanner và đề xuất knowledge update (M06)\")\n .option(\"--incremental\", \"Chỉ scan các file thay đổi từ commit cuối\")\n .option(\"--full\", \"Scan toàn bộ dự án (default)\")\n .option(\"--scanners <list>\", \"tech-stack,conventions,architecture,domain,git-pattern\")\n .option(\"--quiet\", \"Chạy ngầm, ít output (dùng cho git hook)\")\n .action(notImplementedYet(\"scan\", \"Milestone 06\"));\n}\n","// `avatar status [--json]` — Command 06 spec.\n// Read-only snapshot: project name, CLI version, pack version, pending count,\n// backup count, tech-stack first-line. No mutations.\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { discoverEnabledAndAvailableTools } from \"../lib/discover-pack-tools-and-defaults.js\";\nimport { pathExists, readText } from \"../lib/filesystem-helpers.js\";\nimport { listBackups } from \"../lib/pack-backup-manager.js\";\nimport { readCliVersion } from \"../lib/read-cli-version-from-package-json.js\";\nimport { readPinnedPackVersion } from \"../lib/team-pack-submodule-manager.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\n\nexport function registerStatusCommand(program: Command): void {\n program\n .command(\"status\")\n .description(\"Snapshot tức thì: project, pack version, pending, backup\")\n .option(\"--json\", \"Output JSON cho script\")\n .action(async (opts: { json?: boolean }) => {\n try {\n const snapshot = await gatherStatus(process.cwd());\n if (opts.json) {\n process.stdout.write(`${JSON.stringify(snapshot, null, 2)}\\n`);\n } else {\n renderStatusBox(snapshot);\n }\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\ninterface StatusSnapshot {\n projectName: string;\n cliVersion: string;\n packVersion: string | null;\n pendingCount: number;\n backupCount: number;\n techStackSummary: string;\n hasAvatar: boolean;\n toolsEnabled: string[];\n toolsAvailableCount: number;\n}\n\nasync function gatherStatus(cwd: string): Promise<StatusSnapshot> {\n const projectName = cwd.split(\"/\").filter(Boolean).pop() ?? \"unknown\";\n const claudeRoot = join(cwd, \".claude\");\n const hasAvatar = await pathExists(claudeRoot);\n if (!hasAvatar) {\n return {\n projectName,\n cliVersion: readCliVersion(),\n packVersion: null,\n pendingCount: 0,\n backupCount: 0,\n techStackSummary: \"(Avatar chưa init)\",\n hasAvatar: false,\n toolsEnabled: [],\n toolsAvailableCount: 0,\n };\n }\n\n // v1.13.0 perf: 4 probes độc lập (pack, pending, backup, tech-stack) chạy\n // parallel qua Promise.all thay vì sequential. Save ~100-200ms trên repo\n // có nhiều file/backup. Mỗi probe self-catch (.catch → safe fallback) để\n // 1 probe fail không kill cả status command.\n const pendingDir = join(claudeRoot, \"_pending\");\n const [packVersion, pendingCount, backupCount, techStackSummary, tools] = await Promise.all([\n (async (): Promise<string | null> => {\n // v3: pack là tarball (không git) → đọc version từ manifest. null nếu chưa cài.\n if (!(await pathExists(join(claudeRoot, \"pack\")))) return null;\n return readPinnedPackVersion(cwd).catch(() => null);\n })(),\n (async (): Promise<number> => {\n if (!(await pathExists(pendingDir))) return 0;\n const entries = await fs.readdir(pendingDir);\n return entries.filter((n) => n.endsWith(\".diff.md\")).length;\n })(),\n listBackups(cwd)\n .then((b) => b.length)\n .catch(() => 0),\n readTechStackFirstLine(claudeRoot).catch(() => \"(read error)\"),\n discoverEnabledAndAvailableTools(cwd).catch(() => ({ available: [], enabled: [] })),\n ]);\n\n return {\n projectName,\n cliVersion: readCliVersion(),\n packVersion,\n pendingCount,\n backupCount,\n techStackSummary,\n hasAvatar: true,\n toolsEnabled: tools.enabled,\n toolsAvailableCount: tools.available.length,\n };\n}\n\nasync function readTechStackFirstLine(claudeRoot: string): Promise<string> {\n const techStackPath = join(claudeRoot, \"project\", \"tech-stack.md\");\n if (!(await pathExists(techStackPath))) return \"(no tech-stack.md)\";\n const content = await readText(techStackPath);\n const firstNonHeaderLine = content\n .split(\"\\n\")\n .find((l) => l.trim() && !l.startsWith(\"#\") && !l.startsWith(\">\"));\n return firstNonHeaderLine?.trim() ?? \"(empty)\";\n}\n\n// \"prompt-scoring (1/1 available)\" hoặc \"none (1 available)\" / \"none\".\nfunction formatTools(s: StatusSnapshot): string {\n if (s.toolsEnabled.length > 0) {\n return `${s.toolsEnabled.join(\", \")} (${s.toolsEnabled.length}/${s.toolsAvailableCount} available)`;\n }\n if (s.toolsAvailableCount > 0) {\n return `none enabled (${s.toolsAvailableCount} available — avatar tool list)`;\n }\n return \"none\";\n}\n\nfunction renderStatusBox(s: StatusSnapshot): void {\n const lines = [\n `${chalk.bold(\"Avatar Status\")} · ${chalk.cyan(s.projectName)}`,\n \"─\".repeat(48),\n `${chalk.dim(\"CLI version:\")} ${s.cliVersion}`,\n `${chalk.dim(\"Pack version:\")} ${s.packVersion ?? chalk.yellow(\"not installed\")}`,\n `${chalk.dim(\"Pending changes:\")} ${s.pendingCount}${s.pendingCount > 0 ? chalk.dim(\" (avatar review)\") : \"\"}`,\n `${chalk.dim(\"Backups:\")} ${s.backupCount}`,\n `${chalk.dim(\"Tech stack:\")} ${s.techStackSummary}`,\n `${chalk.dim(\"Tools:\")} ${formatTools(s)}`,\n ];\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n","import { promises as fs } from \"node:fs\";\n// Backup .claude/pack/ before `avatar sync --force` so user can `avatar restore`\n// if the sync goes wrong. Naming convention: pack-{currentVersion}-{YYYYMMDD-HHmm}.\nimport { join } from \"node:path\";\nimport { copyDirRecursive, ensureDir, pathExists } from \"./filesystem-helpers.js\";\n\nexport const BACKUP_DIR_NAME = \"_backup\";\n\nfunction timestamp(): string {\n const now = new Date();\n const y = now.getFullYear();\n const m = String(now.getMonth() + 1).padStart(2, \"0\");\n const d = String(now.getDate()).padStart(2, \"0\");\n const h = String(now.getHours()).padStart(2, \"0\");\n const min = String(now.getMinutes()).padStart(2, \"0\");\n return `${y}${m}${d}-${h}${min}`;\n}\n\nexport function buildBackupName(currentVersion: string): string {\n // Strip any leading \"v\" so we don't get pack-vv1.2.3 if someone passes \"v1.2.3\".\n const cleanVersion = currentVersion.replace(/^v/, \"\");\n return `pack-v${cleanVersion}-${timestamp()}`;\n}\n\n// Backup .claude/pack/ to .claude/_backup/{name}/, excluding the submodule's\n// own .git directory (we restore content only, not git history).\nexport async function backupPack(projectRoot: string, currentVersion: string): Promise<string> {\n const name = buildBackupName(currentVersion);\n const srcPath = join(projectRoot, \".claude\", \"pack\");\n const dstPath = join(projectRoot, \".claude\", BACKUP_DIR_NAME, name);\n if (!(await pathExists(srcPath))) {\n throw new Error(\"Không tìm thấy .claude/pack/ để backup\");\n }\n await ensureDir(dstPath);\n await copyDirRecursive(srcPath, dstPath, [\".git\"]);\n return name;\n}\n\nexport async function listBackups(projectRoot: string): Promise<string[]> {\n const dir = join(projectRoot, \".claude\", BACKUP_DIR_NAME);\n if (!(await pathExists(dir))) return [];\n const entries = await fs.readdir(dir, { withFileTypes: true });\n return entries\n .filter((e) => e.isDirectory())\n .map((e) => e.name)\n .sort()\n .reverse();\n}\n","// `avatar sync [--version <tag>] [--dry-run] [--force]` — v3.\n//\n// Tải team-ai-pack tarball mới từ Supabase (thay git submodule update) + tạo\n// symlink farm + merge settings + re-apply tools.\n//\n// Flow:\n// 1. Locate project root (cwd có .claude/pack/)\n// 2. getValidIdToken → fetchSignedPackUrl(version?) → download+extract → manifest\n// (--version pin bản cụ thể; không flag → server trả stable mới nhất)\n// 3. --dry-run? show preview (version diff) và exit\n// 4. syncAllMountDirs() → symlink\n// 5. merge settings + re-apply tools\nimport { join } from \"node:path\";\n\nimport type { Command } from \"commander\";\n\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport { writePackManifest } from \"../lib/pack-manifest-store.js\";\nimport { buildSyncPreview } from \"../lib/preview-team-pack-sync-changes-for-dry-run.js\";\nimport { reapplyEnabledToolsAfterSync } from \"../lib/reapply-enabled-tools-after-sync.js\";\nimport {\n InvalidIdTokenError,\n downloadAndExtractPack,\n fetchSignedPackUrl,\n} from \"../lib/supabase-pack-downloader.js\";\nimport {\n type SyncMountedDirResult,\n syncAllMountDirs,\n} from \"../lib/symlink-farm-for-team-pack-mount-dirs.js\";\nimport { TEAM_PACK_RELATIVE_PATH } from \"../lib/team-pack-submodule-manager.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { getValidIdToken } from \"../lib/user-config-store.js\";\n\ninterface SyncOptions {\n force?: boolean;\n version?: string;\n dryRun?: boolean;\n}\n\nfunction objectToVersion(object: string): string {\n return object.replace(/^pack-/, \"\").replace(/\\.tar\\.gz$/, \"\");\n}\n\nasync function syncAction(opts: SyncOptions): Promise<void> {\n const projectRoot = process.cwd();\n const claudeDir = join(projectRoot, \".claude\");\n const packDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n\n if (!(await pathExists(claudeDir))) {\n log.error(\n \"Không thấy .claude/ — không phải Avatar workspace.\\n Chạy 'avatar init' để khởi tạo.\",\n );\n process.exit(1);\n }\n\n // --dry-run: chỉ preview, không tải.\n if (opts.dryRun) {\n const preview = await buildSyncPreview(packDir, claudeDir, opts.version);\n log.info(`Pack version hiện tại: ${preview.currentVersion}`);\n log.info(`Target version: ${preview.targetVersion}`);\n log.info(\"\\nMount dir statuses:\");\n for (const m of preview.mountDirStatuses) {\n console.log(` ${m.dir.padEnd(12)} ${m.status}`);\n }\n log.info(\"\\nDry-run done. Không apply. Bỏ --dry-run để thực thi.\");\n return;\n }\n\n // Tải tarball mới.\n let idToken: string;\n try {\n idToken = await getValidIdToken();\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n\n log.info(\n opts.version\n ? `Tải team-ai-pack ${opts.version} từ Supabase...`\n : \"Tải team-ai-pack (stable mới nhất) từ Supabase...\",\n );\n let installedVersion: string;\n try {\n const info = await fetchSignedPackUrl(idToken, opts.version);\n await downloadAndExtractPack(info.url, packDir);\n installedVersion = opts.version ?? objectToVersion(info.object);\n await writePackManifest(packDir, {\n version: installedVersion,\n downloadedObject: info.object,\n extractedAt: new Date().toISOString(),\n });\n log.success(`Đã cài pack ${installedVersion}`);\n } catch (err) {\n if (err instanceof InvalidIdTokenError) {\n log.error(`${err.message}`);\n } else {\n log.error(`Tải pack thất bại: ${err instanceof Error ? err.message : err}`);\n }\n process.exit(1);\n }\n\n log.info(\"Tạo symlink farm...\");\n const results = await syncAllMountDirs(packDir, claudeDir, opts.force === true);\n reportResults(results, opts.force === true);\n\n // Merge settings + re-apply tools (giữ nguyên logic v2).\n log.info(\"Merge pack settings.json template vào project settings.json...\");\n try {\n const mergeResult = await mergePackSettingsIntoProjectSettings(projectRoot);\n switch (mergeResult.action) {\n case \"merged\":\n log.success(` ✓ settings.json merged (${mergeResult.changes.join(\"; \")})`);\n break;\n case \"no-change\":\n log.info(\" - settings.json đã sync.\");\n break;\n case \"no-pack-template\":\n log.dim(\" - Pack không có templates/settings.json.tpl, skip.\");\n break;\n }\n } catch (err) {\n log.warn(` ! Merge settings.json fail: ${err instanceof Error ? err.message : err}.`);\n }\n\n try {\n await reapplyEnabledToolsAfterSync(projectRoot);\n } catch (err) {\n log.warn(` ! Re-apply tools fail: ${err instanceof Error ? err.message : err}.`);\n }\n\n log.success(`Synced team-ai-pack → ${installedVersion}.`);\n}\n\nfunction reportResults(results: SyncMountedDirResult[], force: boolean): void {\n for (const r of results) {\n switch (r.action) {\n case \"created\":\n log.info(` ✓ ${r.dir} → symlinked (new)`);\n break;\n case \"updated\":\n log.info(` ✓ ${r.dir} → symlink refreshed`);\n break;\n case \"backed-up-and-linked\":\n log.info(` ✓ ${r.dir} → symlinked (backup: ${r.backupPath})`);\n break;\n case \"source-missing\":\n log.warn(` - ${r.dir} → pack không có dir này, skip`);\n break;\n case \"skipped-conflict\":\n log.warn(` ! ${r.dir} → CONFLICT: existing real dir. Dùng --force để backup + override.`);\n break;\n }\n }\n const conflicts = results.filter((r) => r.action === \"skipped-conflict\").length;\n if (conflicts > 0 && !force) {\n log.warn(`${conflicts} mount dir(s) skip do conflict. Chạy lại với --force.`);\n }\n}\n\nexport function registerSyncCommand(program: Command): void {\n program\n .command(\"sync\")\n .description(\"Tải team-ai-pack mới nhất (tarball Supabase) + tạo symlink farm\")\n .option(\"--force\", \"Override .claude/<dir>/ nếu là real dir (backup trước)\")\n .option(\"--version <tag>\", \"Pin version cụ thể (vd: v0.2.0 hoặc v0.2.0-beta.0)\")\n .option(\"--dry-run\", \"Hiển thị preview, không apply\")\n .action(syncAction);\n}\n","// Preview what `avatar sync` would do, without modifying filesystem.\n//\n// v3: pack là tarball (không git) → không còn git log diff. So version manifest\n// hiện tại vs target. mountDir status không đổi (symlink farm pack-agnostic).\nimport { join } from \"node:path\";\n\nimport { pathExists } from \"./filesystem-helpers.js\";\nimport { readPackVersionFromFile } from \"./pack-manifest-store.js\";\nimport { TEAM_PACK_MOUNT_DIRS } from \"./symlink-farm-for-team-pack-mount-dirs.js\";\n\nexport interface SyncPreview {\n currentVersion: string;\n targetVersion: string;\n mountDirStatuses: Array<{ dir: string; status: string }>;\n}\n\n// Look up which mount dirs already have symlinks vs missing vs conflict.\nasync function inspectMountDir(packDir: string, claudeDir: string, dir: string): Promise<string> {\n const source = join(packDir, dir);\n const dest = join(claudeDir, dir);\n if (!(await pathExists(source))) return \"source-missing\";\n if (!(await pathExists(dest))) return \"needs-creation\";\n const { promises: fs } = await import(\"node:fs\");\n const st = await fs.lstat(dest);\n if (st.isSymbolicLink()) return \"already-linked\";\n return \"conflict-real-dir\";\n}\n\nexport async function buildSyncPreview(\n packDir: string,\n claudeDir: string,\n targetVersion?: string,\n): Promise<SyncPreview> {\n // v3: version hiện tại đọc từ manifest. target = version pin (nếu có) hoặc\n // \"stable mới nhất\" (server resolve — client không biết trước số cụ thể).\n const currentVersion = (await readPackVersionFromFile(packDir)) ?? \"(chưa cài)\";\n const target = targetVersion ?? \"stable mới nhất (server resolve)\";\n\n const mountStatuses = [];\n for (const dir of TEAM_PACK_MOUNT_DIRS) {\n mountStatuses.push({\n dir,\n status: await inspectMountDir(packDir, claudeDir, dir),\n });\n }\n\n return {\n currentVersion,\n targetVersion: target,\n mountDirStatuses: mountStatuses,\n };\n}\n","// reapply-enabled-tools-after-sync.ts\n//\n// Sau khi sync merge lại settings.json.tpl (có thể đổi/ghi đè settings), re-apply\n// mọi tool đang enabled=true để hook tool KHÔNG bị mất. Không có bước này,\n// tool \"biến mất\" sau mỗi `avatar sync`.\n//\n// Idempotent: enable lại không nhân đôi entry (dedupe trong enableTool lo).\n// Fail-mềm: tool trong state nhưng pack đã bỏ manifest → enableToolByName\n// trả false (warn), không crash sync.\n\nimport { log } from \"./terminal-logger.js\";\nimport { enableToolByName } from \"./tool-enable-disable-orchestrator.js\";\nimport { listEnabledTools } from \"./tool-state-store.js\";\n\nexport async function reapplyEnabledToolsAfterSync(workspacePath: string): Promise<void> {\n const enabled = await listEnabledTools(workspacePath);\n if (enabled.length === 0) {\n return;\n }\n log.info(`Re-apply ${enabled.length} tool(s) đang bật vào settings.json...`);\n for (const name of enabled) {\n await enableToolByName(workspacePath, name);\n }\n}\n","// `avatar tool <enable|disable|list> [name]` — quản lý tool toggle.\n//\n// Tool toggle = gói tự-đủ trong pack (tools/<name>/) bật/tắt nguyên khối\n// qua merge/rút manifest vào project settings.json. Khác hook hạ-tầng luôn-bật\n// (scout-block, dev-rules-reminder nằm trong settings.json.tpl).\n//\n// Subcommands:\n// enable <name> merge manifest hooks + deny vào settings.json + state enabled\n// disable <name> rút đúng entry tool ra + state disabled\n// list bảng: tool | available | enabled | version\n// add multi-select tool available để cài (UX, bọc enable)\n// remove multi-select tool đang bật để gỡ (UX, bọc disable)\n//\n// Flags: --target <path>; add/remove thêm --all (chọn hết, auto khi non-TTY) + --yes.\n// Idempotent: enable lại không nhân đôi; disable tool chưa bật → no-op.\n// add/remove tái dùng enableToolByName/disableToolByName (DRY).\n\nimport { resolve } from \"node:path\";\nimport { checkbox, confirm } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport {\n discoverEnabledAndAvailableTools,\n listAvailableTools,\n} from \"../lib/discover-pack-tools-and-defaults.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport {\n buildAddChoices,\n buildRemoveChoices,\n resolveNonInteractiveSelection,\n} from \"../lib/tool-add-remove-selection.js\";\nimport { disableToolByName, enableToolByName } from \"../lib/tool-enable-disable-orchestrator.js\";\nimport { readToolState } from \"../lib/tool-state-store.js\";\n\ninterface ToolOptions {\n target?: string;\n}\n\ninterface AddRemoveOptions extends ToolOptions {\n all?: boolean;\n yes?: boolean;\n}\n\nfunction resolveWorkspace(opts: ToolOptions): string {\n return resolve(opts.target ?? process.cwd());\n}\n\n// Quyết định selection: --all hoặc non-TTY → chọn hết candidate; ngược lại prompt\n// checkbox. Trả về danh sách tên tool đã chọn (có thể rỗng).\nasync function selectTools(\n candidates: string[],\n preChecked: string[],\n opts: AddRemoveOptions,\n verb: \"add\" | \"remove\",\n): Promise<string[]> {\n const nonInteractive = opts.all === true || !process.stdout.isTTY;\n if (nonInteractive) {\n return resolveNonInteractiveSelection(candidates);\n }\n const choices =\n verb === \"add\" ? buildAddChoices(candidates, preChecked) : buildRemoveChoices(candidates);\n return await checkbox<string>({\n message:\n verb === \"add\"\n ? \"Chọn tool để cài (space chọn, enter xác nhận):\"\n : \"Chọn tool để gỡ (space chọn, enter xác nhận):\",\n choices,\n });\n}\n\n// `avatar tool add` — list available, multi-select để enable 1-n.\nasync function runAdd(opts: AddRemoveOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const { available, enabled } = await discoverEnabledAndAvailableTools(ws);\n if (available.length === 0) {\n log.dim(\"Không có tool available (pack chưa có tools/, hoặc chưa sync).\");\n return;\n }\n const selected = await selectTools(available, enabled, opts, \"add\");\n if (selected.length === 0) {\n log.dim(\"Không chọn tool nào — hủy, không thay đổi.\");\n return;\n }\n for (const name of selected) {\n await enableToolByName(ws, name);\n }\n}\n\n// `avatar tool remove` — list enabled, multi-select để disable 1-n.\nasync function runRemove(opts: AddRemoveOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const { enabled } = await discoverEnabledAndAvailableTools(ws);\n if (enabled.length === 0) {\n log.dim(\"Không có tool nào đang bật — không có gì để gỡ.\");\n return;\n }\n const selected = await selectTools(enabled, [], opts, \"remove\");\n if (selected.length === 0) {\n log.dim(\"Không chọn tool nào — hủy, không thay đổi.\");\n return;\n }\n // remove sửa settings.json → xác nhận trước (trừ khi --yes hoặc non-TTY).\n const needConfirm = opts.yes !== true && opts.all !== true && process.stdout.isTTY;\n if (needConfirm) {\n const ok = await confirm({\n message: `Gỡ ${selected.length} tool: ${selected.join(\", \")}?`,\n default: true,\n });\n if (!ok) {\n log.dim(\"Hủy — không gỡ tool nào.\");\n return;\n }\n }\n for (const name of selected) {\n await disableToolByName(ws, name);\n }\n}\n\nasync function runList(opts: ToolOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const available = await listAvailableTools(ws);\n const state = await readToolState(ws);\n\n if (available.length === 0 && Object.keys(state.tools).length === 0) {\n log.dim(\"Không có tool nào (pack chưa có tools/, hoặc chưa sync). Chạy 'avatar sync'.\");\n return;\n }\n\n // Union tên: tool có trong pack + tool từng ghi trong state.\n const names = [...new Set([...available, ...Object.keys(state.tools)])].sort();\n log.info(\"Tools:\");\n console.log(` ${\"NAME\".padEnd(20)} ${\"AVAILABLE\".padEnd(10)} ${\"ENABLED\".padEnd(8)} VERSION`);\n for (const name of names) {\n const isAvailable = available.includes(name) ? \"yes\" : \"no\";\n const entry = state.tools[name];\n const enabled = entry?.enabled ? \"yes\" : \"no\";\n const version = entry?.version ?? \"-\";\n console.log(` ${name.padEnd(20)} ${isAvailable.padEnd(10)} ${enabled.padEnd(8)} ${version}`);\n }\n}\n\nexport function registerToolsCommand(program: Command): void {\n const tools = program\n .command(\"tools\")\n .description(\"Quản lý tool toggle (prompt-scoring, ...) bật/tắt nguyên khối\");\n\n tools\n .command(\"enable <name>\")\n .description(\"Bật tool: merge manifest vào settings.json + ghi state\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (name: string, opts: ToolOptions) => {\n const ws = resolveWorkspace(opts);\n const ok = await enableToolByName(ws, name);\n if (!ok) process.exit(1);\n });\n\n tools\n .command(\"disable <name>\")\n .description(\"Tắt tool: rút đúng entry manifest ra khỏi settings.json + ghi state\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (name: string, opts: ToolOptions) => {\n const ws = resolveWorkspace(opts);\n await disableToolByName(ws, name);\n });\n\n tools\n .command(\"list\")\n .description(\"Liệt kê tool: available | enabled | version\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (opts: ToolOptions) => {\n await runList(opts);\n });\n\n tools\n .command(\"add\")\n .description(\"Chọn (multi-select) tool available để cài\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Cài hết tool available (bỏ qua prompt; auto khi non-TTY)\")\n .option(\"--yes\", \"Bỏ xác nhận\")\n .action(async (opts: AddRemoveOptions) => {\n await runAdd(opts);\n });\n\n tools\n .command(\"remove\")\n .description(\"Chọn (multi-select) tool đang bật để gỡ\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Gỡ hết tool đang bật (bỏ qua prompt; auto khi non-TTY)\")\n .option(\"--yes\", \"Bỏ xác nhận\")\n .action(async (opts: AddRemoveOptions) => {\n await runRemove(opts);\n });\n}\n","// tool-add-remove-selection.ts\n//\n// Logic THUẦN (không UI) cho `avatar tool add` / `remove`:\n// - buildAddChoices: từ available (pack) + enabled (state) → list choice cho\n// checkbox. Cái đã cài hiện \"(installed)\" + pre-checked.\n// - buildRemoveChoices: từ enabled → list choice để gỡ.\n// - resolveNonInteractiveSelection: khi --all hoặc không TTY → trả selection\n// tự động (không hỏi).\n//\n// Tách khỏi command để test được mà không cần stub prompt UI.\n\nexport interface ToolChoice {\n name: string; // label hiển thị\n value: string; // tên tool\n checked: boolean; // pre-check trong checkbox\n}\n\n// ADD: list TẤT CẢ available; cái đang enabled → \"(installed)\" + pre-check.\nexport function buildAddChoices(available: string[], enabled: string[]): ToolChoice[] {\n const enabledSet = new Set(enabled);\n return available.map((name) => ({\n name: enabledSet.has(name) ? `${name} (installed)` : name,\n value: name,\n checked: enabledSet.has(name),\n }));\n}\n\n// REMOVE: chỉ list tool đang enabled (không pre-check — user chủ động chọn gỡ).\nexport function buildRemoveChoices(enabled: string[]): ToolChoice[] {\n return enabled.map((name) => ({ name, value: name, checked: false }));\n}\n\n// Khi --all hoặc non-TTY: chọn hết các giá trị candidate (không prompt).\n// add → tất cả available; remove → tất cả enabled.\nexport function resolveNonInteractiveSelection(candidates: string[]): string[] {\n return [...candidates];\n}\n","// `avatar uninstall` — Command 13 (v1.1).\n// Gỡ Avatar khỏi project + auto-backup vào ~/.avatar/uninstall-backups/.\n// Đối ứng với `avatar init`. Code khách (src/) giữ nguyên.\n\nimport { relative } from \"node:path\";\nimport { confirm } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { createUninstallBackupSnapshot } from \"../lib/create-uninstall-backup-snapshot.js\";\nimport { detectAvatarProjectArtifacts } from \"../lib/detect-avatar-project-artifacts.js\";\nimport { executeUninstallDeletion } from \"../lib/execute-uninstall-deletion.js\";\nimport { readCliVersion } from \"../lib/read-cli-version-from-package-json.js\";\nimport { chalk, log } from \"../lib/terminal-logger.js\";\n\ninterface UninstallOptions {\n yes?: boolean;\n noBackup?: boolean;\n keepSubmodule?: boolean;\n keepHooks?: boolean;\n dryRun?: boolean;\n}\n\nexport function registerUninstallCommand(program: Command): void {\n program\n .command(\"uninstall\")\n .description(\"Gỡ Avatar khỏi project — backup tự động (M11)\")\n .option(\"--yes\", \"Skip confirm prompt\")\n .option(\"--no-backup\", \"Không tạo backup trước khi xóa (nguy hiểm)\")\n .option(\"--keep-submodule\", \"Giữ submodule .claude/pack/\")\n .option(\"--keep-hooks\", \"Giữ git hooks post-merge, pre-push\")\n .option(\"--dry-run\", \"Hiển thị danh sách sẽ xóa, không thực thi\")\n .action(async (opts: UninstallOptions) => {\n try {\n await runUninstall(opts);\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n });\n}\n\nasync function runUninstall(opts: UninstallOptions): Promise<void> {\n const projectRoot = process.cwd();\n const artifacts = detectAvatarProjectArtifacts(projectRoot);\n\n if (!artifacts.hasAnyArtifact) {\n log.info(\"Project chưa cài Avatar — không có gì để gỡ.\");\n return;\n }\n\n // Show summary.\n printUninstallSummary(projectRoot, artifacts, opts);\n\n if (opts.dryRun) {\n log.dim(\"--dry-run: kết thúc, không xóa.\");\n return;\n }\n\n // Confirm.\n if (!opts.yes) {\n const ok = await confirm({\n message: \"Tiếp tục gỡ Avatar?\",\n default: false,\n });\n if (!ok) {\n log.info(\"Đã hủy.\");\n return;\n }\n }\n\n // Backup (trừ khi --no-backup).\n let backupPath: string | null = null;\n if (!opts.noBackup) {\n backupPath = await createUninstallBackupSnapshot(projectRoot, artifacts, readCliVersion());\n log.success(`Backup tạo tại: ${backupPath}`);\n }\n\n // Delete artifacts.\n await executeUninstallDeletion(artifacts, {\n keepSubmodule: opts.keepSubmodule,\n keepHooks: opts.keepHooks,\n });\n\n await appendAuditEntry(\"uninstall\", `project=${projectRoot},backup=${backupPath ?? \"skipped\"}`);\n\n printUninstallSuccessBox(backupPath);\n}\n\nfunction printUninstallSummary(\n projectRoot: string,\n artifacts: ReturnType<typeof detectAvatarProjectArtifacts>,\n opts: UninstallOptions,\n): void {\n log.info(`Project: ${projectRoot}`);\n log.plain(\"\");\n log.plain(\"Các artifact sẽ gỡ:\");\n if (artifacts.claudeDir)\n log.plain(` ${chalk.red(\"✗\")} ${relative(projectRoot, artifacts.claudeDir) || \".claude/\"}`);\n if (artifacts.claudeMd) log.plain(` ${chalk.red(\"✗\")} CLAUDE.md`);\n if (artifacts.postMergeHook && !opts.keepHooks) {\n log.plain(` ${chalk.red(\"✗\")} .git/hooks/post-merge`);\n }\n if (artifacts.prePushHook && !opts.keepHooks) {\n log.plain(` ${chalk.red(\"✗\")} .git/modules/src/hooks/pre-push`);\n }\n if (artifacts.gitignorePath) log.plain(` ${chalk.yellow(\"✎\")} .gitignore (gỡ Avatar block)`);\n if (artifacts.gitmodulesPath && !opts.keepSubmodule) {\n log.plain(` ${chalk.yellow(\"✎\")} .gitmodules (gỡ entry .claude/pack)`);\n }\n log.plain(\"\");\n log.plain(\"Không đụng:\");\n log.plain(` ${chalk.green(\"✓\")} src/ (code khách)`);\n log.plain(` ${chalk.green(\"✓\")} Git history`);\n log.plain(` ${chalk.green(\"✓\")} ~/.avatar/config.json (token SSO)`);\n log.plain(` ${chalk.green(\"✓\")} Secrets trong keychain`);\n log.plain(\"\");\n}\n\nfunction printUninstallSuccessBox(backupPath: string | null): void {\n const lines: string[] = [`${chalk.green(\"✓\")} Avatar đã được gỡ khỏi project`];\n if (backupPath) {\n lines.push(\"\");\n lines.push(` ${chalk.dim(\"Backup:\")} ${backupPath}`);\n lines.push(` ${chalk.dim(\"Restore:\")} ${chalk.cyan(`cp -r \"${backupPath}\"/* .`)}`);\n }\n process.stdout.write(`${boxen(lines.join(\"\\n\"), { padding: 1, borderStyle: \"round\" })}\\n`);\n}\n","// Tạo snapshot backup trước khi uninstall. Folder: ~/.avatar/uninstall-backups/\n// <project-name>-<ts>/ với cấu trúc trong spec doc.\nimport { cp, mkdir, writeFile } from \"node:fs/promises\";\nimport { homedir } from \"node:os\";\nimport { basename, join } from \"node:path\";\nimport type { AvatarProjectArtifacts } from \"./detect-avatar-project-artifacts.js\";\n\nexport interface BackupManifest {\n projectName: string;\n projectPath: string;\n timestamp: string;\n avatarVersion: string;\n artifacts: {\n claudeDir: boolean;\n claudeMd: boolean;\n postMergeHook: boolean;\n prePushHook: boolean;\n };\n}\n\nconst UNINSTALL_BACKUPS_DIR = join(homedir(), \".avatar\", \"uninstall-backups\");\n\nexport async function createUninstallBackupSnapshot(\n projectRoot: string,\n artifacts: AvatarProjectArtifacts,\n avatarVersion: string,\n): Promise<string> {\n const projectName = basename(projectRoot);\n const timestamp = new Date().toISOString().replace(/[:.]/g, \"-\");\n const backupDir = join(UNINSTALL_BACKUPS_DIR, `${projectName}-${timestamp}`);\n\n await mkdir(backupDir, { recursive: true, mode: 0o700 });\n\n // Copy .claude/ và CLAUDE.md nếu tồn tại.\n if (artifacts.claudeDir) {\n await cp(artifacts.claudeDir, join(backupDir, \".claude\"), { recursive: true });\n }\n if (artifacts.claudeMd) {\n await cp(artifacts.claudeMd, join(backupDir, \"CLAUDE.md\"));\n }\n\n // Copy hooks sang backup/hooks/.\n if (artifacts.postMergeHook || artifacts.prePushHook) {\n const hooksBackupDir = join(backupDir, \"hooks\");\n await mkdir(hooksBackupDir, { recursive: true });\n if (artifacts.postMergeHook) {\n await cp(artifacts.postMergeHook, join(hooksBackupDir, \"post-merge\"));\n }\n if (artifacts.prePushHook) {\n await cp(artifacts.prePushHook, join(hooksBackupDir, \"pre-push\"));\n }\n }\n\n // Write manifest.\n const manifest: BackupManifest = {\n projectName,\n projectPath: projectRoot,\n timestamp,\n avatarVersion,\n artifacts: {\n claudeDir: !!artifacts.claudeDir,\n claudeMd: !!artifacts.claudeMd,\n postMergeHook: !!artifacts.postMergeHook,\n prePushHook: !!artifacts.prePushHook,\n },\n };\n await writeFile(join(backupDir, \"manifest.json\"), JSON.stringify(manifest, null, 2), \"utf8\");\n\n return backupDir;\n}\n","// Scan project root để liệt kê các file/folder Avatar đã tạo. Output là blueprint\n// cho uninstall: cái gì sẽ xóa + cái gì sẽ edit (gitignore, gitmodules).\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nexport interface AvatarProjectArtifacts {\n hasAnyArtifact: boolean;\n claudeDir: string | null; // .claude/\n claudeMd: string | null; // CLAUDE.md\n postMergeHook: string | null; // .git/hooks/post-merge\n prePushHook: string | null; // .git/modules/src/hooks/pre-push\n gitignorePath: string | null; // .gitignore (nếu có Avatar block)\n gitmodulesPath: string | null; // .gitmodules (nếu có submodule .claude/pack)\n notesDir: string | null; // notes/ (workspace mode)\n scriptsDir: string | null; // scripts/ (workspace mode)\n}\n\nfunction existsOrNull(path: string): string | null {\n return existsSync(path) ? path : null;\n}\n\nexport function detectAvatarProjectArtifacts(projectRoot: string): AvatarProjectArtifacts {\n const claudeDir = existsOrNull(join(projectRoot, \".claude\"));\n const claudeMd = existsOrNull(join(projectRoot, \"CLAUDE.md\"));\n const postMergeHook = existsOrNull(join(projectRoot, \".git\", \"hooks\", \"post-merge\"));\n const prePushHook = existsOrNull(\n join(projectRoot, \".git\", \"modules\", \"src\", \"hooks\", \"pre-push\"),\n );\n const gitignorePath = existsOrNull(join(projectRoot, \".gitignore\"));\n const gitmodulesPath = existsOrNull(join(projectRoot, \".gitmodules\"));\n const notesDir = existsOrNull(join(projectRoot, \"notes\"));\n const scriptsDir = existsOrNull(join(projectRoot, \"scripts\"));\n\n const hasAnyArtifact = !!(claudeDir || claudeMd || postMergeHook || prePushHook);\n\n return {\n hasAnyArtifact,\n claudeDir,\n claudeMd,\n postMergeHook,\n prePushHook,\n gitignorePath,\n gitmodulesPath,\n notesDir,\n scriptsDir,\n };\n}\n","// Atomic delete các artifact Avatar khỏi project. Gỡ marker block trong\n// .gitignore, remove submodule entry trong .gitmodules. Không đụng src/ + git\n// history + user config.\nimport { readFile, rm, writeFile } from \"node:fs/promises\";\nimport type { AvatarProjectArtifacts } from \"./detect-avatar-project-artifacts.js\";\nimport { AVATAR_MARKER_END, AVATAR_MARKER_START } from \"./gitignore-template-loader.js\";\n\nexport interface UninstallFlags {\n keepSubmodule?: boolean;\n keepHooks?: boolean;\n}\n\nexport async function executeUninstallDeletion(\n artifacts: AvatarProjectArtifacts,\n flags: UninstallFlags,\n): Promise<void> {\n // Delete .claude/ (trừ khi --keep-submodule muốn giữ pack/ — thực tế cả\n // .claude/ chứa nhiều thứ khác, nên --keep-submodule chỉ giữ pack/).\n if (artifacts.claudeDir) {\n if (flags.keepSubmodule) {\n // Chỉ xóa các file/folder không phải pack/ trong .claude/.\n const { readdir } = await import(\"node:fs/promises\");\n const { join } = await import(\"node:path\");\n const entries = await readdir(artifacts.claudeDir);\n for (const entry of entries) {\n if (entry === \"pack\") continue;\n await rm(join(artifacts.claudeDir, entry), { recursive: true, force: true });\n }\n } else {\n await rm(artifacts.claudeDir, { recursive: true, force: true });\n }\n }\n\n if (artifacts.claudeMd) {\n await rm(artifacts.claudeMd, { force: true });\n }\n\n if (!flags.keepHooks) {\n if (artifacts.postMergeHook) await rm(artifacts.postMergeHook, { force: true });\n if (artifacts.prePushHook) await rm(artifacts.prePushHook, { force: true });\n }\n\n // Strip Avatar block khỏi .gitignore (giữ rest).\n if (artifacts.gitignorePath) {\n await stripAvatarBlockFromGitignore(artifacts.gitignorePath);\n }\n\n // Remove submodule entry .claude/pack khỏi .gitmodules (nếu xóa cả pack).\n if (artifacts.gitmodulesPath && !flags.keepSubmodule) {\n await removeSubmoduleEntry(artifacts.gitmodulesPath, \".claude/pack\");\n }\n\n // Workspace mode có notes/, scripts/. Chỉ xóa nếu rỗng (user có thể đã add file).\n for (const dir of [artifacts.notesDir, artifacts.scriptsDir]) {\n if (!dir) continue;\n const { readdir } = await import(\"node:fs/promises\");\n const entries = await readdir(dir);\n if (entries.length === 0) {\n await rm(dir, { recursive: true, force: true });\n }\n }\n}\n\nasync function stripAvatarBlockFromGitignore(path: string): Promise<void> {\n const content = await readFile(path, \"utf8\");\n const startIdx = content.indexOf(AVATAR_MARKER_START);\n const endIdx = content.indexOf(AVATAR_MARKER_END);\n if (startIdx === -1 || endIdx === -1) return;\n\n const before = content.slice(0, startIdx);\n const after = content.slice(endIdx + AVATAR_MARKER_END.length);\n const cleaned = `${before.trimEnd()}\\n${after.trimStart()}`.trim();\n if (cleaned.length === 0) {\n await rm(path, { force: true });\n } else {\n await writeFile(path, `${cleaned}\\n`, \"utf8\");\n }\n}\n\nasync function removeSubmoduleEntry(gitmodulesPath: string, submodulePath: string): Promise<void> {\n const content = await readFile(gitmodulesPath, \"utf8\");\n const lines = content.split(\"\\n\");\n const result: string[] = [];\n let skip = false;\n for (const line of lines) {\n if (line.trim().startsWith(\"[submodule\") && line.includes(submodulePath)) {\n skip = true;\n continue;\n }\n if (skip && line.trim().startsWith(\"[submodule\")) {\n skip = false;\n }\n if (!skip) result.push(line);\n }\n const cleaned = result.join(\"\\n\").trim();\n if (cleaned.length === 0) {\n await rm(gitmodulesPath, { force: true });\n } else {\n await writeFile(gitmodulesPath, `${cleaned}\\n`, \"utf8\");\n }\n}\n"],"mappings":"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","names":["filesystem_helpers_exports","__export","computeFileSha256","copyDirRecursive","downloadFile","ensureDir","extractTarballToDir","pathExists","readJson","readText","relativeFromCwd","removeRecursive","writeJsonAtomic","writeTextAtomic","spawn","spawnSync","createHash","constants","fs","createReadStream","dirname","join","relative","Readable","path","content","mode","tmp","data","source","destination","excludeNames","entries","entry","src","dst","target","url","dest","timeoutMs","DOWNLOAD_TIMEOUT_MS","controller","timer","createWriteStream","res","fileStream","resolve","reject","err","tarAvailable","tarAvailableCache","tarball","destDir","child","stderr","chunk","code","hash","stream","init_filesystem_helpers","__esmMin","git_operations_exports","__export","addSubmodule","checkoutBranchHeadInSubmodule","checkoutTagInSubmodule","currentBranch","currentCommitSha","git","isGitRepo","listTags","tagAtHead","workingTreeIsDirty","join","simpleGit","cwd","pathExists","repoUrl","destPath","submodulePath","tag","submoduleCwd","branch","init_git_operations","__esmMin","init_filesystem_helpers","renderTemplate","source","variables","TEMPLATE_PATTERN","match","key","value","init_mustache_template_engine","__esmMin","existsSync","dirname","join","fileURLToPath","findPackageRoot","startDir","dir","parent","loadTemplate","name","readText","TEMPLATES_ROOT","renderTemplateByName","variables","source","renderTemplate","loadHook","HOOKS_ROOT","HERE","PACKAGE_ROOT","init_template_bundle_loader","__esmMin","init_filesystem_helpers","init_mustache_template_engine","project_tree_scaffolder_exports","__export","AVATAR_MANAGED_PATHS","backupIfExists","createClaudeDirTree","installGitHook","writeClaudeGitignore","writeProjectKnowledgeFiles","writeProjectSettings","writeRootClaudeMd","fs","join","path","pathExists","ts","basePath","backupPath","counter","writeWithBackup","content","mode","backup","writeTextAtomic","projectRoot","claudeRoot","ensureDir","sub","CLAUDE_SUBDIRS","dir","_projectRoot","_vars","vars","renderTemplateByName","CLAUDE_GITIGNORE_CONTENT","gitDir","hookName","loadHook","hooksDir","dest","init_project_tree_scaffolder","__esmMin","init_filesystem_helpers","init_template_bundle_loader","join","manifestPath","workspaceRoot","REPOS_MANIFEST_RELATIVE","readReposManifest","p","pathExists","data","readJson","addRepoToManifest","entry","filtered","r","writeJsonAtomic","repoNameExists","name","init_workspace_repos_manifest_store","__esmMin","init_filesystem_helpers","clone_code_repo_into_src_exports","__export","cloneCodeRepoIntoSrc","folderDirtyStatus","inferRepoNameFromUrl","readFolderRemoteUrl","validateRepoName","join","name","url","args","nameError","repoNameExists","srcDir","ensureDir","destPath","pathExists","git","gitDir","installGitHook","addRepoToManifest","folderPath","status","f","origin","init_clone_code_repo_into_src","__esmMin","init_filesystem_helpers","init_git_operations","init_project_tree_scaffolder","init_workspace_repos_manifest_store","chalk","ora","spinner","text","spinnerWithElapsed","prefix","startMs","sp","formatElapsed","sec","m","s","interval","log","init_terminal_logger","__esmMin","spawnSync","executeGhRepoCreate","input","fullName","args","RepoAlreadyExistsError","init_execute_gh_repo_create","__esmMin","spawnSync","resolveGithubUsernameDefault","r","init_resolve_github_username_default","__esmMin","validateRepoName","name","REPO_NAME_REGEX","InvalidRepoNameError","validateRepoVisibility","v","init_validate_repo_name_and_visibility","__esmMin","create_github_remote_from_folder_exports","__export","createGithubRemoteFromFolder","input","validateRepoName","validateRepoVisibility","org","resolveGithubUsernameDefault","log","urls","executeGhRepoCreate","init_create_github_remote_from_folder","__esmMin","init_execute_gh_repo_create","init_resolve_github_username_default","init_terminal_logger","init_validate_repo_name_and_visibility","create_empty_github_repo_exports","__export","createEmptyGithubRepoAndGetUrl","spawnSync","name","visibility","org","fullName","resolveGithubUsernameDefault","log","r","init_create_empty_github_repo","__esmMin","init_resolve_github_username_default","init_terminal_logger","google_oauth_device_flow_exports","__export","HOSTED_DOMAIN","SCOPES","buildUserConfig","buildVerificationUrl","decodeIdToken","pollForToken","refreshAccessToken","requestDeviceCode","revokeToken","verifyHostedDomain","verifyIdTokenClaims","body","GOOGLE_CLIENT_ID","res","DEVICE_CODE_URL","text","deviceCode","GOOGLE_CLIENT_SECRET","TOKEN_URL","errorCode","idToken","parts","payload","base64","json","claims","VALID_ISSUERS","nowSec","CLOCK_SKEW_SECONDS","ageSec","token","expiresAt","refreshToken","REVOKE_URL","response","url","init_google_oauth_device_flow","__esmMin","Command","init_clone_code_repo_into_src","resolve","confirm","input","select","init_clone_code_repo_into_src","init_filesystem_helpers","init_terminal_logger","spawnSync","join","confirm","input","select","spawnSync","TIMEOUT_MS","classifyRemoteError","stderr","text","tryVerifyGitRemoteAccessible","url","r","spawnSync","TIMEOUT_MS","err","explainRemoteFailure","url","reason","detail","log","triggerGhAuthLogin","r","spawnSync","promptCloneRecovery","choices","select","cloneRepoWithRecovery","args","name","verify","tryVerifyGitRemoteAccessible","action","input","v","confirm","inferRepoNameFromUrl","cloneCodeRepoIntoSrc","err","message","removeRecursive","join","spawnSync","platform","detectHostPlatform","p","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeGitnexusBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeGitnexusVersion","cachedInfo","detectGitnexusInstallation","path","invalidateGitnexusInstallationCache","spawnSync","checkGhCliAuthStatus","r","spawnSync","hasBinary","name","platform","detectHostPlatform","spawnSync","detectPackageManager","candidates","pm","spawnSync","input","select","init_create_github_remote_from_folder","init_filesystem_helpers","init_terminal_logger","spawnSync","fs","basename","join","confirm","select","backupTimestamp","d","backupExistingDotGit","folderPath","gitDir","pathExists","backupName","backupPath","log","reinitGitInFolder","r1","r3","resetFolderGitAndCreateNewRemoteUnderCurrentUser","opts","folderName","repoName","visibility","createGithubRemoteFromFolder","init_terminal_logger","RemoteAccessAbortedError","message","getCurrentGhUser","r","spawnSync","triggerGhAuthLoginInteractive","log","getReasonHint","reason","url","ghUser","isValidGitUrl","trimmed","handleRemoteAccessFailureWithAccountSwitch","args","currentUrl","detail","choices","action","select","reset","resetFolderGitAndCreateNewRemoteUnderCurrentUser","err","input","v","result","tryVerifyGitRemoteAccessible","init_terminal_logger","spawnSync","INSTALL_COMMANDS","installGhCliViaPackageManager","pm","spec","log","r","init_terminal_logger","input","select","UserAbortedRecoveryError","message","promptRetryOrSkip","args","log","choices","init_terminal_logger","spawnSync","setupGitCredentialViaGh","log","init_terminal_logger","init_terminal_logger","spawnSync","triggerGhCliAuthLogin","log","r","ensureGitHubReady","remoteUrl","checkGhCliAuthStatus","log","pm","detectPackageManager","promptRetryOrSkip","UserAbortedRecoveryError","installGhCliViaPackageManager","err","triggerGhCliAuthLogin","setupGitCredentialViaGh","result","tryVerifyGitRemoteAccessible","handleRemoteAccessFailureWithAccountSwitch","existsSync","dirname","join","MAX_WALKUP_LEVELS","isAvatarWorkspace","dir","hasClaudeDir","hasClaudeMd","hasSrcDir","resolveAvatarWorkspaceRootFromCwd","startDir","current","i","parent","init_terminal_logger","spawnSync","existsSync","join","SETUP_TIMEOUT_MS","ANALYZE_TIMEOUT_MS","GitnexusOperationError","operation","reason","message","exitCode","stderr","classifyOperationFailure","signal","stderrSample","tailLines","text","n","runGitnexusSetup","sp","spinnerWithElapsed","result","stdout","runGitnexusAnalyze","workspacePath","metaPath","spawnSync","existsSync","join","confirm","REASONING_PATTERNS","isReasoningModel","modelName","pattern","init_filesystem_helpers","init_terminal_logger","fs","homedir","dirname","join","GITNEXUS_CONFIG_DIR","GITNEXUS_CONFIG_PATH","readExistingConfig","raw","parsed","writeConfigAtomic","content","tmpPath","writeGitnexusConfigForWikiRun","payload","merged","WIKI_TIMEOUT_MS","FALLBACK_LLMLITE_MODEL","FALLBACK_ANTHROPIC_MODEL","normalizeAnthropicBaseUrl","rawBaseUrl","cleaned","readSettingsForWikiCredentials","workspacePath","settingsPath","join","pathExists","settings","readJson","env","baseUrl","topLevelModel","envModel","userModel","resolveKey","envVarName","fromProcess","fromSettings","providerHint","baseUrlSuggestsAnthropic","anthropicKey","llmliteToken","confirmWikiGeneration","model","confirm","tailLines","text","n","runGitnexusWikiConditional","runDir","creds","log","reasoningMode","isReasoningModel","writeGitnexusConfigForWikiRun","args","sp","spinnerWithElapsed","result","spawnSync","reason","stderr","stdout","wikiPath","existsSync","init_terminal_logger","registerAddCommand","program","opts","runAddRepo","err","log","ensureWorkspace","root","resolveAvatarWorkspaceRootFromCwd","workspaceRoot","addMore","firstUrl","firstName","url","resolveRepoUrl","name","promptRepoName","cloned","skipped","cloneRepoWithRecovery","maybeIndexRepo","confirm","presetUrl","source","select","input","v","folderPath","resolve","dirty","folderDirtyStatus","remote","readFolderRemoteUrl","ensureFolderHasRemote","ensureGitHubReady","projectName","visibility","createEmptyGithubRepoAndGetUrl","basename","git","pathExists","g","repoName","createGithubRemoteFromFolder","urls","inferred","inferRepoNameFromUrl","repoPath","autoYes","detectGitnexusInstallation","runGitnexusAnalyze","wiki","runGitnexusWikiConditional","init_filesystem_helpers","fs","join","confirm","fs","homedir","join","z","userConfigSchema","userStateSchema","projectSettingsSchema","initModeSchema","init_filesystem_helpers","AVATAR_HOME","join","homedir","USER_CONFIG_PATH","USER_STATE_PATH","AUDIT_LOG_PATH","BACKUPS_DIR","SECRET_FILE_MODE","ensureAvatarHome","ensureDir","readUserConfig","pathExists","raw","readJson","parsed","userConfigSchema","writeUserConfig","config","writeJsonAtomic","clearUserConfig","fs","isTokenExpired","config","expiresAt","NoValidTokenError","message","idTokenIsExpired","idToken","decodeIdToken","claims","nowSec","getValidIdToken","readUserConfig","refreshAccessToken","verifyIdTokenClaims","refreshed","err","updated","writeUserConfig","ensureAuditLogPermissions","fs","AUDIT_LOG_PATH","appendAuditEntry","action","detail","ensureAvatarHome","entry","line","init_terminal_logger","spawnSync","QUOTA_VERIFY_TIMEOUT_MS","QUOTA_VERIFY_PROMPT","readClaudeCodeAuthInfo","result","spawnSync","stdout","parsed","triggerClaudeCodeAuthLogin","log","classifyQuotaError","combinedOutput","text","getQuotaErrorHint","reason","verifyClaudeCodeQuota","QUOTA_VERIFY_PROMPT","QUOTA_VERIFY_TIMEOUT_MS","stderr","stdoutTrimmed","stderrLower","spawnSync","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeClaudeBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeClaudeVersion","cachedInfo","detectClaudeCodeInstallation","path","invalidateClaudeCodeInstallationCache","spawnSync","init_terminal_logger","NPM_INSTALL_TIMEOUT_MS","CLAUDE_CODE_PACKAGE","InstallClaudeCodeError","reason","message","exitCode","classifyNpmFailure","stderrSample","stderr","installClaudeCodeViaNpm","log","result","spawnSync","invalidateClaudeCodeInstallationCache","probe","detectClaudeCodeInstallation","readFileSync","homedir","join","select","getGlobalSettingsPath","detectGlobalClaudeSettings","path","raw","parsed","env","baseUrl","hasToken","model","promptAiProviderChoice","globalInfo","init_terminal_logger","password","select","ANTHROPIC_BASE_URL","ANTHROPIC_API_VERSION","FETCH_TIMEOUT_MS","maskAnthropicKey","key","validateAnthropicKeyFormat","trimmed","promptAnthropicKeyHidden","fetchAnthropicModels","apiKey","controller","timer","res","models","m","id","err","promptAnthropicModelChoice","only","log","sorted","a","b","score","lower","setupAnthropicApiKeyAndModel","model","init_terminal_logger","input","password","select","DEFAULT_BASE_URL","FETCH_TIMEOUT_MS","maskApiKey","key","promptApiKeyHidden","v","promptBaseUrl","defaultUrl","fetchAvailableModels","baseUrl","apiKey","controller","timer","res","models","m","id","err","vpnHint","errMsg","promptModelChoice","claudeAliases","only","log","choiceList","setupLLMLiteApiKeyAndModel","model","init_terminal_logger","init_filesystem_helpers","fs","join","SECRET_FILE_MODE","getClaudeSettingsPath","workspacePath","readExistingSettings","path","pathExists","readJson","err","applySubscription","existing","model","existingEnv","rest","merged","_b","_t","_k","envRest","stripAllProviderKeys","env","applyLLMLite","apiKey","baseUrl","skipApiKey","applyAnthropic","applyUseGlobal","source","sourceEnv","sourceModel","writeClaudeSettings","input","writeJsonAtomic","SUBSCRIPTION_DEFAULT_MODEL","runAiSetupPhase","args","log","info","detectClaudeCodeInstallation","installClaudeCodeViaNpm","invalidateClaudeCodeInstallationCache","globalInfo","detectGlobalClaudeSettings","promptAiProviderChoice","authInfo","readClaudeCodeAuthInfo","triggerClaudeCodeAuthLogin","writeClaudeSettings","appendAuditEntry","quota","verifyClaudeCodeQuota","reason","getQuotaErrorHint","llmConfig","setupLLMLiteApiKeyAndModel","anthropicConfig","setupAnthropicApiKeyAndModel","err","message","init_terminal_logger","spawnSync","init_terminal_logger","FETCH_TIMEOUT_MS","CLAUDE_PRINT_TIMEOUT_MS","TEST_CHAT_MAX_TOKENS","TEST_CHAT_PROMPT","ANTHROPIC_API_VERSION","testLLMLiteProvider","baseUrl","token","model","log","maskApiKey","controller","timer","modelsRes","models","m","id","preview","more","chatRes","errBody","chatJson","reply","tokens","err","testSubscriptionProvider","result","spawnSync","stderr","testAnthropicProvider","apiKey","msgRes","errText","text","c","testAiProviderByDetectedMode","settings","env","ensureWorkspaceCwd","cwd","workspaceRoot","resolveAvatarWorkspaceRootFromCwd","log","readWorkspaceSettings","workspacePath","settingsPath","join","pathExists","readJson","runAiSetup","runAiSetupPhase","runAiStatus","settings","env","baseUrl","token","apiKey","model","provider","credentialDisplay","maskApiKey","runAiTest","result","testAiProviderByDetectedMode","err","runAiReset","opts","confirm","existingEnv","rest","reset","_b","_t","_k","envRest","fs","writeJsonAtomic","registerAiCommand","program","ai","spawnSync","fs","join","boxen","join","init_filesystem_helpers","fs","join","init_filesystem_helpers","init_terminal_logger","fs","path","join","isStatusLineCommandResolvable","workspacePath","command","match","filePath","log","fullPath","wsResolved","targetResolved","pathExists","backupFilename","originalPath","d","stamp","unionDedupe","a","b","seen","out","item","key","normalizeCommandForMigration","extractCommandsFromEntry","entry","obj","hooks","cmds","h","cmd","migrateStaleRelativePathHooks","entries","indexed","index","commands","hasVar","c","byNormalized","ie","list","toDrop","items","it","_","i","mergeHooksPerEvent","packHooks","userHooks","touched","merged","totalMigrated","event","userEntries","migrated","droppedCount","packEntries","union","postMigrate","postDropped","mergePackSettingsIntoProjectSettings","packTemplatePath","projectSettingsPath","packTemplate","raw","readText","err","userSettings","projectHasSettings","readJson","changes","mergedEnv","envChanged","k","v","userAllow","userDeny","packAllow","packDeny","mergedAllow","mergedDeny","mergedHooks","touchedEvents","migratedCount","backupPath","writeJsonAtomic","featureManifestPath","workspacePath","name","join","readToolManifest","path","pathExists","readJson","PROJECT_SETTINGS_REL","projectSettingsPath","hookEntryKey","entry","commands","h","loadProjectSettings","err","backupAndWrite","settings","existed","backupPath","backupFilename","fs","writeJsonAtomic","normalizeHookEntryForMigration","entryUsesProjectDirVar","migrateStaleEntries","userEntries","featureEntries","featureNormalized","fe","dropped","ue","enableTool","manifest","merged","changes","mHooks","userHooks","outHooks","touched","totalMigrated","event","featEntries","existing","migratedUser","droppedCount","seen","toAdd","e","featDeny","userDeny","unionDeny","unionDedupe","disableTool","_h","_p","rest","removed","entries","featKeys","kept","keptDeny","d","_d","permRest","newPerms","init_filesystem_helpers","init_filesystem_helpers","join","TOOL_STATE_RELATIVE_PATH","emptyState","stateFilePath","workspacePath","readToolState","path","pathExists","readJson","writeToolState","state","writeJsonAtomic","setToolState","name","entry","listEnabledTools","manifestHooksPresent","settings","manifest","mHooks","sHooks","event","featEntries","present","presentCommands","e","h","entry","checkEnabledToolsHealth","cwd","enabled","listEnabledTools","settingsPath","join","pathExists","readJson","checks","name","readToolManifest","enableTool","init_filesystem_helpers","init_terminal_logger","registerDoctorCommand","program","opts","checks","runChecks","renderChecks","applyFixes","err","log","cwd","nodeVer","major","minor","n","nodeOk","config","readUserConfig","isTokenExpired","packPath","join","claudeMdPath","hasPack","hasClaudeMd","pathExists","claudeGitignorePath","settingsProtected","fs","writeClaudeGitignore","pythonCheck","spawnSync","python3Check","hasPython","hasPython3","settingsPath","settingsRaw","settings","match","refFile","fullPath","fileExists","which","hasClaudeCli","toolChecks","checkEnabledToolsHealth","lines","chalk","passed","issues","fixable","c","icon","boxen","count","init_filesystem_helpers","spawnSync","fs","join","confirm","boxen","spawnSync","init_terminal_logger","NPM_INSTALL_TIMEOUT_MS","GITNEXUS_PACKAGE","InstallGitnexusError","reason","message","exitCode","classifyNpmFailure","stderrSample","stderr","installGitnexusViaNpm","log","result","spawnSync","invalidateGitnexusInstallationCache","probe","detectGitnexusInstallation","init_filesystem_helpers","init_terminal_logger","fs","homedir","join","MCP_FILE_MODE","EXPECTED_GITNEXUS_ENTRY","getMcpServersPath","isEntryEqual","a","b","backupExistingFile","path","ts","backupPath","registerGitnexusMcpServer","existing","fileExisted","pathExists","readJson","err","existingEntry","log","backup","merged","writeJsonAtomic","init_terminal_logger","promptInstallGitnexus","lines","chalk","boxen","confirm","installWithRecovery","installGitnexusViaNpm","err","message","hint","InstallGitnexusError","action","promptRetryOrSkip","UserAbortedRecoveryError","analyzeWithRecovery","workspacePath","runGitnexusAnalyze","GitnexusOperationError","runGitnexusSetupPhase","args","result","log","info","detectGitnexusInstallation","appendAuditEntry","invalidateGitnexusInstallationCache","runGitnexusSetup","wikiResult","runGitnexusWikiConditional","mcpResult","registerGitnexusMcpServer","init_terminal_logger","ensureWorkspaceCwd","cwd","workspaceRoot","resolveAvatarWorkspaceRootFromCwd","log","runGitnexusInstall","workspacePath","result","runGitnexusSetupPhase","runGitnexusStatus","metaPath","join","pathExists","meta","readJson","headResult","spawnSync","wikiPath","stat","fs","err","runGitnexusAnalyzeCommand","runGitnexusAnalyze","registerGitnexusCommand","program","gx","resolve","input","chalk","BANNER_LINES","GRADIENT_STOPS","lerpChannel","a","b","t","gradientAt","scaled","lo","hi","localT","renderAvatarBanner","opts","colored","line","idx","r","g","printAvatarBanner","readdirSync","select","simpleGit","existsSync","statSync","join","simpleGit","existsSync","join","readFileSync","dirname","join","fileURLToPath","__dirname","CANDIDATE_DIRS","AVATAR_MARKER_START","AVATAR_MARKER_END","init_terminal_logger","existsSync","readFileSync","writeFileSync","join","InitAbortedByUserError","message","join","init_filesystem_helpers","join","PACK_MANIFEST_FILENAME","PACK_VERSION_FILENAME","manifestPath","packDir","writePackManifest","data","writeJsonAtomic","readPackManifest","p","pathExists","readJson","readPackVersionFromFile","manifest","versionFile","v","readText","init_filesystem_helpers","fs","join","DEFAULT_GET_PACK_ENDPOINT","getPackEndpoint","InvalidIdTokenError","message","PackNetworkError","PackParseError","fetchSignedPackUrl","idToken","version","res","err","detail","safeErrorText","body","downloadAndExtractPack","signedUrl","packDir","parent","ensureDir","tmpTarball","tmpExtractDir","downloadFile","removeRecursive","extractTarballToDir","backupDir","hadOld","pathExists","TEAM_PACK_RELATIVE_PATH","TeamPackAccessAbortedError","message","addTeamPackSubmodule","projectRoot","version","idToken","getValidIdToken","info","fetchSignedPackUrl","packDir","join","downloadAndExtractPack","installedVersion","objectToVersion","writePackManifest","object","readPinnedPackVersion","readPackVersionFromFile","init_terminal_logger","init_filesystem_helpers","join","relative","resolve","input","select","boxen","init_terminal_logger","boxen","PACK_COMMAND_CHEATSHEET","formatPackCommandsCheatsheetBox","maxCmdWidth","e","header","chalk","subheader","lines","footer","content","init_terminal_logger","init_filesystem_helpers","init_project_tree_scaffolder","readdir","join","isEmptyOrMissing","path","pathExists","e","findAlternativeWorkspaceName","parent","desiredName","maxAttempts","i","candidate","join","isEmptyOrMissing","assertWorkspaceNameSafe","parent","name","resolved","resolve","join","resolvedParent","resolveWorkspacePath","desiredName","force","desired","isEmptyOrMissing","log","alternative","findAlternativeWorkspaceName","choices","action","select","UserAbortedRecoveryError","newName","input","v","trimmed","newPath","promptTeamOwner","currentUserEmail","ensureWorkspaceFolder","workspacePath","ensureDir","err","e","formatAiStatusLine","aiResult","chalk","modelPart","formatGitnexusStatusLine","result","parts","printInitSuccessBox","rootPath","gitnexusResult","lines","relative","boxen","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","formatPackCommandsCheatsheetBox","boxen","open","init_google_oauth_device_flow","init_terminal_logger","registerLoginCommand","program","opts","runLogin","err","log","printAvatarBanner","clearUserConfig","appendAuditEntry","existing","readUserConfig","isTokenExpired","deviceSpinner","spinner","deviceCode","requestDeviceCode","verificationUrl","buildVerificationUrl","instructions","chalk","boxen","open","waitSpinner","intervalMs","deadline","token","sleep","pollForToken","claims","decodeIdToken","verifyHostedDomain","revokeToken","userConfig","buildUserConfig","writeUserConfig","USER_CONFIG_PATH","ms","resolve","join","init_terminal_logger","MAX_NETWORK_RETRIES","addTeamPackSubmoduleWithRetryOnNetworkFail","projectRoot","version","_ssoEmail","_latest","networkAttempt","addTeamPackSubmodule","err","TeamPackAccessAbortedError","InvalidIdTokenError","log","action","promptRetryOrSkip","UserAbortedRecoveryError","PackNetworkError","sleep","message","ms","resolve","init_filesystem_helpers","init_project_tree_scaffolder","confirm","init_filesystem_helpers","fs","join","PACK_FEATURES_REL","packToolsDir","workspacePath","join","listAvailableTools","dir","pathExists","entries","fs","names","entry","readDefaultTools","path","readJson","discoverEnabledAndAvailableTools","available","enabled","listEnabledTools","init_terminal_logger","spawnSync","init_terminal_logger","warnIfRuntimeMissing","runtime","probe","spawnSync","log","logApplyResult","name","result","enableToolByName","workspacePath","opts","manifest","readToolManifest","enableTool","setToolState","disableToolByName","prevVersion","readToolState","disableTool","DISPLAY_NAME","setupDefaultToolsOnInit","workspacePath","opts","defaults","readDefaultTools","name","display","wantEnable","confirm","log","enableToolByName","fs","dirname","join","relative","fs","timestamp","d","pad","backupDirBeforeReplace","targetPath","backupPath","init_filesystem_helpers","TEAM_PACK_MOUNT_DIRS","isSymbolicLink","path","fs","syncMountedDir","source","dest","force","dir","relative","dirname","pathExists","backupPath","backupDirBeforeReplace","relativeSource","syncAllMountDirs","packDir","claudeDir","results","join","init_terminal_logger","readFileSync","dirname","resolve","fileURLToPath","cachedVersion","readCliVersion","here","i","candidate","raw","pkg","buildGitnexusSection","gitnexusReady","buildScaffoldVariables","args","readCliVersion","scaffoldThinWorkspace","args","ensureWorkspaceFolder","ensureDir","join","pinnedTag","log","spinner","result","addTeamPackSubmoduleWithRetryOnNetworkFail","vars","buildScaffoldVariables","createClaudeDirTree","writeProjectKnowledgeFiles","writeRootClaudeMd","writeProjectSettings","writeClaudeGitignore","autoSyncPackOnInit","appendAuditEntry","aiResult","runAiSetupPhase","gitnexusResult","runGitnexusSetupPhase","printInitSuccessBox","workspacePath","autoYes","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","claudeDir","results","syncAllMountDirs","created","r","missing","mergeResult","mergePackSettingsIntoProjectSettings","setupDefaultToolsOnInit","err","registerInitCommand","program","workspaceName","opts","runInit","err","InitAbortedByUserError","TeamPackAccessAbortedError","RemoteAccessAbortedError","UserAbortedRecoveryError","log","printAvatarBanner","userConfig","readUserConfig","isTokenExpired","runLogin","name","input","v","teamOwner","promptTeamOwner","workspaceParent","resolve","workspacePath","resolveWorkspacePath","scaffoldThinWorkspace","init_terminal_logger","notImplementedYet","commandName","milestone","chalk","registerMcpRunCommand","program","notImplementedYet","init_filesystem_helpers","join","boxen","init_terminal_logger","PACK_RELATIVE_PATH","registerPackCommand","program","opts","snap","gatherPackStatus","renderPackStatus","err","log","cwd","packDir","join","pathExists","readPinnedPackVersion","s","lines","chalk","boxen","current","registerRestoreCommand","program","notImplementedYet","registerReviewCommand","program","notImplementedYet","registerScanCommand","program","notImplementedYet","fs","join","boxen","init_filesystem_helpers","init_filesystem_helpers","fs","join","BACKUP_DIR_NAME","listBackups","projectRoot","dir","join","BACKUP_DIR_NAME","pathExists","fs","e","init_terminal_logger","registerStatusCommand","program","opts","snapshot","gatherStatus","renderStatusBox","err","log","cwd","projectName","claudeRoot","join","pathExists","readCliVersion","pendingDir","packVersion","pendingCount","backupCount","techStackSummary","tools","readPinnedPackVersion","fs","n","listBackups","b","readTechStackFirstLine","discoverEnabledAndAvailableTools","techStackPath","readText","l","formatTools","s","lines","chalk","boxen","init_filesystem_helpers","join","init_filesystem_helpers","join","inspectMountDir","packDir","claudeDir","dir","source","join","dest","pathExists","fs","buildSyncPreview","targetVersion","currentVersion","readPackVersionFromFile","target","mountStatuses","TEAM_PACK_MOUNT_DIRS","init_terminal_logger","reapplyEnabledToolsAfterSync","workspacePath","enabled","listEnabledTools","log","name","enableToolByName","init_terminal_logger","objectToVersion","object","syncAction","opts","projectRoot","claudeDir","join","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","log","preview","buildSyncPreview","m","idToken","getValidIdToken","err","installedVersion","info","fetchSignedPackUrl","downloadAndExtractPack","writePackManifest","InvalidIdTokenError","results","syncAllMountDirs","reportResults","mergeResult","mergePackSettingsIntoProjectSettings","reapplyEnabledToolsAfterSync","force","conflicts","registerSyncCommand","program","resolve","checkbox","confirm","init_terminal_logger","buildAddChoices","available","enabled","enabledSet","name","buildRemoveChoices","resolveNonInteractiveSelection","candidates","resolveWorkspace","opts","resolve","selectTools","candidates","preChecked","verb","resolveNonInteractiveSelection","choices","buildAddChoices","buildRemoveChoices","checkbox","runAdd","ws","available","enabled","discoverEnabledAndAvailableTools","log","selected","name","enableToolByName","runRemove","confirm","disableToolByName","runList","listAvailableTools","state","readToolState","names","isAvailable","entry","version","registerToolsCommand","program","tools","relative","confirm","boxen","cp","mkdir","writeFile","homedir","basename","join","UNINSTALL_BACKUPS_DIR","createUninstallBackupSnapshot","projectRoot","artifacts","avatarVersion","projectName","timestamp","backupDir","hooksBackupDir","manifest","existsSync","join","existsOrNull","path","detectAvatarProjectArtifacts","projectRoot","claudeDir","claudeMd","postMergeHook","prePushHook","gitignorePath","gitmodulesPath","notesDir","scriptsDir","readFile","rm","writeFile","executeUninstallDeletion","artifacts","flags","readdir","join","entries","entry","rm","stripAvatarBlockFromGitignore","removeSubmoduleEntry","dir","path","content","readFile","startIdx","AVATAR_MARKER_START","endIdx","AVATAR_MARKER_END","before","after","cleaned","writeFile","gitmodulesPath","submodulePath","lines","result","skip","line","init_terminal_logger","registerUninstallCommand","program","opts","runUninstall","err","log","projectRoot","artifacts","detectAvatarProjectArtifacts","printUninstallSummary","confirm","backupPath","createUninstallBackupSnapshot","readCliVersion","executeUninstallDeletion","appendAuditEntry","printUninstallSuccessBox","chalk","relative","lines","boxen","CLI_VERSION","readCliVersion","program","Command","renderAvatarBanner","isVersionCall","printAvatarBanner","registerLoginCommand","registerInitCommand","registerSyncCommand","registerScanCommand","registerReviewCommand","registerStatusCommand","registerDoctorCommand","registerRestoreCommand","registerToolsCommand","registerMcpRunCommand","registerAiCommand","registerGitnexusCommand","registerPackCommand","registerAddCommand","registerUninstallCommand","err","msg"]}