@nalvietnam/avatar-cli 1.18.0 → 1.20.0
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 +86 -71
- package/dist/index.js.map +1 -1
- package/dist/templates/gitignore.tpl +9 -0
- package/package.json +1 -1
- package/src/templates/gitignore.tpl +9 -0
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/commands/ai.ts","../src/lib/filesystem-helpers.ts","../src/lib/resolve-avatar-workspace-root-from-cwd.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/terminal-logger.ts","../src/lib/detect-claude-code-installation.ts","../src/lib/detect-host-platform.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/write-claude-settings-json-per-project.ts","../src/lib/run-ai-setup-phase.ts","../src/lib/test-ai-provider-by-detected-mode.ts","../src/commands/commit.ts","../src/lib/execute-commit-with-target-selection.ts","../src/commands/doctor.ts","../src/lib/check-enabled-features-health.ts","../src/lib/apply-feature-manifest-to-settings.ts","../src/lib/merge-pack-settings-into-project-settings.ts","../src/lib/feature-state-store.ts","../src/lib/git-operations.ts","../src/lib/project-tree-scaffolder.ts","../src/lib/template-bundle-loader.ts","../src/lib/mustache-template-engine.ts","../src/commands/feature.ts","../src/lib/discover-pack-features-and-defaults.ts","../src/lib/feature-add-remove-selection.ts","../src/lib/feature-enable-disable-orchestrator.ts","../src/commands/gitnexus.ts","../src/lib/run-gitnexus-setup-and-analyze.ts","../src/lib/run-gitnexus-setup-phase.ts","../src/lib/detect-gitnexus-installation.ts","../src/lib/install-gitnexus-via-npm.ts","../src/lib/prompt-recovery-action-on-failure.ts","../src/lib/register-gitnexus-mcp-server.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/init.ts","../src/lib/avatar-ascii-banner.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/resolve-github-username-default.ts","../src/lib/validate-repo-name-and-visibility.ts","../src/lib/create-github-remote-from-folder.ts","../src/lib/verify-git-remote-accessible.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/check-team-pack-access-with-retry-loop.ts","../src/lib/pick-latest-stable-semver-tag.ts","../src/lib/resolve-team-pack-repo-url.ts","../src/commands/init-flow-handlers-for-each-project-status.ts","../src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts","../src/lib/check-gh-cli-auth-status.ts","../src/lib/detect-package-manager.ts","../src/lib/install-gh-cli-via-package-manager.ts","../src/lib/setup-git-credential-via-gh.ts","../src/lib/trigger-gh-cli-auth-login.ts","../src/lib/git-auth-and-install-orchestrator.ts","../src/commands/init-options-and-bootstrap-strategy-parser.ts","../src/lib/read-cli-version-from-package-json.ts","../src/commands/init-scaffold-variable-builders.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/workspace-scaffold-and-finalize-orchestrator.ts","../src/lib/create-workspace-remote-via-gh.ts","../src/lib/setup-default-features-on-init.ts","../src/lib/symlink-farm-for-team-pack-mount-dirs.ts","../src/lib/backup-existing-dir-before-symlink-override.ts","../src/commands/login.ts","../src/lib/google-oauth-device-flow.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/secrets.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-features-after-sync.ts","../src/commands/tools.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":["// 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 { registerAiCommand } from \"./commands/ai.js\";\nimport { registerCommitCommand } from \"./commands/commit.js\";\nimport { registerDoctorCommand } from \"./commands/doctor.js\";\nimport { registerFeatureCommand } from \"./commands/feature.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 { registerSecretsCommand } from \"./commands/secrets.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);\nregisterCommitCommand(program);\nregisterToolsCommand(program);\nregisterSecretsCommand(program);\nregisterMcpRunCommand(program);\nregisterAiCommand(program);\nregisterGitnexusCommand(program);\nregisterFeatureCommand(program);\nregisterPackCommand(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","// `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","// 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 { constants, promises as fs } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\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","// 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// Strict mode: cả 3 marker phải có (giảm false positive).\nfunction isAvatarWorkspace(dir: string): boolean {\n const hasClaudeDir = existsSync(join(dir, \".claude\"));\n const hasClaudeMd = existsSync(join(dir, \"CLAUDE.md\"));\n if (!hasClaudeDir || !hasClaudeMd) return false;\n\n // Avatar workspace HOẶC có .gitmodules với src/ entry, HOẶC có src/ folder\n // (cover cả case clone fresh chưa có .gitmodules nhưng đã có src/).\n const hasSrcDir = existsSync(join(dir, \"src\"));\n const gitmodulesPath = join(dir, \".gitmodules\");\n if (hasSrcDir) return true;\n if (existsSync(gitmodulesPath)) {\n try {\n const content = readFileSync(gitmodulesPath, \"utf8\");\n return content.includes(\"path = src\") || content.includes(\"path = ./src\");\n } catch {\n return false;\n }\n }\n return false;\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","// 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","// 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","// 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","// 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","// 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","// 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 llm.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)\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://llm.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 path Subscription / LLMLite / Anthropic direct.\n return (await select({\n message: \"Chọn provider cho AI features:\",\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. LLMLite API key (llm.nal.vn — NAL gateway, 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://llm.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://llm.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 (llm.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: llm.nal.vn là internal endpoint NAL — kiểm tra VPN NAL đã bật chưa, hoặc tcp test bằng `curl -v https://llm.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 ? \" (llm.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","// 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, 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\nexport type ClaudeSettingsInput =\n | { provider: \"subscription\"; model: string }\n | { provider: \"llmlite\"; apiKey: string; baseUrl: string; model: string }\n | { provider: \"anthropic\"; apiKey: string; baseUrl: string; model: string }\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 [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// LLMLite path: ANTHROPIC_AUTH_TOKEN (Bearer auth) + baseUrl gateway + model.\n// Clear ANTHROPIC_API_KEY nếu có (tránh xung đột khi switch provider).\nfunction applyLLMLite(\n existing: ClaudeSettings,\n apiKey: string,\n baseUrl: string,\n model: string,\n): ClaudeSettings {\n const { ANTHROPIC_API_KEY: _k, ...envRest } = existing.env || {};\n return {\n ...existing,\n env: {\n ...envRest,\n ANTHROPIC_BASE_URL: baseUrl,\n ANTHROPIC_AUTH_TOKEN: apiKey,\n },\n model,\n };\n}\n\n// Anthropic direct path: ANTHROPIC_API_KEY (x-api-key auth) + baseUrl official + model.\n// Clear ANTHROPIC_AUTH_TOKEN nếu có (tránh Claude Code prefer wrong header).\nfunction applyAnthropic(\n existing: ClaudeSettings,\n apiKey: string,\n baseUrl: string,\n model: string,\n): ClaudeSettings {\n const { ANTHROPIC_AUTH_TOKEN: _t, ...envRest } = existing.env || {};\n return {\n ...existing,\n env: {\n ...envRest,\n ANTHROPIC_BASE_URL: baseUrl,\n ANTHROPIC_API_KEY: apiKey,\n },\n model,\n };\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(existing, input.apiKey, input.baseUrl, input.model);\n break;\n case \"anthropic\":\n merged = applyAnthropic(existing, input.apiKey, input.baseUrl, input.model);\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","// 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.7.0: Warn user về API key plaintext trong settings.json — security reminder.\n// Show sau khi provider setup thành công (LLMLite / Anthropic Direct). Avatar đã\n// add `.claude/settings.json` vào .gitignore từ v1.7.0, nhưng workspace cũ\n// (pre-1.7) có thể chưa có entry này → user cần verify.\nfunction warnAboutPlaintextSecret(providerLabel: string): void {\n log.warn(\n `🔒 ${providerLabel} key đã lưu PLAINTEXT vào .claude/settings.json.\\n File này được gitignore từ Avatar v1.7.0, nhưng workspace cũ có thể CHƯA.\\n Kiểm tra: grep '.claude/settings.json' .gitignore\\n Nếu chưa có → THÊM NGAY, tránh leak key khi commit/push.`,\n );\n}\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 await writeClaudeSettings(args.workspacePath, {\n provider: \"llmlite\",\n apiKey: llmConfig.apiKey,\n baseUrl: llmConfig.baseUrl,\n model: llmConfig.model,\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=llmlite,result=ok,model=${llmConfig.model},base=${llmConfig.baseUrl}`,\n );\n log.success(`AI ready · LLMLite · model=${llmConfig.model} · ${llmConfig.baseUrl}`);\n warnAboutPlaintextSecret(\"LLMLite\");\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 // Verify qua GET https://api.anthropic.com/v1/models trước khi write settings.\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 });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=anthropic,result=ok,model=${anthropicConfig.model}`,\n );\n log.success(\n `AI ready · Anthropic Direct · model=${anthropicConfig.model} · ${anthropicConfig.baseUrl}`,\n );\n warnAboutPlaintextSecret(\"Anthropic Direct API\");\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","// 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","// `avatar commit src [-m <msg>] [--push]` — Command 09.\n//\n// Member dev CHỈ ĐƯỢC commit code khách trong src/. Workspace state team-shared\n// (.claude/, CLAUDE.md, knowledge) là responsibility của team admin sync — không\n// để member commit/push trực tiếp tránh leak knowledge và race conflict.\n//\n// → Không expose `workspace` hoặc `all` subcommands. Chỉ `avatar commit src`.\nimport { input } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport { executeCommitWithTargetSelection } from \"../lib/execute-commit-with-target-selection.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\ninterface CommitOptions {\n message?: string;\n push?: boolean;\n}\n\nexport function registerCommitCommand(program: Command): void {\n const commit = program\n .command(\"commit\")\n .description(\"Commit code khách trong src/ (Avatar state do admin sync) (M07)\");\n\n commit\n .command(\"src\")\n .description(\"Commit src/ → push lên client repo remote\")\n .option(\"-m, --message <msg>\", \"Commit message\")\n .option(\"--push\", \"Auto push sau commit (default: chỉ commit)\")\n .action(async (opts: CommitOptions) => {\n await runCommitSrc(opts);\n });\n}\n\n// Run commit src only — không subcommand khác để member không vô tình ghi workspace state.\nasync function runCommitSrc(opts: CommitOptions): Promise<void> {\n try {\n const message =\n opts.message ??\n (await input({\n message: \"Commit message:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Message bắt buộc\"),\n }));\n\n const result = await executeCommitWithTargetSelection({\n workspaceRoot: process.cwd(),\n target: \"src\",\n options: { message, push: opts.push },\n });\n\n if (result.srcCommitSha) {\n log.success(`src/: ${result.srcCommitSha.slice(0, 7)}${result.srcPushed ? \" (pushed)\" : \"\"}`);\n }\n if (result.skipped && result.skipped.length > 0) {\n log.dim(`Skipped (nothing to commit): ${result.skipped.join(\", \")}`);\n }\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n","// Core logic cho `avatar commit src` — wrap git operations với awareness về\n// workspace structure 2-repo (src/ submodule + workspace root).\n//\n// Mục đích: Claude Code/member dev không cần biết detail layout, chỉ gọi\n// `avatar commit src` và Avatar route đúng vào src/ repo.\n//\n// v1.3.2: Bỏ targets workspace/all khỏi member-facing API. Workspace state\n// team-shared, chỉ admin sync (Avatar không expose lệnh cho member). Type\n// `CommitTarget` giữ extensible cho admin tools tương lai nhưng hiện tại\n// chỉ \"src\" được caller dùng. Caller pass target=workspace/all sẽ vẫn work\n// internally (admin scripts), không expose qua CLI.\n//\n// Behavior matrix (admin-only paths):\n// target=src → cd src/ → git add . && commit + optionally push\n// target=workspace → workspace root → git add ALL_EXCEPT_src + commit + push\n// target=all → src trước (push), update gitlink pointer, workspace sau\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { log } from \"./terminal-logger.js\";\n\nexport type CommitTarget = \"src\" | \"workspace\" | \"all\";\n\nexport interface CommitOptions {\n message: string;\n push?: boolean;\n}\n\nexport interface CommitResult {\n target: CommitTarget;\n srcCommitSha?: string; // SHA của commit src/ nếu có\n workspaceCommitSha?: string; // SHA của commit workspace nếu có\n srcPushed?: boolean;\n workspacePushed?: boolean;\n skipped?: string[]; // List repos skip do \"nothing to commit\"\n}\n\n// Check workspace structure — phải có src/ là submodule + workspace có .git riêng.\n// Throw nếu cwd không phải Avatar workspace root.\nexport function assertAvatarWorkspaceRoot(cwd: string): void {\n const srcGit = join(cwd, \"src\", \".git\");\n const workspaceGit = join(cwd, \".git\");\n const claudeDir = join(cwd, \".claude\");\n\n if (!existsSync(workspaceGit)) {\n throw new Error(\n `Không phải workspace root: ${cwd}\\n Chạy 'avatar commit' trong workspace dir (có .git và .claude/).`,\n );\n }\n if (!existsSync(claudeDir)) {\n throw new Error(\n `Không thấy .claude/ trong ${cwd}.\\n Chạy 'avatar commit' trong Avatar workspace, không phải project bình thường.`,\n );\n }\n if (!existsSync(srcGit)) {\n throw new Error(\n `Không thấy src/.git trong ${cwd}.\\n Workspace thiếu submodule src/. Chạy 'avatar init' lại?`,\n );\n }\n}\n\n// Spawn git command + capture output. Trả stdout trimmed, throw nếu fail với stderr.\nfunction gitExec(cwd: string, args: string[]): string {\n const r = spawnSync(\"git\", [\"-C\", cwd, ...args], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.status !== 0) {\n const stderr = (r.stderr || \"\").trim();\n throw new Error(`git ${args.join(\" \")} (in ${cwd}) failed:\\n${stderr}`);\n }\n return (r.stdout || \"\").trim();\n}\n\n// Check repo có changes staged hoặc unstaged không. Trả true nếu dirty.\nfunction isDirty(cwd: string): boolean {\n const status = gitExec(cwd, [\"status\", \"--porcelain\"]);\n return status.length > 0;\n}\n\n// Commit src/ submodule. Add all files trong src/, commit với message, optionally push.\n// Trả SHA commit mới, hoặc undefined nếu nothing to commit.\nasync function commitSrc(\n workspaceRoot: string,\n opts: CommitOptions,\n): Promise<{ sha?: string; pushed?: boolean }> {\n const srcPath = join(workspaceRoot, \"src\");\n if (!isDirty(srcPath)) {\n log.dim(\"src/: nothing to commit (clean)\");\n return {};\n }\n\n log.info(\"Committing src/ ...\");\n gitExec(srcPath, [\"add\", \".\"]);\n gitExec(srcPath, [\"commit\", \"-m\", opts.message]);\n const sha = gitExec(srcPath, [\"rev-parse\", \"HEAD\"]);\n log.success(`src/ committed: ${sha.slice(0, 7)}`);\n\n let pushed = false;\n if (opts.push) {\n log.info(\"Pushing src/ ...\");\n gitExec(srcPath, [\"push\"]);\n log.success(\"src/ pushed\");\n pushed = true;\n }\n return { sha, pushed };\n}\n\n// Commit workspace root EXCEPT src/. Git tự exclude submodule content khi\n// dùng `git add` ở parent — chỉ track gitlink (SHA pointer).\n// Nếu src/ vừa commit (gitlink update), workspace add sẽ stage update đó.\nasync function commitWorkspace(\n workspaceRoot: string,\n opts: CommitOptions,\n): Promise<{ sha?: string; pushed?: boolean }> {\n if (!isDirty(workspaceRoot)) {\n log.dim(\"workspace: nothing to commit (clean)\");\n return {};\n }\n\n log.info(\"Committing workspace root ...\");\n gitExec(workspaceRoot, [\"add\", \".\"]);\n gitExec(workspaceRoot, [\"commit\", \"-m\", opts.message]);\n const sha = gitExec(workspaceRoot, [\"rev-parse\", \"HEAD\"]);\n log.success(`workspace committed: ${sha.slice(0, 7)}`);\n\n let pushed = false;\n if (opts.push) {\n log.info(\"Pushing workspace ...\");\n gitExec(workspaceRoot, [\"push\"]);\n log.success(\"workspace pushed\");\n pushed = true;\n }\n return { sha, pushed };\n}\n\n// Main entry — route theo target.\n// v1.3.2: Bỏ warnIfOtherTargetDirty (Q2=c: silent ignore workspace state).\n// Member dev chỉ care src/ commit. Workspace dirty state là responsibility\n// của admin sync, không phải concern của member khi commit code.\nexport async function executeCommitWithTargetSelection(args: {\n workspaceRoot: string;\n target: CommitTarget;\n options: CommitOptions;\n}): Promise<CommitResult> {\n assertAvatarWorkspaceRoot(args.workspaceRoot);\n\n const result: CommitResult = { target: args.target, skipped: [] };\n\n if (args.target === \"src\" || args.target === \"all\") {\n const srcOutcome = await commitSrc(args.workspaceRoot, args.options);\n result.srcCommitSha = srcOutcome.sha;\n result.srcPushed = srcOutcome.pushed;\n if (!srcOutcome.sha) result.skipped?.push(\"src\");\n }\n\n if (args.target === \"workspace\" || args.target === \"all\") {\n // Khi target=all + src vừa commit: workspace sẽ stage gitlink update + Avatar state.\n const wsOutcome = await commitWorkspace(args.workspaceRoot, args.options);\n result.workspaceCommitSha = wsOutcome.sha;\n result.workspacePushed = wsOutcome.pushed;\n if (!wsOutcome.sha) result.skipped?.push(\"workspace\");\n }\n\n return result;\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 { checkEnabledFeaturesHealth } from \"../lib/check-enabled-features-health.js\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { isGitRepo } from \"../lib/git-operations.js\";\nimport { installGitHook } from \"../lib/project-tree-scaffolder.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 const packPath = join(cwd, \".claude\", \"pack\");\n const claudeMdPath = join(cwd, \"CLAUDE.md\");\n const hookPath = join(cwd, \".git\", \"hooks\", \"post-merge\");\n const [gitRepo, hasPack, hasClaudeMd, hasHook] = await Promise.all([\n isGitRepo(cwd),\n pathExists(packPath),\n pathExists(claudeMdPath),\n pathExists(hookPath),\n ]);\n\n // 3. Git repo.\n checks.push({\n name: \"Git repository\",\n status: gitRepo ? \"ok\" : \"warn\",\n detail: gitRepo ? cwd : \"Không phải git repo (cần cho 'avatar init')\",\n fixable: false,\n });\n\n // 4. .claude/pack/ submodule.\n checks.push({\n name: \"team-ai-pack submodule\",\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 // 6. post-merge hook (uses hasHook from batch above).\n if (gitRepo && hasPack) {\n checks.push({\n name: \"Git hook post-merge\",\n status: hasHook ? \"ok\" : \"fail\",\n detail: hasHook ? \"installed\" : \"missing — fixable\",\n fixable: !hasHook,\n fix: hasHook\n ? undefined\n : async () => {\n await installGitHook(join(cwd, \".git\"), \"post-merge\");\n },\n });\n }\n\n // 7. .gitignore has Avatar entries.\n const gitignorePath = join(cwd, \".gitignore\");\n let gitignoreContent = \"\";\n if (gitRepo) {\n let gitignoreOk = false;\n if (await pathExists(gitignorePath)) {\n gitignoreContent = await fs.readFile(gitignorePath, \"utf8\");\n gitignoreOk = gitignoreContent.includes(\".claude/_pending/\");\n }\n checks.push({\n name: \".gitignore Avatar entries\",\n status: gitignoreOk ? \"ok\" : hasPack ? \"fail\" : \"warn\",\n detail: gitignoreOk ? \"có .claude/_pending/, .claude/_backup/\" : \"thiếu entries\",\n fixable: false,\n });\n\n // v1.7.0 — 7b. SECURITY: .claude/settings.json gitignored?\n // File chứa raw API key. Nếu chưa gitignore → user dễ vô tình commit + leak.\n const settingsGitignored =\n gitignoreContent.includes(\".claude/settings.json\") ||\n gitignoreContent.includes(\"/.claude/settings.json\") ||\n gitignoreContent.includes(\".claude/*.json\");\n checks.push({\n name: \"🔒 settings.json gitignored\",\n status: settingsGitignored ? \"ok\" : \"fail\",\n detail: settingsGitignored\n ? \"an toàn — settings.json không commit nhầm\"\n : \"CRITICAL: settings.json chứa API key — chạy 'avatar doctor --fix' để add gitignore\",\n fixable: !settingsGitignored,\n fix: settingsGitignored\n ? undefined\n : async () => {\n const addition =\n \"\\n# Avatar v1.7.0 — Security: settings.json chứa raw API key, KHÔNG commit.\\n.claude/settings.json\\n.claude/settings.json.backup-*\\n\";\n await fs.appendFile(gitignorePath, addition);\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. Feature toggle health (prompt-scoring, ...). Mỗi feature 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 featureChecks = await checkEnabledFeaturesHealth(cwd);\n checks.push(...featureChecks);\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-features-health.ts\n//\n// Doctor check: với mỗi feature 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 feature 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 FeatureManifest,\n enableFeature,\n readFeatureManifest,\n} from \"./apply-feature-manifest-to-settings.js\";\nimport { listEnabledFeatures } from \"./feature-state-store.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\n\nexport interface FeatureCheck {\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 feature đã có mặt trong settings.json chưa?\nfunction manifestHooksPresent(settings: SettingsShape, manifest: FeatureManifest): 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 checkEnabledFeaturesHealth(cwd: string): Promise<FeatureCheck[]> {\n const enabled = await listEnabledFeatures(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: FeatureCheck[] = [];\n for (const name of enabled) {\n const manifest = await readFeatureManifest(cwd, name);\n if (!manifest) {\n checks.push({\n name: `Feature: ${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: `Feature: ${name}`,\n status: \"ok\",\n detail: `enabled, hook active (v${manifest.version})`,\n fixable: false,\n });\n } else {\n checks.push({\n name: `Feature: ${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 enableFeature(cwd, manifest);\n },\n });\n }\n }\n return checks;\n}\n","// apply-feature-manifest-to-settings.ts\n//\n// Áp / rút manifest của 1 feature 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à feature.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/features/<name>/feature.json\n// (submodule mount tại .claude/pack/). Manifest.settings chỉ chứa subset cần cho\n// feature: 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 feature, 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 feature 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 FeatureManifest {\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 ApplyFeatureResult {\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\", \"features\", name, \"feature.json\");\n}\n\n// Đọc manifest feature. Thiếu → null (caller fail mềm: \"pack chưa hỗ trợ feature\").\nexport async function readFeatureManifest(\n workspacePath: string,\n name: string,\n): Promise<FeatureManifest | null> {\n const path = featureManifestPath(workspacePath, name);\n if (!(await pathExists(path))) {\n return null;\n }\n return await readJson<FeatureManifest>(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 feature.`,\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À feature 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à feature đ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 feature đ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 feature trong user\n// settings — drop old keep new. Trigger khi user upgrade pack từ pre-v0.7.1.\nexport async function enableFeature(\n workspacePath: string,\n manifest: FeatureManifest,\n): Promise<ApplyFeatureResult> {\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 feature, 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 feature đ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 disableFeature(\n workspacePath: string,\n manifest: FeatureManifest,\n): Promise<ApplyFeatureResult> {\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 feature, 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 feature → 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-feature-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-feature-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/feature\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 feature.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 if (packTemplate.env) {\n const mergedEnv: Record<string, string> = { ...(userSettings.env || {}) };\n let envChanged = false;\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 if (envChanged) {\n merged.env = mergedEnv;\n changes.push(\"env vars added from pack\");\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","// feature-state-store.ts\n//\n// Đọc/ghi <project>/.claude/avatar-features.json — state file project-level ghi\n// nhận feature 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 feature 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// \"features\": {\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 FEATURE_STATE_RELATIVE_PATH = \".claude/avatar-features.json\";\n\nexport interface FeatureStateEntry {\n enabled: boolean;\n version: string;\n appliedAt: string; // ISO timestamp lần enable/disable gần nhất\n}\n\nexport interface FeatureState {\n features: Record<string, FeatureStateEntry>;\n}\n\nfunction emptyState(): FeatureState {\n return { features: {} };\n}\n\nfunction stateFilePath(workspacePath: string): string {\n return join(workspacePath, FEATURE_STATE_RELATIVE_PATH);\n}\n\n// Đọc state. File thiếu hoặc parse lỗi → empty state (fail-soft).\nexport async function readFeatureState(workspacePath: string): Promise<FeatureState> {\n const path = stateFilePath(workspacePath);\n if (!(await pathExists(path))) {\n return emptyState();\n }\n try {\n const parsed = await readJson<Partial<FeatureState>>(path);\n // Defensive: file tồn tại nhưng thiếu `features` → normalize.\n return { features: parsed.features ?? {} };\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 writeFeatureState(workspacePath: string, state: FeatureState): Promise<void> {\n await writeJsonAtomic(stateFilePath(workspacePath), state);\n}\n\n// Set 1 feature enabled/disabled + stamp version & appliedAt. Trả về state mới.\nexport async function setFeatureState(\n workspacePath: string,\n name: string,\n entry: { enabled: boolean; version: string },\n): Promise<FeatureState> {\n const state = await readFeatureState(workspacePath);\n state.features[name] = {\n enabled: entry.enabled,\n version: entry.version,\n appliedAt: new Date().toISOString(),\n };\n await writeFeatureState(workspacePath, state);\n return state;\n}\n\n// Tiện ích: danh sách tên feature đang enabled=true (dùng cho sync re-apply).\nexport async function listEnabledFeatures(workspacePath: string): Promise<string[]> {\n const state = await readFeatureState(workspacePath);\n return Object.entries(state.features)\n .filter(([, entry]) => entry.enabled)\n .map(([name]) => name);\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","import { promises as fs } from \"node:fs\";\n// Scaffold the .claude/ directory tree inside a project after submodule add.\n// Used by `avatar init` for all three modes (internal/client/library).\n//\n// Spec source: Chapter 02 (folder map) + Chapter 09 Command 02 BEHAVIOR.\nimport { join } from \"node:path\";\nimport type { InitMode } from \"../types/config-schema.js\";\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// `.gitmodules` is managed by git submodule add (not direct write), but it\n// gets modified during init so it's listed here for conflict warnings.\nexport const AVATAR_MANAGED_PATHS = [\".claude\", \"CLAUDE.md\", \".gitmodules\"] 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 by the submodule manager — listed here for reference only.\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 mode: InitMode;\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// Append Avatar entries to project .gitignore (or create if missing).\n// Idempotent — skips if our marker line already present.\nexport async function appendGitignoreEntries(projectRoot: string): Promise<void> {\n const path = join(projectRoot, \".gitignore\");\n const tpl = await renderTemplateByName(\"gitignore\", {});\n const marker = \"# Avatar — git-ignored entries injected on `avatar init`\";\n\n let existing = \"\";\n if (await pathExists(path)) {\n existing = await fs.readFile(path, \"utf8\");\n if (existing.includes(marker)) return;\n }\n\n const separator = existing.endsWith(\"\\n\") || existing.length === 0 ? \"\" : \"\\n\";\n await writeTextAtomic(path, `${existing}${separator}\\n${tpl}`);\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","// 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 | \"gitignore\"\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","// 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","// `avatar feature <enable|disable|list> [name]` — quản lý feature toggle.\n//\n// Feature toggle = gói tự-đủ trong pack (features/<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 feature ra + state disabled\n// list bảng: feature | available | enabled | version\n// add multi-select feature available để cài (UX, bọc enable)\n// remove multi-select feature đ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 feature chưa bật → no-op.\n// add/remove tái dùng enableFeatureByName/disableFeatureByName (DRY).\n\nimport { resolve } from \"node:path\";\nimport { checkbox, confirm } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport {\n discoverEnabledAndAvailableFeatures,\n listAvailableFeatures,\n} from \"../lib/discover-pack-features-and-defaults.js\";\nimport {\n buildAddChoices,\n buildRemoveChoices,\n resolveNonInteractiveSelection,\n} from \"../lib/feature-add-remove-selection.js\";\nimport {\n disableFeatureByName,\n enableFeatureByName,\n} from \"../lib/feature-enable-disable-orchestrator.js\";\nimport { readFeatureState } from \"../lib/feature-state-store.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\ninterface FeatureOptions {\n target?: string;\n}\n\ninterface AddRemoveOptions extends FeatureOptions {\n all?: boolean;\n yes?: boolean;\n}\n\nfunction resolveWorkspace(opts: FeatureOptions): 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 feature đã chọn (có thể rỗng).\nasync function selectFeatures(\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 feature để cài (space chọn, enter xác nhận):\"\n : \"Chọn feature để gỡ (space chọn, enter xác nhận):\",\n choices,\n });\n}\n\n// `avatar feature 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 discoverEnabledAndAvailableFeatures(ws);\n if (available.length === 0) {\n log.dim(\"Không có feature available (pack chưa có features/, hoặc chưa sync).\");\n return;\n }\n const selected = await selectFeatures(available, enabled, opts, \"add\");\n if (selected.length === 0) {\n log.dim(\"Không chọn feature nào — hủy, không thay đổi.\");\n return;\n }\n for (const name of selected) {\n await enableFeatureByName(ws, name);\n }\n}\n\n// `avatar feature remove` — list enabled, multi-select để disable 1-n.\nasync function runRemove(opts: AddRemoveOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const { enabled } = await discoverEnabledAndAvailableFeatures(ws);\n if (enabled.length === 0) {\n log.dim(\"Không có feature nào đang bật — không có gì để gỡ.\");\n return;\n }\n const selected = await selectFeatures(enabled, [], opts, \"remove\");\n if (selected.length === 0) {\n log.dim(\"Không chọn feature 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} feature: ${selected.join(\", \")}?`,\n default: true,\n });\n if (!ok) {\n log.dim(\"Hủy — không gỡ feature nào.\");\n return;\n }\n }\n for (const name of selected) {\n await disableFeatureByName(ws, name);\n }\n}\n\nasync function runList(opts: FeatureOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const available = await listAvailableFeatures(ws);\n const state = await readFeatureState(ws);\n\n if (available.length === 0 && Object.keys(state.features).length === 0) {\n log.dim(\"Không có feature nào (pack chưa có features/, hoặc chưa sync). Chạy 'avatar sync'.\");\n return;\n }\n\n // Union tên: feature có trong pack + feature từng ghi trong state.\n const names = [...new Set([...available, ...Object.keys(state.features)])].sort();\n log.info(\"Features:\");\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.features[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 registerFeatureCommand(program: Command): void {\n const feat = program\n .command(\"feature\")\n .description(\"Quản lý feature toggle (prompt-scoring, ...) bật/tắt nguyên khối\");\n\n feat\n .command(\"enable <name>\")\n .description(\"Bật feature: merge manifest vào settings.json + ghi state\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (name: string, opts: FeatureOptions) => {\n const ws = resolveWorkspace(opts);\n const ok = await enableFeatureByName(ws, name);\n if (!ok) process.exit(1);\n });\n\n feat\n .command(\"disable <name>\")\n .description(\"Tắt feature: 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: FeatureOptions) => {\n const ws = resolveWorkspace(opts);\n await disableFeatureByName(ws, name);\n });\n\n feat\n .command(\"list\")\n .description(\"Liệt kê feature: available | enabled | version\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (opts: FeatureOptions) => {\n await runList(opts);\n });\n\n feat\n .command(\"add\")\n .description(\"Chọn (multi-select) feature available để cài\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Cài hết feature 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 feat\n .command(\"remove\")\n .description(\"Chọn (multi-select) feature đang bật để gỡ\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Gỡ hết feature đ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","// discover-pack-features-and-defaults.ts\n//\n// Quét pack/features/ để biết feature nào CÓ SẴN + đọc danh sách feature init bật\n// mặc định (features/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 { listEnabledFeatures } from \"./feature-state-store.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\n\nconst PACK_FEATURES_REL = [\".claude\", \"pack\", \"features\"];\n\nfunction packFeaturesDir(workspacePath: string): string {\n return join(workspacePath, ...PACK_FEATURES_REL);\n}\n\n// Liệt kê feature có manifest trong pack (mỗi dir con có feature.json).\nexport async function listAvailableFeatures(workspacePath: string): Promise<string[]> {\n const dir = packFeaturesDir(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, \"feature.json\"))) {\n names.push(entry.name);\n }\n }\n return names.sort();\n}\n\ninterface DefaultsFile {\n defaultFeatures?: string[];\n}\n\n// Đọc features/defaults.json → danh sách feature init bật mặc định. Thiếu → [].\nexport async function readDefaultFeatures(workspacePath: string): Promise<string[]> {\n const path = join(packFeaturesDir(workspacePath), \"defaults.json\");\n if (!(await pathExists(path))) {\n return [];\n }\n try {\n const parsed = await readJson<DefaultsFile>(path);\n return parsed.defaultFeatures ?? [];\n } catch {\n return [];\n }\n}\n\nexport interface FeaturesOverview {\n available: string[]; // có manifest trong pack\n enabled: string[]; // enabled=true trong state\n}\n\n// Tổng hợp cho status/doctor: feature có sẵn (pack) + đang bật (state).\nexport async function discoverEnabledAndAvailableFeatures(\n workspacePath: string,\n): Promise<FeaturesOverview> {\n const [available, enabled] = await Promise.all([\n listAvailableFeatures(workspacePath),\n listEnabledFeatures(workspacePath),\n ]);\n return { available, enabled };\n}\n","// feature-add-remove-selection.ts\n//\n// Logic THUẦN (không UI) cho `avatar feature 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 FeatureChoice {\n name: string; // label hiển thị\n value: string; // tên feature\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[]): FeatureChoice[] {\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 feature đang enabled (không pre-check — user chủ động chọn gỡ).\nexport function buildRemoveChoices(enabled: string[]): FeatureChoice[] {\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","// feature-enable-disable-orchestrator.ts\n//\n// Orchestrate 1 lần enable/disable feature: đọc manifest → (kiểm runtime) → apply\n// vào settings.json → cập nhật avatar-features.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ó features/ (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 ApplyFeatureResult,\n disableFeature,\n enableFeature,\n readFeatureManifest,\n} from \"./apply-feature-manifest-to-settings.js\";\nimport { readFeatureState, setFeatureState } from \"./feature-state-store.js\";\nimport { log } from \"./terminal-logger.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 `Feature 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: ApplyFeatureResult): void {\n switch (result.action) {\n case \"enabled\":\n log.success(`Feature '${name}' enabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"disabled\":\n log.success(`Feature '${name}' disabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"no-change\":\n log.dim(`Feature '${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 feature. 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 feature default có thể chưa có trong pack cũ).\nexport async function enableFeatureByName(\n workspacePath: string,\n name: string,\n opts: { silent?: boolean } = {},\n): Promise<boolean> {\n const manifest = await readFeatureManifest(workspacePath, name);\n if (!manifest) {\n if (!opts.silent) {\n log.warn(\n `Pack version hiện tại chưa hỗ trợ feature '${name}' (thiếu .claude/pack/features/${name}/feature.json). Chạy 'avatar sync' để pull pack mới, hoặc kiểm tên feature.`,\n );\n }\n return false;\n }\n warnIfRuntimeMissing(manifest.requires?.runtime);\n const result = await enableFeature(workspacePath, manifest);\n logApplyResult(name, result);\n await setFeatureState(workspacePath, name, { enabled: true, version: manifest.version });\n return true;\n}\n\n// Disable 1 feature. Đọc manifest để biết CHÍNH XÁC entry nào của feature.\nexport async function disableFeatureByName(workspacePath: string, name: string): Promise<boolean> {\n const manifest = await readFeatureManifest(workspacePath, name);\n if (!manifest) {\n log.warn(\n `Không tìm thấy manifest feature '${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 readFeatureState(workspacePath);\n const prevVersion = state.features[name]?.version ?? \"unknown\";\n await setFeatureState(workspacePath, name, { enabled: false, version: prevVersion });\n return false;\n }\n const result = await disableFeature(workspacePath, manifest);\n logApplyResult(name, result);\n await setFeatureState(workspacePath, name, { enabled: false, version: manifest.version });\n return true;\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","// 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","// 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}\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 // 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 // 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","// 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","// 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","// 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","// 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","// 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// IMPORTANT (v1.6.3 fix): Model được đọc từ TOP-LEVEL `settings.json.model` (Claude Code spec).\n// Trước đó (v1.6.0-1.6.2) đọc nhầm từ `env.ANTHROPIC_MODEL` — field này KHÔNG TỒN TẠI trong\n// settings do Avatar ghi, dẫn đến wiki LUÔN dùng fallback hardcoded thay vì model user chọn.\n//\n// Trả null nếu Subscription mode hoặc không có settings (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 }>(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 // Backward-compat: cũng accept env.ANTHROPIC_MODEL nếu user manual set (legacy).\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 // Anthropic Direct branch (v1.6.0+) — check ANTHROPIC_API_KEY trước.\n const anthropicKey = typeof env.ANTHROPIC_API_KEY === \"string\" ? env.ANTHROPIC_API_KEY : null;\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\n // LLMLite branch — fallback nếu chỉ có ANTHROPIC_AUTH_TOKEN.\n const llmliteToken =\n typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" ? env.ANTHROPIC_AUTH_TOKEN : null;\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 // Có base URL nhưng không có key nào → coi như subscription (OAuth, không expose key).\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.\nexport async function runGitnexusWikiConditional(\n workspacePath: string,\n): Promise<GitnexusWikiResult> {\n // Branch 1: detect provider mode.\n const creds = await readSettingsForWikiCredentials(workspacePath);\n if (!creds) {\n log.warn(\"Subscription mode (OAuth, không có API key trong settings.json) → skip wiki gen.\");\n log.dim(\n \"Để gen wiki sau, 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: workspacePath,\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.\n const wikiPath = join(workspacePath, \".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 init` — Command 02 spec (v1.1 redesign).\n//\n// Bỏ khái niệm --mode internal/client/library. Thay bằng wizard 3-câu hỏi tự\n// nhận diện tình trạng dự án:\n// 1. Đã có repo git remote (URL có sẵn) → flow=existing-remote\n// 2. Đã có folder code local → flow=existing-folder\n// 3. Dự án mới hoàn toàn → flow=new-project\n//\n// Mọi flow đều scaffold workspace tách biệt (không còn mode internal). Avatar\n// luôn check + tự cài gh CLI nếu thiếu, auto-bootstrap git cho folder chưa\n// init, auto-detect .gitignore theo tech stack.\n//\n// Entry point: flag parsing, login check, flow dispatch.\n// Chi tiết từng flow → init-flow-handlers-for-each-project-status.ts\n// Scaffold + finalize → workspace-scaffold-and-finalize-orchestrator.ts\n// Success box + helpers → init-success-rendering-and-helpers.ts\n\nimport { select } 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 {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} 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 {\n runInitFromExistingFolder,\n runInitFromExistingRemote,\n runInitFromScratch,\n} from \"./init-flow-handlers-for-each-project-status.js\";\nimport { runLogin } from \"./login.js\";\n\n// Re-export resolveWorkspacePath so existing imports from init.ts continue to work.\n// Function lives in init-success-rendering-and-helpers.ts (moved during modularization).\nexport { resolveWorkspacePath } from \"./init-success-rendering-and-helpers.js\";\n\n// Re-export types + parser từ shared file để giữ public API ổn định.\n// File này (init.ts) là entry point — không define types/parsers trực tiếp nữa,\n// các handler có thể import shared mà không tạo circular dep với init.ts.\nexport type { InitOptions, ProjectStatus } from \"./init-options-and-bootstrap-strategy-parser.js\";\nexport { parseBootstrapStrategyOpts } from \"./init-options-and-bootstrap-strategy-parser.js\";\n\nimport type { InitOptions, ProjectStatus } from \"./init-options-and-bootstrap-strategy-parser.js\";\n\nexport function registerInitCommand(program: Command): void {\n program\n .command(\"init\")\n .description(\"Khởi tạo Avatar — 3 flow tự nhận diện (repo / folder / new)\")\n .option(\"--project-status <val>\", \"existing-remote | existing-folder | new-project\")\n .option(\"--folder-path <path>\", \"Đường dẫn folder hiện có (flow existing-folder)\")\n .option(\"--create-remote\", \"Force tạo remote qua gh (flow existing-folder hoặc new-project)\")\n .option(\"--repo-visibility <val>\", \"private (mặc định) | public\")\n .option(\"--repo-org <name>\", \"GitHub org/owner cho repo mới\")\n .option(\"--client-repo <url>\", \"URL git remote (flow existing-remote)\")\n .option(\"--workspace-name <name>\", \"Tên workspace\")\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ể\")\n .option(\"--latest\", \"Pull HEAD của team-ai-pack main branch (bỏ qua tag SemVer, bleeding-edge)\")\n .option(\"--team-owner <email>\", \"Email team owner (bỏ qua prompt)\")\n .option(\"--description <text>\", \"Mô tả 1 dòng của dự án\")\n .option(\"--skip-scan\", \"Bỏ qua project-scanner sau scaffold\")\n .option(\"--skip-team-pack\", \"Bỏ qua submodule 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(\"--no-commit\", \"Skip commit workspace initial state (mặc định LUÔN commit)\")\n .option(\"--workspace-remote\", \"Tạo GitHub remote cho workspace root (default: prompt)\")\n .option(\"--ai-skip\", \"Bỏ qua phase AI setup (CI/test mode — chạy `avatar ai setup` sau)\")\n .option(\n \"--gitnexus-skip\",\n \"Bỏ qua phase GitNexus setup (M10 — chạy `avatar gitnexus install` sau)\",\n )\n .option(\n \"--bootstrap-strategy <s>\",\n \"Xử lý folder dirty: stash | commit-all | skip | branch (default: prompt)\",\n )\n .option(\"--preserve-uncommitted\", \"Alias cho --bootstrap-strategy=stash (giữ changes user)\")\n .option(\"--mode <mode>\", \"[DEPRECATED] Dùng --project-status thay thế\")\n .action(async (opts: InitOptions) => {\n try {\n await runInit(opts);\n } catch (err) {\n // Skip strategy → exit 0 với hướng dẫn, không phải error thật.\n if (err instanceof InitAbortedByUserError) {\n log.dim(err.message);\n process.exit(0);\n }\n // User chọn \"Tạm ngưng\" trong pre-flight check team-ai-pack → exit 0 graceful.\n if (err instanceof TeamPackAccessAbortedError) {\n log.dim(err.message);\n process.exit(0);\n }\n // User chọn \"Tạm ngưng\" trong remote access recovery → exit 0 graceful.\n if (err instanceof RemoteAccessAbortedError) {\n log.dim(err.message);\n process.exit(0);\n }\n // User abort generic recovery prompt → exit 0 graceful.\n if (err instanceof UserAbortedRecoveryError) {\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(opts: InitOptions): Promise<void> {\n if (!opts.yes) printAvatarBanner({ tagline: \"Khởi tạo Avatar trong dự án của bạn\" });\n\n if (opts.mode) {\n log.warn(\"Flag --mode đã deprecated từ v1.1. Dùng --project-status thay thế.\");\n }\n\n // Login check: nếu chưa login hoặc token hết hạn → tự trigger login flow inline,\n // không bắt user re-run init. Sau login OK → re-read config + tiếp tục.\n // v1.2.5: Loop retry — sau login attempt fail, prompt user thay vì exit thẳng.\n let userConfig = await readUserConfig();\n while (!userConfig || isTokenExpired(userConfig)) {\n log.info(\"Chưa đăng nhập — chạy login flow trước khi init...\");\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\n const action = await promptRetryOrSkip({\n taskName: \"Đăng nhập SSO Google\",\n reason: \"Token chưa được tạo hoặc đã hết hạn.\",\n allowSkip: false, // Login bắt buộc, không skip được.\n hint: \"Đảm bảo bạn chọn 'Allow' trên browser và dùng email @nal.vn.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\n \"User abort tại bước login. Chạy 'avatar login' tay rồi 'avatar init' lại.\",\n );\n }\n // action === \"retry\" → loop tiếp.\n }\n\n const status = opts.projectStatus ?? (await promptProjectStatus());\n\n switch (status) {\n case \"existing-remote\":\n await runInitFromExistingRemote(opts, userConfig.email);\n break;\n case \"existing-folder\":\n await runInitFromExistingFolder(opts, userConfig.email);\n break;\n case \"new-project\":\n await runInitFromScratch(opts, userConfig.email);\n break;\n }\n}\n\nasync function promptProjectStatus(): Promise<ProjectStatus> {\n return (await select({\n message: \"Tình trạng dự án của bạn?\",\n choices: [\n { name: \"1. Đã có repo git remote (URL có sẵn)\", value: \"existing-remote\" as const },\n { name: \"2. Đã có folder code local\", value: \"existing-folder\" as const },\n { name: \"3. Dự án mới hoàn toàn\", value: \"new-project\" as const },\n ],\n })) as ProjectStatus;\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","// 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","// 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","// 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","// 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","// Manage the team-ai-pack git submodule lifecycle: add, update, pin-to-tag,\n// changelog extraction. Used by `avatar init` and `avatar sync`.\nimport { join } from \"node:path\";\nimport {\n ensureTeamPackAccessWithRetry,\n parseRepoSlugFromGitUrl,\n} from \"./check-team-pack-access-with-retry-loop.js\";\nimport {\n addSubmodule,\n checkoutBranchHeadInSubmodule,\n checkoutTagInSubmodule,\n currentCommitSha,\n listTags,\n tagAtHead,\n} from \"./git-operations.js\";\nimport { pickLatestStableSemVerTag } from \"./pick-latest-stable-semver-tag.js\";\nimport { resolveTeamPackRepoUrl } from \"./resolve-team-pack-repo-url.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Resolve URL động qua resolveTeamPackRepoUrl() — không hardcode. Export getter\n// để legacy code đọc URL được nhưng kết quả phụ thuộc env + gh user hiện tại.\nexport function getTeamPackRepoUrl(): string {\n return resolveTeamPackRepoUrl();\n}\n\n// Backward-compat: snapshot URL lúc load module (v1.0/1.1 đã expose). Sẽ remove\n// ở v2.0.0. New code dùng getTeamPackRepoUrl() để luôn pickup env latest.\nexport const TEAM_PACK_REPO_URL = resolveTeamPackRepoUrl();\nexport const TEAM_PACK_RELATIVE_PATH = \".claude/pack\";\n\n// Custom error báo user abort sau pre-flight check (chọn \"Tạm ngưng\" trong menu).\n// Caller init.ts catch để exit 0 graceful thay vì error.\nexport class TeamPackAccessAbortedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TeamPackAccessAbortedError\";\n }\n}\n\n// Add the team-ai-pack submodule into a fresh project and pin it to a tag.\n// If `tag` is omitted, checks out the latest tag in the freshly-cloned submodule.\n//\n// v1.2.3 flow:\n// 1. Pre-flight check qua gh API — user có quyền access repo chưa?\n// 2. Nếu chưa → loop menu \"Đã add / Tạm ngưng\" cho đến khi user có access hoặc abort\n// 3. Có access → addSubmodule như cũ\n// Throws TeamPackAccessAbortedError nếu user chọn abort.\n// v1.10.0: Default branch khi --latest. Hardcode \"main\" — match với avatar sync.\nconst DEFAULT_PACK_BRANCH = \"main\";\n\nexport async function addTeamPackSubmodule(\n projectRoot: string,\n tag?: string,\n ssoEmail?: string,\n // v1.10.0: latest=true → checkout HEAD của main branch thay vì tag SemVer.\n // Conflict với tag → tag wins (explicit > implicit).\n latest = false,\n): Promise<{ pinnedTag: string | null }> {\n const url = resolveTeamPackRepoUrl();\n\n // Pre-flight check — chỉ work với URL GitHub (có slug parse được).\n const repoSlug = parseRepoSlugFromGitUrl(url);\n if (repoSlug) {\n const hasAccess = await ensureTeamPackAccessWithRetry({ repoSlug, ssoEmail });\n if (!hasAccess) {\n throw new TeamPackAccessAbortedError(\n \"User chọn tạm ngưng. Chạy lại 'avatar init' sau khi được add vào org.\",\n );\n }\n }\n\n try {\n await addSubmodule(url, TEAM_PACK_RELATIVE_PATH, projectRoot);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"Repository not found\") || msg.includes(\"not found\")) {\n log.error(\n `Repo team-ai-pack không tồn tại: ${url}\\n Cách fix:\\n 1. Tạo repo: gh repo create <owner>/team-ai-pack --private --add-readme\\n 2. Hoặc override URL: export AVATAR_TEAM_PACK_REPO_URL=<url-repo-của-bạn>\\n 3. Hoặc dùng flag --skip-team-pack`,\n );\n }\n throw err;\n }\n\n // v1.10.0: Resolve pin target — 3 cases priority:\n // 1. tag (explicit --pack-version) → use as-is (tag wins, dù latest=true)\n // 2. latest=true → checkout HEAD của main branch, pinnedTag = \"main (HEAD)\"\n // 3. Default → pick latest stable SemVer tag (v1.7.1 logic)\n if (tag) {\n await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, tag, projectRoot);\n return { pinnedTag: tag };\n }\n\n if (latest) {\n await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);\n return { pinnedTag: `${DEFAULT_PACK_BRANCH} (HEAD)` };\n }\n\n const submoduleDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n const allTags = await listTags(submoduleDir);\n const target = pickLatestStableSemVerTag(allTags);\n if (target) {\n await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);\n }\n return { pinnedTag: target };\n}\n\n// Read the current pinned version of the pack submodule. Returns the tag name\n// if HEAD MATCHES a tag exactly, otherwise the short SHA.\n// v1.7.1 fix: trước đó dùng latestTag (alphabetical sort of all tags) → trả tag\n// \"mới nhất bất kỳ\" thay vì tag tại HEAD. Giờ dùng `git describe --tags\n// --exact-match HEAD` semantics đúng.\nexport async function readPinnedPackVersion(projectRoot: string): Promise<string> {\n const submoduleRoot = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n const tag = await tagAtHead(submoduleRoot);\n if (tag) return tag;\n const sha = await currentCommitSha(submoduleRoot);\n return sha.slice(0, 7);\n}\n","// Pre-flight check user có quyền clone team-ai-pack chưa.\n// Nếu chưa → highlight warning + 3-option menu loop:\n// 1. \"Đã được grant access với <hiện tại> — re-check\"\n// 2. \"Đã grant với account khác — switch gh + login\"\n// 3. \"Tạm ngưng\"\n//\n// v1.2.6 fixes:\n// - Boxen red highlight cho warning (không chỉ log.warn)\n// - ssoEmail truyền từ caller (avatar login email)\n// - 3 options menu thay 2 (thêm switch account)\n// - getCurrentGhUser() re-call mỗi lần loop (sau switch thì khác)\nimport { spawnSync } from \"node:child_process\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport { chalk, log } from \"./terminal-logger.js\";\n\n// Repo slug ngắn (owner/name) parse từ git URL để tránh dùng URL full.\n// gh api repos/<slug> work với slug.\nexport function parseRepoSlugFromGitUrl(url: string): string | null {\n // Support: https://github.com/owner/name.git, git@github.com:owner/name.git\n const httpsMatch = url.match(/github\\.com[/:]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n return httpsMatch?.[1] ?? null;\n}\n\n// Check user có quyền truy cập repo private qua gh CLI (token user trong keychain).\n// Exit 0 = có access; non-zero = không (404/403 đều fail).\nexport function checkRepoAccess(repoSlug: string): boolean {\n const r = spawnSync(\"gh\", [\"api\", `repos/${repoSlug}`], { stdio: \"ignore\" });\n return r.status === 0;\n}\n\n// Lấy github username của user đang gh-auth — re-call mỗi lần loop để reflect\n// account vừa switch (nếu user pick \"switch account\").\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 --web` 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}. Có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Copy text vào clipboard qua clipboardy. Hỏi user trước (tránh override).\nasync function copyInfoToClipboardWithConsent(info: string): Promise<void> {\n const ok = await confirm({\n message: \"Copy thông tin (GitHub username + email) vào clipboard để dán vào Slack/email?\",\n default: true,\n });\n if (!ok) return;\n try {\n const { default: clipboardy } = await import(\"clipboardy\");\n await clipboardy.write(info);\n log.success(\"Đã copy vào clipboard\");\n } catch (err) {\n log.dim(`Copy clipboard fail: ${(err as Error).message}`);\n }\n}\n\n// Render warning box highlight với boxen red border. Eye-catching hơn log.warn.\nfunction printAccessWarningBox(\n repoSlug: string,\n ghUser: string | null,\n ssoEmail: string | null,\n): void {\n const lines = [\n `${chalk.red(\"⛔ KHÔNG CÓ QUYỀN ACCESS\")}`,\n \"\",\n `Repo: ${chalk.bold(repoSlug)}`,\n \"\",\n \"Bạn cần được admin add vào org để pull team-ai-pack.\",\n \"\",\n `${chalk.dim(\"Thông tin gửi admin:\")}`,\n ` GitHub username: ${chalk.cyan(ghUser ?? \"(chưa gh auth — chạy: gh auth login)\")}`,\n ` NAL email: ${chalk.cyan(ssoEmail ?? \"(chưa avatar login — chạy: avatar login)\")}`,\n ` Date: ${new Date().toISOString().split(\"T\")[0]}`,\n \"\",\n `${chalk.dim(\"Liên hệ:\")} luke@nal.vn (Slack #avatar-setup)`,\n ];\n process.stdout.write(\n `${boxen(lines.join(\"\\n\"), { padding: 1, borderColor: \"red\", borderStyle: \"round\" })}\\n`,\n );\n}\n\n// Build info text user cần gửi admin (clipboard version — plain text).\nfunction buildAccessRequestInfo(\n repoSlug: string,\n ghUser: string | null,\n ssoEmail: string | null,\n): string {\n return [\n `Request access ${repoSlug} (NAL)`,\n \"\",\n `GitHub username: ${ghUser ?? \"(chưa gh auth — chạy: gh auth login)\"}`,\n `NAL email: ${ssoEmail ?? \"(chưa avatar login — chạy: avatar login)\"}`,\n `Date: ${new Date().toISOString().split(\"T\")[0]}`,\n ].join(\"\\n\");\n}\n\n// Main entry — loop menu cho đến khi user có access hoặc abort.\n// Return true nếu access OK; false nếu user abort.\nexport async function ensureTeamPackAccessWithRetry(args: {\n repoSlug: string;\n ssoEmail?: string;\n}): Promise<boolean> {\n // Fast path: đã có access.\n if (checkRepoAccess(args.repoSlug)) return true;\n\n // First display: highlight warning box + offer clipboard copy.\n const initialGhUser = getCurrentGhUser();\n printAccessWarningBox(args.repoSlug, initialGhUser, args.ssoEmail ?? null);\n await copyInfoToClipboardWithConsent(\n buildAccessRequestInfo(args.repoSlug, initialGhUser, args.ssoEmail ?? null),\n );\n\n // Loop 3-option menu.\n while (true) {\n const ghUser = getCurrentGhUser(); // Re-call: account có thể đã switch giữa các lần loop.\n const ghUserDisplay = ghUser ?? \"(chưa gh auth)\";\n\n const action = (await select({\n message: \"Cách xử lý?\",\n choices: [\n {\n name: `Đã được grant access với GitHub username '${ghUserDisplay}' — kiểm tra lại`,\n value: \"retry-same\" as const,\n },\n {\n name: \"Đã grant với GitHub account khác — switch gh (mở browser)\",\n value: \"switch-account\" as const,\n },\n {\n name: \"Tạm ngưng — chạy lại 'avatar init' sau\",\n value: \"abort\" as const,\n },\n ],\n })) as \"retry-same\" | \"switch-account\" | \"abort\";\n\n if (action === \"abort\") {\n log.dim(\"Tạm ngưng. Chạy lại 'avatar init' sau khi đã accept invite từ GitHub.\");\n return false;\n }\n\n if (action === \"switch-account\") {\n triggerGhAuthLoginInteractive();\n // Sau switch → loop tiếp re-check với account mới.\n }\n\n // Re-check (cả 2 case retry-same + switch-account).\n log.info(\"Kiểm tra access...\");\n if (checkRepoAccess(args.repoSlug)) {\n const finalUser = getCurrentGhUser();\n log.success(`Đã có access với '${finalUser ?? \"(unknown)\"}' — tiếp tục.`);\n return true;\n }\n\n log.warn(\n `Vẫn chưa có access với account '${ghUser ?? \"(unknown)\"}'. Đảm bảo đã accept email invite hoặc switch đúng account.`,\n );\n // Loop tiếp.\n }\n}\n","// Pick latest stable SemVer tag from a list — used by `avatar sync` để chọn\n// pack version mới nhất một cách độc lập với pack team.\n//\n// Background:\n// Trước v1.7.1 Avatar dùng `git tags` rồi `tags[length-1]` → alphabetical sort.\n// Hoạt động OK với v0.1 → v0.4, NHƯNG break silently khi:\n// - v0.10.0 < v0.4.0 (alphabetical)\n// - v1.0.0 < v0.9.0 (alphabetical)\n// - Pre-release v0.5.0-rc1 lẫn vào (user không muốn pick auto)\n//\n// v1.7.1 fix: parse SemVer chuẩn, sort numeric, default SKIP pre-release.\n// User muốn pre-release → pass `--version v0.5.0-rc1` explicit.\n//\n// Decision (Q1=SemVer sort, Q2=skip pre-release):\n// - Parse `vMAJOR.MINOR.PATCH` (optional 'v' prefix, optional `-PRERELEASE`)\n// - Tags không match SemVer pattern → skip (vd \"release-1\", \"2026-05-29\")\n// - Default skip pre-release (có dấu `-` sau PATCH)\n// - Sort numeric tăng dần [major, minor, patch] → return tag cuối\n\n// Strict SemVer regex: optional v prefix, 3 số, optional -PRERELEASE.\n// Pre-release matches: -rc1, -beta.2, -alpha, ...\nconst SEMVER_REGEX = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\ninterface ParsedSemVer {\n raw: string; // Original tag string (vd \"v0.4.0\")\n major: number;\n minor: number;\n patch: number;\n prerelease: string | null; // null cho stable, string cho rc/beta/alpha\n}\n\n// Parse 1 tag string. Trả null nếu không match SemVer.\nexport function parseSemVerTag(tag: string): ParsedSemVer | null {\n const match = tag.match(SEMVER_REGEX);\n if (!match) return null;\n const [, major, minor, patch, prerelease] = match;\n return {\n raw: tag,\n major: Number.parseInt(major ?? \"0\", 10),\n minor: Number.parseInt(minor ?? \"0\", 10),\n patch: Number.parseInt(patch ?? \"0\", 10),\n prerelease: prerelease ?? null,\n };\n}\n\n// Pick latest stable tag từ array. Mặc định skip pre-release.\n// includePrerelease=true → cho phép pre-release (vd CI test bleeding edge).\n//\n// Trả null nếu KHÔNG có tag SemVer nào (caller xử lý: error hoặc fallback).\nexport function pickLatestStableSemVerTag(\n tags: string[],\n includePrerelease = false,\n): string | null {\n const parsed = tags\n .map(parseSemVerTag)\n .filter((t): t is ParsedSemVer => t !== null)\n .filter((t) => includePrerelease || t.prerelease === null);\n\n if (parsed.length === 0) return null;\n\n // Sort tăng dần: [major, minor, patch] numeric.\n parsed.sort((a, b) => {\n if (a.major !== b.major) return a.major - b.major;\n if (a.minor !== b.minor) return a.minor - b.minor;\n return a.patch - b.patch;\n });\n\n // Last element = latest.\n return parsed[parsed.length - 1]?.raw ?? null;\n}\n","// Resolve URL của team-ai-pack submodule. Thứ tự ưu tiên:\n// 1. Env var AVATAR_TEAM_PACK_REPO_URL (explicit override — per-machine/CI)\n// 2. Fallback \"nalvn/team-ai-pack\" qua SSH (NAL org default, private)\n//\n// LƯU Ý v1.2.3: bỏ logic \"smart default = <gh-user>/team-ai-pack\" của v1.2.x\n// vì team-ai-pack là SHARED resource của org NAL, KHÔNG phải per-user repo.\n// User không thuộc nalvn org → cần admin invite (xem pre-flight check trong orchestrator).\n//\n// v1.2.8: Default đổi từ HTTPS → SSH (git@github.com:nalvn/team-ai-pack.git).\n// Lý do: HTTPS clone với gh credential helper negotiate WRITE permission ở handshake.\n// User chỉ có Read role (pull:true, push:false) → server trả 403 \"Write access not granted\".\n// SSH với key user đã setup → authenticate chính xác per-role, work cho read-only access.\n//\n// v1.13.0 SECURITY: Allowlist URL trước khi return. Trước fix:\n// `AVATAR_TEAM_PACK_REPO_URL=\"file:///etc\" avatar init` → `git submodule add file:///etc`\n// = local filesystem exfil. Hoặc URL gắn malicious `post-checkout` git hooks = RCE.\n// Allowlist chỉ GitHub HTTPS / SSH với .git suffix bắt buộc.\nconst ORG_DEFAULT = \"git@github.com:nalvn/team-ai-pack.git\";\n\n// Github SSH: git@github.com:owner/repo.git\n// Github HTTPS: https://github.com/owner/repo.git\n// Owner/repo: chỉ alphanum, hyphen, underscore, dot.\nconst GITHUB_URL_ALLOWLIST =\n /^(git@github\\.com:|https:\\/\\/github\\.com\\/)[A-Za-z0-9._-]+\\/[A-Za-z0-9._-]+\\.git$/;\n\nexport class InvalidTeamPackUrlError extends Error {\n url: string;\n constructor(url: string) {\n super(\n `AVATAR_TEAM_PACK_REPO_URL không hợp lệ: ${url}\\n Yêu cầu: GitHub URL có .git suffix, vd:\\n git@github.com:owner/repo.git\\n https://github.com/owner/repo.git\\n Lý do block: ngăn file:// (filesystem exfil) + URL malicious (git hook RCE).`,\n );\n this.name = \"InvalidTeamPackUrlError\";\n this.url = url;\n }\n}\n\nfunction assertAllowedUrl(url: string): void {\n if (!GITHUB_URL_ALLOWLIST.test(url)) {\n throw new InvalidTeamPackUrlError(url);\n }\n}\n\nexport function resolveTeamPackRepoUrl(): string {\n const override = process.env.AVATAR_TEAM_PACK_REPO_URL;\n if (override) {\n assertAllowedUrl(override);\n return override;\n }\n // ORG_DEFAULT hardcoded → trust nhưng vẫn validate defensively (catch future typo).\n assertAllowedUrl(ORG_DEFAULT);\n return ORG_DEFAULT;\n}\n","// init-flow-handlers-for-each-project-status.ts\n//\n// 3 flow handlers ứng với từng trạng thái dự án mà user chọn ở wizard:\n// - runInitFromExistingRemote: dự án đã có remote URL → clone làm src/\n// - runInitFromExistingFolder: dự án đã có folder code local → link/tạo remote\n// - runInitFromScratch: dự án mới hoàn toàn → tạo remote mới qua gh\n//\n// Mỗi flow cuối cùng đều gọi scaffoldWorkspaceWithSrcSubmodule hoặc\n// finalizeWorkspaceScaffold từ workspace-scaffold-and-finalize-orchestrator.ts.\n// File này chỉ xử lý logic khác biệt giữa 3 flow (prompt, bootstrap, remote check).\n\nimport { basename, join, resolve } from \"node:path\";\nimport { input, select } from \"@inquirer/prompts\";\nimport { addTeamPackSubmoduleWithRetryOnNetworkFail } from \"../lib/add-team-pack-submodule-with-retry-on-network-fail.js\";\nimport { createGithubRemoteFromFolder } from \"../lib/create-github-remote-from-folder.js\";\nimport { ensureDir } from \"../lib/filesystem-helpers.js\";\nimport { ensureGitHubReady } from \"../lib/git-auth-and-install-orchestrator.js\";\nimport { git } from \"../lib/git-operations.js\";\nimport { handleRemoteAccessFailureWithAccountSwitch } from \"../lib/handle-remote-access-failure-with-account-switch.js\";\nimport {\n type BootstrapStrategy,\n safeBootstrapGitInFolder,\n} from \"../lib/safe-bootstrap-for-dirty-folder.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { spinner } from \"../lib/terminal-logger.js\";\nimport type { RepoVisibility } from \"../lib/validate-repo-name-and-visibility.js\";\nimport { tryVerifyGitRemoteAccessible } from \"../lib/verify-git-remote-accessible.js\";\nimport {\n type InitOptions,\n type ProjectStatus,\n parseBootstrapStrategyOpts,\n} from \"./init-options-and-bootstrap-strategy-parser.js\";\nimport { inferWorkspaceName } from \"./init-scaffold-variable-builders.js\";\nimport { promptTeamOwner, resolveWorkspacePath } from \"./init-success-rendering-and-helpers.js\";\nimport {\n finalizeWorkspaceScaffold,\n getOrCreateOriginRemote,\n scaffoldWorkspaceWithSrcSubmodule,\n} from \"./workspace-scaffold-and-finalize-orchestrator.js\";\n\n// ─── FLOW 1: EXISTING REMOTE ────────────────────────────────────────────────\nexport async function runInitFromExistingRemote(\n opts: InitOptions,\n ownerEmail: string,\n): Promise<void> {\n const initialRemoteUrl =\n opts.clientRepo ??\n (await input({\n message: \"URL git của repo:\",\n validate: (v) => (v.length > 0 ? true : \"URL bắt buộc\"),\n }));\n\n // v1.2.7: ensureGitHubReady có thể trả URL khác (user re-nhập trong recovery menu\n // khi URL gốc sai). Dùng resolvedUrl cho mọi step sau.\n const { resolvedRemoteUrl } = await ensureGitHubReady(initialRemoteUrl);\n const remoteUrl = resolvedRemoteUrl ?? initialRemoteUrl;\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(ownerEmail));\n const inferredName = inferWorkspaceName(remoteUrl);\n const workspaceName =\n opts.workspaceName ?? (await input({ message: \"Tên workspace:\", default: inferredName }));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);\n\n await scaffoldWorkspaceWithSrcSubmodule({\n workspacePath,\n workspaceName,\n srcRemoteUrl: remoteUrl,\n teamOwner,\n skipTeamPack: opts.skipTeamPack,\n description: opts.description ?? `Avatar workspace cho ${remoteUrl}`,\n packVersion: opts.packVersion,\n packLatest: opts.latest,\n autoYes: opts.yes,\n skipCommit: opts.commit === false,\n createWorkspaceRemote: opts.workspaceRemote,\n repoVisibility: opts.repoVisibility,\n repoOrg: opts.repoOrg,\n flow: \"existing-remote\",\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n ssoEmail: ownerEmail,\n });\n}\n\n// ─── FLOW 2: EXISTING FOLDER ────────────────────────────────────────────────\nexport async function runInitFromExistingFolder(\n opts: InitOptions,\n ownerEmail: string,\n): Promise<void> {\n const folderPath = resolve(\n opts.folderPath ??\n (await input({\n message: \"Đường dẫn folder hiện có:\",\n validate: (v) => (v.length > 0 ? true : \"Path bắt buộc\"),\n })),\n );\n\n // Safe bootstrap: nếu folder dirty → prompt 4 strategy bảo vệ user changes.\n await safeBootstrapGitInFolder(folderPath, {\n presetStrategy: parseBootstrapStrategyOpts(opts),\n autoYes: opts.yes,\n });\n\n // Check remote origin. Có → dùng luôn. Chưa có → hỏi tạo mới (default Có).\n let remoteUrl = await getOrCreateOriginRemote(folderPath, opts);\n\n // v1.12.0: Verify remote accessible TRƯỚC khi submodule add — common case\n // folder clone từ team khác xuống, user không có quyền access repo gốc.\n // Recovery menu cho phép user \"reset folder & tạo repo mới của mình\" thay vì\n // bị block sâu trong submodule add với error khó hiểu \"Repository not found\".\n if (remoteUrl) {\n const verify = tryVerifyGitRemoteAccessible(remoteUrl);\n if (!verify.ok) {\n log.warn(`Remote ${remoteUrl} không accessible (${verify.reason ?? \"unknown\"}).`);\n const recovered = await handleRemoteAccessFailureWithAccountSwitch({\n url: remoteUrl,\n initialReason: verify.reason ?? \"unknown\",\n initialDetail: verify.detail,\n folderPath, // enable option \"Reset folder & tạo repo mới\"\n defaultVisibility: opts.repoVisibility as \"private\" | \"public\" | undefined,\n });\n remoteUrl = recovered.resolvedUrl;\n }\n }\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(ownerEmail));\n const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;\n const workspaceName =\n opts.workspaceName ?? (await input({ message: \"Tên workspace:\", default: inferredName }));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);\n\n await scaffoldWorkspaceWithSrcSubmodule({\n workspacePath,\n workspaceName,\n srcRemoteUrl: remoteUrl ?? folderPath, // fallback local path nếu user từ chối tạo remote\n teamOwner,\n skipTeamPack: opts.skipTeamPack,\n description: opts.description ?? `Avatar workspace cho folder ${folderPath}`,\n packVersion: opts.packVersion,\n packLatest: opts.latest,\n autoYes: opts.yes,\n skipCommit: opts.commit === false,\n createWorkspaceRemote: opts.workspaceRemote,\n repoVisibility: opts.repoVisibility,\n repoOrg: opts.repoOrg,\n flow: \"existing-folder\",\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n ssoEmail: ownerEmail,\n });\n}\n\n// ─── FLOW 3: NEW PROJECT ────────────────────────────────────────────────────\nexport async function runInitFromScratch(opts: InitOptions, ownerEmail: string): Promise<void> {\n await ensureGitHubReady();\n\n const projectName =\n opts.workspaceName ??\n (await input({\n message: \"Tên dự án:\",\n validate: (v) => (v.length > 0 ? true : \"Tên bắt buộc\"),\n }));\n const visibility = (opts.repoVisibility ??\n (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 RepoVisibility;\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(ownerEmail));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);\n const srcPath = join(workspacePath, \"src\");\n\n // Tạo workspace dir + src/ rỗng + bootstrap git trong src/.\n // srcPath vừa tạo nên empty → safe bootstrap skip prompt thẳng.\n await ensureDir(workspacePath);\n await ensureDir(srcPath);\n await safeBootstrapGitInFolder(srcPath, { autoYes: true });\n\n // Tạo remote GitHub + push initial commit.\n const urls = createGithubRemoteFromFolder({\n folder: srcPath,\n name: projectName,\n visibility,\n org: opts.repoOrg,\n });\n\n // Workspace setup: init git, submodule add cho src/.\n await git(workspacePath).init();\n const sp = spinner(\n opts.skipTeamPack ? \"Add submodule src/...\" : \"Add submodule src/ + team-ai-pack...\",\n );\n try {\n await git(workspacePath).subModule([\"add\", urls.sshUrl, \"src\"]);\n let pinnedTag = \"HEAD\";\n if (!opts.skipTeamPack) {\n // v1.2.6: Stop spinner trước pre-flight access check để menu prompt không bị spinner đè.\n sp.stop();\n const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(\n workspacePath,\n opts.packVersion,\n ownerEmail,\n opts.latest === true && !opts.packVersion, // v1.10.0: latest mode khi flag set + không có explicit tag\n );\n pinnedTag = result.pinnedTag ?? \"HEAD\";\n sp.succeed(`Pin team-ai-pack vào ${pinnedTag}`);\n } else {\n sp.succeed(\"Skip team-ai-pack (--skip-team-pack)\");\n }\n await finalizeWorkspaceScaffold({\n workspacePath,\n workspaceName: projectName,\n teamOwner,\n description: opts.description ?? `Dự án mới: ${projectName}`,\n packVersion: pinnedTag,\n autoYes: opts.yes,\n skipCommit: opts.commit === false,\n createWorkspaceRemote: opts.workspaceRemote,\n repoVisibility: opts.repoVisibility,\n repoOrg: opts.repoOrg,\n flow: \"new-project\",\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n });\n } catch (err) {\n sp.fail(\"Init workspace thất bại\");\n throw err;\n }\n}\n","// Wrapper retry cho addTeamPackSubmodule — git submodule add có thể fail vì:\n// - Network glitch (DNS, timeout, packet drop) → simple retry\n// - SSH permission denied (publickey) → cần switch account / dùng HTTPS / setup SSH\n// - gh token expire giữa flow → cần re-auth\n// - Repo permission issue đã fix → retry sau accept invite\n//\n// Pre-flight check trong addTeamPackSubmodule đã handle \"chưa được invite\" case.\n// Wrapper này handle các fail TRONG submodule add bằng menu recovery rộng hơn.\n//\n// v1.2.11: Detect SSH-specific error → menu mở rộng (switch account / HTTPS fallback /\n// retry / skip / abort). Trước v1.2.10 chỉ có Retry/Skip/Abort generic không đủ.\nimport { spawnSync } from \"node:child_process\";\nimport { select } from \"@inquirer/prompts\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.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\n// Detect SSH-specific errors từ stderr submodule add.\nfunction isSshPermissionError(message: string): boolean {\n const text = message.toLowerCase();\n return (\n text.includes(\"permission denied (publickey)\") ||\n text.includes(\"publickey)\") ||\n text.includes(\"ssh: could not resolve\") ||\n text.includes(\"host key verification failed\")\n );\n}\n\n// Trigger gh auth login interactive — block đến khi user complete browser.\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}. Có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Open GitHub SSH key settings page để user add SSH public key.\nfunction openGithubSshKeysPage(): void {\n log.info(\"Mở trang GitHub Settings → SSH Keys...\");\n const r = spawnSync(\"open\", [\"https://github.com/settings/keys\"], { stdio: \"ignore\" });\n if (r.status !== 0) {\n // Fallback Linux/Windows — chỉ in URL.\n log.info(\"URL: https://github.com/settings/keys\");\n }\n}\n\n// Show menu khi SSH permission fail — nhiều option hơn generic retry/skip.\nasync function handleSshPermissionError(): Promise<\n \"retry\" | \"switch\" | \"https\" | \"skip\" | \"abort\"\n> {\n return (await select({\n message: \"SSH permission denied. Cách xử lý?\",\n choices: [\n {\n name: \"Switch GitHub account (gh auth login — mở browser)\",\n value: \"switch\" as const,\n },\n {\n name: \"Dùng HTTPS thay SSH (override URL bằng env AVATAR_TEAM_PACK_REPO_URL)\",\n value: \"https\" as const,\n },\n {\n name: \"Tôi vừa add SSH key lên GitHub — retry\",\n value: \"retry\" as const,\n },\n {\n name: \"Bỏ qua team-ai-pack (dùng avatar sync sau)\",\n value: \"skip\" as const,\n },\n {\n name: \"Tạm ngưng init — fix SSH key tay rồi chạy lại\",\n value: \"abort\" as const,\n },\n ],\n })) as \"retry\" | \"switch\" | \"https\" | \"skip\" | \"abort\";\n}\n\n// Wrap addTeamPackSubmodule với loop retry. Re-throw TeamPackAccessAbortedError\n// (đã có recovery menu riêng bên trong) → init.ts catch.\n// ssoEmail (v1.2.6): pass xuống để hiển thị trong access request info gửi admin.\nexport async function addTeamPackSubmoduleWithRetryOnNetworkFail(\n projectRoot: string,\n tag?: string,\n ssoEmail?: string,\n // v1.10.0: latest=true → forward xuống addTeamPackSubmodule để checkout HEAD main.\n latest = false,\n): Promise<AddTeamPackResult> {\n while (true) {\n try {\n const result = await addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest);\n return { pinnedTag: result.pinnedTag, skipped: false };\n } catch (err) {\n // Pre-flight access abort → propagate cho init.ts (đã có graceful exit).\n if (err instanceof TeamPackAccessAbortedError) throw err;\n\n const message = err instanceof Error ? err.message : String(err);\n\n // v1.2.11: Detect SSH-specific error → menu rộng hơn.\n if (isSshPermissionError(message)) {\n log.warn(\"Pull team-ai-pack thất bại: SSH permission denied (publickey).\");\n log.dim(\n \" → Máy này chưa có SSH key được register trên GitHub, hoặc key thuộc account khác.\",\n );\n\n const action = await handleSshPermissionError();\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\n \"User abort tại bước pull team-ai-pack. Fix SSH key (https://github.com/settings/keys) rồi chạy lại 'avatar init'.\",\n );\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng không có shared knowledge. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n if (action === \"switch\") {\n triggerGhAuthLoginInteractive();\n continue;\n }\n if (action === \"https\") {\n // Override env URL → HTTPS variant, loop tiếp.\n process.env.AVATAR_TEAM_PACK_REPO_URL = \"https://github.com/nalvn/team-ai-pack.git\";\n log.info(\"Override URL sang HTTPS. Lưu ý: HTTPS có thể fail 403 nếu read-only role.\");\n // Open SSH key page in background để user có option fix sau.\n openGithubSshKeysPage();\n continue;\n }\n // action === \"retry\" → loop tiếp với SSH URL hiện tại.\n continue;\n }\n\n // Generic error → menu retry/skip/abort cũ.\n const action = await promptRetryOrSkip({\n taskName: \"Pull team-ai-pack submodule\",\n reason: message,\n allowSkip: true,\n hint: \"Network glitch? Retry thường work. Nếu skip, dùng `avatar sync` sau để pull pack.\",\n });\n\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước pull team-ai-pack.\");\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng không có shared knowledge. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n // retry → loop tiếp.\n }\n }\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","// 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","// 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","// 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","// 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","// Shared types + helpers cho `avatar init` flow handlers.\n//\n// Tách file riêng để break circular import giữa init.ts (entry point) và\n// init-flow-handlers-for-each-project-status.ts (3 flow handler concrete).\n// Cả 2 nơi đều consume `InitOptions`, `ProjectStatus`, `parseBootstrapStrategyOpts` —\n// nếu để ở init.ts thì flow-handlers import init.ts = circular ở runtime.\n// File này không import gì từ ./init.js → không tạo cycle.\n\nimport type { BootstrapStrategy } from \"../lib/safe-bootstrap-for-dirty-folder.js\";\n\n// 3 flow values + deprecated legacy \"mode\" alias.\nexport type ProjectStatus = \"existing-remote\" | \"existing-folder\" | \"new-project\";\n\nexport interface InitOptions {\n // New v1.1 flags\n projectStatus?: ProjectStatus;\n folderPath?: string;\n createRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n // Carried over\n skipScan?: boolean;\n skipTeamPack?: boolean;\n packVersion?: string;\n // v1.10.0: --latest → pin pack vào HEAD của main branch (bleeding-edge),\n // bỏ qua tag SemVer. Conflict với --pack-version → ưu tiên --pack-version.\n // Commander auto-map --latest → opts.latest (camelCase).\n latest?: boolean;\n clientRepo?: string; // reused: URL khi projectStatus=existing-remote\n workspaceName?: string;\n workspaceParent?: string;\n force?: boolean;\n teamOwner?: string;\n description?: string;\n yes?: boolean;\n commit?: boolean; // commander tự set false khi user pass --no-commit\n workspaceRemote?: boolean; // tạo remote cho workspace root (--workspace-remote)\n aiSkip?: boolean; // Skip AI setup phase (CI / test mode)\n gitnexusSkip?: boolean; // Skip GitNexus phase (v1.4.0)\n bootstrapStrategy?: string; // stash | commit-all | skip | branch\n preserveUncommitted?: boolean; // Alias cho --bootstrap-strategy=stash\n // Legacy (deprecated)\n mode?: string;\n}\n\n// Parse 2 flag mới → BootstrapStrategy enum hoặc undefined (no preset → prompt).\n// --preserve-uncommitted override --bootstrap-strategy=other (UX: shortcut win).\nexport function parseBootstrapStrategyOpts(opts: InitOptions): BootstrapStrategy | undefined {\n if (opts.preserveUncommitted) return \"stash\";\n if (!opts.bootstrapStrategy) return undefined;\n const valid: BootstrapStrategy[] = [\"stash\", \"commit-all\", \"skip\", \"branch\"];\n if (valid.includes(opts.bootstrapStrategy as BootstrapStrategy)) {\n return opts.bootstrapStrategy as BootstrapStrategy;\n }\n throw new Error(\n `--bootstrap-strategy không hợp lệ: ${opts.bootstrapStrategy}. Chọn: ${valid.join(\" | \")}`,\n );\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\";\nimport type { InitMode } from \"../types/config-schema.js\";\n\n// Last path segment of an absolute project root, used as fallback project\n// name when user doesn't supply one explicitly. Handles trailing slashes.\nexport function projectNameOf(projectRoot: string): string {\n return projectRoot.split(\"/\").filter(Boolean).pop() ?? \"avatar-project\";\n}\n\n// Infer workspace folder name from a git remote URL.\n// \"git@github.com:org/repo.git\" → \"avatar-repo-workspace\"\n// \"https://github.com/org/repo.git\" → \"avatar-repo-workspace\"\n// \"https://github.com/org/repo\" → \"avatar-repo-workspace\"\n// \"org/repo\" (shorthand) → \"avatar-repo-workspace\"\n// \"repo\" (bare name) → \"avatar-repo-workspace\"\n// invalid input → \"avatar-client-workspace\" (last-resort fallback)\n//\n// v1.2.8 fixes:\n// - Support bare repo name (không có \"/\" hoặc \":\") — trước fallback \"client\"\n// - Strip \"avatar-\" prefix nếu repo name đã có → tránh double \"avatar-avatar-...\"\n// - Trim trailing .git và trailing slash\nexport function inferWorkspaceName(repoUrl: string): string {\n // Strip trailing slash + tách last path segment (work cả https://, git@, plain name).\n const trimmed = repoUrl.trim().replace(/\\/+$/, \"\");\n const lastSegment = trimmed.split(/[/:]/).pop() ?? \"\";\n // Strip .git suffix nếu có.\n const baseName = lastSegment.replace(/\\.git$/, \"\");\n if (!baseName) return \"avatar-client-workspace\";\n // Strip \"avatar-\" prefix sẵn có để tránh double prefix (\"avatar-avatar-...\").\n const withoutPrefix = baseName.replace(/^avatar-/, \"\");\n return `avatar-${withoutPrefix}-workspace`;\n}\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 submodule)\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.\nexport function buildScaffoldVariables(args: {\n projectName: string;\n projectDescription: string;\n teamOwner: string;\n packVersion: string;\n mode: InitMode;\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 mode: args.mode,\n gitnexusSection: buildGitnexusSection(args.gitnexusReady ?? false),\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 { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { formatPackCommandsCheatsheetBox } from \"../lib/format-pack-commands-cheatsheet-box.js\";\nimport { git } from \"../lib/git-operations.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\";\nimport type { ProjectStatus } from \"./init.js\";\n\n// ─── shared utilities (giữ từ v1.0.1, chỉ đổi signature mode → flow) ───────\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\nexport async function maybeCommitWorkspace(\n workspacePath: string,\n skipCommit?: boolean,\n): Promise<void> {\n // Mặc định LUÔN commit — giữ workspace ở git state hợp lệ (có baseline).\n // User muốn review trước commit thì pass --no-commit để skip.\n if (skipCommit) {\n log.warn(\"Skip commit (--no-commit). Chạy 'git status' + commit thủ công sau.\");\n return;\n }\n const g = git(workspacePath);\n await g.add([\"CLAUDE.md\", \".claude/\", \".gitignore\", \".gitmodules\", \"notes/\", \"scripts/\"]);\n await g.commit(\"chore: initialize Avatar workspace\");\n log.success(\"Đã commit workspace\");\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 flow: ProjectStatus,\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 ` ${chalk.dim(`(flow: ${flow})`)}`,\n formatAiStatusLine(aiResult),\n formatGitnexusStatusLine(gitnexusResult),\n \"\",\n ` ${chalk.cyan(`cd ${rootPath}`)}`,\n ` ${chalk.cyan(\"claude\")} Mở Claude Code ở workspace root`,\n \"\",\n ` ${chalk.cyan(\"avatar commit src\")} Commit code lên client remote`,\n ` ${chalk.cyan(\"avatar sync\")} Pull 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 feature\" },\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 feature 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","// workspace-scaffold-and-finalize-orchestrator.ts\n//\n// Orchestrate toàn bộ quá trình scaffold workspace Avatar:\n// - getOrCreateOriginRemote: kiểm tra / tạo remote origin cho folder hiện có\n// - scaffoldWorkspaceWithSrcSubmodule: scaffold workspace dùng cho flow 1 + 2\n// (workspace = parent, src/ là submodule trỏ tới remote hoặc local path)\n// - finalizeWorkspaceScaffold: bước chung sau khi src/ + team-ai-pack đã add xong\n// - autoSyncPackOnInit: auto-sync pack content vào .claude/ (v1.6.2)\n// - maybeCreateWorkspaceRemote: hỏi/tạo remote GitHub cho workspace root\n//\n// Tách ra khỏi init.ts để init.ts giữ vai trò entry point / flag parsing thuần.\n\nimport { join } from \"node:path\";\nimport { basename } from \"node:path\";\nimport { confirm, input, select } from \"@inquirer/prompts\";\nimport { addTeamPackSubmoduleWithRetryOnNetworkFail } from \"../lib/add-team-pack-submodule-with-retry-on-network-fail.js\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { createGithubRemoteFromFolder } from \"../lib/create-github-remote-from-folder.js\";\nimport {\n CreateWorkspaceRemoteError,\n createWorkspaceRemoteViaGh,\n linkExistingRemoteToWorkspace,\n} from \"../lib/create-workspace-remote-via-gh.js\";\nimport { ensureDir, pathExists } from \"../lib/filesystem-helpers.js\";\nimport { ensureGitHubReady } from \"../lib/git-auth-and-install-orchestrator.js\";\nimport { git } from \"../lib/git-operations.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport {\n appendGitignoreEntries,\n createClaudeDirTree,\n installGitHook,\n writeProjectKnowledgeFiles,\n writeProjectSettings,\n writeRootClaudeMd,\n} from \"../lib/project-tree-scaffolder.js\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"../lib/prompt-recovery-action-on-failure.js\";\nimport { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { setupDefaultFeaturesOnInit } from \"../lib/setup-default-features-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 type { RepoVisibility } from \"../lib/validate-repo-name-and-visibility.js\";\nimport { buildScaffoldVariables } from \"./init-scaffold-variable-builders.js\";\nimport { maybeCommitWorkspace, printInitSuccessBox } from \"./init-success-rendering-and-helpers.js\";\nimport type { InitOptions, ProjectStatus } from \"./init.js\";\n\n// Check origin remote của folder; nếu chưa có thì hỏi tạo (default Có) → gh repo create.\n// Trả về URL remote nếu có, undefined nếu user từ chối (caller fallback local path).\nexport async function getOrCreateOriginRemote(\n folderPath: string,\n opts: InitOptions,\n): Promise<string | undefined> {\n const remotes = await git(folderPath).getRemotes(true);\n const origin = remotes.find((r) => r.name === \"origin\");\n if (origin?.refs.push) {\n log.success(`Folder đã có remote origin: ${origin.refs.push}`);\n return origin.refs.push;\n }\n\n const shouldCreate =\n opts.createRemote ??\n (await confirm({\n message: \"Folder chưa có remote. Tạo GitHub repo ngay để share team?\",\n default: true,\n }));\n if (!shouldCreate) {\n log.warn(\"Tiếp tục với local path. Workspace chỉ chạy được trên máy bạn.\");\n return undefined;\n }\n\n await ensureGitHubReady();\n const visibility = (opts.repoVisibility ??\n (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 RepoVisibility;\n const repoName = await input({\n message: \"Tên repo:\",\n default: basename(folderPath),\n });\n const urls = createGithubRemoteFromFolder({\n folder: folderPath,\n name: repoName,\n visibility,\n org: opts.repoOrg,\n });\n return urls.sshUrl;\n}\n\n// Scaffold workspace dùng cho flow 1 + flow 2: workspace = parent của src/.\n// src/ là submodule trỏ tới remote URL hoặc local path.\nexport async function scaffoldWorkspaceWithSrcSubmodule(args: {\n workspacePath: string;\n workspaceName: string;\n srcRemoteUrl: string;\n teamOwner: string;\n description: string;\n packVersion?: string;\n packLatest?: boolean; // v1.10.0: pull HEAD main branch thay vì tag\n autoYes?: boolean;\n skipCommit?: boolean;\n createWorkspaceRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n skipTeamPack?: boolean;\n flow: ProjectStatus;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n ssoEmail?: string;\n}): Promise<void> {\n await ensureDir(args.workspacePath);\n await git(args.workspacePath).init();\n\n const sp = spinner(\n args.skipTeamPack ? \"Add submodule src/...\" : \"Add submodule src/ + team-ai-pack...\",\n );\n try {\n await git(args.workspacePath).subModule([\"add\", args.srcRemoteUrl, \"src\"]);\n let pinnedTag = \"HEAD\";\n if (!args.skipTeamPack) {\n // v1.2.6: Stop spinner trước pre-flight access check để menu prompt không bị spinner đè.\n sp.stop();\n const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(\n args.workspacePath,\n args.packVersion,\n args.ssoEmail,\n args.packLatest === true && !args.packVersion, // v1.10.0\n );\n pinnedTag = result.pinnedTag ?? \"HEAD\";\n sp.succeed(`Pin team-ai-pack vào ${pinnedTag}`);\n } else {\n sp.succeed(\"Skip team-ai-pack (--skip-team-pack)\");\n }\n\n await finalizeWorkspaceScaffold({\n workspacePath: args.workspacePath,\n workspaceName: args.workspaceName,\n teamOwner: args.teamOwner,\n description: args.description,\n packVersion: pinnedTag,\n autoYes: args.autoYes,\n skipCommit: args.skipCommit,\n createWorkspaceRemote: args.createWorkspaceRemote,\n repoVisibility: args.repoVisibility,\n repoOrg: args.repoOrg,\n flow: args.flow,\n aiSkip: args.aiSkip,\n gitnexusSkip: args.gitnexusSkip,\n });\n } catch (err) {\n sp.fail(\"Init workspace thất bại\");\n throw err;\n }\n}\n\n// Common scaffold step sau khi src/ + team-ai-pack đã add xong.\nexport async function finalizeWorkspaceScaffold(args: {\n workspacePath: string;\n workspaceName: string;\n teamOwner: string;\n description: string;\n packVersion: string;\n autoYes?: boolean;\n skipCommit?: boolean;\n createWorkspaceRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n flow: ProjectStatus;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n}): Promise<void> {\n // Mode \"client\" được dùng trong scaffold-variable builder cũ — giữ giá trị\n // này để compatible với template hiện tại. Sẽ rename trong release sau.\n const vars = buildScaffoldVariables({\n projectName: args.workspaceName,\n projectDescription: args.description,\n teamOwner: args.teamOwner,\n packVersion: args.packVersion,\n mode: \"client\",\n });\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 appendGitignoreEntries(args.workspacePath);\n await ensureDir(join(args.workspacePath, \"notes\"));\n await ensureDir(join(args.workspacePath, \"scripts\"));\n\n await installGitHook(join(args.workspacePath, \".git\"), \"post-merge\");\n await installGitHook(join(args.workspacePath, \".git\", \"modules\", \"src\"), \"pre-push\");\n log.success(\"Cài post-merge (workspace) + pre-push (src/)\");\n\n // v1.6.2: Auto-sync pack content vào convention paths Claude Code scan.\n // Pack submodule đã add vào .claude/pack/ ở bước trước → giờ tạo symlinks\n // + merge hooks pack vào settings.json để Claude Code load skills/agents/commands/hooks.\n // Trước v1.6.2 user phải chạy thêm `avatar sync` thủ công → bug UX. Giờ auto.\n await autoSyncPackOnInit(args.workspacePath, args.autoYes);\n\n await appendAuditEntry(\"init\", `flow=${args.flow},workspace=${args.workspaceName}`);\n await maybeCommitWorkspace(args.workspacePath, args.skipCommit);\n await maybeCreateWorkspaceRemote(args);\n\n let aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null = null;\n if (args.aiSkip) {\n log.dim(\"Bỏ qua AI setup (--ai-skip). Setup sau qua: avatar ai setup\");\n } else {\n aiResult = await runAiSetupPhase({ workspacePath: args.workspacePath });\n }\n\n // v1.4.0 Phase 10: GitNexus setup. Auto-skip nếu --ai-skip (logical chain —\n // wiki cần API key từ AI setup phase). Hoặc explicit --gitnexus-skip.\n let gitnexusResult: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null = null;\n const skipGitnexus = args.aiSkip || args.gitnexusSkip;\n if (skipGitnexus) {\n if (args.gitnexusSkip) {\n log.dim(\"Bỏ qua GitNexus setup (--gitnexus-skip). Setup sau: avatar gitnexus install\");\n } else {\n log.dim(\"Bỏ qua GitNexus setup (auto-skip do --ai-skip).\");\n }\n } else {\n gitnexusResult = await runGitnexusSetupPhase({ workspacePath: args.workspacePath });\n }\n\n // v1.4.0: Re-render CLAUDE.md với gitnexusReady=true nếu GitNexus install OK.\n // Lý do: writeRootClaudeMd chạy sớm trong scaffold (trước GitNexus phase) → cần re-write\n // để add section \"🧠 CODEBASE INTELLIGENCE\" conditional dựa vào kết quả.\n if (gitnexusResult?.ok) {\n const updatedVars = buildScaffoldVariables({\n projectName: args.workspaceName,\n projectDescription: args.description,\n teamOwner: args.teamOwner,\n packVersion: args.packVersion,\n mode: \"client\",\n gitnexusReady: true,\n });\n await writeRootClaudeMd(args.workspacePath, updatedVars);\n log.dim(\"Updated CLAUDE.md với GitNexus section\");\n }\n\n await printInitSuccessBox(args.workspacePath, args.flow, aiResult, gitnexusResult);\n}\n\n// v1.6.2: Auto-sync pack content vào convention paths Claude Code scan.\n// Trước v1.6.2 user phải chạy thêm `avatar sync` thủ công → bug UX nghiêm trọng:\n// /avatar:* commands + skills + agents từ pack KHÔNG load được dù pack đã clone.\n//\n// Flow:\n// 1. Skip nếu pack submodule chưa tồn tại (vd --skip-team-pack hoặc clone fail)\n// 2. syncAllMountDirs: tạo symlinks .claude/{skills,agents,commands,hooks,workflows,scripts,knowledge}\n// → .claude/pack/<dir>/\n// 3. mergePackSettingsIntoProjectSettings: merge pack/templates/settings.json.tpl\n// (hooks + statusLine) vào .claude/settings.json\n// 4. Fail-soft: lỗi không block init (workspace vẫn usable + user có thể rerun\n// `avatar sync` thủ công), chỉ log warn.\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 submodule không tồn tại (skip auto-sync). Có thể 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 merge)...\");\n\n try {\n // Step 1: Symlink farm.\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 (pack thiếu dir)` : \"\"}`,\n );\n\n // Step 2: Settings merge (hooks + statusLine từ pack template).\n const mergeResult = await mergePackSettingsIntoProjectSettings(workspacePath);\n switch (mergeResult.action) {\n case \"merged\":\n log.success(` ✓ settings.json merged (${mergeResult.changes.join(\"; \")})`);\n break;\n case \"no-change\":\n log.dim(\" - settings.json đã sync, không cần thay đổi.\");\n break;\n case \"no-pack-template\":\n log.dim(\" - Pack không có templates/settings.json.tpl, skip merge.\");\n break;\n }\n\n // Step 3: Default features (vd prompt-scoring). Hỏi y/n (default yes); CI → auto yes.\n // Merge hook feature SAU settings merge để hook feature append cạnh scout-block.\n await setupDefaultFeaturesOnInit(workspacePath, { autoYes });\n } catch (err) {\n // Fail-soft: workspace vẫn usable, user rerun `avatar sync` thủ công nếu cần.\n log.warn(\n `Auto-sync pack fail: ${err instanceof Error ? err.message : err}. Chạy \\`avatar sync\\` thủ công để retry.`,\n );\n }\n}\n\n// Hỏi/tạo remote GitHub cho workspace root. Chỉ chạy được khi đã commit\n// (gh repo create --push cần ít nhất 1 commit). Nếu user skip commit hoặc\n// từ chối tạo, workspace local-only — vẫn dùng được nhưng không share team.\nexport async function maybeCreateWorkspaceRemote(args: {\n workspacePath: string;\n workspaceName: string;\n autoYes?: boolean;\n skipCommit?: boolean;\n createWorkspaceRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n}): Promise<void> {\n // Skip nếu chưa có commit (gh repo create --push sẽ fail).\n if (args.skipCommit) {\n log.dim(\"Skip workspace remote (chưa commit). Setup sau qua: gh repo create ...\");\n return;\n }\n\n // Resolve từ flag hoặc prompt.\n let shouldCreate = args.createWorkspaceRemote;\n if (shouldCreate === undefined) {\n if (args.autoYes) return; // CI mode: default skip để không hỏi\n shouldCreate = await confirm({\n message: \"Tạo remote GitHub cho workspace để share team? (Avatar state)\",\n default: false,\n });\n }\n if (!shouldCreate) return;\n\n const visibility = ((args.repoVisibility as \"private\" | \"public\" | undefined) ??\n (args.autoYes\n ? \"private\"\n : await select({\n message: \"Workspace visibility?\",\n choices: [\n { name: \"private (mặc định, an toàn)\", value: \"private\" as const },\n { name: \"public\", value: \"public\" as const },\n ],\n }))) as \"private\" | \"public\";\n\n // Loop retry — user có thể switch gh account / sửa --repo-org / skip / abort.\n while (true) {\n try {\n await createWorkspaceRemoteViaGh({\n workspacePath: args.workspacePath,\n workspaceName: args.workspaceName,\n visibility,\n org: args.repoOrg,\n });\n return; // Success → exit loop.\n } catch (err) {\n // v1.2.10: Branch xử lý reason code structured.\n // \"repo-exists\" → offer extra option \"Dùng remote đã có\".\n if (err instanceof CreateWorkspaceRemoteError && err.reason === \"repo-exists\") {\n const fullName = err.fullName;\n const reuseAction = (await select({\n message: `Repo '${fullName}' đã tồn tại trên GitHub. Cách xử lý?`,\n choices: [\n {\n name: \"Dùng remote đã có (link workspace local vào repo này)\",\n value: \"reuse\" as const,\n },\n {\n name: \"Nhập tên workspace khác (tạo repo mới)\",\n value: \"rename\" as const,\n },\n { name: \"Bỏ qua (workspace local-only)\", value: \"skip\" as const },\n { name: \"Tạm ngưng init\", value: \"abort\" as const },\n ],\n })) as \"reuse\" | \"rename\" | \"skip\" | \"abort\";\n\n if (reuseAction === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước tạo workspace remote.\");\n }\n if (reuseAction === \"skip\") {\n log.warn(\"Workspace vẫn sẵn sàng local-only. Setup remote sau khi cần.\");\n return;\n }\n if (reuseAction === \"reuse\") {\n // Sync function (spawnSync internally) — không cần await dù codebase async-heavy.\n linkExistingRemoteToWorkspace({\n workspacePath: args.workspacePath,\n fullName,\n });\n return;\n }\n // rename → prompt new name, loop tiếp với name mới.\n const newName = await input({\n message: \"Tên workspace mới (sẽ tạo repo new):\",\n validate: (v) => (v.trim().length > 0 ? true : \"Tên không được rỗng\"),\n });\n args.workspaceName = newName.trim();\n continue; // Loop tiếp tạo với name mới.\n }\n\n // Reason khác → generic retry/skip menu.\n const action = await promptRetryOrSkip({\n taskName: \"Tạo workspace remote trên GitHub\",\n reason: err instanceof Error ? err.message : String(err),\n allowSkip: true, // Workspace remote OPTIONAL — skip OK, workspace local vẫn dùng được.\n hint: \"Tip: sai org? Pass --repo-org=<your-gh-user>. Hoặc switch gh: gh auth login.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước tạo workspace remote.\");\n }\n if (action === \"skip\") {\n log.warn(\"Workspace vẫn sẵn sàng local-only. Setup remote sau khi cần.\");\n return;\n }\n // retry → loop tiếp.\n }\n }\n}\n","// Tạo remote GitHub cho workspace + push initial commit. Khác với\n// create-github-remote-from-folder (dành cho src/): hàm này dành cho workspace\n// root (đã commit Avatar state) và setup tracking branch.\n//\n// Workspace remote là OPTIONAL: chỉ cần khi team muốn share Avatar state\n// (knowledge, hooks, settings) qua git. Workspace local-only vẫn dùng được cho\n// 1 dev cá nhân, nhưng không sync được cross-machine.\n//\n// v1.2.3 fix Bug B: validate `org` vs gh authenticated user TRƯỚC khi spawn\n// `gh repo create`. Nếu org != ghUser AND user không phải member org → fail-fast\n// với hint actionable.\n//\n// v1.2.10: Pre-flight check \"repo đã tồn tại\" + capture stderr full + throw\n// structured error code để caller offer \"Dùng remote đã có\" option.\nimport { spawnSync } from \"node:child_process\";\nimport { ensureGitHubReady } from \"./git-auth-and-install-orchestrator.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 CreateWorkspaceRemoteInput {\n workspacePath: string;\n workspaceName: string;\n visibility: RepoVisibility;\n org?: string;\n}\n\nexport interface CreateWorkspaceRemoteOutput {\n sshUrl: string;\n httpsUrl: string;\n}\n\n// v1.2.10: Structured error với reason code để caller branch recovery menu.\nexport type WorkspaceRemoteFailReason =\n | \"repo-exists\" // Repo cùng tên đã có trên GitHub — offer \"dùng remote đã có\"\n | \"no-permission\" // gh user không quyền tạo dưới namespace\n | \"name-invalid\" // Name vi phạm GitHub naming rules\n | \"network\" // DNS / connectivity\n | \"unknown\";\n\nexport class CreateWorkspaceRemoteError extends Error {\n reason: WorkspaceRemoteFailReason;\n fullName: string;\n stderr?: string;\n constructor(\n reason: WorkspaceRemoteFailReason,\n fullName: string,\n message: string,\n stderr?: string,\n ) {\n super(message);\n this.name = \"CreateWorkspaceRemoteError\";\n this.reason = reason;\n this.fullName = fullName;\n this.stderr = stderr;\n }\n}\n\n// Classify gh repo create stderr → reason code chuẩn hóa.\nfunction classifyGhCreateError(stderr: string): WorkspaceRemoteFailReason {\n const text = stderr.toLowerCase();\n if (\n text.includes(\"name already exists\") ||\n text.includes(\"already exists on this account\") ||\n text.includes(\"repository already exists\")\n ) {\n return \"repo-exists\";\n }\n if (\n text.includes(\"403\") ||\n text.includes(\"permission\") ||\n text.includes(\"not authorized\") ||\n text.includes(\"forbidden\")\n ) {\n return \"no-permission\";\n }\n if (text.includes(\"invalid\") && text.includes(\"name\")) {\n return \"name-invalid\";\n }\n if (\n text.includes(\"could not resolve\") ||\n text.includes(\"network\") ||\n text.includes(\"connection refused\")\n ) {\n return \"network\";\n }\n return \"unknown\";\n}\n\n// Check repo có tồn tại trên GitHub không (silent — chỉ check status).\n// Trả về true nếu repo tồn tại (user có thể access).\nfunction repoExistsOnGitHub(fullName: string): boolean {\n const r = spawnSync(\"gh\", [\"repo\", \"view\", fullName, \"--json\", \"name\"], {\n stdio: \"ignore\",\n });\n return r.status === 0;\n}\n\n// Check user (gh-authenticated) có quyền tạo repo dưới namespace `org` không.\n// Org = chính ghUser → OK (personal repo).\n// Org != ghUser → check membership trong org đó (cần là member).\nfunction canCreateInNamespace(\n org: string,\n ghUser: string,\n): {\n ok: boolean;\n reason?: string;\n} {\n // Same identity → luôn OK (tạo repo personal).\n if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };\n\n // Check user có là member của org không qua gh api.\n // GET /orgs/{org}/members/{username} → 204 = member, 404 = không.\n const r = spawnSync(\"gh\", [\"api\", `orgs/${org}/members/${ghUser}`, \"--silent\"], {\n stdio: \"ignore\",\n });\n if (r.status === 0) return { ok: true };\n\n // Check xem org có tồn tại không — phân biệt \"org không tồn tại\" vs \"user không là member\".\n const orgCheck = spawnSync(\"gh\", [\"api\", `orgs/${org}`, \"--silent\"], { stdio: \"ignore\" });\n if (orgCheck.status !== 0) {\n // Org không tồn tại — có thể là user account khác (vd \"xgirl2510-ops\" thực ra là user).\n const userCheck = spawnSync(\"gh\", [\"api\", `users/${org}`, \"--silent\"], { stdio: \"ignore\" });\n if (userCheck.status === 0) {\n return {\n ok: false,\n reason: `'${org}' là personal account khác (không phải org). Bạn (${ghUser}) không thể tạo repo dưới account của user khác. Pass --repo-org=${ghUser} hoặc switch gh: gh auth login`,\n };\n }\n return {\n ok: false,\n reason: `'${org}' không tồn tại trên GitHub. Check chính tả hoặc tạo org trước.`,\n };\n }\n\n // Org tồn tại nhưng user không là member.\n return {\n ok: false,\n reason: `'${ghUser}' không phải member của org '${org}'. Liên hệ admin org để được invite, hoặc pass --repo-org=${ghUser} tạo dưới personal account.`,\n };\n}\n\n// Idempotent: nếu workspace đã có remote `origin`, skip. Nếu repo trên GitHub\n// đã tồn tại nhưng workspace chưa có remote, thử add remote + push.\nexport async function createWorkspaceRemoteViaGh(\n input: CreateWorkspaceRemoteInput,\n): Promise<CreateWorkspaceRemoteOutput> {\n validateRepoName(input.workspaceName);\n validateRepoVisibility(input.visibility);\n\n await ensureGitHubReady();\n const ghUser = resolveGithubUsernameDefault();\n const org = input.org ?? ghUser;\n\n // Pre-flight validate namespace permission — fail-fast với hint clear.\n const namespaceCheck = canCreateInNamespace(org, ghUser);\n if (!namespaceCheck.ok) {\n throw new Error(`Không thể tạo repo dưới '${org}/': ${namespaceCheck.reason}`);\n }\n\n const fullName = `${org}/${input.workspaceName}`;\n\n // v1.2.10: Pre-flight check repo đã tồn tại chưa — detect sớm trước khi\n // spawn gh repo create. Tránh fail mơ hồ với stderr không capture được.\n if (repoExistsOnGitHub(fullName)) {\n throw new CreateWorkspaceRemoteError(\n \"repo-exists\",\n fullName,\n `Repo '${fullName}' đã tồn tại trên GitHub. Có thể bạn đã init workspace này trước đó.`,\n );\n }\n\n log.info(`Tạo GitHub repo cho workspace: ${fullName} (${input.visibility})...`);\n\n // gh repo create --source=workspace --remote=origin --push tạo repo + add\n // remote `origin` + push branch hiện tại (main). Auto-track origin/main.\n // v1.2.10: Capture cả stdout + stderr để dump khi fail (trước đây stdio:inherit\n // nuốt context, user không biết lý do).\n const r = spawnSync(\n \"gh\",\n [\n \"repo\",\n \"create\",\n fullName,\n `--${input.visibility}`,\n \"--source\",\n input.workspacePath,\n \"--remote\",\n \"origin\",\n \"--push\",\n ],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"], encoding: \"utf8\" },\n );\n\n if (r.status !== 0) {\n const stderr = (r.stderr || \"\").trim();\n const stdout = (r.stdout || \"\").trim();\n const combined = [stderr, stdout].filter(Boolean).join(\"\\n\");\n const reason = classifyGhCreateError(combined);\n\n // In stderr ra terminal để user thấy raw error từ gh CLI.\n if (combined) {\n process.stderr.write(`\\n${combined}\\n\\n`);\n }\n\n throw new CreateWorkspaceRemoteError(\n reason,\n fullName,\n `Tạo workspace remote thất bại (exit ${r.status}): ${reason}.`,\n combined,\n );\n }\n\n const sshUrl = `git@github.com:${fullName}.git`;\n const httpsUrl = `https://github.com/${fullName}.git`;\n log.success(`Workspace remote: ${sshUrl}`);\n return { sshUrl, httpsUrl };\n}\n\n// v1.2.10: Helper hook existing remote vào workspace local (không tạo mới).\n// Dùng khi user chọn \"Dùng remote đã có\" trong recovery menu.\nexport function linkExistingRemoteToWorkspace(args: {\n workspacePath: string;\n fullName: string;\n}): CreateWorkspaceRemoteOutput {\n const sshUrl = `git@github.com:${args.fullName}.git`;\n const httpsUrl = `https://github.com/${args.fullName}.git`;\n\n // Add remote origin (idempotent — nếu đã có thì set-url).\n const addResult = spawnSync(\n \"git\",\n [\"-C\", args.workspacePath, \"remote\", \"add\", \"origin\", sshUrl],\n {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n },\n );\n if (addResult.status !== 0) {\n // Có thể remote origin đã tồn tại — set-url override.\n spawnSync(\"git\", [\"-C\", args.workspacePath, \"remote\", \"set-url\", \"origin\", sshUrl], {\n stdio: \"ignore\",\n });\n }\n log.success(`Linked existing remote: ${sshUrl}`);\n return { sshUrl, httpsUrl };\n}\n","// setup-default-features-on-init.ts\n//\n// Bước init: hỏi user có bật các default feature (đọc từ pack features/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 <feature> (y/n)\" default yes mỗi feature.\n// - Non-interactive (autoYes / CI): tự động yes (giữ default bật), không hỏi.\n// - Fail-mềm: pack chưa có features/ → defaults rỗng → no-op im lặng.\n\nimport { confirm } from \"@inquirer/prompts\";\nimport { readDefaultFeatures } from \"./discover-pack-features-and-defaults.js\";\nimport { enableFeatureByName } from \"./feature-enable-disable-orchestrator.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Tên hiển thị thân thiện cho prompt (fallback = tên feature).\nconst DISPLAY_NAME: Record<string, string> = {\n \"prompt-scoring\": \"prompt scoring\",\n};\n\nexport async function setupDefaultFeaturesOnInit(\n workspacePath: string,\n opts: { autoYes?: boolean } = {},\n): Promise<void> {\n const defaults = await readDefaultFeatures(workspacePath);\n if (defaults.length === 0) {\n return; // pack chưa có features/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 feature '${name}' (user opt-out).`);\n continue;\n }\n // silent: feature default có thể chưa có trong pack cũ → không spam warning.\n await enableFeatureByName(workspacePath, name, { silent: true });\n }\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","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","// 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","// 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 <subcommand>` — group lệnh quản lý team-ai-pack submodule.\n// Subcommand đầu tiên: `status` — hiển thị tag đang pin + tag mới nhất từ remote\n// để member biết có cần `avatar sync` không.\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { git, isGitRepo, listTags } from \"../lib/git-operations.js\";\nimport { pickLatestStableSemVerTag } from \"../lib/pick-latest-stable-semver-tag.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 submodule (status, ...)\");\n\n pack\n .command(\"status\")\n .description(\"Hiển thị tag pack đang pin (default offline; --fetch để check remote)\")\n .option(\"--json\", \"Output JSON cho script\")\n // v1.13.0 perf: Default OFFLINE. `git fetch --tags origin` tốn 1-5s trên mạng VN.\n // `avatar pack status` được gọi thường xuyên (mỗi terminal mở repo) → không nên\n // block 5s mặc định. User opt-in bằng `--fetch` khi muốn so sánh với remote.\n .option(\"--fetch\", \"Fetch remote tags trước khi so sánh (chậm thêm 1-5s)\")\n .action(async (opts: { json?: boolean; fetch?: boolean }) => {\n try {\n const snap = await gatherPackStatus(process.cwd(), opts.fetch === true);\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 currentTag: string | null; // null khi chưa cài\n latestTag: string | null; // null khi không fetch được hoặc không có tag\n upToDate: boolean | null; // null khi chưa biết (không fetch)\n fetched: boolean;\n}\n\nasync function gatherPackStatus(cwd: string, doFetch: boolean): Promise<PackStatus> {\n const packDir = join(cwd, PACK_RELATIVE_PATH);\n\n if (!(await pathExists(packDir)) || !(await isGitRepo(packDir))) {\n return {\n installed: false,\n currentTag: null,\n latestTag: null,\n upToDate: null,\n fetched: false,\n };\n }\n\n const currentTag = await readPinnedPackVersion(cwd).catch(() => null);\n\n let fetched = false;\n if (doFetch) {\n // Fetch tags từ origin để biết tag mới nhất. Silent fail nếu offline.\n try {\n await git(packDir).fetch([\"--tags\", \"origin\"]);\n fetched = true;\n } catch {\n fetched = false;\n }\n }\n\n const allTags = await listTags(packDir).catch(() => [] as string[]);\n const latestTag = pickLatestStableSemVerTag(allTags);\n\n const upToDate = currentTag && latestTag ? currentTag === latestTag : null;\n\n return {\n installed: true,\n currentTag,\n latestTag,\n upToDate,\n fetched,\n };\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.currentTag ?? chalk.yellow(\"(unknown)\");\n const latest = s.latestTag ?? chalk.dim(s.fetched ? \"(no tags)\" : \"(không fetch)\");\n\n let verdict: string;\n if (s.upToDate === true) {\n verdict = chalk.green(\"✓ Đang dùng tag mới nhất\");\n } else if (s.upToDate === false) {\n verdict = `${chalk.yellow(\"⚠ Có version mới\")} ${chalk.dim(\"→ avatar sync\")}`;\n } else {\n verdict = chalk.dim(\"(không so sánh được)\");\n }\n\n const lines = [\n `${chalk.bold(\"team-ai-pack status\")}`,\n \"─\".repeat(48),\n `${chalk.dim(\"Tag hiện tại:\")} ${current}`,\n `${chalk.dim(\"Tag mới nhất:\")} ${latest}`,\n `${chalk.dim(\"Trạng thái:\")} ${verdict}`,\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 secrets {list,set,get,rm,check}` — Command 13 spec.\n// Backed by OS keychain via @napi-rs/keyring. Service prefix: \"avatar\".\n// Audit log entries are written for set/rm; values NEVER logged.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerSecretsCommand(program: Command): void {\n const secrets = program.command(\"secrets\").description(\"Quản lý secrets trong OS keychain (M09)\");\n\n secrets\n .command(\"list\")\n .description(\"Liệt kê secrets đã set (chỉ tên, không value)\")\n .action(notImplementedYet(\"secrets list\", \"Milestone 09\"));\n\n secrets\n .command(\"set <service> <name>\")\n .description(\"Set/update secret (prompt ẩn)\")\n .action(notImplementedYet(\"secrets set\", \"Milestone 09\"));\n\n secrets\n .command(\"get <service> <name>\")\n .description(\"Lấy secret, copy clipboard, auto-xóa sau 30s\")\n .action(notImplementedYet(\"secrets get\", \"Milestone 09\"));\n\n secrets\n .command(\"rm <service> <name>\")\n .description(\"Xóa secret khỏi keychain\")\n .action(notImplementedYet(\"secrets rm\", \"Milestone 09\"));\n\n secrets\n .command(\"check\")\n .description(\"Verify mọi secret required bởi MCP đã enabled\")\n .action(notImplementedYet(\"secrets check\", \"Milestone 09\"));\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 { discoverEnabledAndAvailableFeatures } from \"../lib/discover-pack-features-and-defaults.js\";\nimport { pathExists, readText } from \"../lib/filesystem-helpers.js\";\nimport { isGitRepo } from \"../lib/git-operations.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 featuresEnabled: string[];\n featuresAvailableCount: 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 featuresEnabled: [],\n featuresAvailableCount: 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, features] = await Promise.all([\n (async (): Promise<string | null> => {\n const isPackRepo = await isGitRepo(join(claudeRoot, \"pack\"));\n if (!isPackRepo) 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 discoverEnabledAndAvailableFeatures(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 featuresEnabled: features.enabled,\n featuresAvailableCount: features.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 formatFeatures(s: StatusSnapshot): string {\n if (s.featuresEnabled.length > 0) {\n return `${s.featuresEnabled.join(\", \")} (${s.featuresEnabled.length}/${s.featuresAvailableCount} available)`;\n }\n if (s.featuresAvailableCount > 0) {\n return `none enabled (${s.featuresAvailableCount} available — avatar feature 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(\"Features:\")} ${formatFeatures(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 [--force] [--version <tag>] [--dry-run]` — Milestone 08.\n//\n// Pulls latest team-ai-pack into .claude/pack/ and creates symlink farm so\n// Claude Code sees skills/agents/commands/hooks/workflows/scripts at\n// .claude/<dir> by symlink to .claude/pack/<dir>.\n//\n// Flow:\n// 1. Locate project root (cwd with .claude/pack/ submodule)\n// 2. git fetch --tags in submodule\n// 3. Determine target tag (--version or latest)\n// 4. --dry-run? show preview and exit\n// 5. checkout target tag in submodule\n// 6. syncAllMountDirs() → create/update symlinks\n// 7. Report results, warn on conflicts\nimport { join } from \"node:path\";\n\nimport type { Command } from \"commander\";\n\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport {\n checkoutBranchHeadInSubmodule,\n checkoutTagInSubmodule,\n currentCommitSha,\n git,\n listTags,\n} from \"../lib/git-operations.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport { pickLatestStableSemVerTag } from \"../lib/pick-latest-stable-semver-tag.js\";\nimport { buildSyncPreview } from \"../lib/preview-team-pack-sync-changes-for-dry-run.js\";\nimport { reapplyEnabledFeaturesAfterSync } from \"../lib/reapply-enabled-features-after-sync.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\";\n\ninterface SyncOptions {\n force?: boolean;\n version?: string;\n dryRun?: boolean;\n latest?: boolean; // v1.8.0: pull HEAD của main branch thay vì tag SemVer\n}\n\n// v1.8.0: Default branch để follow khi --latest. Hardcode \"main\" vì team-ai-pack\n// dùng main. Future: có thể đọc từ .ck.json nếu cần.\nconst DEFAULT_PACK_BRANCH = \"main\";\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(packDir))) {\n log.error(\n `team-ai-pack submodule chưa được khởi tạo ở ${TEAM_PACK_RELATIVE_PATH}/.\\n Chạy 'avatar init' để add submodule, hoặc 'git submodule update --init' nếu đã clone repo.`,\n );\n process.exit(1);\n }\n\n // Fetch latest tags from origin before resolving target version\n try {\n await git(packDir).fetch([\"--tags\", \"origin\"]);\n } catch (err) {\n log.warn(\n `Không fetch được tags từ origin (${err instanceof Error ? err.message : err}). Sẽ dùng tag local hiện có.`,\n );\n }\n\n // v1.8.0: --latest mode → pull HEAD của main branch (bleeding-edge).\n // Bypass SemVer tag picker. Conflict với --version → ưu tiên --version (explicit).\n const useLatestMode = opts.latest === true && !opts.version;\n\n // v1.7.1: Pick latest tag bằng SemVer-aware parser thay vì alphabetical sort.\n // - Skip pre-release tags (v0.5.0-rc1, -beta) → stable only mặc định\n // - Handle v0.10.0 > v0.4.0 đúng (alphabetical sort sai)\n // User muốn pin pre-release → pass --version explicit.\n const allTags = await listTags(packDir);\n let targetVersion: string;\n if (useLatestMode) {\n targetVersion = `${DEFAULT_PACK_BRANCH} (HEAD)`;\n } else {\n const picked = opts.version ?? pickLatestStableSemVerTag(allTags);\n if (!picked) {\n log.error(\n `Không tìm thấy stable SemVer tag (vMAJOR.MINOR.PATCH) trong team-ai-pack submodule.\\n Tags hiện có: ${allTags.length > 0 ? allTags.join(\", \") : \"(none)\"}\\n Pass --version <tag> rõ ràng, hoặc dùng --latest để pull HEAD branch ${DEFAULT_PACK_BRANCH}.`,\n );\n process.exit(1);\n }\n targetVersion = picked;\n }\n\n if (opts.dryRun) {\n const preview = await buildSyncPreview(packDir, claudeDir, targetVersion);\n log.info(`Pack version hiện tại: ${preview.currentVersion}`);\n log.info(`Target version: ${preview.targetVersion}`);\n if (preview.commitsBehind.length === 0) {\n log.info(\"Đã ở target version, không có commit mới.\");\n } else {\n log.info(`Commits cần pull (${preview.commitsBehind.length}):`);\n for (const c of preview.commitsBehind.slice(0, 20)) {\n console.log(` ${c}`);\n }\n if (preview.commitsBehind.length > 20) {\n console.log(` ... và ${preview.commitsBehind.length - 20} commits khác`);\n }\n }\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 thay đổi. Bỏ --dry-run để thực thi.\");\n return;\n }\n\n // Apply: checkout target (tag hoặc branch HEAD) then create symlinks.\n // v1.8.0: --latest → checkout HEAD của main branch (bleeding-edge mode).\n if (useLatestMode) {\n log.info(`Pulling HEAD của branch ${DEFAULT_PACK_BRANCH} (bleeding-edge mode)...`);\n await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);\n const sha = await currentCommitSha(packDir);\n log.dim(` HEAD = ${sha.slice(0, 7)}`);\n log.warn(\n \"⚠ --latest mode: workspace pin vào commit floating, không reproducible. \" +\n \"Chuyển về tag stable: avatar sync (no flag).\",\n );\n } else {\n log.info(`Checking out ${targetVersion} trong submodule...`);\n await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, targetVersion, projectRoot);\n }\n\n log.info(\"Creating symlink farm...\");\n const results = await syncAllMountDirs(packDir, claudeDir, opts.force === true);\n\n reportResults(results, opts.force === true);\n\n // Merge pack settings.json template vào project settings.json để activate hooks/statusLine.\n // Fail-soft: lỗi merger không block sync (symlinks đã xong). Log warn để user biết.\n log.info(\"Merging pack settings.json template into project settings.json...\");\n try {\n const mergeResult = await mergePackSettingsIntoProjectSettings(projectRoot);\n switch (mergeResult.action) {\n case \"merged\":\n log.success(\n ` ✓ settings.json merged (${mergeResult.changes.join(\"; \")}). Backup: ${mergeResult.backupPath ?? \"n/a\"}`,\n );\n break;\n case \"no-change\":\n log.info(\" - settings.json đã sync với pack, không có thay đổi.\");\n break;\n case \"no-pack-template\":\n log.dim(\" - Pack không có templates/settings.json.tpl, skip merge.\");\n break;\n }\n } catch (err) {\n log.warn(\n ` ! Merge settings.json fail: ${err instanceof Error ? err.message : err}. Symlinks đã tạo OK, hooks có thể chưa active. Manual merge nếu cần.`,\n );\n }\n\n // Re-apply feature đang bật (vd prompt-scoring) — merge tpl ở trên có thể đã\n // ghi đè settings → hook feature mất nếu không re-apply. Fail-soft.\n try {\n await reapplyEnabledFeaturesAfterSync(projectRoot);\n } catch (err) {\n log.warn(\n ` ! Re-apply features fail: ${err instanceof Error ? err.message : err}. Chạy 'avatar feature enable <name>' thủ công nếu cần.`,\n );\n }\n\n log.success(`Synced team-ai-pack to ${targetVersion}.`);\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(\n ` ! ${r.dir} → CONFLICT: existing real dir. ` +\n `Dùng --force để backup + override (sẽ giữ data ở ${r.dir}.backup-<timestamp>/).`,\n );\n break;\n }\n }\n const conflicts = results.filter((r) => r.action === \"skipped-conflict\").length;\n if (conflicts > 0 && !force) {\n log.warn(\n `${conflicts} mount dir(s) bị skip do conflict. Chạy lại với --force nếu muốn override (backup tự động).`,\n );\n }\n}\n\nexport function registerSyncCommand(program: Command): void {\n program\n .command(\"sync\")\n .description(\"Pull team-ai-pack mới nhất + tạo symlink farm vào .claude/\")\n .option(\"--force\", \"Override .claude/<dir>/ nếu là real dir (backup trước)\")\n .option(\"--version <tag>\", \"Pin vào version cụ thể (vd: v0.2.0)\")\n .option(\"--latest\", \"Bleeding-edge: pull HEAD của main branch (bỏ qua tag SemVer)\")\n .option(\"--dry-run\", \"Hiển thị preview, không apply thay đổi\")\n .action(syncAction);\n}\n","// Preview what `avatar sync` would do, without modifying filesystem.\n//\n// Used by --dry-run flag. Compares current pinned version vs target version,\n// lists changed files via git log, reports which mount dirs need linking.\nimport { join } from \"node:path\";\n\nimport { pathExists } from \"./filesystem-helpers.js\";\nimport { currentCommitSha, git, listTags, tagAtHead } from \"./git-operations.js\";\nimport { pickLatestStableSemVerTag } from \"./pick-latest-stable-semver-tag.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 commitsBehind: 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 // Cannot use fs.lstat result easily here without import; rely on simple check\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\n// Compute the diff between current pinned version and target. Returns one-line\n// commit summaries (subject only), empty array if already at target.\nasync function listCommitsBetween(\n packDir: string,\n fromSha: string,\n toRef: string,\n): Promise<string[]> {\n try {\n const result = await git(packDir).raw([\"log\", \"--oneline\", `${fromSha}..${toRef}`]);\n return result\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n } catch {\n return [];\n }\n}\n\nexport async function buildSyncPreview(\n packDir: string,\n claudeDir: string,\n targetVersion?: string,\n): Promise<SyncPreview> {\n // v1.7.1: currentVersion ưu tiên tag tại HEAD, fallback SHA. targetVersion\n // dùng SemVer-aware picker (skip pre-release, sort numeric).\n const currentTagOrNull = await tagAtHead(packDir);\n const currentSha = await currentCommitSha(packDir);\n const currentVersion = currentTagOrNull ?? currentSha.slice(0, 7);\n\n const allTags = await listTags(packDir);\n const target = targetVersion ?? pickLatestStableSemVerTag(allTags) ?? \"HEAD\";\n\n const commits = await listCommitsBetween(packDir, currentSha, target);\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 commitsBehind: commits,\n mountDirStatuses: mountStatuses,\n };\n}\n","// reapply-enabled-features-after-sync.ts\n//\n// Sau khi sync merge lại settings.json.tpl (có thể đổi/ghi đè settings), re-apply\n// mọi feature đang enabled=true để hook feature KHÔNG bị mất. Không có bước này,\n// feature \"biến mất\" sau mỗi `avatar sync`.\n//\n// Idempotent: enable lại không nhân đôi entry (dedupe trong enableFeature lo).\n// Fail-mềm: feature trong state nhưng pack đã bỏ manifest → enableFeatureByName\n// trả false (warn), không crash sync.\n\nimport { enableFeatureByName } from \"./feature-enable-disable-orchestrator.js\";\nimport { listEnabledFeatures } from \"./feature-state-store.js\";\nimport { log } from \"./terminal-logger.js\";\n\nexport async function reapplyEnabledFeaturesAfterSync(workspacePath: string): Promise<void> {\n const enabled = await listEnabledFeatures(workspacePath);\n if (enabled.length === 0) {\n return;\n }\n log.info(`Re-apply ${enabled.length} feature(s) đang bật vào settings.json...`);\n for (const name of enabled) {\n await enableFeatureByName(workspacePath, name);\n }\n}\n","// `avatar tools {list,install,remove}` — Commands 10/11/12 spec (Chapter 12 v4).\n// Lifecycle management for system dependencies (git, gh, node, uv, docker) and\n// MCP servers (gitnexus, context7, serena, github, filesystem, playwright).\n// Registry source: team-ai-pack/tools/registry.yaml.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerToolsCommand(program: Command): void {\n const tools = program.command(\"tools\").description(\"Quản lý system tools + MCP servers (M09)\");\n\n tools\n .command(\"list\")\n .description(\"Liệt kê tool đã cài / còn thiếu\")\n .option(\"--installed\", \"Chỉ liệt kê tool đã cài\")\n .option(\"--missing\", \"Chỉ liệt kê tool còn thiếu\")\n .option(\"--json\", \"Output JSON cho script\")\n .action(notImplementedYet(\"tools list\", \"Milestone 09\"));\n\n tools\n .command(\"install [tool-ids...]\")\n .description(\"Cài tool và đăng ký vào ~/.claude.json\")\n .option(\"--all-recommended\", \"Cài mọi MCP được recommend cho project type\")\n .option(\"--verify\", \"Chạy MCP thử để verify (mất ~30s/tool)\")\n .option(\"--no-secrets\", \"Skip prompt secrets, set sau qua 'avatar secrets'\")\n .action(notImplementedYet(\"tools install\", \"Milestone 09\"));\n\n tools\n .command(\"remove <tool-id>\")\n .description(\"Gỡ tool khỏi ~/.claude.json (optional uninstall binary)\")\n .option(\"--keep-secrets\", \"Không xóa secrets khỏi keychain\")\n .option(\"--keep-binary\", \"Không uninstall npm global binary\")\n .action(notImplementedYet(\"tools remove\", \"Milestone 09\"));\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":["Command","fs","join","confirm","constants","fs","dirname","join","relative","pathExists","path","ensureDir","readText","readJson","writeTextAtomic","content","mode","tmp","writeJsonAtomic","data","existsSync","readFileSync","dirname","join","MAX_WALKUP_LEVELS","isAvatarWorkspace","dir","hasClaudeDir","hasClaudeMd","hasSrcDir","gitmodulesPath","content","resolveAvatarWorkspaceRootFromCwd","startDir","current","i","parent","fs","homedir","join","z","userConfigSchema","userStateSchema","projectSettingsSchema","initModeSchema","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","ensureAuditLogPermissions","fs","AUDIT_LOG_PATH","appendAuditEntry","action","detail","ensureAvatarHome","entry","line","spawnSync","chalk","ora","log","m","spinner","text","spinnerWithElapsed","prefix","startMs","sp","formatElapsed","sec","s","interval","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","platform","detectHostPlatform","p","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeClaudeBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeClaudeVersion","cachedInfo","detectClaudeCodeInstallation","path","invalidateClaudeCodeInstallationCache","spawnSync","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","password","select","ANTHROPIC_BASE_URL","ANTHROPIC_API_VERSION","FETCH_TIMEOUT_MS","maskAnthropicKey","key","validateAnthropicKeyFormat","trimmed","promptAnthropicKeyHidden","password","fetchAnthropicModels","apiKey","controller","timer","res","models","m","id","err","promptAnthropicModelChoice","only","log","sorted","a","b","score","lower","select","setupAnthropicApiKeyAndModel","model","input","password","select","DEFAULT_BASE_URL","FETCH_TIMEOUT_MS","maskApiKey","key","promptApiKeyHidden","password","v","promptBaseUrl","defaultUrl","input","fetchAvailableModels","baseUrl","apiKey","controller","timer","res","models","m","id","err","vpnHint","errMsg","promptModelChoice","claudeAliases","only","log","choiceList","select","setupLLMLiteApiKeyAndModel","model","fs","join","SECRET_FILE_MODE","getClaudeSettingsPath","workspacePath","join","readExistingSettings","path","pathExists","readJson","err","applySubscription","existing","model","existingEnv","rest","merged","_b","_t","_k","envRest","applyLLMLite","apiKey","baseUrl","applyAnthropic","applyUseGlobal","source","sourceEnv","sourceModel","writeClaudeSettings","input","writeJsonAtomic","fs","SUBSCRIPTION_DEFAULT_MODEL","warnAboutPlaintextSecret","providerLabel","log","runAiSetupPhase","args","info","detectClaudeCodeInstallation","installClaudeCodeViaNpm","invalidateClaudeCodeInstallationCache","globalInfo","detectGlobalClaudeSettings","promptAiProviderChoice","authInfo","readClaudeCodeAuthInfo","triggerClaudeCodeAuthLogin","writeClaudeSettings","appendAuditEntry","quota","verifyClaudeCodeQuota","reason","getQuotaErrorHint","llmConfig","setupLLMLiteApiKeyAndModel","anthropicConfig","setupAnthropicApiKeyAndModel","err","message","spawnSync","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","input","spawnSync","existsSync","join","assertAvatarWorkspaceRoot","cwd","srcGit","join","workspaceGit","claudeDir","existsSync","gitExec","args","r","spawnSync","stderr","isDirty","commitSrc","workspaceRoot","opts","srcPath","log","sha","pushed","commitWorkspace","executeCommitWithTargetSelection","result","srcOutcome","wsOutcome","registerCommitCommand","program","opts","runCommitSrc","message","input","v","result","executeCommitWithTargetSelection","log","err","spawnSync","fs","join","boxen","join","fs","join","fs","path","join","isStatusLineCommandResolvable","workspacePath","command","match","filePath","log","fullPath","join","wsResolved","path","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","fs","writeJsonAtomic","featureManifestPath","workspacePath","name","join","readFeatureManifest","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","enableFeature","manifest","merged","changes","mHooks","userHooks","outHooks","touched","totalMigrated","event","featEntries","existing","migratedUser","droppedCount","seen","toAdd","e","featDeny","userDeny","unionDeny","unionDedupe","disableFeature","_h","_p","rest","removed","entries","featKeys","kept","keptDeny","d","_d","permRest","newPerms","join","FEATURE_STATE_RELATIVE_PATH","emptyState","stateFilePath","workspacePath","join","readFeatureState","path","pathExists","readJson","writeFeatureState","state","writeJsonAtomic","setFeatureState","name","entry","listEnabledFeatures","manifestHooksPresent","settings","manifest","mHooks","sHooks","event","featEntries","present","presentCommands","e","h","entry","checkEnabledFeaturesHealth","cwd","enabled","listEnabledFeatures","settingsPath","join","pathExists","readJson","checks","name","readFeatureManifest","enableFeature","join","simpleGit","git","cwd","simpleGit","isGitRepo","pathExists","join","addSubmodule","repoUrl","destPath","cwd","git","checkoutTagInSubmodule","submodulePath","tag","submoduleCwd","join","checkoutBranchHeadInSubmodule","branch","listTags","tagAtHead","currentCommitSha","fs","join","existsSync","dirname","join","fileURLToPath","TEMPLATE_PATTERN","renderTemplate","source","variables","match","key","value","HERE","dirname","fileURLToPath","PACKAGE_ROOT","findPackageRoot","TEMPLATES_ROOT","join","HOOKS_ROOT","startDir","dir","existsSync","parent","loadTemplate","name","readText","renderTemplateByName","variables","source","renderTemplate","loadHook","backupIfExists","path","pathExists","ts","basePath","backupPath","counter","fs","writeWithBackup","content","mode","backup","writeTextAtomic","CLAUDE_SUBDIRS","createClaudeDirTree","projectRoot","claudeRoot","join","ensureDir","sub","dir","writeProjectKnowledgeFiles","_projectRoot","_vars","writeRootClaudeMd","vars","renderTemplateByName","writeProjectSettings","appendGitignoreEntries","tpl","marker","existing","separator","installGitHook","gitDir","hookName","loadHook","hooksDir","dest","registerDoctorCommand","program","opts","checks","runChecks","renderChecks","applyFixes","err","log","cwd","nodeVer","major","minor","n","nodeOk","config","readUserConfig","isTokenExpired","packPath","join","claudeMdPath","hookPath","gitRepo","hasPack","hasClaudeMd","hasHook","isGitRepo","pathExists","installGitHook","gitignorePath","gitignoreContent","gitignoreOk","fs","settingsGitignored","pythonCheck","spawnSync","python3Check","hasPython","hasPython3","settingsPath","settingsRaw","settings","match","refFile","fullPath","fileExists","which","hasClaudeCli","featureChecks","checkEnabledFeaturesHealth","lines","chalk","passed","issues","fixable","c","icon","boxen","count","resolve","checkbox","confirm","fs","join","PACK_FEATURES_REL","packFeaturesDir","workspacePath","join","listAvailableFeatures","dir","pathExists","entries","fs","names","entry","readDefaultFeatures","path","readJson","discoverEnabledAndAvailableFeatures","available","enabled","listEnabledFeatures","buildAddChoices","available","enabled","enabledSet","name","buildRemoveChoices","resolveNonInteractiveSelection","candidates","spawnSync","warnIfRuntimeMissing","runtime","probe","spawnSync","log","logApplyResult","name","result","enableFeatureByName","workspacePath","opts","manifest","readFeatureManifest","enableFeature","setFeatureState","disableFeatureByName","prevVersion","readFeatureState","disableFeature","resolveWorkspace","opts","resolve","selectFeatures","candidates","preChecked","verb","resolveNonInteractiveSelection","choices","buildAddChoices","buildRemoveChoices","checkbox","runAdd","ws","available","enabled","discoverEnabledAndAvailableFeatures","log","selected","name","enableFeatureByName","runRemove","confirm","disableFeatureByName","runList","listAvailableFeatures","state","readFeatureState","names","isAvailable","entry","version","registerFeatureCommand","program","feat","spawnSync","fs","join","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","spawnSync","stdout","runGitnexusAnalyze","workspacePath","metaPath","join","existsSync","confirm","boxen","spawnSync","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeGitnexusBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeGitnexusVersion","cachedInfo","detectGitnexusInstallation","path","invalidateGitnexusInstallationCache","spawnSync","NPM_INSTALL_TIMEOUT_MS","GITNEXUS_PACKAGE","InstallGitnexusError","reason","message","exitCode","classifyNpmFailure","stderrSample","stderr","installGitnexusViaNpm","log","result","spawnSync","invalidateGitnexusInstallationCache","probe","detectGitnexusInstallation","input","select","UserAbortedRecoveryError","message","promptRetryOrSkip","args","log","choices","select","fs","homedir","join","MCP_FILE_MODE","EXPECTED_GITNEXUS_ENTRY","getMcpServersPath","join","homedir","isEntryEqual","a","b","backupExistingFile","path","ts","backupPath","fs","registerGitnexusMcpServer","existing","fileExisted","pathExists","readJson","err","existingEntry","log","backup","merged","writeJsonAtomic","spawnSync","existsSync","join","confirm","REASONING_PATTERNS","isReasoningModel","modelName","pattern","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","anthropicKey","llmliteToken","confirmWikiGeneration","model","confirm","tailLines","text","n","runGitnexusWikiConditional","creds","log","reasoningMode","isReasoningModel","writeGitnexusConfigForWikiRun","args","sp","spinnerWithElapsed","result","spawnSync","reason","stderr","stdout","wikiPath","existsSync","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","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","select","chalk","BANNER_LINES","GRADIENT_STOPS","lerpChannel","a","b","t","gradientAt","scaled","lo","hi","localT","renderAvatarBanner","opts","colored","line","idx","r","g","printAvatarBanner","spawnSync","input","select","spawnSync","fs","basename","join","confirm","select","spawnSync","RepoAlreadyExistsError","fullName","executeGhRepoCreate","input","args","r","spawnSync","resolveGithubUsernameDefault","r","REPO_NAME_REGEX","InvalidRepoNameError","name","validateRepoName","validateRepoVisibility","v","createGithubRemoteFromFolder","input","validateRepoName","validateRepoVisibility","org","resolveGithubUsernameDefault","log","urls","executeGhRepoCreate","backupTimestamp","d","backupExistingDotGit","folderPath","gitDir","join","pathExists","backupName","backupPath","fs","log","reinitGitInFolder","r1","spawnSync","r3","resetFolderGitAndCreateNewRemoteUnderCurrentUser","opts","folderName","basename","repoName","confirm","visibility","select","createGithubRemoteFromFolder","spawnSync","TIMEOUT_MS","classifyRemoteError","stderr","text","tryVerifyGitRemoteAccessible","url","r","spawnSync","TIMEOUT_MS","err","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","readdirSync","select","simpleGit","existsSync","statSync","join","checkFolderHasGit","folderPath","gitPath","stat","simpleGit","INITIAL_COMMIT_MESSAGE","createInitialGitCommit","folderPath","g","status","existsSync","join","SIGNATURES","detectFolderTechStack","folderPath","matched","stack","files","f","readFileSync","dirname","join","fileURLToPath","__dirname","CANDIDATE_DIRS","AVATAR_MARKER_START","AVATAR_MARKER_END","readTemplate","stack","dir","composeGitignoreContent","stacks","sections","s","existsSync","readFileSync","writeFileSync","join","writeOrMergeGitignore","folderPath","avatarBlock","path","join","existsSync","writeFileSync","existing","readFileSync","startIdx","AVATAR_MARKER_START","endIdx","AVATAR_MARKER_END","before","after","InitAbortedByUserError","message","detectFolderGitState","folderPath","checkFolderHasGit","simpleGit","readdirSync","e","promptBootstrapStrategy","state","opts","select","stashUserChanges","g","stashName","status","log","restoreStash","err","getCurrentBranch","branch","writeAvatarGitignore","stacks","detectFolderTechStack","writeOrMergeGitignore","composeGitignoreContent","executeBootstrapWithStrategy","strategy","stashed","createInitialGitCommit","originalBranch","safeBootstrapGitInFolder","appendAuditEntry","join","spawnSync","confirm","select","boxen","parseRepoSlugFromGitUrl","url","checkRepoAccess","repoSlug","spawnSync","getCurrentGhUser","r","triggerGhAuthLoginInteractive","log","copyInfoToClipboardWithConsent","info","confirm","clipboardy","err","printAccessWarningBox","ghUser","ssoEmail","lines","chalk","boxen","buildAccessRequestInfo","ensureTeamPackAccessWithRetry","args","initialGhUser","action","select","finalUser","SEMVER_REGEX","parseSemVerTag","tag","match","major","minor","patch","prerelease","pickLatestStableSemVerTag","tags","includePrerelease","parsed","t","a","b","ORG_DEFAULT","GITHUB_URL_ALLOWLIST","InvalidTeamPackUrlError","url","assertAllowedUrl","resolveTeamPackRepoUrl","override","TEAM_PACK_REPO_URL","resolveTeamPackRepoUrl","TEAM_PACK_RELATIVE_PATH","TeamPackAccessAbortedError","message","DEFAULT_PACK_BRANCH","addTeamPackSubmodule","projectRoot","tag","ssoEmail","latest","url","repoSlug","parseRepoSlugFromGitUrl","ensureTeamPackAccessWithRetry","addSubmodule","err","msg","log","checkoutTagInSubmodule","checkoutBranchHeadInSubmodule","submoduleDir","join","allTags","listTags","target","pickLatestStableSemVerTag","readPinnedPackVersion","submoduleRoot","tagAtHead","currentCommitSha","basename","join","resolve","input","select","spawnSync","select","isSshPermissionError","message","text","triggerGhAuthLoginInteractive","log","r","spawnSync","openGithubSshKeysPage","handleSshPermissionError","select","addTeamPackSubmoduleWithRetryOnNetworkFail","projectRoot","tag","ssoEmail","latest","addTeamPackSubmodule","err","TeamPackAccessAbortedError","action","UserAbortedRecoveryError","promptRetryOrSkip","spawnSync","checkGhCliAuthStatus","r","spawnSync","hasBinary","name","platform","detectHostPlatform","spawnSync","detectPackageManager","candidates","pm","spawnSync","INSTALL_COMMANDS","installGhCliViaPackageManager","pm","spec","log","r","spawnSync","spawnSync","setupGitCredentialViaGh","spawnSync","log","spawnSync","triggerGhCliAuthLogin","log","r","spawnSync","ensureGitHubReady","remoteUrl","checkGhCliAuthStatus","log","pm","detectPackageManager","promptRetryOrSkip","UserAbortedRecoveryError","installGhCliViaPackageManager","err","triggerGhCliAuthLogin","setupGitCredentialViaGh","result","tryVerifyGitRemoteAccessible","handleRemoteAccessFailureWithAccountSwitch","parseBootstrapStrategyOpts","opts","valid","readFileSync","dirname","resolve","fileURLToPath","cachedVersion","readCliVersion","here","i","candidate","raw","pkg","inferWorkspaceName","repoUrl","baseName","buildGitnexusSection","gitnexusReady","buildScaffoldVariables","args","readCliVersion","join","relative","resolve","input","select","boxen","boxen","PACK_COMMAND_CHEATSHEET","formatPackCommandsCheatsheetBox","maxCmdWidth","e","header","chalk","subheader","lines","footer","content","boxen","readdir","join","isEmptyOrMissing","path","pathExists","readdir","e","findAlternativeWorkspaceName","parent","desiredName","maxAttempts","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","maybeCommitWorkspace","workspacePath","skipCommit","g","git","formatAiStatusLine","aiResult","chalk","modelPart","formatGitnexusStatusLine","result","parts","printInitSuccessBox","rootPath","flow","gitnexusResult","lines","relative","boxen","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","formatPackCommandsCheatsheetBox","join","basename","confirm","input","select","spawnSync","CreateWorkspaceRemoteError","reason","fullName","message","stderr","classifyGhCreateError","text","repoExistsOnGitHub","spawnSync","canCreateInNamespace","org","ghUser","createWorkspaceRemoteViaGh","input","validateRepoName","validateRepoVisibility","ensureGitHubReady","resolveGithubUsernameDefault","namespaceCheck","log","r","stdout","combined","sshUrl","httpsUrl","linkExistingRemoteToWorkspace","args","confirm","DISPLAY_NAME","setupDefaultFeaturesOnInit","workspacePath","opts","defaults","readDefaultFeatures","name","display","wantEnable","confirm","log","enableFeatureByName","fs","dirname","join","relative","fs","timestamp","d","pad","backupDirBeforeReplace","targetPath","backupPath","TEAM_PACK_MOUNT_DIRS","isSymbolicLink","path","fs","syncMountedDir","source","dest","force","dir","relative","dirname","pathExists","backupPath","backupDirBeforeReplace","relativeSource","syncAllMountDirs","packDir","claudeDir","results","join","getOrCreateOriginRemote","folderPath","opts","origin","git","r","log","confirm","ensureGitHubReady","visibility","select","repoName","input","basename","createGithubRemoteFromFolder","scaffoldWorkspaceWithSrcSubmodule","args","ensureDir","sp","spinner","pinnedTag","addTeamPackSubmoduleWithRetryOnNetworkFail","finalizeWorkspaceScaffold","err","vars","buildScaffoldVariables","createClaudeDirTree","writeProjectKnowledgeFiles","writeRootClaudeMd","writeProjectSettings","appendGitignoreEntries","join","installGitHook","autoSyncPackOnInit","appendAuditEntry","maybeCommitWorkspace","maybeCreateWorkspaceRemote","aiResult","runAiSetupPhase","gitnexusResult","runGitnexusSetupPhase","updatedVars","printInitSuccessBox","workspacePath","autoYes","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","claudeDir","results","syncAllMountDirs","created","missing","mergeResult","mergePackSettingsIntoProjectSettings","setupDefaultFeaturesOnInit","shouldCreate","createWorkspaceRemoteViaGh","CreateWorkspaceRemoteError","fullName","reuseAction","UserAbortedRecoveryError","linkExistingRemoteToWorkspace","newName","v","action","promptRetryOrSkip","runInitFromExistingRemote","opts","ownerEmail","initialRemoteUrl","input","v","resolvedRemoteUrl","ensureGitHubReady","remoteUrl","teamOwner","promptTeamOwner","inferredName","inferWorkspaceName","workspaceName","workspaceParent","resolve","workspacePath","resolveWorkspacePath","scaffoldWorkspaceWithSrcSubmodule","runInitFromExistingFolder","folderPath","safeBootstrapGitInFolder","parseBootstrapStrategyOpts","getOrCreateOriginRemote","verify","tryVerifyGitRemoteAccessible","log","handleRemoteAccessFailureWithAccountSwitch","basename","runInitFromScratch","projectName","visibility","select","srcPath","join","ensureDir","urls","createGithubRemoteFromFolder","git","sp","spinner","pinnedTag","addTeamPackSubmoduleWithRetryOnNetworkFail","finalizeWorkspaceScaffold","err","boxen","open","GOOGLE_CLIENT_ID","GOOGLE_CLIENT_SECRET","HOSTED_DOMAIN","SCOPES","DEVICE_CODE_URL","TOKEN_URL","REVOKE_URL","requestDeviceCode","body","res","text","pollForToken","deviceCode","errorCode","decodeIdToken","idToken","parts","payload","base64","json","VALID_ISSUERS","CLOCK_SKEW_SECONDS","verifyIdTokenClaims","claims","nowSec","ageSec","verifyHostedDomain","buildUserConfig","token","expiresAt","revokeToken","token","body","REVOKE_URL","buildVerificationUrl","response","url","HOSTED_DOMAIN","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","registerInitCommand","program","opts","runInit","err","InitAbortedByUserError","log","TeamPackAccessAbortedError","RemoteAccessAbortedError","UserAbortedRecoveryError","printAvatarBanner","userConfig","readUserConfig","isTokenExpired","runLogin","promptRetryOrSkip","promptProjectStatus","runInitFromExistingRemote","runInitFromExistingFolder","runInitFromScratch","select","notImplementedYet","commandName","milestone","chalk","registerMcpRunCommand","program","notImplementedYet","join","boxen","PACK_RELATIVE_PATH","registerPackCommand","program","opts","snap","gatherPackStatus","renderPackStatus","err","log","cwd","doFetch","packDir","join","pathExists","isGitRepo","currentTag","readPinnedPackVersion","fetched","git","allTags","listTags","latestTag","pickLatestStableSemVerTag","s","lines","chalk","boxen","current","latest","verdict","registerRestoreCommand","program","notImplementedYet","registerReviewCommand","program","notImplementedYet","registerScanCommand","program","notImplementedYet","registerSecretsCommand","program","secrets","notImplementedYet","fs","join","boxen","fs","join","BACKUP_DIR_NAME","listBackups","projectRoot","dir","join","BACKUP_DIR_NAME","pathExists","fs","e","registerStatusCommand","program","opts","snapshot","gatherStatus","renderStatusBox","err","log","cwd","projectName","claudeRoot","join","pathExists","readCliVersion","pendingDir","packVersion","pendingCount","backupCount","techStackSummary","features","isGitRepo","readPinnedPackVersion","fs","n","listBackups","b","readTechStackFirstLine","discoverEnabledAndAvailableFeatures","techStackPath","readText","l","formatFeatures","s","lines","chalk","boxen","join","join","inspectMountDir","packDir","claudeDir","dir","source","join","dest","pathExists","fs","listCommitsBetween","fromSha","toRef","git","l","buildSyncPreview","targetVersion","currentTagOrNull","tagAtHead","currentSha","currentCommitSha","currentVersion","allTags","listTags","target","pickLatestStableSemVerTag","commits","mountStatuses","TEAM_PACK_MOUNT_DIRS","reapplyEnabledFeaturesAfterSync","workspacePath","enabled","listEnabledFeatures","log","name","enableFeatureByName","DEFAULT_PACK_BRANCH","syncAction","opts","projectRoot","claudeDir","join","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","log","git","err","useLatestMode","allTags","listTags","targetVersion","picked","pickLatestStableSemVerTag","preview","buildSyncPreview","c","m","checkoutBranchHeadInSubmodule","sha","currentCommitSha","checkoutTagInSubmodule","results","syncAllMountDirs","reportResults","mergeResult","mergePackSettingsIntoProjectSettings","reapplyEnabledFeaturesAfterSync","force","r","conflicts","registerSyncCommand","program","registerToolsCommand","program","tools","notImplementedYet","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","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","registerCommitCommand","registerToolsCommand","registerSecretsCommand","registerMcpRunCommand","registerAiCommand","registerGitnexusCommand","registerFeatureCommand","registerPackCommand","registerUninstallCommand","err","msg"]}
|
|
1
|
+
{"version":3,"sources":["../src/lib/filesystem-helpers.ts","../src/lib/write-envrc-with-per-project-secrets.ts","../src/lib/migrate-anthropic-key-from-settings-json-to-envrc.ts","../src/index.ts","../src/commands/ai.ts","../src/lib/resolve-avatar-workspace-root-from-cwd.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/terminal-logger.ts","../src/lib/detect-claude-code-installation.ts","../src/lib/detect-host-platform.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-google-gemini-api-key-and-model.ts","../src/lib/setup-llmlite-api-key-and-model.ts","../src/lib/setup-openai-codex-api-key-and-model.ts","../src/lib/store-provider-key-via-direnv-or-fallback.ts","../src/lib/detect-direnv-installation-status.ts","../src/lib/install-direnv-via-package-manager.ts","../src/lib/write-claude-settings-json-per-project.ts","../src/lib/run-ai-setup-phase.ts","../src/lib/test-ai-provider-by-detected-mode.ts","../src/commands/commit.ts","../src/lib/execute-commit-with-target-selection.ts","../src/commands/doctor.ts","../src/lib/check-enabled-features-health.ts","../src/lib/apply-feature-manifest-to-settings.ts","../src/lib/merge-pack-settings-into-project-settings.ts","../src/lib/feature-state-store.ts","../src/lib/git-operations.ts","../src/lib/project-tree-scaffolder.ts","../src/lib/template-bundle-loader.ts","../src/lib/mustache-template-engine.ts","../src/commands/feature.ts","../src/lib/discover-pack-features-and-defaults.ts","../src/lib/feature-add-remove-selection.ts","../src/lib/feature-enable-disable-orchestrator.ts","../src/commands/gitnexus.ts","../src/lib/run-gitnexus-setup-and-analyze.ts","../src/lib/run-gitnexus-setup-phase.ts","../src/lib/detect-gitnexus-installation.ts","../src/lib/install-gitnexus-via-npm.ts","../src/lib/prompt-recovery-action-on-failure.ts","../src/lib/register-gitnexus-mcp-server.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/init.ts","../src/lib/avatar-ascii-banner.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/resolve-github-username-default.ts","../src/lib/validate-repo-name-and-visibility.ts","../src/lib/create-github-remote-from-folder.ts","../src/lib/verify-git-remote-accessible.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/check-team-pack-access-with-retry-loop.ts","../src/lib/pick-latest-stable-semver-tag.ts","../src/lib/resolve-team-pack-repo-url.ts","../src/commands/init-flow-handlers-for-each-project-status.ts","../src/lib/add-team-pack-submodule-with-retry-on-network-fail.ts","../src/lib/check-gh-cli-auth-status.ts","../src/lib/detect-package-manager.ts","../src/lib/install-gh-cli-via-package-manager.ts","../src/lib/setup-git-credential-via-gh.ts","../src/lib/trigger-gh-cli-auth-login.ts","../src/lib/git-auth-and-install-orchestrator.ts","../src/commands/init-options-and-bootstrap-strategy-parser.ts","../src/lib/read-cli-version-from-package-json.ts","../src/commands/init-scaffold-variable-builders.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/workspace-scaffold-and-finalize-orchestrator.ts","../src/lib/create-workspace-remote-via-gh.ts","../src/lib/setup-default-features-on-init.ts","../src/lib/symlink-farm-for-team-pack-mount-dirs.ts","../src/lib/backup-existing-dir-before-symlink-override.ts","../src/commands/login.ts","../src/lib/google-oauth-device-flow.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/secrets.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-features-after-sync.ts","../src/commands/tools.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":["// 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 { constants, promises as fs } from \"node:fs\";\nimport { dirname, join, relative } from \"node:path\";\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","// Write/update a workspace .envrc file with per-project secrets.\n//\n// Safety contract:\n// - chmod 600 file (owner-only read/write) AFTER write\n// - Atomic write via temp + rename\n// - Idempotent merge: existing vars preserved unless explicitly updated\n// - Banner line so user can identify Avatar-managed block\n// - Gitignore enforcement: caller must run ensureGitignoreCoversEnvrc()\n// - direnv allow auto-invoked after write (so file activates immediately)\n//\n// Format:\n// # Avatar per-project secrets — DO NOT COMMIT\n// # Managed by avatar-cli (avatar secrets set/get/rm).\n// export KEY1=\"value1\"\n// export KEY2=\"value2\"\n//\n// Caller responsibilities:\n// - User confirmation before overwriting existing user-managed .envrc\n// - Backup creation if needed\n// - Print final banner (\"Loaded N secrets for <project>\") via terminal-logger\nimport { spawnSync } from \"node:child_process\";\nimport { chmodSync, existsSync, readFileSync, renameSync, writeFileSync } from \"node:fs\";\nimport { join } from \"node:path\";\n\nconst ENVRC_FILENAME = \".envrc\";\nconst AVATAR_BLOCK_BEGIN = \"# >>> avatar-cli secrets >>>\";\nconst AVATAR_BLOCK_END = \"# <<< avatar-cli secrets <<<\";\nconst AVATAR_BANNER = `${AVATAR_BLOCK_BEGIN}\n# Per-project secrets managed by avatar-cli.\n# DO NOT COMMIT — must be gitignored (avatar enforces this on setup).\n# Edit via: avatar secrets set <NAME>\n# Remove via: avatar secrets rm <NAME>\n# View: avatar secrets list`;\n\nexport interface WriteEnvrcResult {\n envrcPath: string;\n varsBefore: number; // count of avatar-managed vars before write\n varsAfter: number;\n direnvAllowOk: boolean | null; // null = skipped (direnv missing)\n hadExistingNonAvatarContent: boolean;\n}\n\nexport interface EnvVarUpdate {\n // null value → remove the key\n [name: string]: string | null;\n}\n\nexport function envrcPath(workspacePath: string): string {\n return join(workspacePath, ENVRC_FILENAME);\n}\n\n// Parse Avatar-managed block. Returns the chunk between BEGIN/END markers, or\n// null if no Avatar block exists yet.\nfunction extractAvatarBlock(content: string): string | null {\n const beginIdx = content.indexOf(AVATAR_BLOCK_BEGIN);\n if (beginIdx === -1) return null;\n const endIdx = content.indexOf(AVATAR_BLOCK_END, beginIdx);\n if (endIdx === -1) return null;\n return content.slice(beginIdx, endIdx + AVATAR_BLOCK_END.length);\n}\n\n// Parse `export KEY=\"value\"` lines from Avatar block. Returns map.\nfunction parseAvatarBlockVars(block: string): Map<string, string> {\n const vars = new Map<string, string>();\n // Match: export KEY=\"value with spaces and quotes \\\\\"escaped\\\\\"\"\n // Conservative parser: accept double-quoted only (matches our writer output).\n const re = /^export\\s+([A-Z][A-Z0-9_]*)=\"((?:[^\"\\\\]|\\\\.)*)\"$/gm;\n let m: RegExpExecArray | null;\n // biome-ignore lint/suspicious/noAssignInExpressions: standard regex.exec pattern\n while ((m = re.exec(block)) !== null) {\n const key = m[1];\n const rawValue = m[2];\n if (!key || rawValue === undefined) continue;\n // Unescape: \\\" → \", \\\\ → \\\n const value = rawValue.replace(/\\\\\"/g, '\"').replace(/\\\\\\\\/g, \"\\\\\");\n vars.set(key, value);\n }\n return vars;\n}\n\nfunction escapeForDoubleQuotes(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\nfunction buildAvatarBlock(vars: Map<string, string>): string {\n const lines = [AVATAR_BANNER];\n // Sort for deterministic output (diff-friendly).\n const keys = [...vars.keys()].sort();\n for (const key of keys) {\n const value = vars.get(key);\n if (value === undefined) continue;\n lines.push(`export ${key}=\"${escapeForDoubleQuotes(value)}\"`);\n }\n lines.push(AVATAR_BLOCK_END);\n return lines.join(\"\\n\");\n}\n\n// Apply updates to existing Avatar-managed block (insert/update/delete keys).\n// All non-Avatar content (user's own .envrc additions) is preserved verbatim.\nexport async function writeEnvrcWithSecrets(\n workspacePath: string,\n updates: EnvVarUpdate,\n): Promise<WriteEnvrcResult> {\n const path = envrcPath(workspacePath);\n let existing = \"\";\n if (existsSync(path)) {\n existing = readFileSync(path, \"utf8\");\n }\n\n const oldBlock = extractAvatarBlock(existing);\n const existingVars = oldBlock ? parseAvatarBlockVars(oldBlock) : new Map<string, string>();\n const varsBefore = existingVars.size;\n\n for (const [key, value] of Object.entries(updates)) {\n if (value === null) {\n existingVars.delete(key);\n } else {\n existingVars.set(key, value);\n }\n }\n\n const newBlock = buildAvatarBlock(existingVars);\n\n let newContent: string;\n let hadExistingNonAvatarContent = false;\n if (oldBlock) {\n newContent = existing.replace(oldBlock, newBlock);\n // Check non-Avatar content\n hadExistingNonAvatarContent = existing.replace(oldBlock, \"\").trim().length > 0;\n } else if (existing.trim()) {\n // User has existing .envrc without Avatar block — append our block.\n hadExistingNonAvatarContent = true;\n newContent = `${existing.replace(/\\s+$/, \"\")}\\n\\n${newBlock}\\n`;\n } else {\n newContent = `${newBlock}\\n`;\n }\n\n // Atomic write: temp + rename\n const tmpPath = `${path}.avatar-tmp-${process.pid}`;\n writeFileSync(tmpPath, newContent, { encoding: \"utf8\", mode: 0o600 });\n renameSync(tmpPath, path);\n // Chmod again post-rename in case umask interfered\n chmodSync(path, 0o600);\n\n // direnv allow — file is now trusted. Best-effort; if direnv missing,\n // user still gets warning from terminal-logger banner in caller.\n let direnvAllowOk: boolean | null = null;\n try {\n const result = spawnSync(\"direnv\", [\"allow\", path], {\n encoding: \"utf8\",\n timeout: 5000,\n });\n direnvAllowOk = result.status === 0;\n } catch {\n direnvAllowOk = null; // direnv not on PATH\n }\n\n return {\n envrcPath: path,\n varsBefore,\n varsAfter: existingVars.size,\n direnvAllowOk,\n hadExistingNonAvatarContent,\n };\n}\n\n// Read current Avatar-managed vars (key + value). Used by secrets list/get.\nexport function readAvatarEnvrcVars(workspacePath: string): Map<string, string> {\n const path = envrcPath(workspacePath);\n if (!existsSync(path)) return new Map();\n const content = readFileSync(path, \"utf8\");\n const block = extractAvatarBlock(content);\n if (!block) return new Map();\n return parseAvatarBlockVars(block);\n}\n","// Migrate plaintext ANTHROPIC_API_KEY (or any env secret) from project\n// settings.json INTO .envrc managed by avatar-cli.\n//\n// Flow:\n// 1. Read .claude/settings.json.env — if no secrets found → no-op\n// 2. For each KEY in MIGRATABLE_KEYS that exists in settings.json.env:\n// - Move to .envrc (Avatar block)\n// - Remove from settings.json.env\n// 3. Backup settings.json before write\n// 4. Update gitignore to cover .envrc + settings.json* + .claude/.env*\n//\n// Why: settings.json is per-machine config in Claude Code's documented\n// hierarchy, BUT it's also the default place avatar-init wrote ANTHROPIC_API_KEY\n// in v1.0–v1.18. Committing settings.json would leak the key. Direnv .envrc\n// is gitignored by default → safer.\n//\n// Callers: `avatar init` (auto-migrate after detect), `avatar secrets migrate` (explicit).\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { pathExists, readJson, writeJsonAtomic } from \"./filesystem-helpers.js\";\nimport { writeEnvrcWithSecrets } from \"./write-envrc-with-per-project-secrets.js\";\n\n// Whitelist secrets we know are safe to migrate. Other custom keys user added\n// → leave alone (don't assume).\nexport const MIGRATABLE_SECRET_KEYS = [\n \"ANTHROPIC_API_KEY\",\n \"ANTHROPIC_AUTH_TOKEN\",\n \"OPENAI_API_KEY\",\n \"GEMINI_API_KEY\",\n \"DEEPSEEK_API_KEY\",\n \"GROQ_API_KEY\",\n \"PERPLEXITY_API_KEY\",\n \"ANTHROPIC_VERTEX_PROJECT_ID\",\n \"GOOGLE_APPLICATION_CREDENTIALS\",\n] as const;\n\nexport interface MigrationResult {\n // What we actually moved (key list, no values)\n migratedKeys: string[];\n settingsJsonBackupPath: string | null;\n // True if no migration needed (no plaintext secrets found)\n noOp: boolean;\n}\n\ninterface SettingsJsonShape {\n env?: Record<string, string>;\n [key: string]: unknown;\n}\n\nconst SETTINGS_JSON_REL = [\".claude\", \"settings.json\"];\n\nfunction settingsJsonPath(workspacePath: string): string {\n return join(workspacePath, ...SETTINGS_JSON_REL);\n}\n\n// Generate backup filename with timestamp YYMMDD-HHMM (matches existing\n// backupFilename helper in merge-pack-settings-into-project-settings.ts).\nfunction backupName(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-secrets-migration-${stamp}`;\n}\n\nexport async function detectPlaintextSecretsInSettings(\n workspacePath: string,\n): Promise<{ keys: string[]; settingsPath: string }> {\n const path = settingsJsonPath(workspacePath);\n if (!(await pathExists(path))) {\n return { keys: [], settingsPath: path };\n }\n let settings: SettingsJsonShape;\n try {\n settings = await readJson<SettingsJsonShape>(path);\n } catch {\n return { keys: [], settingsPath: path };\n }\n const env = settings.env ?? {};\n const found: string[] = [];\n for (const key of MIGRATABLE_SECRET_KEYS) {\n if (typeof env[key] === \"string\" && env[key].length > 0) {\n found.push(key);\n }\n }\n return { keys: found, settingsPath: path };\n}\n\nexport async function migrateSecretsFromSettingsToEnvrc(\n workspacePath: string,\n): Promise<MigrationResult> {\n const { keys, settingsPath } = await detectPlaintextSecretsInSettings(workspacePath);\n if (keys.length === 0) {\n return { migratedKeys: [], settingsJsonBackupPath: null, noOp: true };\n }\n\n // Read settings + extract values\n const settings = await readJson<SettingsJsonShape>(settingsPath);\n const env = settings.env ?? {};\n const updates: Record<string, string | null> = {};\n for (const key of keys) {\n const value = env[key];\n if (typeof value === \"string\" && value.length > 0) {\n updates[key] = value;\n }\n }\n\n // 1. Write to .envrc Avatar block\n await writeEnvrcWithSecrets(workspacePath, updates);\n\n // 2. Backup settings.json\n const backupPath = backupName(settingsPath);\n await fs.copyFile(settingsPath, backupPath);\n\n // 3. Remove migrated keys from settings.json.env\n for (const key of keys) {\n // biome-ignore lint/performance/noDelete: env is mutated in-place; delete is correct\n delete env[key];\n }\n // Drop env field if it became empty\n if (Object.keys(env).length === 0) {\n settings.env = undefined;\n } else {\n settings.env = env;\n }\n await writeJsonAtomic(settingsPath, settings);\n\n return {\n migratedKeys: keys,\n settingsJsonBackupPath: backupPath,\n noOp: false,\n };\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 { registerAiCommand } from \"./commands/ai.js\";\nimport { registerCommitCommand } from \"./commands/commit.js\";\nimport { registerDoctorCommand } from \"./commands/doctor.js\";\nimport { registerFeatureCommand } from \"./commands/feature.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 { registerSecretsCommand } from \"./commands/secrets.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);\nregisterCommitCommand(program);\nregisterToolsCommand(program);\nregisterSecretsCommand(program);\nregisterMcpRunCommand(program);\nregisterAiCommand(program);\nregisterGitnexusCommand(program);\nregisterFeatureCommand(program);\nregisterPackCommand(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","// `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","// 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// Strict mode: cả 3 marker phải có (giảm false positive).\nfunction isAvatarWorkspace(dir: string): boolean {\n const hasClaudeDir = existsSync(join(dir, \".claude\"));\n const hasClaudeMd = existsSync(join(dir, \"CLAUDE.md\"));\n if (!hasClaudeDir || !hasClaudeMd) return false;\n\n // Avatar workspace HOẶC có .gitmodules với src/ entry, HOẶC có src/ folder\n // (cover cả case clone fresh chưa có .gitmodules nhưng đã có src/).\n const hasSrcDir = existsSync(join(dir, \"src\"));\n const gitmodulesPath = join(dir, \".gitmodules\");\n if (hasSrcDir) return true;\n if (existsSync(gitmodulesPath)) {\n try {\n const content = readFileSync(gitmodulesPath, \"utf8\");\n return content.includes(\"path = src\") || content.includes(\"path = ./src\");\n } catch {\n return false;\n }\n }\n return false;\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","// 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","// 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","// 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","// 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","// 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","// 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// - codex : OpenAI GPT-5.5+ direct (key sk-... / sk-proj-...) (v1.20.0)\n// - gemini : Google AI Studio direct (key AIza...) (v1.20.0)\n// - use-global : Copy ~/.claude/settings.json (đã có config sẵn từ Claude Code global)\nexport type AiProviderChoice =\n | \"use-global\"\n | \"subscription\"\n | \"llmlite\"\n | \"anthropic\"\n | \"codex\"\n | \"gemini\";\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 — 5 paths.\n return (await select({\n message: \"Chọn provider cho AI features:\",\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 name: \"4. OpenAI/Codex API key (platform.openai.com, key sk-... cho GPT-5.5+)\",\n value: \"codex\" as const,\n },\n {\n name: \"5. Google Gemini API key (aistudio.google.com, key AIza... cho Gemini 2.x+)\",\n value: \"gemini\" as const,\n },\n ],\n })) as \"subscription\" | \"llmlite\" | \"anthropic\" | \"codex\" | \"gemini\";\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","// Google Gemini AI Studio API direct setup wizard.\n//\n// Flow (parallels setup-anthropic-api-key-and-model.ts):\n// 1) Prompt API key ẩn (password input, format AIza...)\n// 2) Verify GET https://generativelanguage.googleapis.com/v1beta/models\n// 3) Smart picker: filter gemini-2.x / gemini-3.x families, prompt\n//\n// Auth: API key qua query param `?key=<KEY>` (Google AI Studio standard).\n// Base URL: https://generativelanguage.googleapis.com/v1beta\n// Settings.json output dùng GEMINI_API_KEY (env var name).\n//\n// NB: Đây là AI Studio API (đơn giản, free tier OK). Vertex AI là endpoint\n// khác (service account JSON) — không support trong v1.20.0.\nimport { password, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface GeminiConfig {\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\nconst GEMINI_BASE_URL = \"https://generativelanguage.googleapis.com/v1beta\";\nconst FETCH_TIMEOUT_MS = 10_000;\n\nexport function maskGeminiKey(key: string): string {\n if (key.length <= 12) return \"AIza***\";\n return `${key.slice(0, 6)}...${key.slice(-4)}`;\n}\n\n// Google API key format: 'AIza' prefix + base64-like rest (39 chars total typical).\nfunction validateGeminiKeyFormat(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(\"AIza\")) {\n return \"Google AI Studio key thường bắt đầu bằng 'AIza' (lấy từ aistudio.google.com/apikey).\";\n }\n return true;\n}\n\nasync function promptGeminiKeyHidden(): Promise<string> {\n return await password({\n message: \"Gemini API key (AIza..., ẩn input):\",\n mask: \"*\",\n validate: validateGeminiKeyFormat,\n });\n}\n\ninterface GeminiModelsResponse {\n models: Array<{\n name: string; // \"models/gemini-2.0-flash-exp\"\n displayName?: string;\n supportedGenerationMethods?: string[];\n }>;\n}\n\nexport async function fetchGeminiModels(apiKey: string): Promise<string[]> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const url = `${GEMINI_BASE_URL}/models?key=${encodeURIComponent(apiKey)}`;\n const resp = await fetch(url, {\n method: \"GET\",\n headers: { Accept: \"application/json\" },\n signal: controller.signal,\n });\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n if (resp.status === 400 || resp.status === 403) {\n throw new Error(\n `Key không valid (${resp.status}). Kiểm tra lại key từ aistudio.google.com.`,\n );\n }\n if (resp.status === 429) {\n throw new Error(\"Rate limit (429). Chờ vài giây rồi thử lại.\");\n }\n throw new Error(`Gemini /v1beta/models trả ${resp.status}: ${body.slice(0, 200)}`);\n }\n const json = (await resp.json()) as GeminiModelsResponse;\n // Strip \"models/\" prefix → just the model id\n return json.models\n .filter((m) => m.supportedGenerationMethods?.includes(\"generateContent\") ?? true)\n .map((m) => m.name.replace(/^models\\//, \"\"));\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Connect ${GEMINI_BASE_URL} timeout sau ${FETCH_TIMEOUT_MS / 1000}s.`);\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n// Filter Gemini 2.x+ models (Flash/Pro/Ultra families). Drop legacy 1.0 / 1.5.\nexport function filterPreferredGeminiModels(allModels: string[]): string[] {\n const preferred = allModels.filter((id) => {\n return /gemini-[23456789]/.test(id);\n });\n return preferred.length > 0 ? preferred.sort() : allModels.sort();\n}\n\nexport async function promptGeminiModelChoice(models: string[]): Promise<string> {\n if (models.length === 0) {\n throw new Error(\"Không có model nào trả về từ /v1beta/models.\");\n }\n if (models.length === 1) {\n log.info(`Chỉ có 1 model khả dụng: ${models[0]}. Auto-pick.`);\n return models[0] as string;\n }\n return await select({\n message: \"Chọn Gemini model:\",\n choices: models.map((m) => ({ name: m, value: m })),\n });\n}\n\nexport async function setupGeminiApiKeyAndModel(): Promise<GeminiConfig> {\n const apiKey = await promptGeminiKeyHidden();\n log.info(`Verify key (${maskGeminiKey(apiKey)}) qua ${GEMINI_BASE_URL}/models...`);\n const allModels = await fetchGeminiModels(apiKey);\n log.success(`Key valid (${allModels.length} models available)`);\n const filtered = filterPreferredGeminiModels(allModels);\n if (filtered.length !== allModels.length) {\n log.dim(` Filter cho Gemini 2.x+: ${filtered.length}/${allModels.length}`);\n }\n const model = await promptGeminiModelChoice(filtered);\n return { apiKey, baseUrl: GEMINI_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","// OpenAI Codex / GPT API direct setup wizard.\n//\n// Flow (parallels setup-anthropic-api-key-and-model.ts):\n// 1) Prompt API key ẩn (password input, format sk-...)\n// 2) Verify GET https://api.openai.com/v1/models với Authorization Bearer\n// 3) Smart picker: filter gpt-5* / gpt-5.5* / o*-* models, prompt\n//\n// Auth header: Authorization: Bearer <key> (OpenAI standard)\n// Base URL: https://api.openai.com/v1 (hardcoded — user cần proxy edit settings.json sau)\n// Settings.json output dùng OPENAI_API_KEY (env var name).\nimport { password, select } from \"@inquirer/prompts\";\nimport { log } from \"./terminal-logger.js\";\n\nexport interface CodexConfig {\n apiKey: string;\n baseUrl: string;\n model: string;\n}\n\nconst OPENAI_BASE_URL = \"https://api.openai.com/v1\";\nconst FETCH_TIMEOUT_MS = 10_000;\n\nexport function maskOpenAIKey(key: string): string {\n if (key.length <= 12) return \"sk-***\";\n return `${key.slice(0, 7)}...${key.slice(-4)}`;\n}\n\n// OpenAI key formats:\n// - sk-... legacy (project keys)\n// - sk-proj-... new project-scoped keys\n// - sk-svcacct-... service account keys\nfunction validateOpenAIKeyFormat(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-\")) {\n return \"OpenAI API key thường bắt đầu bằng 'sk-' (lấy từ platform.openai.com/api-keys).\";\n }\n return true;\n}\n\nasync function promptOpenAIKeyHidden(): Promise<string> {\n return await password({\n message: \"OpenAI API key (sk-..., ẩn input):\",\n mask: \"*\",\n validate: validateOpenAIKeyFormat,\n });\n}\n\ninterface OpenAIModelsResponse {\n data: Array<{ id: string; object?: string; owned_by?: string }>;\n}\n\n// Live fetch model list. Filter cho models phù hợp coding (gpt-5*, o*-* family).\n// Throws Error nếu network fail / key invalid / quota exceeded.\nexport async function fetchOpenAIModels(apiKey: string): Promise<string[]> {\n const controller = new AbortController();\n const timeout = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n const resp = await fetch(`${OPENAI_BASE_URL}/models`, {\n method: \"GET\",\n headers: {\n Authorization: `Bearer ${apiKey}`,\n Accept: \"application/json\",\n },\n signal: controller.signal,\n });\n if (!resp.ok) {\n const body = await resp.text().catch(() => \"\");\n if (resp.status === 401) {\n throw new Error(\"Key không valid (401 Unauthorized). Kiểm tra lại key.\");\n }\n if (resp.status === 429) {\n throw new Error(\"Rate limit (429). Chờ vài giây rồi thử lại.\");\n }\n throw new Error(`OpenAI /v1/models trả ${resp.status}: ${body.slice(0, 200)}`);\n }\n const json = (await resp.json()) as OpenAIModelsResponse;\n return json.data.map((m) => m.id);\n } catch (err) {\n if (err instanceof Error && err.name === \"AbortError\") {\n throw new Error(`Connect ${OPENAI_BASE_URL} timeout sau ${FETCH_TIMEOUT_MS / 1000}s.`);\n }\n throw err;\n } finally {\n clearTimeout(timeout);\n }\n}\n\n// Filter danh sách → preferred GPT-5+ / o-series coding models.\n// Fallback: include all if no preferred match (defensive).\nexport function filterPreferredCodingModels(allModels: string[]): string[] {\n const preferred = allModels.filter((id) => {\n const lower = id.toLowerCase();\n return (\n lower.startsWith(\"gpt-5\") ||\n lower.startsWith(\"o1\") ||\n lower.startsWith(\"o2\") ||\n lower.startsWith(\"o3\") ||\n lower.startsWith(\"o4\") ||\n lower.startsWith(\"o5\") ||\n lower === \"gpt-4o\" ||\n lower === \"gpt-4o-mini\"\n );\n });\n return preferred.length > 0 ? preferred.sort() : allModels.sort();\n}\n\nexport async function promptOpenAIModelChoice(models: string[]): Promise<string> {\n if (models.length === 0) {\n throw new Error(\"Không có model nào trả về từ /v1/models — key có thể không có quyền.\");\n }\n if (models.length === 1) {\n log.info(`Chỉ có 1 model khả dụng: ${models[0]}. Auto-pick.`);\n return models[0] as string;\n }\n return await select({\n message: \"Chọn model:\",\n choices: models.map((m) => ({ name: m, value: m })),\n });\n}\n\nexport async function setupOpenAIApiKeyAndModel(): Promise<CodexConfig> {\n const apiKey = await promptOpenAIKeyHidden();\n log.info(`Verify key (${maskOpenAIKey(apiKey)}) qua ${OPENAI_BASE_URL}/models...`);\n const allModels = await fetchOpenAIModels(apiKey);\n log.success(`Key valid (${allModels.length} models available)`);\n const filtered = filterPreferredCodingModels(allModels);\n if (filtered.length !== allModels.length) {\n log.dim(` Filter cho coding models: ${filtered.length}/${allModels.length}`);\n }\n const model = await promptOpenAIModelChoice(filtered);\n return { apiKey, baseUrl: OPENAI_BASE_URL, model };\n}\n","// Store provider API key SECURELY (per-project) — auto-setup direnv if missing.\n//\n// Flow (called by run-ai-setup-phase after user picks provider + enters key):\n// 1. Try setup direnv silently:\n// - If installed → just write .envrc Avatar block\n// - If missing → install via brew/apt/etc + add shell hook\n// 2. On direnv install fail → fallback .claude/.env (gitignored, but no auto-load)\n// 3. Log final storage path so user knows where key lives\n//\n// Returns: { stored: boolean, location: string, autoLoad: boolean }\n// Caller uses this to decide whether to write key into settings.json env field\n// or skip it (autoLoad=true → settings.json doesn't need env key, direnv handles it).\n//\n// Why no prompts: AI setup already prompted user N times. Adding \"Cài direnv?\"\n// prompt right after key entry is friction. Be silent + log result.\nimport { promises as fs } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { detectDirenvStatus } from \"./detect-direnv-installation-status.js\";\nimport {\n ensureDirenvShellHookInZshrc,\n installDirenv,\n} from \"./install-direnv-via-package-manager.js\";\nimport { log } from \"./terminal-logger.js\";\nimport { writeEnvrcWithSecrets } from \"./write-envrc-with-per-project-secrets.js\";\n\nexport type StorageMethod = \"envrc\" | \"claude-env-fallback\";\n\nexport interface StoreKeyResult {\n storageMethod: StorageMethod;\n storagePath: string;\n autoLoad: boolean; // true = direnv loads on cd, false = user must manually source\n installedDirenvNow: boolean; // true if we installed direnv during this call\n addedShellHook: boolean; // true if we appended hook to ~/.zshrc\n}\n\nconst CLAUDE_ENV_REL = [\".claude\", \".env\"];\n\nasync function writeClaudeEnvFallback(\n workspacePath: string,\n secrets: Record<string, string>,\n): Promise<string> {\n const envPath = join(workspacePath, ...CLAUDE_ENV_REL);\n await fs.mkdir(join(workspacePath, \".claude\"), { recursive: true });\n // Read existing (if any), preserve user lines, replace/add our keys.\n let existing = \"\";\n try {\n existing = await fs.readFile(envPath, \"utf8\");\n } catch {\n // file doesn't exist\n }\n const lines = existing ? existing.split(\"\\n\") : [];\n const ours = new Set(Object.keys(secrets));\n const filtered = lines.filter((line) => {\n // Drop lines that set keys we're about to overwrite\n const m = line.match(/^([A-Z][A-Z0-9_]*)=/);\n return !(m?.[1] && ours.has(m[1]));\n });\n for (const [key, value] of Object.entries(secrets)) {\n // Escape: use single quotes, escape literal single quote as '\\'\\'\n const escaped = value.replace(/'/g, \"'\\\\''\");\n filtered.push(`${key}='${escaped}'`);\n }\n const newContent = filtered.filter((l, i, arr) => !(l === \"\" && arr[i - 1] === \"\")).join(\"\\n\");\n await fs.writeFile(envPath, newContent.endsWith(\"\\n\") ? newContent : `${newContent}\\n`, {\n encoding: \"utf8\",\n mode: 0o600,\n });\n await fs.chmod(envPath, 0o600);\n return envPath;\n}\n\nexport async function storeProviderKeyViaSecretsSubsystem(\n workspacePath: string,\n secrets: Record<string, string>,\n): Promise<StoreKeyResult> {\n // Step 1: Check direnv status\n let direnv = await detectDirenvStatus();\n let installedNow = false;\n let addedHook = false;\n\n // Step 2: If direnv missing, try silent install\n if (!direnv.installed) {\n log.info(\"Đang setup direnv để bảo vệ API key (silent)...\");\n const installResult = await installDirenv();\n if (installResult.success) {\n log.dim(` ✓ direnv installed via ${installResult.method}`);\n installedNow = true;\n // Re-detect to refresh shellHookInZshrc check\n direnv = await detectDirenvStatus();\n } else {\n log.warn(\n ` ! direnv install fail (${installResult.message}). Fallback sang .claude/.env (gitignored).`,\n );\n const fallbackPath = await writeClaudeEnvFallback(workspacePath, secrets);\n log.warn(\n ` ⚠ Key đã ghi vào ${fallbackPath} (chmod 600) nhưng KHÔNG auto-load — cần 'source ${fallbackPath}' mỗi terminal mới.`,\n );\n log.dim(` Manual install direnv sau: ${installResult.manualInstructionsUrl}`);\n return {\n storageMethod: \"claude-env-fallback\",\n storagePath: fallbackPath,\n autoLoad: false,\n installedDirenvNow: false,\n addedShellHook: false,\n };\n }\n }\n\n // Step 3: Ensure shell hook\n if (!direnv.shellHookInZshrc) {\n const hookResult = ensureDirenvShellHookInZshrc();\n if (hookResult.modified) {\n log.dim(` ✓ Added direnv hook to ${hookResult.zshrcPath}`);\n log.dim(\" Reload shell: 'source ~/.zshrc' hoặc mở terminal mới\");\n addedHook = true;\n }\n }\n\n // Step 4: Write secrets to .envrc Avatar block\n const writeResult = await writeEnvrcWithSecrets(workspacePath, secrets);\n log.success(`✓ Key đã lưu (${Object.keys(secrets).length} vars) vào ${writeResult.envrcPath}`);\n if (writeResult.direnvAllowOk === false) {\n log.warn(\" direnv allow fail — chạy 'direnv allow' trong workspace để activate.\");\n }\n\n return {\n storageMethod: \"envrc\",\n storagePath: writeResult.envrcPath,\n autoLoad: true,\n installedDirenvNow: installedNow,\n addedShellHook: addedHook,\n };\n}\n","// Detect whether direnv binary is installed and operational on the host.\n//\n// Why direnv: per-project secrets via .envrc auto-loaded on cd. Beats\n// settings.json plaintext (leak on git commit) and beats ~/.zshrc global\n// vars (cross-project bleed).\n//\n// Detection strategy:\n// 1. `direnv --version` exit 0 → installed.\n// 2. `which direnv` exit 0 → installed (fallback if version fails).\n// 3. Otherwise → not installed.\n//\n// Side note: this checks direnv binary existence only. Shell hook integration\n// (`eval \"$(direnv hook zsh)\"` trong ~/.zshrc) is separate concern checked by\n// detectDirenvShellHookInstalled().\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync, readFileSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface DirenvStatus {\n installed: boolean;\n version: string | null; // semver string if available\n binaryPath: string | null;\n shellHookInZshrc: boolean; // does ~/.zshrc contain `direnv hook` invocation?\n}\n\nfunction tryRun(cmd: string, args: string[]): { ok: boolean; stdout: string } {\n try {\n const result = spawnSync(cmd, args, { encoding: \"utf8\", timeout: 5000 });\n return { ok: result.status === 0, stdout: (result.stdout ?? \"\").trim() };\n } catch {\n return { ok: false, stdout: \"\" };\n }\n}\n\nexport async function detectDirenvStatus(): Promise<DirenvStatus> {\n // 1. Try direnv --version\n const version = tryRun(\"direnv\", [\"--version\"]);\n if (version.ok && version.stdout) {\n const binary = tryRun(\"which\", [\"direnv\"]);\n return {\n installed: true,\n version: version.stdout,\n binaryPath: binary.ok ? binary.stdout : null,\n shellHookInZshrc: checkShellHookInZshrc(),\n };\n }\n // 2. Try which direnv (binary present but version fails for some reason)\n const which = tryRun(\"which\", [\"direnv\"]);\n if (which.ok && which.stdout) {\n return {\n installed: true,\n version: null,\n binaryPath: which.stdout,\n shellHookInZshrc: checkShellHookInZshrc(),\n };\n }\n return {\n installed: false,\n version: null,\n binaryPath: null,\n shellHookInZshrc: false,\n };\n}\n\n// Check if user's ~/.zshrc has the direnv shell hook line. Without this hook,\n// direnv binary alone won't auto-load .envrc on cd — user gets file but\n// nothing happens.\nfunction checkShellHookInZshrc(): boolean {\n const zshrc = join(homedir(), \".zshrc\");\n if (!existsSync(zshrc)) return false;\n try {\n const content = readFileSync(zshrc, \"utf8\");\n // Match common forms: `eval \"$(direnv hook zsh)\"`, `direnv hook zsh`, etc.\n return /\\bdirenv\\s+hook\\s+(zsh|bash|sh)\\b/.test(content);\n } catch {\n return false;\n }\n}\n","// Install direnv via available package manager on the host.\n//\n// Detection order (best-effort, first-match-wins):\n// macOS: brew (95% NAL dev)\n// Linux: apt-get → dnf → pacman\n// Other: bail with manual-install instructions URL\n//\n// Returns InstallResult with success flag + method + log messages.\n// Never throws — caller decides whether to hard-fail or fallback to\n// .claude/.env strategy.\n//\n// Shell hook (eval \"$(direnv hook zsh)\") is a SEPARATE concern — installing\n// direnv binary doesn't auto-wire the hook. ensureDirenvShellHookInZshrc()\n// handles that. Caller should run both for full setup.\nimport { spawnSync } from \"node:child_process\";\nimport { appendFileSync, existsSync, readFileSync } from \"node:fs\";\nimport { homedir, platform } from \"node:os\";\nimport { join } from \"node:path\";\n\nexport interface InstallResult {\n success: boolean;\n method: \"brew\" | \"apt-get\" | \"dnf\" | \"pacman\" | null;\n message: string;\n manualInstructionsUrl: string;\n}\n\nconst MANUAL_URL = \"https://direnv.net/docs/installation.html\";\n\nfunction commandExists(cmd: string): boolean {\n const result = spawnSync(\"which\", [cmd], { encoding: \"utf8\", timeout: 3000 });\n return result.status === 0;\n}\n\nfunction runInstall(cmd: string, args: string[]): { ok: boolean; output: string } {\n try {\n const result = spawnSync(cmd, args, { encoding: \"utf8\", timeout: 120_000 });\n return {\n ok: result.status === 0,\n output: ((result.stdout ?? \"\") + (result.stderr ?? \"\")).trim(),\n };\n } catch (err) {\n return { ok: false, output: err instanceof Error ? err.message : String(err) };\n }\n}\n\nexport async function installDirenv(): Promise<InstallResult> {\n const os = platform();\n if (os === \"darwin\") {\n if (commandExists(\"brew\")) {\n const r = runInstall(\"brew\", [\"install\", \"direnv\"]);\n return {\n success: r.ok,\n method: \"brew\",\n message: r.ok ? \"Installed via Homebrew\" : `brew install failed: ${r.output.slice(0, 500)}`,\n manualInstructionsUrl: MANUAL_URL,\n };\n }\n return {\n success: false,\n method: null,\n message: \"macOS detected but Homebrew not found. Install brew first: https://brew.sh\",\n manualInstructionsUrl: MANUAL_URL,\n };\n }\n if (os === \"linux\") {\n if (commandExists(\"apt-get\")) {\n const r = runInstall(\"sudo\", [\"apt-get\", \"install\", \"-y\", \"direnv\"]);\n return {\n success: r.ok,\n method: \"apt-get\",\n message: r.ok\n ? \"Installed via apt-get\"\n : `apt-get install failed: ${r.output.slice(0, 500)}`,\n manualInstructionsUrl: MANUAL_URL,\n };\n }\n if (commandExists(\"dnf\")) {\n const r = runInstall(\"sudo\", [\"dnf\", \"install\", \"-y\", \"direnv\"]);\n return {\n success: r.ok,\n method: \"dnf\",\n message: r.ok ? \"Installed via dnf\" : `dnf install failed: ${r.output.slice(0, 500)}`,\n manualInstructionsUrl: MANUAL_URL,\n };\n }\n if (commandExists(\"pacman\")) {\n const r = runInstall(\"sudo\", [\"pacman\", \"-S\", \"--noconfirm\", \"direnv\"]);\n return {\n success: r.ok,\n method: \"pacman\",\n message: r.ok ? \"Installed via pacman\" : `pacman install failed: ${r.output.slice(0, 500)}`,\n manualInstructionsUrl: MANUAL_URL,\n };\n }\n return {\n success: false,\n method: null,\n message: \"Linux detected but no supported package manager (apt-get/dnf/pacman) found.\",\n manualInstructionsUrl: MANUAL_URL,\n };\n }\n return {\n success: false,\n method: null,\n message: `Unsupported platform: ${os}. Install direnv manually.`,\n manualInstructionsUrl: MANUAL_URL,\n };\n}\n\n// Ensure ~/.zshrc has the `eval \"$(direnv hook zsh)\"` line. Idempotent:\n// detects existing hook and skips append. Returns true if file was modified\n// (caller should tell user to `source ~/.zshrc` or open new shell).\nexport function ensureDirenvShellHookInZshrc(): { modified: boolean; zshrcPath: string } {\n const zshrcPath = join(homedir(), \".zshrc\");\n const hookLine = 'eval \"$(direnv hook zsh)\"';\n const hookMarker = \"# direnv shell integration (added by avatar-cli)\";\n\n let existing = \"\";\n if (existsSync(zshrcPath)) {\n try {\n existing = readFileSync(zshrcPath, \"utf8\");\n } catch {\n // Can't read → still try append below\n }\n }\n\n if (/\\bdirenv\\s+hook\\s+(zsh|bash|sh)\\b/.test(existing)) {\n return { modified: false, zshrcPath };\n }\n\n const block = `\\n${hookMarker}\\n${hookLine}\\n`;\n try {\n appendFileSync(zshrcPath, block, \"utf8\");\n return { modified: true, zshrcPath };\n } catch {\n return { modified: false, zshrcPath };\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// 6 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// - codex : OpenAI direct, dùng OPENAI_API_KEY (v1.20.0)\n// - gemini : Google AI Studio direct, dùng GEMINI_API_KEY (v1.20.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.\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: \"codex\"; apiKey: string; baseUrl: string; model: string; skipApiKey?: boolean }\n | { provider: \"gemini\"; 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 OPENAI_API_KEY?: string; // OpenAI/Codex direct — v1.20.0\n GEMINI_API_KEY?: string; // Google AI Studio direct — v1.20.0\n [k: string]: unknown;\n };\n model?: string;\n // v1.20.0: provider label (informational only, Claude Code không đọc)\n // — giúp identify settings.json đang config cho provider nào khi debug.\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 {\n ANTHROPIC_AUTH_TOKEN: _t,\n ANTHROPIC_API_KEY: _k,\n OPENAI_API_KEY: _o,\n GEMINI_API_KEY: _g,\n ...rest\n } = 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\nfunction applyCodex(\n existing: ClaudeSettings,\n apiKey: string,\n baseUrl: string,\n model: string,\n skipApiKey: boolean,\n): ClaudeSettings {\n const envRest = stripAllProviderKeys(existing.env);\n // Codex/OpenAI uses different env structure — store base URL under a generic\n // key so Avatar tooling knows; Claude Code itself doesn't natively support\n // OpenAI, so user needs proxy/LiteLLM. Settings here serves as metadata.\n const env: ClaudeSettings[\"env\"] = {\n ...envRest,\n OPENAI_BASE_URL: baseUrl,\n };\n if (!skipApiKey) env.OPENAI_API_KEY = apiKey;\n return { ...existing, env, model, avatarProvider: \"codex\" };\n}\n\nfunction applyGemini(\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 GEMINI_BASE_URL: baseUrl,\n };\n if (!skipApiKey) env.GEMINI_API_KEY = apiKey;\n return { ...existing, env, model, avatarProvider: \"gemini\" };\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 \"codex\":\n merged = applyCodex(\n existing,\n input.apiKey,\n input.baseUrl,\n input.model,\n input.skipApiKey === true,\n );\n break;\n case \"gemini\":\n merged = applyGemini(\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","// 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 { setupGeminiApiKeyAndModel } from \"./setup-google-gemini-api-key-and-model.js\";\nimport { setupLLMLiteApiKeyAndModel } from \"./setup-llmlite-api-key-and-model.js\";\nimport { setupOpenAIApiKeyAndModel } from \"./setup-openai-codex-api-key-and-model.js\";\nimport { storeProviderKeyViaSecretsSubsystem } from \"./store-provider-key-via-direnv-or-fallback.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 // v1.20.0: Store key via direnv .envrc (skipApiKey=true → settings.json\n // không chứa plaintext token). Fallback .claude/.env nếu direnv không\n // cài được.\n const storeResult = await storeProviderKeyViaSecretsSubsystem(args.workspacePath, {\n ANTHROPIC_AUTH_TOKEN: llmConfig.apiKey,\n });\n await writeClaudeSettings(args.workspacePath, {\n provider: \"llmlite\",\n apiKey: llmConfig.apiKey,\n baseUrl: llmConfig.baseUrl,\n model: llmConfig.model,\n skipApiKey: storeResult.storageMethod === \"envrc\",\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=llmlite,result=ok,model=${llmConfig.model},base=${llmConfig.baseUrl},storage=${storeResult.storageMethod}`,\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 // v1.20.0: Key lưu vào .envrc qua direnv (không plaintext trong settings.json).\n const anthropicConfig = await setupAnthropicApiKeyAndModel();\n const storeResult = await storeProviderKeyViaSecretsSubsystem(args.workspacePath, {\n ANTHROPIC_API_KEY: anthropicConfig.apiKey,\n });\n await writeClaudeSettings(args.workspacePath, {\n provider: \"anthropic\",\n apiKey: anthropicConfig.apiKey,\n baseUrl: anthropicConfig.baseUrl,\n model: anthropicConfig.model,\n skipApiKey: storeResult.storageMethod === \"envrc\",\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=anthropic,result=ok,model=${anthropicConfig.model},storage=${storeResult.storageMethod}`,\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 \"codex\": {\n // v1.20.0: OpenAI/Codex direct (GPT-5.5+).\n const codexConfig = await setupOpenAIApiKeyAndModel();\n const storeResult = await storeProviderKeyViaSecretsSubsystem(args.workspacePath, {\n OPENAI_API_KEY: codexConfig.apiKey,\n });\n await writeClaudeSettings(args.workspacePath, {\n provider: \"codex\",\n apiKey: codexConfig.apiKey,\n baseUrl: codexConfig.baseUrl,\n model: codexConfig.model,\n skipApiKey: storeResult.storageMethod === \"envrc\",\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=codex,result=ok,model=${codexConfig.model},storage=${storeResult.storageMethod}`,\n );\n log.success(\n `AI ready · OpenAI/Codex Direct · model=${codexConfig.model} · ${codexConfig.baseUrl}`,\n );\n log.dim(\n \" Note: Claude Code native không support OpenAI — cần proxy qua LiteLLM/ai.nal.vn để dùng làm main AI. Key đã lưu để skills khác dùng (vd ai-multimodal).\",\n );\n return { ok: true, provider: \"codex\", model: codexConfig.model };\n }\n\n case \"gemini\": {\n // v1.20.0: Google Gemini AI Studio direct.\n const geminiConfig = await setupGeminiApiKeyAndModel();\n const storeResult = await storeProviderKeyViaSecretsSubsystem(args.workspacePath, {\n GEMINI_API_KEY: geminiConfig.apiKey,\n });\n await writeClaudeSettings(args.workspacePath, {\n provider: \"gemini\",\n apiKey: geminiConfig.apiKey,\n baseUrl: geminiConfig.baseUrl,\n model: geminiConfig.model,\n skipApiKey: storeResult.storageMethod === \"envrc\",\n });\n await appendAuditEntry(\n \"ai_setup\",\n `provider=gemini,result=ok,model=${geminiConfig.model},storage=${storeResult.storageMethod}`,\n );\n log.success(\n `AI ready · Google Gemini Direct · model=${geminiConfig.model} · ${geminiConfig.baseUrl}`,\n );\n log.dim(\n \" Note: Claude Code native không support Gemini — cần proxy qua LiteLLM/ai.nal.vn để dùng làm main AI. Key đã lưu để skills khác dùng.\",\n );\n return { ok: true, provider: \"gemini\", model: geminiConfig.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","// 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","// `avatar commit src [-m <msg>] [--push]` — Command 09.\n//\n// Member dev CHỈ ĐƯỢC commit code khách trong src/. Workspace state team-shared\n// (.claude/, CLAUDE.md, knowledge) là responsibility của team admin sync — không\n// để member commit/push trực tiếp tránh leak knowledge và race conflict.\n//\n// → Không expose `workspace` hoặc `all` subcommands. Chỉ `avatar commit src`.\nimport { input } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport { executeCommitWithTargetSelection } from \"../lib/execute-commit-with-target-selection.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\ninterface CommitOptions {\n message?: string;\n push?: boolean;\n}\n\nexport function registerCommitCommand(program: Command): void {\n const commit = program\n .command(\"commit\")\n .description(\"Commit code khách trong src/ (Avatar state do admin sync) (M07)\");\n\n commit\n .command(\"src\")\n .description(\"Commit src/ → push lên client repo remote\")\n .option(\"-m, --message <msg>\", \"Commit message\")\n .option(\"--push\", \"Auto push sau commit (default: chỉ commit)\")\n .action(async (opts: CommitOptions) => {\n await runCommitSrc(opts);\n });\n}\n\n// Run commit src only — không subcommand khác để member không vô tình ghi workspace state.\nasync function runCommitSrc(opts: CommitOptions): Promise<void> {\n try {\n const message =\n opts.message ??\n (await input({\n message: \"Commit message:\",\n validate: (v) => (v.trim().length > 0 ? true : \"Message bắt buộc\"),\n }));\n\n const result = await executeCommitWithTargetSelection({\n workspaceRoot: process.cwd(),\n target: \"src\",\n options: { message, push: opts.push },\n });\n\n if (result.srcCommitSha) {\n log.success(`src/: ${result.srcCommitSha.slice(0, 7)}${result.srcPushed ? \" (pushed)\" : \"\"}`);\n }\n if (result.skipped && result.skipped.length > 0) {\n log.dim(`Skipped (nothing to commit): ${result.skipped.join(\", \")}`);\n }\n } catch (err) {\n log.error(err instanceof Error ? err.message : String(err));\n process.exit(1);\n }\n}\n","// Core logic cho `avatar commit src` — wrap git operations với awareness về\n// workspace structure 2-repo (src/ submodule + workspace root).\n//\n// Mục đích: Claude Code/member dev không cần biết detail layout, chỉ gọi\n// `avatar commit src` và Avatar route đúng vào src/ repo.\n//\n// v1.3.2: Bỏ targets workspace/all khỏi member-facing API. Workspace state\n// team-shared, chỉ admin sync (Avatar không expose lệnh cho member). Type\n// `CommitTarget` giữ extensible cho admin tools tương lai nhưng hiện tại\n// chỉ \"src\" được caller dùng. Caller pass target=workspace/all sẽ vẫn work\n// internally (admin scripts), không expose qua CLI.\n//\n// Behavior matrix (admin-only paths):\n// target=src → cd src/ → git add . && commit + optionally push\n// target=workspace → workspace root → git add ALL_EXCEPT_src + commit + push\n// target=all → src trước (push), update gitlink pointer, workspace sau\nimport { spawnSync } from \"node:child_process\";\nimport { existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { log } from \"./terminal-logger.js\";\n\nexport type CommitTarget = \"src\" | \"workspace\" | \"all\";\n\nexport interface CommitOptions {\n message: string;\n push?: boolean;\n}\n\nexport interface CommitResult {\n target: CommitTarget;\n srcCommitSha?: string; // SHA của commit src/ nếu có\n workspaceCommitSha?: string; // SHA của commit workspace nếu có\n srcPushed?: boolean;\n workspacePushed?: boolean;\n skipped?: string[]; // List repos skip do \"nothing to commit\"\n}\n\n// Check workspace structure — phải có src/ là submodule + workspace có .git riêng.\n// Throw nếu cwd không phải Avatar workspace root.\nexport function assertAvatarWorkspaceRoot(cwd: string): void {\n const srcGit = join(cwd, \"src\", \".git\");\n const workspaceGit = join(cwd, \".git\");\n const claudeDir = join(cwd, \".claude\");\n\n if (!existsSync(workspaceGit)) {\n throw new Error(\n `Không phải workspace root: ${cwd}\\n Chạy 'avatar commit' trong workspace dir (có .git và .claude/).`,\n );\n }\n if (!existsSync(claudeDir)) {\n throw new Error(\n `Không thấy .claude/ trong ${cwd}.\\n Chạy 'avatar commit' trong Avatar workspace, không phải project bình thường.`,\n );\n }\n if (!existsSync(srcGit)) {\n throw new Error(\n `Không thấy src/.git trong ${cwd}.\\n Workspace thiếu submodule src/. Chạy 'avatar init' lại?`,\n );\n }\n}\n\n// Spawn git command + capture output. Trả stdout trimmed, throw nếu fail với stderr.\nfunction gitExec(cwd: string, args: string[]): string {\n const r = spawnSync(\"git\", [\"-C\", cwd, ...args], {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n });\n if (r.status !== 0) {\n const stderr = (r.stderr || \"\").trim();\n throw new Error(`git ${args.join(\" \")} (in ${cwd}) failed:\\n${stderr}`);\n }\n return (r.stdout || \"\").trim();\n}\n\n// Check repo có changes staged hoặc unstaged không. Trả true nếu dirty.\nfunction isDirty(cwd: string): boolean {\n const status = gitExec(cwd, [\"status\", \"--porcelain\"]);\n return status.length > 0;\n}\n\n// Commit src/ submodule. Add all files trong src/, commit với message, optionally push.\n// Trả SHA commit mới, hoặc undefined nếu nothing to commit.\nasync function commitSrc(\n workspaceRoot: string,\n opts: CommitOptions,\n): Promise<{ sha?: string; pushed?: boolean }> {\n const srcPath = join(workspaceRoot, \"src\");\n if (!isDirty(srcPath)) {\n log.dim(\"src/: nothing to commit (clean)\");\n return {};\n }\n\n log.info(\"Committing src/ ...\");\n gitExec(srcPath, [\"add\", \".\"]);\n gitExec(srcPath, [\"commit\", \"-m\", opts.message]);\n const sha = gitExec(srcPath, [\"rev-parse\", \"HEAD\"]);\n log.success(`src/ committed: ${sha.slice(0, 7)}`);\n\n let pushed = false;\n if (opts.push) {\n log.info(\"Pushing src/ ...\");\n gitExec(srcPath, [\"push\"]);\n log.success(\"src/ pushed\");\n pushed = true;\n }\n return { sha, pushed };\n}\n\n// Commit workspace root EXCEPT src/. Git tự exclude submodule content khi\n// dùng `git add` ở parent — chỉ track gitlink (SHA pointer).\n// Nếu src/ vừa commit (gitlink update), workspace add sẽ stage update đó.\nasync function commitWorkspace(\n workspaceRoot: string,\n opts: CommitOptions,\n): Promise<{ sha?: string; pushed?: boolean }> {\n if (!isDirty(workspaceRoot)) {\n log.dim(\"workspace: nothing to commit (clean)\");\n return {};\n }\n\n log.info(\"Committing workspace root ...\");\n gitExec(workspaceRoot, [\"add\", \".\"]);\n gitExec(workspaceRoot, [\"commit\", \"-m\", opts.message]);\n const sha = gitExec(workspaceRoot, [\"rev-parse\", \"HEAD\"]);\n log.success(`workspace committed: ${sha.slice(0, 7)}`);\n\n let pushed = false;\n if (opts.push) {\n log.info(\"Pushing workspace ...\");\n gitExec(workspaceRoot, [\"push\"]);\n log.success(\"workspace pushed\");\n pushed = true;\n }\n return { sha, pushed };\n}\n\n// Main entry — route theo target.\n// v1.3.2: Bỏ warnIfOtherTargetDirty (Q2=c: silent ignore workspace state).\n// Member dev chỉ care src/ commit. Workspace dirty state là responsibility\n// của admin sync, không phải concern của member khi commit code.\nexport async function executeCommitWithTargetSelection(args: {\n workspaceRoot: string;\n target: CommitTarget;\n options: CommitOptions;\n}): Promise<CommitResult> {\n assertAvatarWorkspaceRoot(args.workspaceRoot);\n\n const result: CommitResult = { target: args.target, skipped: [] };\n\n if (args.target === \"src\" || args.target === \"all\") {\n const srcOutcome = await commitSrc(args.workspaceRoot, args.options);\n result.srcCommitSha = srcOutcome.sha;\n result.srcPushed = srcOutcome.pushed;\n if (!srcOutcome.sha) result.skipped?.push(\"src\");\n }\n\n if (args.target === \"workspace\" || args.target === \"all\") {\n // Khi target=all + src vừa commit: workspace sẽ stage gitlink update + Avatar state.\n const wsOutcome = await commitWorkspace(args.workspaceRoot, args.options);\n result.workspaceCommitSha = wsOutcome.sha;\n result.workspacePushed = wsOutcome.pushed;\n if (!wsOutcome.sha) result.skipped?.push(\"workspace\");\n }\n\n return result;\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 { checkEnabledFeaturesHealth } from \"../lib/check-enabled-features-health.js\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { isGitRepo } from \"../lib/git-operations.js\";\nimport { installGitHook } from \"../lib/project-tree-scaffolder.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 const packPath = join(cwd, \".claude\", \"pack\");\n const claudeMdPath = join(cwd, \"CLAUDE.md\");\n const hookPath = join(cwd, \".git\", \"hooks\", \"post-merge\");\n const [gitRepo, hasPack, hasClaudeMd, hasHook] = await Promise.all([\n isGitRepo(cwd),\n pathExists(packPath),\n pathExists(claudeMdPath),\n pathExists(hookPath),\n ]);\n\n // 3. Git repo.\n checks.push({\n name: \"Git repository\",\n status: gitRepo ? \"ok\" : \"warn\",\n detail: gitRepo ? cwd : \"Không phải git repo (cần cho 'avatar init')\",\n fixable: false,\n });\n\n // 4. .claude/pack/ submodule.\n checks.push({\n name: \"team-ai-pack submodule\",\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 // 6. post-merge hook (uses hasHook from batch above).\n if (gitRepo && hasPack) {\n checks.push({\n name: \"Git hook post-merge\",\n status: hasHook ? \"ok\" : \"fail\",\n detail: hasHook ? \"installed\" : \"missing — fixable\",\n fixable: !hasHook,\n fix: hasHook\n ? undefined\n : async () => {\n await installGitHook(join(cwd, \".git\"), \"post-merge\");\n },\n });\n }\n\n // 7. .gitignore has Avatar entries.\n const gitignorePath = join(cwd, \".gitignore\");\n let gitignoreContent = \"\";\n if (gitRepo) {\n let gitignoreOk = false;\n if (await pathExists(gitignorePath)) {\n gitignoreContent = await fs.readFile(gitignorePath, \"utf8\");\n gitignoreOk = gitignoreContent.includes(\".claude/_pending/\");\n }\n checks.push({\n name: \".gitignore Avatar entries\",\n status: gitignoreOk ? \"ok\" : hasPack ? \"fail\" : \"warn\",\n detail: gitignoreOk ? \"có .claude/_pending/, .claude/_backup/\" : \"thiếu entries\",\n fixable: false,\n });\n\n // v1.7.0 — 7b. SECURITY: .claude/settings.json gitignored?\n // File chứa raw API key. Nếu chưa gitignore → user dễ vô tình commit + leak.\n const settingsGitignored =\n gitignoreContent.includes(\".claude/settings.json\") ||\n gitignoreContent.includes(\"/.claude/settings.json\") ||\n gitignoreContent.includes(\".claude/*.json\");\n checks.push({\n name: \"🔒 settings.json gitignored\",\n status: settingsGitignored ? \"ok\" : \"fail\",\n detail: settingsGitignored\n ? \"an toàn — settings.json không commit nhầm\"\n : \"CRITICAL: settings.json chứa API key — chạy 'avatar doctor --fix' để add gitignore\",\n fixable: !settingsGitignored,\n fix: settingsGitignored\n ? undefined\n : async () => {\n const addition =\n \"\\n# Avatar v1.7.0 — Security: settings.json chứa raw API key, KHÔNG commit.\\n.claude/settings.json\\n.claude/settings.json.backup-*\\n\";\n await fs.appendFile(gitignorePath, addition);\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. Feature toggle health (prompt-scoring, ...). Mỗi feature 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 featureChecks = await checkEnabledFeaturesHealth(cwd);\n checks.push(...featureChecks);\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-features-health.ts\n//\n// Doctor check: với mỗi feature 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 feature 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 FeatureManifest,\n enableFeature,\n readFeatureManifest,\n} from \"./apply-feature-manifest-to-settings.js\";\nimport { listEnabledFeatures } from \"./feature-state-store.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\n\nexport interface FeatureCheck {\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 feature đã có mặt trong settings.json chưa?\nfunction manifestHooksPresent(settings: SettingsShape, manifest: FeatureManifest): 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 checkEnabledFeaturesHealth(cwd: string): Promise<FeatureCheck[]> {\n const enabled = await listEnabledFeatures(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: FeatureCheck[] = [];\n for (const name of enabled) {\n const manifest = await readFeatureManifest(cwd, name);\n if (!manifest) {\n checks.push({\n name: `Feature: ${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: `Feature: ${name}`,\n status: \"ok\",\n detail: `enabled, hook active (v${manifest.version})`,\n fixable: false,\n });\n } else {\n checks.push({\n name: `Feature: ${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 enableFeature(cwd, manifest);\n },\n });\n }\n }\n return checks;\n}\n","// apply-feature-manifest-to-settings.ts\n//\n// Áp / rút manifest của 1 feature 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à feature.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/features/<name>/feature.json\n// (submodule mount tại .claude/pack/). Manifest.settings chỉ chứa subset cần cho\n// feature: 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 feature, 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 feature 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 FeatureManifest {\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 ApplyFeatureResult {\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\", \"features\", name, \"feature.json\");\n}\n\n// Đọc manifest feature. Thiếu → null (caller fail mềm: \"pack chưa hỗ trợ feature\").\nexport async function readFeatureManifest(\n workspacePath: string,\n name: string,\n): Promise<FeatureManifest | null> {\n const path = featureManifestPath(workspacePath, name);\n if (!(await pathExists(path))) {\n return null;\n }\n return await readJson<FeatureManifest>(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 feature.`,\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À feature 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à feature đ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 feature đ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 feature trong user\n// settings — drop old keep new. Trigger khi user upgrade pack từ pre-v0.7.1.\nexport async function enableFeature(\n workspacePath: string,\n manifest: FeatureManifest,\n): Promise<ApplyFeatureResult> {\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 feature, 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 feature đ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 disableFeature(\n workspacePath: string,\n manifest: FeatureManifest,\n): Promise<ApplyFeatureResult> {\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 feature, 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 feature → 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-feature-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-feature-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/feature\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 feature.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","// feature-state-store.ts\n//\n// Đọc/ghi <project>/.claude/avatar-features.json — state file project-level ghi\n// nhận feature 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 feature 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// \"features\": {\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 FEATURE_STATE_RELATIVE_PATH = \".claude/avatar-features.json\";\n\nexport interface FeatureStateEntry {\n enabled: boolean;\n version: string;\n appliedAt: string; // ISO timestamp lần enable/disable gần nhất\n}\n\nexport interface FeatureState {\n features: Record<string, FeatureStateEntry>;\n}\n\nfunction emptyState(): FeatureState {\n return { features: {} };\n}\n\nfunction stateFilePath(workspacePath: string): string {\n return join(workspacePath, FEATURE_STATE_RELATIVE_PATH);\n}\n\n// Đọc state. File thiếu hoặc parse lỗi → empty state (fail-soft).\nexport async function readFeatureState(workspacePath: string): Promise<FeatureState> {\n const path = stateFilePath(workspacePath);\n if (!(await pathExists(path))) {\n return emptyState();\n }\n try {\n const parsed = await readJson<Partial<FeatureState>>(path);\n // Defensive: file tồn tại nhưng thiếu `features` → normalize.\n return { features: parsed.features ?? {} };\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 writeFeatureState(workspacePath: string, state: FeatureState): Promise<void> {\n await writeJsonAtomic(stateFilePath(workspacePath), state);\n}\n\n// Set 1 feature enabled/disabled + stamp version & appliedAt. Trả về state mới.\nexport async function setFeatureState(\n workspacePath: string,\n name: string,\n entry: { enabled: boolean; version: string },\n): Promise<FeatureState> {\n const state = await readFeatureState(workspacePath);\n state.features[name] = {\n enabled: entry.enabled,\n version: entry.version,\n appliedAt: new Date().toISOString(),\n };\n await writeFeatureState(workspacePath, state);\n return state;\n}\n\n// Tiện ích: danh sách tên feature đang enabled=true (dùng cho sync re-apply).\nexport async function listEnabledFeatures(workspacePath: string): Promise<string[]> {\n const state = await readFeatureState(workspacePath);\n return Object.entries(state.features)\n .filter(([, entry]) => entry.enabled)\n .map(([name]) => name);\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","import { promises as fs } from \"node:fs\";\n// Scaffold the .claude/ directory tree inside a project after submodule add.\n// Used by `avatar init` for all three modes (internal/client/library).\n//\n// Spec source: Chapter 02 (folder map) + Chapter 09 Command 02 BEHAVIOR.\nimport { join } from \"node:path\";\nimport type { InitMode } from \"../types/config-schema.js\";\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// `.gitmodules` is managed by git submodule add (not direct write), but it\n// gets modified during init so it's listed here for conflict warnings.\nexport const AVATAR_MANAGED_PATHS = [\".claude\", \"CLAUDE.md\", \".gitmodules\"] 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 by the submodule manager — listed here for reference only.\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 mode: InitMode;\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// Append Avatar entries to project .gitignore (or create if missing).\n// Idempotent — skips if our marker line already present.\nexport async function appendGitignoreEntries(projectRoot: string): Promise<void> {\n const path = join(projectRoot, \".gitignore\");\n const tpl = await renderTemplateByName(\"gitignore\", {});\n const marker = \"# Avatar — git-ignored entries injected on `avatar init`\";\n\n let existing = \"\";\n if (await pathExists(path)) {\n existing = await fs.readFile(path, \"utf8\");\n if (existing.includes(marker)) return;\n }\n\n const separator = existing.endsWith(\"\\n\") || existing.length === 0 ? \"\" : \"\\n\";\n await writeTextAtomic(path, `${existing}${separator}\\n${tpl}`);\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","// 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 | \"gitignore\"\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","// 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","// `avatar feature <enable|disable|list> [name]` — quản lý feature toggle.\n//\n// Feature toggle = gói tự-đủ trong pack (features/<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 feature ra + state disabled\n// list bảng: feature | available | enabled | version\n// add multi-select feature available để cài (UX, bọc enable)\n// remove multi-select feature đ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 feature chưa bật → no-op.\n// add/remove tái dùng enableFeatureByName/disableFeatureByName (DRY).\n\nimport { resolve } from \"node:path\";\nimport { checkbox, confirm } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport {\n discoverEnabledAndAvailableFeatures,\n listAvailableFeatures,\n} from \"../lib/discover-pack-features-and-defaults.js\";\nimport {\n buildAddChoices,\n buildRemoveChoices,\n resolveNonInteractiveSelection,\n} from \"../lib/feature-add-remove-selection.js\";\nimport {\n disableFeatureByName,\n enableFeatureByName,\n} from \"../lib/feature-enable-disable-orchestrator.js\";\nimport { readFeatureState } from \"../lib/feature-state-store.js\";\nimport { log } from \"../lib/terminal-logger.js\";\n\ninterface FeatureOptions {\n target?: string;\n}\n\ninterface AddRemoveOptions extends FeatureOptions {\n all?: boolean;\n yes?: boolean;\n}\n\nfunction resolveWorkspace(opts: FeatureOptions): 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 feature đã chọn (có thể rỗng).\nasync function selectFeatures(\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 feature để cài (space chọn, enter xác nhận):\"\n : \"Chọn feature để gỡ (space chọn, enter xác nhận):\",\n choices,\n });\n}\n\n// `avatar feature 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 discoverEnabledAndAvailableFeatures(ws);\n if (available.length === 0) {\n log.dim(\"Không có feature available (pack chưa có features/, hoặc chưa sync).\");\n return;\n }\n const selected = await selectFeatures(available, enabled, opts, \"add\");\n if (selected.length === 0) {\n log.dim(\"Không chọn feature nào — hủy, không thay đổi.\");\n return;\n }\n for (const name of selected) {\n await enableFeatureByName(ws, name);\n }\n}\n\n// `avatar feature remove` — list enabled, multi-select để disable 1-n.\nasync function runRemove(opts: AddRemoveOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const { enabled } = await discoverEnabledAndAvailableFeatures(ws);\n if (enabled.length === 0) {\n log.dim(\"Không có feature nào đang bật — không có gì để gỡ.\");\n return;\n }\n const selected = await selectFeatures(enabled, [], opts, \"remove\");\n if (selected.length === 0) {\n log.dim(\"Không chọn feature 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} feature: ${selected.join(\", \")}?`,\n default: true,\n });\n if (!ok) {\n log.dim(\"Hủy — không gỡ feature nào.\");\n return;\n }\n }\n for (const name of selected) {\n await disableFeatureByName(ws, name);\n }\n}\n\nasync function runList(opts: FeatureOptions): Promise<void> {\n const ws = resolveWorkspace(opts);\n const available = await listAvailableFeatures(ws);\n const state = await readFeatureState(ws);\n\n if (available.length === 0 && Object.keys(state.features).length === 0) {\n log.dim(\"Không có feature nào (pack chưa có features/, hoặc chưa sync). Chạy 'avatar sync'.\");\n return;\n }\n\n // Union tên: feature có trong pack + feature từng ghi trong state.\n const names = [...new Set([...available, ...Object.keys(state.features)])].sort();\n log.info(\"Features:\");\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.features[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 registerFeatureCommand(program: Command): void {\n const feat = program\n .command(\"feature\")\n .description(\"Quản lý feature toggle (prompt-scoring, ...) bật/tắt nguyên khối\");\n\n feat\n .command(\"enable <name>\")\n .description(\"Bật feature: merge manifest vào settings.json + ghi state\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (name: string, opts: FeatureOptions) => {\n const ws = resolveWorkspace(opts);\n const ok = await enableFeatureByName(ws, name);\n if (!ok) process.exit(1);\n });\n\n feat\n .command(\"disable <name>\")\n .description(\"Tắt feature: 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: FeatureOptions) => {\n const ws = resolveWorkspace(opts);\n await disableFeatureByName(ws, name);\n });\n\n feat\n .command(\"list\")\n .description(\"Liệt kê feature: available | enabled | version\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .action(async (opts: FeatureOptions) => {\n await runList(opts);\n });\n\n feat\n .command(\"add\")\n .description(\"Chọn (multi-select) feature available để cài\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Cài hết feature 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 feat\n .command(\"remove\")\n .description(\"Chọn (multi-select) feature đang bật để gỡ\")\n .option(\"--target <path>\", \"Workspace root (default: cwd)\")\n .option(\"--all\", \"Gỡ hết feature đ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","// discover-pack-features-and-defaults.ts\n//\n// Quét pack/features/ để biết feature nào CÓ SẴN + đọc danh sách feature init bật\n// mặc định (features/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 { listEnabledFeatures } from \"./feature-state-store.js\";\nimport { pathExists, readJson } from \"./filesystem-helpers.js\";\n\nconst PACK_FEATURES_REL = [\".claude\", \"pack\", \"features\"];\n\nfunction packFeaturesDir(workspacePath: string): string {\n return join(workspacePath, ...PACK_FEATURES_REL);\n}\n\n// Liệt kê feature có manifest trong pack (mỗi dir con có feature.json).\nexport async function listAvailableFeatures(workspacePath: string): Promise<string[]> {\n const dir = packFeaturesDir(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, \"feature.json\"))) {\n names.push(entry.name);\n }\n }\n return names.sort();\n}\n\ninterface DefaultsFile {\n defaultFeatures?: string[];\n}\n\n// Đọc features/defaults.json → danh sách feature init bật mặc định. Thiếu → [].\nexport async function readDefaultFeatures(workspacePath: string): Promise<string[]> {\n const path = join(packFeaturesDir(workspacePath), \"defaults.json\");\n if (!(await pathExists(path))) {\n return [];\n }\n try {\n const parsed = await readJson<DefaultsFile>(path);\n return parsed.defaultFeatures ?? [];\n } catch {\n return [];\n }\n}\n\nexport interface FeaturesOverview {\n available: string[]; // có manifest trong pack\n enabled: string[]; // enabled=true trong state\n}\n\n// Tổng hợp cho status/doctor: feature có sẵn (pack) + đang bật (state).\nexport async function discoverEnabledAndAvailableFeatures(\n workspacePath: string,\n): Promise<FeaturesOverview> {\n const [available, enabled] = await Promise.all([\n listAvailableFeatures(workspacePath),\n listEnabledFeatures(workspacePath),\n ]);\n return { available, enabled };\n}\n","// feature-add-remove-selection.ts\n//\n// Logic THUẦN (không UI) cho `avatar feature 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 FeatureChoice {\n name: string; // label hiển thị\n value: string; // tên feature\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[]): FeatureChoice[] {\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 feature đang enabled (không pre-check — user chủ động chọn gỡ).\nexport function buildRemoveChoices(enabled: string[]): FeatureChoice[] {\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","// feature-enable-disable-orchestrator.ts\n//\n// Orchestrate 1 lần enable/disable feature: đọc manifest → (kiểm runtime) → apply\n// vào settings.json → cập nhật avatar-features.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ó features/ (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 ApplyFeatureResult,\n disableFeature,\n enableFeature,\n readFeatureManifest,\n} from \"./apply-feature-manifest-to-settings.js\";\nimport { readFeatureState, setFeatureState } from \"./feature-state-store.js\";\nimport { log } from \"./terminal-logger.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 `Feature 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: ApplyFeatureResult): void {\n switch (result.action) {\n case \"enabled\":\n log.success(`Feature '${name}' enabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"disabled\":\n log.success(`Feature '${name}' disabled (${result.changes.join(\"; \")}).`);\n if (result.backupPath) log.dim(` Backup: ${result.backupPath}`);\n break;\n case \"no-change\":\n log.dim(`Feature '${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 feature. 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 feature default có thể chưa có trong pack cũ).\nexport async function enableFeatureByName(\n workspacePath: string,\n name: string,\n opts: { silent?: boolean } = {},\n): Promise<boolean> {\n const manifest = await readFeatureManifest(workspacePath, name);\n if (!manifest) {\n if (!opts.silent) {\n log.warn(\n `Pack version hiện tại chưa hỗ trợ feature '${name}' (thiếu .claude/pack/features/${name}/feature.json). Chạy 'avatar sync' để pull pack mới, hoặc kiểm tên feature.`,\n );\n }\n return false;\n }\n warnIfRuntimeMissing(manifest.requires?.runtime);\n const result = await enableFeature(workspacePath, manifest);\n logApplyResult(name, result);\n await setFeatureState(workspacePath, name, { enabled: true, version: manifest.version });\n return true;\n}\n\n// Disable 1 feature. Đọc manifest để biết CHÍNH XÁC entry nào của feature.\nexport async function disableFeatureByName(workspacePath: string, name: string): Promise<boolean> {\n const manifest = await readFeatureManifest(workspacePath, name);\n if (!manifest) {\n log.warn(\n `Không tìm thấy manifest feature '${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 readFeatureState(workspacePath);\n const prevVersion = state.features[name]?.version ?? \"unknown\";\n await setFeatureState(workspacePath, name, { enabled: false, version: prevVersion });\n return false;\n }\n const result = await disableFeature(workspacePath, manifest);\n logApplyResult(name, result);\n await setFeatureState(workspacePath, name, { enabled: false, version: manifest.version });\n return true;\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","// 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","// 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}\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 // 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 // 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","// 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","// 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","// 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","// 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","// 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// IMPORTANT (v1.6.3 fix): Model được đọc từ TOP-LEVEL `settings.json.model` (Claude Code spec).\n// Trước đó (v1.6.0-1.6.2) đọc nhầm từ `env.ANTHROPIC_MODEL` — field này KHÔNG TỒN TẠI trong\n// settings do Avatar ghi, dẫn đến wiki LUÔN dùng fallback hardcoded thay vì model user chọn.\n//\n// Trả null nếu Subscription mode hoặc không có settings (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 }>(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 // Backward-compat: cũng accept env.ANTHROPIC_MODEL nếu user manual set (legacy).\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 // Anthropic Direct branch (v1.6.0+) — check ANTHROPIC_API_KEY trước.\n const anthropicKey = typeof env.ANTHROPIC_API_KEY === \"string\" ? env.ANTHROPIC_API_KEY : null;\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\n // LLMLite branch — fallback nếu chỉ có ANTHROPIC_AUTH_TOKEN.\n const llmliteToken =\n typeof env.ANTHROPIC_AUTH_TOKEN === \"string\" ? env.ANTHROPIC_AUTH_TOKEN : null;\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 // Có base URL nhưng không có key nào → coi như subscription (OAuth, không expose key).\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.\nexport async function runGitnexusWikiConditional(\n workspacePath: string,\n): Promise<GitnexusWikiResult> {\n // Branch 1: detect provider mode.\n const creds = await readSettingsForWikiCredentials(workspacePath);\n if (!creds) {\n log.warn(\"Subscription mode (OAuth, không có API key trong settings.json) → skip wiki gen.\");\n log.dim(\n \"Để gen wiki sau, 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: workspacePath,\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.\n const wikiPath = join(workspacePath, \".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 init` — Command 02 spec (v1.1 redesign).\n//\n// Bỏ khái niệm --mode internal/client/library. Thay bằng wizard 3-câu hỏi tự\n// nhận diện tình trạng dự án:\n// 1. Đã có repo git remote (URL có sẵn) → flow=existing-remote\n// 2. Đã có folder code local → flow=existing-folder\n// 3. Dự án mới hoàn toàn → flow=new-project\n//\n// Mọi flow đều scaffold workspace tách biệt (không còn mode internal). Avatar\n// luôn check + tự cài gh CLI nếu thiếu, auto-bootstrap git cho folder chưa\n// init, auto-detect .gitignore theo tech stack.\n//\n// Entry point: flag parsing, login check, flow dispatch.\n// Chi tiết từng flow → init-flow-handlers-for-each-project-status.ts\n// Scaffold + finalize → workspace-scaffold-and-finalize-orchestrator.ts\n// Success box + helpers → init-success-rendering-and-helpers.ts\n\nimport { select } 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 {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} 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 {\n runInitFromExistingFolder,\n runInitFromExistingRemote,\n runInitFromScratch,\n} from \"./init-flow-handlers-for-each-project-status.js\";\nimport { runLogin } from \"./login.js\";\n\n// Re-export resolveWorkspacePath so existing imports from init.ts continue to work.\n// Function lives in init-success-rendering-and-helpers.ts (moved during modularization).\nexport { resolveWorkspacePath } from \"./init-success-rendering-and-helpers.js\";\n\n// Re-export types + parser từ shared file để giữ public API ổn định.\n// File này (init.ts) là entry point — không define types/parsers trực tiếp nữa,\n// các handler có thể import shared mà không tạo circular dep với init.ts.\nexport type { InitOptions, ProjectStatus } from \"./init-options-and-bootstrap-strategy-parser.js\";\nexport { parseBootstrapStrategyOpts } from \"./init-options-and-bootstrap-strategy-parser.js\";\n\nimport type { InitOptions, ProjectStatus } from \"./init-options-and-bootstrap-strategy-parser.js\";\n\nexport function registerInitCommand(program: Command): void {\n program\n .command(\"init\")\n .description(\"Khởi tạo Avatar — 3 flow tự nhận diện (repo / folder / new)\")\n .option(\"--project-status <val>\", \"existing-remote | existing-folder | new-project\")\n .option(\"--folder-path <path>\", \"Đường dẫn folder hiện có (flow existing-folder)\")\n .option(\"--create-remote\", \"Force tạo remote qua gh (flow existing-folder hoặc new-project)\")\n .option(\"--repo-visibility <val>\", \"private (mặc định) | public\")\n .option(\"--repo-org <name>\", \"GitHub org/owner cho repo mới\")\n .option(\"--client-repo <url>\", \"URL git remote (flow existing-remote)\")\n .option(\"--workspace-name <name>\", \"Tên workspace\")\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ể\")\n .option(\"--latest\", \"Pull HEAD của team-ai-pack main branch (bỏ qua tag SemVer, bleeding-edge)\")\n .option(\"--team-owner <email>\", \"Email team owner (bỏ qua prompt)\")\n .option(\"--description <text>\", \"Mô tả 1 dòng của dự án\")\n .option(\"--skip-scan\", \"Bỏ qua project-scanner sau scaffold\")\n .option(\"--skip-team-pack\", \"Bỏ qua submodule 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(\"--no-commit\", \"Skip commit workspace initial state (mặc định LUÔN commit)\")\n .option(\"--workspace-remote\", \"Tạo GitHub remote cho workspace root (default: prompt)\")\n .option(\"--ai-skip\", \"Bỏ qua phase AI setup (CI/test mode — chạy `avatar ai setup` sau)\")\n .option(\n \"--gitnexus-skip\",\n \"Bỏ qua phase GitNexus setup (M10 — chạy `avatar gitnexus install` sau)\",\n )\n .option(\n \"--bootstrap-strategy <s>\",\n \"Xử lý folder dirty: stash | commit-all | skip | branch (default: prompt)\",\n )\n .option(\"--preserve-uncommitted\", \"Alias cho --bootstrap-strategy=stash (giữ changes user)\")\n .option(\"--mode <mode>\", \"[DEPRECATED] Dùng --project-status thay thế\")\n .action(async (opts: InitOptions) => {\n try {\n await runInit(opts);\n } catch (err) {\n // Skip strategy → exit 0 với hướng dẫn, không phải error thật.\n if (err instanceof InitAbortedByUserError) {\n log.dim(err.message);\n process.exit(0);\n }\n // User chọn \"Tạm ngưng\" trong pre-flight check team-ai-pack → exit 0 graceful.\n if (err instanceof TeamPackAccessAbortedError) {\n log.dim(err.message);\n process.exit(0);\n }\n // User chọn \"Tạm ngưng\" trong remote access recovery → exit 0 graceful.\n if (err instanceof RemoteAccessAbortedError) {\n log.dim(err.message);\n process.exit(0);\n }\n // User abort generic recovery prompt → exit 0 graceful.\n if (err instanceof UserAbortedRecoveryError) {\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(opts: InitOptions): Promise<void> {\n if (!opts.yes) printAvatarBanner({ tagline: \"Khởi tạo Avatar trong dự án của bạn\" });\n\n if (opts.mode) {\n log.warn(\"Flag --mode đã deprecated từ v1.1. Dùng --project-status thay thế.\");\n }\n\n // Login check: nếu chưa login hoặc token hết hạn → tự trigger login flow inline,\n // không bắt user re-run init. Sau login OK → re-read config + tiếp tục.\n // v1.2.5: Loop retry — sau login attempt fail, prompt user thay vì exit thẳng.\n let userConfig = await readUserConfig();\n while (!userConfig || isTokenExpired(userConfig)) {\n log.info(\"Chưa đăng nhập — chạy login flow trước khi init...\");\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\n const action = await promptRetryOrSkip({\n taskName: \"Đăng nhập SSO Google\",\n reason: \"Token chưa được tạo hoặc đã hết hạn.\",\n allowSkip: false, // Login bắt buộc, không skip được.\n hint: \"Đảm bảo bạn chọn 'Allow' trên browser và dùng email @nal.vn.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\n \"User abort tại bước login. Chạy 'avatar login' tay rồi 'avatar init' lại.\",\n );\n }\n // action === \"retry\" → loop tiếp.\n }\n\n const status = opts.projectStatus ?? (await promptProjectStatus());\n\n switch (status) {\n case \"existing-remote\":\n await runInitFromExistingRemote(opts, userConfig.email);\n break;\n case \"existing-folder\":\n await runInitFromExistingFolder(opts, userConfig.email);\n break;\n case \"new-project\":\n await runInitFromScratch(opts, userConfig.email);\n break;\n }\n}\n\nasync function promptProjectStatus(): Promise<ProjectStatus> {\n return (await select({\n message: \"Tình trạng dự án của bạn?\",\n choices: [\n { name: \"1. Đã có repo git remote (URL có sẵn)\", value: \"existing-remote\" as const },\n { name: \"2. Đã có folder code local\", value: \"existing-folder\" as const },\n { name: \"3. Dự án mới hoàn toàn\", value: \"new-project\" as const },\n ],\n })) as ProjectStatus;\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","// 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","// 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","// 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","// 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","// Manage the team-ai-pack git submodule lifecycle: add, update, pin-to-tag,\n// changelog extraction. Used by `avatar init` and `avatar sync`.\nimport { join } from \"node:path\";\nimport {\n ensureTeamPackAccessWithRetry,\n parseRepoSlugFromGitUrl,\n} from \"./check-team-pack-access-with-retry-loop.js\";\nimport {\n addSubmodule,\n checkoutBranchHeadInSubmodule,\n checkoutTagInSubmodule,\n currentCommitSha,\n listTags,\n tagAtHead,\n} from \"./git-operations.js\";\nimport { pickLatestStableSemVerTag } from \"./pick-latest-stable-semver-tag.js\";\nimport { resolveTeamPackRepoUrl } from \"./resolve-team-pack-repo-url.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Resolve URL động qua resolveTeamPackRepoUrl() — không hardcode. Export getter\n// để legacy code đọc URL được nhưng kết quả phụ thuộc env + gh user hiện tại.\nexport function getTeamPackRepoUrl(): string {\n return resolveTeamPackRepoUrl();\n}\n\n// Backward-compat: snapshot URL lúc load module (v1.0/1.1 đã expose). Sẽ remove\n// ở v2.0.0. New code dùng getTeamPackRepoUrl() để luôn pickup env latest.\nexport const TEAM_PACK_REPO_URL = resolveTeamPackRepoUrl();\nexport const TEAM_PACK_RELATIVE_PATH = \".claude/pack\";\n\n// Custom error báo user abort sau pre-flight check (chọn \"Tạm ngưng\" trong menu).\n// Caller init.ts catch để exit 0 graceful thay vì error.\nexport class TeamPackAccessAbortedError extends Error {\n constructor(message: string) {\n super(message);\n this.name = \"TeamPackAccessAbortedError\";\n }\n}\n\n// Add the team-ai-pack submodule into a fresh project and pin it to a tag.\n// If `tag` is omitted, checks out the latest tag in the freshly-cloned submodule.\n//\n// v1.2.3 flow:\n// 1. Pre-flight check qua gh API — user có quyền access repo chưa?\n// 2. Nếu chưa → loop menu \"Đã add / Tạm ngưng\" cho đến khi user có access hoặc abort\n// 3. Có access → addSubmodule như cũ\n// Throws TeamPackAccessAbortedError nếu user chọn abort.\n// v1.10.0: Default branch khi --latest. Hardcode \"main\" — match với avatar sync.\nconst DEFAULT_PACK_BRANCH = \"main\";\n\nexport async function addTeamPackSubmodule(\n projectRoot: string,\n tag?: string,\n ssoEmail?: string,\n // v1.10.0: latest=true → checkout HEAD của main branch thay vì tag SemVer.\n // Conflict với tag → tag wins (explicit > implicit).\n latest = false,\n): Promise<{ pinnedTag: string | null }> {\n const url = resolveTeamPackRepoUrl();\n\n // Pre-flight check — chỉ work với URL GitHub (có slug parse được).\n const repoSlug = parseRepoSlugFromGitUrl(url);\n if (repoSlug) {\n const hasAccess = await ensureTeamPackAccessWithRetry({ repoSlug, ssoEmail });\n if (!hasAccess) {\n throw new TeamPackAccessAbortedError(\n \"User chọn tạm ngưng. Chạy lại 'avatar init' sau khi được add vào org.\",\n );\n }\n }\n\n try {\n await addSubmodule(url, TEAM_PACK_RELATIVE_PATH, projectRoot);\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n if (msg.includes(\"Repository not found\") || msg.includes(\"not found\")) {\n log.error(\n `Repo team-ai-pack không tồn tại: ${url}\\n Cách fix:\\n 1. Tạo repo: gh repo create <owner>/team-ai-pack --private --add-readme\\n 2. Hoặc override URL: export AVATAR_TEAM_PACK_REPO_URL=<url-repo-của-bạn>\\n 3. Hoặc dùng flag --skip-team-pack`,\n );\n }\n throw err;\n }\n\n // v1.10.0: Resolve pin target — 3 cases priority:\n // 1. tag (explicit --pack-version) → use as-is (tag wins, dù latest=true)\n // 2. latest=true → checkout HEAD của main branch, pinnedTag = \"main (HEAD)\"\n // 3. Default → pick latest stable SemVer tag (v1.7.1 logic)\n if (tag) {\n await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, tag, projectRoot);\n return { pinnedTag: tag };\n }\n\n if (latest) {\n await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);\n return { pinnedTag: `${DEFAULT_PACK_BRANCH} (HEAD)` };\n }\n\n const submoduleDir = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n const allTags = await listTags(submoduleDir);\n const target = pickLatestStableSemVerTag(allTags);\n if (target) {\n await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, target, projectRoot);\n }\n return { pinnedTag: target };\n}\n\n// Read the current pinned version of the pack submodule. Returns the tag name\n// if HEAD MATCHES a tag exactly, otherwise the short SHA.\n// v1.7.1 fix: trước đó dùng latestTag (alphabetical sort of all tags) → trả tag\n// \"mới nhất bất kỳ\" thay vì tag tại HEAD. Giờ dùng `git describe --tags\n// --exact-match HEAD` semantics đúng.\nexport async function readPinnedPackVersion(projectRoot: string): Promise<string> {\n const submoduleRoot = join(projectRoot, TEAM_PACK_RELATIVE_PATH);\n const tag = await tagAtHead(submoduleRoot);\n if (tag) return tag;\n const sha = await currentCommitSha(submoduleRoot);\n return sha.slice(0, 7);\n}\n","// Pre-flight check user có quyền clone team-ai-pack chưa.\n// Nếu chưa → highlight warning + 3-option menu loop:\n// 1. \"Đã được grant access với <hiện tại> — re-check\"\n// 2. \"Đã grant với account khác — switch gh + login\"\n// 3. \"Tạm ngưng\"\n//\n// v1.2.6 fixes:\n// - Boxen red highlight cho warning (không chỉ log.warn)\n// - ssoEmail truyền từ caller (avatar login email)\n// - 3 options menu thay 2 (thêm switch account)\n// - getCurrentGhUser() re-call mỗi lần loop (sau switch thì khác)\nimport { spawnSync } from \"node:child_process\";\nimport { confirm, select } from \"@inquirer/prompts\";\nimport boxen from \"boxen\";\nimport { chalk, log } from \"./terminal-logger.js\";\n\n// Repo slug ngắn (owner/name) parse từ git URL để tránh dùng URL full.\n// gh api repos/<slug> work với slug.\nexport function parseRepoSlugFromGitUrl(url: string): string | null {\n // Support: https://github.com/owner/name.git, git@github.com:owner/name.git\n const httpsMatch = url.match(/github\\.com[/:]([^/]+\\/[^/]+?)(?:\\.git)?$/);\n return httpsMatch?.[1] ?? null;\n}\n\n// Check user có quyền truy cập repo private qua gh CLI (token user trong keychain).\n// Exit 0 = có access; non-zero = không (404/403 đều fail).\nexport function checkRepoAccess(repoSlug: string): boolean {\n const r = spawnSync(\"gh\", [\"api\", `repos/${repoSlug}`], { stdio: \"ignore\" });\n return r.status === 0;\n}\n\n// Lấy github username của user đang gh-auth — re-call mỗi lần loop để reflect\n// account vừa switch (nếu user pick \"switch account\").\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 --web` 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}. Có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Copy text vào clipboard qua clipboardy. Hỏi user trước (tránh override).\nasync function copyInfoToClipboardWithConsent(info: string): Promise<void> {\n const ok = await confirm({\n message: \"Copy thông tin (GitHub username + email) vào clipboard để dán vào Slack/email?\",\n default: true,\n });\n if (!ok) return;\n try {\n const { default: clipboardy } = await import(\"clipboardy\");\n await clipboardy.write(info);\n log.success(\"Đã copy vào clipboard\");\n } catch (err) {\n log.dim(`Copy clipboard fail: ${(err as Error).message}`);\n }\n}\n\n// Render warning box highlight với boxen red border. Eye-catching hơn log.warn.\nfunction printAccessWarningBox(\n repoSlug: string,\n ghUser: string | null,\n ssoEmail: string | null,\n): void {\n const lines = [\n `${chalk.red(\"⛔ KHÔNG CÓ QUYỀN ACCESS\")}`,\n \"\",\n `Repo: ${chalk.bold(repoSlug)}`,\n \"\",\n \"Bạn cần được admin add vào org để pull team-ai-pack.\",\n \"\",\n `${chalk.dim(\"Thông tin gửi admin:\")}`,\n ` GitHub username: ${chalk.cyan(ghUser ?? \"(chưa gh auth — chạy: gh auth login)\")}`,\n ` NAL email: ${chalk.cyan(ssoEmail ?? \"(chưa avatar login — chạy: avatar login)\")}`,\n ` Date: ${new Date().toISOString().split(\"T\")[0]}`,\n \"\",\n `${chalk.dim(\"Liên hệ:\")} luke@nal.vn (Slack #avatar-setup)`,\n ];\n process.stdout.write(\n `${boxen(lines.join(\"\\n\"), { padding: 1, borderColor: \"red\", borderStyle: \"round\" })}\\n`,\n );\n}\n\n// Build info text user cần gửi admin (clipboard version — plain text).\nfunction buildAccessRequestInfo(\n repoSlug: string,\n ghUser: string | null,\n ssoEmail: string | null,\n): string {\n return [\n `Request access ${repoSlug} (NAL)`,\n \"\",\n `GitHub username: ${ghUser ?? \"(chưa gh auth — chạy: gh auth login)\"}`,\n `NAL email: ${ssoEmail ?? \"(chưa avatar login — chạy: avatar login)\"}`,\n `Date: ${new Date().toISOString().split(\"T\")[0]}`,\n ].join(\"\\n\");\n}\n\n// Main entry — loop menu cho đến khi user có access hoặc abort.\n// Return true nếu access OK; false nếu user abort.\nexport async function ensureTeamPackAccessWithRetry(args: {\n repoSlug: string;\n ssoEmail?: string;\n}): Promise<boolean> {\n // Fast path: đã có access.\n if (checkRepoAccess(args.repoSlug)) return true;\n\n // First display: highlight warning box + offer clipboard copy.\n const initialGhUser = getCurrentGhUser();\n printAccessWarningBox(args.repoSlug, initialGhUser, args.ssoEmail ?? null);\n await copyInfoToClipboardWithConsent(\n buildAccessRequestInfo(args.repoSlug, initialGhUser, args.ssoEmail ?? null),\n );\n\n // Loop 3-option menu.\n while (true) {\n const ghUser = getCurrentGhUser(); // Re-call: account có thể đã switch giữa các lần loop.\n const ghUserDisplay = ghUser ?? \"(chưa gh auth)\";\n\n const action = (await select({\n message: \"Cách xử lý?\",\n choices: [\n {\n name: `Đã được grant access với GitHub username '${ghUserDisplay}' — kiểm tra lại`,\n value: \"retry-same\" as const,\n },\n {\n name: \"Đã grant với GitHub account khác — switch gh (mở browser)\",\n value: \"switch-account\" as const,\n },\n {\n name: \"Tạm ngưng — chạy lại 'avatar init' sau\",\n value: \"abort\" as const,\n },\n ],\n })) as \"retry-same\" | \"switch-account\" | \"abort\";\n\n if (action === \"abort\") {\n log.dim(\"Tạm ngưng. Chạy lại 'avatar init' sau khi đã accept invite từ GitHub.\");\n return false;\n }\n\n if (action === \"switch-account\") {\n triggerGhAuthLoginInteractive();\n // Sau switch → loop tiếp re-check với account mới.\n }\n\n // Re-check (cả 2 case retry-same + switch-account).\n log.info(\"Kiểm tra access...\");\n if (checkRepoAccess(args.repoSlug)) {\n const finalUser = getCurrentGhUser();\n log.success(`Đã có access với '${finalUser ?? \"(unknown)\"}' — tiếp tục.`);\n return true;\n }\n\n log.warn(\n `Vẫn chưa có access với account '${ghUser ?? \"(unknown)\"}'. Đảm bảo đã accept email invite hoặc switch đúng account.`,\n );\n // Loop tiếp.\n }\n}\n","// Pick latest stable SemVer tag from a list — used by `avatar sync` để chọn\n// pack version mới nhất một cách độc lập với pack team.\n//\n// Background:\n// Trước v1.7.1 Avatar dùng `git tags` rồi `tags[length-1]` → alphabetical sort.\n// Hoạt động OK với v0.1 → v0.4, NHƯNG break silently khi:\n// - v0.10.0 < v0.4.0 (alphabetical)\n// - v1.0.0 < v0.9.0 (alphabetical)\n// - Pre-release v0.5.0-rc1 lẫn vào (user không muốn pick auto)\n//\n// v1.7.1 fix: parse SemVer chuẩn, sort numeric, default SKIP pre-release.\n// User muốn pre-release → pass `--version v0.5.0-rc1` explicit.\n//\n// Decision (Q1=SemVer sort, Q2=skip pre-release):\n// - Parse `vMAJOR.MINOR.PATCH` (optional 'v' prefix, optional `-PRERELEASE`)\n// - Tags không match SemVer pattern → skip (vd \"release-1\", \"2026-05-29\")\n// - Default skip pre-release (có dấu `-` sau PATCH)\n// - Sort numeric tăng dần [major, minor, patch] → return tag cuối\n\n// Strict SemVer regex: optional v prefix, 3 số, optional -PRERELEASE.\n// Pre-release matches: -rc1, -beta.2, -alpha, ...\nconst SEMVER_REGEX = /^v?(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?$/;\n\ninterface ParsedSemVer {\n raw: string; // Original tag string (vd \"v0.4.0\")\n major: number;\n minor: number;\n patch: number;\n prerelease: string | null; // null cho stable, string cho rc/beta/alpha\n}\n\n// Parse 1 tag string. Trả null nếu không match SemVer.\nexport function parseSemVerTag(tag: string): ParsedSemVer | null {\n const match = tag.match(SEMVER_REGEX);\n if (!match) return null;\n const [, major, minor, patch, prerelease] = match;\n return {\n raw: tag,\n major: Number.parseInt(major ?? \"0\", 10),\n minor: Number.parseInt(minor ?? \"0\", 10),\n patch: Number.parseInt(patch ?? \"0\", 10),\n prerelease: prerelease ?? null,\n };\n}\n\n// Pick latest stable tag từ array. Mặc định skip pre-release.\n// includePrerelease=true → cho phép pre-release (vd CI test bleeding edge).\n//\n// Trả null nếu KHÔNG có tag SemVer nào (caller xử lý: error hoặc fallback).\nexport function pickLatestStableSemVerTag(\n tags: string[],\n includePrerelease = false,\n): string | null {\n const parsed = tags\n .map(parseSemVerTag)\n .filter((t): t is ParsedSemVer => t !== null)\n .filter((t) => includePrerelease || t.prerelease === null);\n\n if (parsed.length === 0) return null;\n\n // Sort tăng dần: [major, minor, patch] numeric.\n parsed.sort((a, b) => {\n if (a.major !== b.major) return a.major - b.major;\n if (a.minor !== b.minor) return a.minor - b.minor;\n return a.patch - b.patch;\n });\n\n // Last element = latest.\n return parsed[parsed.length - 1]?.raw ?? null;\n}\n","// Resolve URL của team-ai-pack submodule. Thứ tự ưu tiên:\n// 1. Env var AVATAR_TEAM_PACK_REPO_URL (explicit override — per-machine/CI)\n// 2. Fallback \"nalvn/team-ai-pack\" qua SSH (NAL org default, private)\n//\n// LƯU Ý v1.2.3: bỏ logic \"smart default = <gh-user>/team-ai-pack\" của v1.2.x\n// vì team-ai-pack là SHARED resource của org NAL, KHÔNG phải per-user repo.\n// User không thuộc nalvn org → cần admin invite (xem pre-flight check trong orchestrator).\n//\n// v1.2.8: Default đổi từ HTTPS → SSH (git@github.com:nalvn/team-ai-pack.git).\n// Lý do: HTTPS clone với gh credential helper negotiate WRITE permission ở handshake.\n// User chỉ có Read role (pull:true, push:false) → server trả 403 \"Write access not granted\".\n// SSH với key user đã setup → authenticate chính xác per-role, work cho read-only access.\n//\n// v1.13.0 SECURITY: Allowlist URL trước khi return. Trước fix:\n// `AVATAR_TEAM_PACK_REPO_URL=\"file:///etc\" avatar init` → `git submodule add file:///etc`\n// = local filesystem exfil. Hoặc URL gắn malicious `post-checkout` git hooks = RCE.\n// Allowlist chỉ GitHub HTTPS / SSH với .git suffix bắt buộc.\nconst ORG_DEFAULT = \"git@github.com:nalvn/team-ai-pack.git\";\n\n// Github SSH: git@github.com:owner/repo.git\n// Github HTTPS: https://github.com/owner/repo.git\n// Owner/repo: chỉ alphanum, hyphen, underscore, dot.\nconst GITHUB_URL_ALLOWLIST =\n /^(git@github\\.com:|https:\\/\\/github\\.com\\/)[A-Za-z0-9._-]+\\/[A-Za-z0-9._-]+\\.git$/;\n\nexport class InvalidTeamPackUrlError extends Error {\n url: string;\n constructor(url: string) {\n super(\n `AVATAR_TEAM_PACK_REPO_URL không hợp lệ: ${url}\\n Yêu cầu: GitHub URL có .git suffix, vd:\\n git@github.com:owner/repo.git\\n https://github.com/owner/repo.git\\n Lý do block: ngăn file:// (filesystem exfil) + URL malicious (git hook RCE).`,\n );\n this.name = \"InvalidTeamPackUrlError\";\n this.url = url;\n }\n}\n\nfunction assertAllowedUrl(url: string): void {\n if (!GITHUB_URL_ALLOWLIST.test(url)) {\n throw new InvalidTeamPackUrlError(url);\n }\n}\n\nexport function resolveTeamPackRepoUrl(): string {\n const override = process.env.AVATAR_TEAM_PACK_REPO_URL;\n if (override) {\n assertAllowedUrl(override);\n return override;\n }\n // ORG_DEFAULT hardcoded → trust nhưng vẫn validate defensively (catch future typo).\n assertAllowedUrl(ORG_DEFAULT);\n return ORG_DEFAULT;\n}\n","// init-flow-handlers-for-each-project-status.ts\n//\n// 3 flow handlers ứng với từng trạng thái dự án mà user chọn ở wizard:\n// - runInitFromExistingRemote: dự án đã có remote URL → clone làm src/\n// - runInitFromExistingFolder: dự án đã có folder code local → link/tạo remote\n// - runInitFromScratch: dự án mới hoàn toàn → tạo remote mới qua gh\n//\n// Mỗi flow cuối cùng đều gọi scaffoldWorkspaceWithSrcSubmodule hoặc\n// finalizeWorkspaceScaffold từ workspace-scaffold-and-finalize-orchestrator.ts.\n// File này chỉ xử lý logic khác biệt giữa 3 flow (prompt, bootstrap, remote check).\n\nimport { basename, join, resolve } from \"node:path\";\nimport { input, select } from \"@inquirer/prompts\";\nimport { addTeamPackSubmoduleWithRetryOnNetworkFail } from \"../lib/add-team-pack-submodule-with-retry-on-network-fail.js\";\nimport { createGithubRemoteFromFolder } from \"../lib/create-github-remote-from-folder.js\";\nimport { ensureDir } from \"../lib/filesystem-helpers.js\";\nimport { ensureGitHubReady } from \"../lib/git-auth-and-install-orchestrator.js\";\nimport { git } from \"../lib/git-operations.js\";\nimport { handleRemoteAccessFailureWithAccountSwitch } from \"../lib/handle-remote-access-failure-with-account-switch.js\";\nimport {\n type BootstrapStrategy,\n safeBootstrapGitInFolder,\n} from \"../lib/safe-bootstrap-for-dirty-folder.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport { spinner } from \"../lib/terminal-logger.js\";\nimport type { RepoVisibility } from \"../lib/validate-repo-name-and-visibility.js\";\nimport { tryVerifyGitRemoteAccessible } from \"../lib/verify-git-remote-accessible.js\";\nimport {\n type InitOptions,\n type ProjectStatus,\n parseBootstrapStrategyOpts,\n} from \"./init-options-and-bootstrap-strategy-parser.js\";\nimport { inferWorkspaceName } from \"./init-scaffold-variable-builders.js\";\nimport { promptTeamOwner, resolveWorkspacePath } from \"./init-success-rendering-and-helpers.js\";\nimport {\n finalizeWorkspaceScaffold,\n getOrCreateOriginRemote,\n scaffoldWorkspaceWithSrcSubmodule,\n} from \"./workspace-scaffold-and-finalize-orchestrator.js\";\n\n// ─── FLOW 1: EXISTING REMOTE ────────────────────────────────────────────────\nexport async function runInitFromExistingRemote(\n opts: InitOptions,\n ownerEmail: string,\n): Promise<void> {\n const initialRemoteUrl =\n opts.clientRepo ??\n (await input({\n message: \"URL git của repo:\",\n validate: (v) => (v.length > 0 ? true : \"URL bắt buộc\"),\n }));\n\n // v1.2.7: ensureGitHubReady có thể trả URL khác (user re-nhập trong recovery menu\n // khi URL gốc sai). Dùng resolvedUrl cho mọi step sau.\n const { resolvedRemoteUrl } = await ensureGitHubReady(initialRemoteUrl);\n const remoteUrl = resolvedRemoteUrl ?? initialRemoteUrl;\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(ownerEmail));\n const inferredName = inferWorkspaceName(remoteUrl);\n const workspaceName =\n opts.workspaceName ?? (await input({ message: \"Tên workspace:\", default: inferredName }));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);\n\n await scaffoldWorkspaceWithSrcSubmodule({\n workspacePath,\n workspaceName,\n srcRemoteUrl: remoteUrl,\n teamOwner,\n skipTeamPack: opts.skipTeamPack,\n description: opts.description ?? `Avatar workspace cho ${remoteUrl}`,\n packVersion: opts.packVersion,\n packLatest: opts.latest,\n autoYes: opts.yes,\n skipCommit: opts.commit === false,\n createWorkspaceRemote: opts.workspaceRemote,\n repoVisibility: opts.repoVisibility,\n repoOrg: opts.repoOrg,\n flow: \"existing-remote\",\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n ssoEmail: ownerEmail,\n });\n}\n\n// ─── FLOW 2: EXISTING FOLDER ────────────────────────────────────────────────\nexport async function runInitFromExistingFolder(\n opts: InitOptions,\n ownerEmail: string,\n): Promise<void> {\n const folderPath = resolve(\n opts.folderPath ??\n (await input({\n message: \"Đường dẫn folder hiện có:\",\n validate: (v) => (v.length > 0 ? true : \"Path bắt buộc\"),\n })),\n );\n\n // Safe bootstrap: nếu folder dirty → prompt 4 strategy bảo vệ user changes.\n await safeBootstrapGitInFolder(folderPath, {\n presetStrategy: parseBootstrapStrategyOpts(opts),\n autoYes: opts.yes,\n });\n\n // Check remote origin. Có → dùng luôn. Chưa có → hỏi tạo mới (default Có).\n let remoteUrl = await getOrCreateOriginRemote(folderPath, opts);\n\n // v1.12.0: Verify remote accessible TRƯỚC khi submodule add — common case\n // folder clone từ team khác xuống, user không có quyền access repo gốc.\n // Recovery menu cho phép user \"reset folder & tạo repo mới của mình\" thay vì\n // bị block sâu trong submodule add với error khó hiểu \"Repository not found\".\n if (remoteUrl) {\n const verify = tryVerifyGitRemoteAccessible(remoteUrl);\n if (!verify.ok) {\n log.warn(`Remote ${remoteUrl} không accessible (${verify.reason ?? \"unknown\"}).`);\n const recovered = await handleRemoteAccessFailureWithAccountSwitch({\n url: remoteUrl,\n initialReason: verify.reason ?? \"unknown\",\n initialDetail: verify.detail,\n folderPath, // enable option \"Reset folder & tạo repo mới\"\n defaultVisibility: opts.repoVisibility as \"private\" | \"public\" | undefined,\n });\n remoteUrl = recovered.resolvedUrl;\n }\n }\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(ownerEmail));\n const inferredName = opts.workspaceName ?? `${basename(folderPath)}-avatar-workspace`;\n const workspaceName =\n opts.workspaceName ?? (await input({ message: \"Tên workspace:\", default: inferredName }));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, workspaceName, opts.force);\n\n await scaffoldWorkspaceWithSrcSubmodule({\n workspacePath,\n workspaceName,\n srcRemoteUrl: remoteUrl ?? folderPath, // fallback local path nếu user từ chối tạo remote\n teamOwner,\n skipTeamPack: opts.skipTeamPack,\n description: opts.description ?? `Avatar workspace cho folder ${folderPath}`,\n packVersion: opts.packVersion,\n packLatest: opts.latest,\n autoYes: opts.yes,\n skipCommit: opts.commit === false,\n createWorkspaceRemote: opts.workspaceRemote,\n repoVisibility: opts.repoVisibility,\n repoOrg: opts.repoOrg,\n flow: \"existing-folder\",\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n ssoEmail: ownerEmail,\n });\n}\n\n// ─── FLOW 3: NEW PROJECT ────────────────────────────────────────────────────\nexport async function runInitFromScratch(opts: InitOptions, ownerEmail: string): Promise<void> {\n await ensureGitHubReady();\n\n const projectName =\n opts.workspaceName ??\n (await input({\n message: \"Tên dự án:\",\n validate: (v) => (v.length > 0 ? true : \"Tên bắt buộc\"),\n }));\n const visibility = (opts.repoVisibility ??\n (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 RepoVisibility;\n\n const teamOwner = opts.teamOwner ?? (await promptTeamOwner(ownerEmail));\n const workspaceParent = resolve(opts.workspaceParent ?? \".\");\n const workspacePath = await resolveWorkspacePath(workspaceParent, projectName, opts.force);\n const srcPath = join(workspacePath, \"src\");\n\n // Tạo workspace dir + src/ rỗng + bootstrap git trong src/.\n // srcPath vừa tạo nên empty → safe bootstrap skip prompt thẳng.\n await ensureDir(workspacePath);\n await ensureDir(srcPath);\n await safeBootstrapGitInFolder(srcPath, { autoYes: true });\n\n // Tạo remote GitHub + push initial commit.\n const urls = createGithubRemoteFromFolder({\n folder: srcPath,\n name: projectName,\n visibility,\n org: opts.repoOrg,\n });\n\n // Workspace setup: init git, submodule add cho src/.\n await git(workspacePath).init();\n const sp = spinner(\n opts.skipTeamPack ? \"Add submodule src/...\" : \"Add submodule src/ + team-ai-pack...\",\n );\n try {\n await git(workspacePath).subModule([\"add\", urls.sshUrl, \"src\"]);\n let pinnedTag = \"HEAD\";\n if (!opts.skipTeamPack) {\n // v1.2.6: Stop spinner trước pre-flight access check để menu prompt không bị spinner đè.\n sp.stop();\n const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(\n workspacePath,\n opts.packVersion,\n ownerEmail,\n opts.latest === true && !opts.packVersion, // v1.10.0: latest mode khi flag set + không có explicit tag\n );\n pinnedTag = result.pinnedTag ?? \"HEAD\";\n sp.succeed(`Pin team-ai-pack vào ${pinnedTag}`);\n } else {\n sp.succeed(\"Skip team-ai-pack (--skip-team-pack)\");\n }\n await finalizeWorkspaceScaffold({\n workspacePath,\n workspaceName: projectName,\n teamOwner,\n description: opts.description ?? `Dự án mới: ${projectName}`,\n packVersion: pinnedTag,\n autoYes: opts.yes,\n skipCommit: opts.commit === false,\n createWorkspaceRemote: opts.workspaceRemote,\n repoVisibility: opts.repoVisibility,\n repoOrg: opts.repoOrg,\n flow: \"new-project\",\n aiSkip: opts.aiSkip,\n gitnexusSkip: opts.gitnexusSkip,\n });\n } catch (err) {\n sp.fail(\"Init workspace thất bại\");\n throw err;\n }\n}\n","// Wrapper retry cho addTeamPackSubmodule — git submodule add có thể fail vì:\n// - Network glitch (DNS, timeout, packet drop) → simple retry\n// - SSH permission denied (publickey) → cần switch account / dùng HTTPS / setup SSH\n// - gh token expire giữa flow → cần re-auth\n// - Repo permission issue đã fix → retry sau accept invite\n//\n// Pre-flight check trong addTeamPackSubmodule đã handle \"chưa được invite\" case.\n// Wrapper này handle các fail TRONG submodule add bằng menu recovery rộng hơn.\n//\n// v1.2.11: Detect SSH-specific error → menu mở rộng (switch account / HTTPS fallback /\n// retry / skip / abort). Trước v1.2.10 chỉ có Retry/Skip/Abort generic không đủ.\nimport { spawnSync } from \"node:child_process\";\nimport { select } from \"@inquirer/prompts\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"./prompt-recovery-action-on-failure.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\n// Detect SSH-specific errors từ stderr submodule add.\nfunction isSshPermissionError(message: string): boolean {\n const text = message.toLowerCase();\n return (\n text.includes(\"permission denied (publickey)\") ||\n text.includes(\"publickey)\") ||\n text.includes(\"ssh: could not resolve\") ||\n text.includes(\"host key verification failed\")\n );\n}\n\n// Trigger gh auth login interactive — block đến khi user complete browser.\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}. Có thể chạy tay rồi quay lại retry.`);\n }\n}\n\n// Open GitHub SSH key settings page để user add SSH public key.\nfunction openGithubSshKeysPage(): void {\n log.info(\"Mở trang GitHub Settings → SSH Keys...\");\n const r = spawnSync(\"open\", [\"https://github.com/settings/keys\"], { stdio: \"ignore\" });\n if (r.status !== 0) {\n // Fallback Linux/Windows — chỉ in URL.\n log.info(\"URL: https://github.com/settings/keys\");\n }\n}\n\n// Show menu khi SSH permission fail — nhiều option hơn generic retry/skip.\nasync function handleSshPermissionError(): Promise<\n \"retry\" | \"switch\" | \"https\" | \"skip\" | \"abort\"\n> {\n return (await select({\n message: \"SSH permission denied. Cách xử lý?\",\n choices: [\n {\n name: \"Switch GitHub account (gh auth login — mở browser)\",\n value: \"switch\" as const,\n },\n {\n name: \"Dùng HTTPS thay SSH (override URL bằng env AVATAR_TEAM_PACK_REPO_URL)\",\n value: \"https\" as const,\n },\n {\n name: \"Tôi vừa add SSH key lên GitHub — retry\",\n value: \"retry\" as const,\n },\n {\n name: \"Bỏ qua team-ai-pack (dùng avatar sync sau)\",\n value: \"skip\" as const,\n },\n {\n name: \"Tạm ngưng init — fix SSH key tay rồi chạy lại\",\n value: \"abort\" as const,\n },\n ],\n })) as \"retry\" | \"switch\" | \"https\" | \"skip\" | \"abort\";\n}\n\n// Wrap addTeamPackSubmodule với loop retry. Re-throw TeamPackAccessAbortedError\n// (đã có recovery menu riêng bên trong) → init.ts catch.\n// ssoEmail (v1.2.6): pass xuống để hiển thị trong access request info gửi admin.\nexport async function addTeamPackSubmoduleWithRetryOnNetworkFail(\n projectRoot: string,\n tag?: string,\n ssoEmail?: string,\n // v1.10.0: latest=true → forward xuống addTeamPackSubmodule để checkout HEAD main.\n latest = false,\n): Promise<AddTeamPackResult> {\n while (true) {\n try {\n const result = await addTeamPackSubmodule(projectRoot, tag, ssoEmail, latest);\n return { pinnedTag: result.pinnedTag, skipped: false };\n } catch (err) {\n // Pre-flight access abort → propagate cho init.ts (đã có graceful exit).\n if (err instanceof TeamPackAccessAbortedError) throw err;\n\n const message = err instanceof Error ? err.message : String(err);\n\n // v1.2.11: Detect SSH-specific error → menu rộng hơn.\n if (isSshPermissionError(message)) {\n log.warn(\"Pull team-ai-pack thất bại: SSH permission denied (publickey).\");\n log.dim(\n \" → Máy này chưa có SSH key được register trên GitHub, hoặc key thuộc account khác.\",\n );\n\n const action = await handleSshPermissionError();\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\n \"User abort tại bước pull team-ai-pack. Fix SSH key (https://github.com/settings/keys) rồi chạy lại 'avatar init'.\",\n );\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng không có shared knowledge. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n if (action === \"switch\") {\n triggerGhAuthLoginInteractive();\n continue;\n }\n if (action === \"https\") {\n // Override env URL → HTTPS variant, loop tiếp.\n process.env.AVATAR_TEAM_PACK_REPO_URL = \"https://github.com/nalvn/team-ai-pack.git\";\n log.info(\"Override URL sang HTTPS. Lưu ý: HTTPS có thể fail 403 nếu read-only role.\");\n // Open SSH key page in background để user có option fix sau.\n openGithubSshKeysPage();\n continue;\n }\n // action === \"retry\" → loop tiếp với SSH URL hiện tại.\n continue;\n }\n\n // Generic error → menu retry/skip/abort cũ.\n const action = await promptRetryOrSkip({\n taskName: \"Pull team-ai-pack submodule\",\n reason: message,\n allowSkip: true,\n hint: \"Network glitch? Retry thường work. Nếu skip, dùng `avatar sync` sau để pull pack.\",\n });\n\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước pull team-ai-pack.\");\n }\n if (action === \"skip\") {\n log.warn(\n \"Skip team-ai-pack. Workspace dùng được nhưng không có shared knowledge. Pull sau qua `avatar sync`.\",\n );\n return { pinnedTag: null, skipped: true };\n }\n // retry → loop tiếp.\n }\n }\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","// 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","// 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","// 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","// 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","// Shared types + helpers cho `avatar init` flow handlers.\n//\n// Tách file riêng để break circular import giữa init.ts (entry point) và\n// init-flow-handlers-for-each-project-status.ts (3 flow handler concrete).\n// Cả 2 nơi đều consume `InitOptions`, `ProjectStatus`, `parseBootstrapStrategyOpts` —\n// nếu để ở init.ts thì flow-handlers import init.ts = circular ở runtime.\n// File này không import gì từ ./init.js → không tạo cycle.\n\nimport type { BootstrapStrategy } from \"../lib/safe-bootstrap-for-dirty-folder.js\";\n\n// 3 flow values + deprecated legacy \"mode\" alias.\nexport type ProjectStatus = \"existing-remote\" | \"existing-folder\" | \"new-project\";\n\nexport interface InitOptions {\n // New v1.1 flags\n projectStatus?: ProjectStatus;\n folderPath?: string;\n createRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n // Carried over\n skipScan?: boolean;\n skipTeamPack?: boolean;\n packVersion?: string;\n // v1.10.0: --latest → pin pack vào HEAD của main branch (bleeding-edge),\n // bỏ qua tag SemVer. Conflict với --pack-version → ưu tiên --pack-version.\n // Commander auto-map --latest → opts.latest (camelCase).\n latest?: boolean;\n clientRepo?: string; // reused: URL khi projectStatus=existing-remote\n workspaceName?: string;\n workspaceParent?: string;\n force?: boolean;\n teamOwner?: string;\n description?: string;\n yes?: boolean;\n commit?: boolean; // commander tự set false khi user pass --no-commit\n workspaceRemote?: boolean; // tạo remote cho workspace root (--workspace-remote)\n aiSkip?: boolean; // Skip AI setup phase (CI / test mode)\n gitnexusSkip?: boolean; // Skip GitNexus phase (v1.4.0)\n bootstrapStrategy?: string; // stash | commit-all | skip | branch\n preserveUncommitted?: boolean; // Alias cho --bootstrap-strategy=stash\n // Legacy (deprecated)\n mode?: string;\n}\n\n// Parse 2 flag mới → BootstrapStrategy enum hoặc undefined (no preset → prompt).\n// --preserve-uncommitted override --bootstrap-strategy=other (UX: shortcut win).\nexport function parseBootstrapStrategyOpts(opts: InitOptions): BootstrapStrategy | undefined {\n if (opts.preserveUncommitted) return \"stash\";\n if (!opts.bootstrapStrategy) return undefined;\n const valid: BootstrapStrategy[] = [\"stash\", \"commit-all\", \"skip\", \"branch\"];\n if (valid.includes(opts.bootstrapStrategy as BootstrapStrategy)) {\n return opts.bootstrapStrategy as BootstrapStrategy;\n }\n throw new Error(\n `--bootstrap-strategy không hợp lệ: ${opts.bootstrapStrategy}. Chọn: ${valid.join(\" | \")}`,\n );\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\";\nimport type { InitMode } from \"../types/config-schema.js\";\n\n// Last path segment of an absolute project root, used as fallback project\n// name when user doesn't supply one explicitly. Handles trailing slashes.\nexport function projectNameOf(projectRoot: string): string {\n return projectRoot.split(\"/\").filter(Boolean).pop() ?? \"avatar-project\";\n}\n\n// Infer workspace folder name from a git remote URL.\n// \"git@github.com:org/repo.git\" → \"avatar-repo-workspace\"\n// \"https://github.com/org/repo.git\" → \"avatar-repo-workspace\"\n// \"https://github.com/org/repo\" → \"avatar-repo-workspace\"\n// \"org/repo\" (shorthand) → \"avatar-repo-workspace\"\n// \"repo\" (bare name) → \"avatar-repo-workspace\"\n// invalid input → \"avatar-client-workspace\" (last-resort fallback)\n//\n// v1.2.8 fixes:\n// - Support bare repo name (không có \"/\" hoặc \":\") — trước fallback \"client\"\n// - Strip \"avatar-\" prefix nếu repo name đã có → tránh double \"avatar-avatar-...\"\n// - Trim trailing .git và trailing slash\nexport function inferWorkspaceName(repoUrl: string): string {\n // Strip trailing slash + tách last path segment (work cả https://, git@, plain name).\n const trimmed = repoUrl.trim().replace(/\\/+$/, \"\");\n const lastSegment = trimmed.split(/[/:]/).pop() ?? \"\";\n // Strip .git suffix nếu có.\n const baseName = lastSegment.replace(/\\.git$/, \"\");\n if (!baseName) return \"avatar-client-workspace\";\n // Strip \"avatar-\" prefix sẵn có để tránh double prefix (\"avatar-avatar-...\").\n const withoutPrefix = baseName.replace(/^avatar-/, \"\");\n return `avatar-${withoutPrefix}-workspace`;\n}\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 submodule)\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.\nexport function buildScaffoldVariables(args: {\n projectName: string;\n projectDescription: string;\n teamOwner: string;\n packVersion: string;\n mode: InitMode;\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 mode: args.mode,\n gitnexusSection: buildGitnexusSection(args.gitnexusReady ?? false),\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 { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { formatPackCommandsCheatsheetBox } from \"../lib/format-pack-commands-cheatsheet-box.js\";\nimport { git } from \"../lib/git-operations.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\";\nimport type { ProjectStatus } from \"./init.js\";\n\n// ─── shared utilities (giữ từ v1.0.1, chỉ đổi signature mode → flow) ───────\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\nexport async function maybeCommitWorkspace(\n workspacePath: string,\n skipCommit?: boolean,\n): Promise<void> {\n // Mặc định LUÔN commit — giữ workspace ở git state hợp lệ (có baseline).\n // User muốn review trước commit thì pass --no-commit để skip.\n if (skipCommit) {\n log.warn(\"Skip commit (--no-commit). Chạy 'git status' + commit thủ công sau.\");\n return;\n }\n const g = git(workspacePath);\n await g.add([\"CLAUDE.md\", \".claude/\", \".gitignore\", \".gitmodules\", \"notes/\", \"scripts/\"]);\n await g.commit(\"chore: initialize Avatar workspace\");\n log.success(\"Đã commit workspace\");\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 flow: ProjectStatus,\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 ` ${chalk.dim(`(flow: ${flow})`)}`,\n formatAiStatusLine(aiResult),\n formatGitnexusStatusLine(gitnexusResult),\n \"\",\n ` ${chalk.cyan(`cd ${rootPath}`)}`,\n ` ${chalk.cyan(\"claude\")} Mở Claude Code ở workspace root`,\n \"\",\n ` ${chalk.cyan(\"avatar commit src\")} Commit code lên client remote`,\n ` ${chalk.cyan(\"avatar sync\")} Pull 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 feature\" },\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 feature 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","// workspace-scaffold-and-finalize-orchestrator.ts\n//\n// Orchestrate toàn bộ quá trình scaffold workspace Avatar:\n// - getOrCreateOriginRemote: kiểm tra / tạo remote origin cho folder hiện có\n// - scaffoldWorkspaceWithSrcSubmodule: scaffold workspace dùng cho flow 1 + 2\n// (workspace = parent, src/ là submodule trỏ tới remote hoặc local path)\n// - finalizeWorkspaceScaffold: bước chung sau khi src/ + team-ai-pack đã add xong\n// - autoSyncPackOnInit: auto-sync pack content vào .claude/ (v1.6.2)\n// - maybeCreateWorkspaceRemote: hỏi/tạo remote GitHub cho workspace root\n//\n// Tách ra khỏi init.ts để init.ts giữ vai trò entry point / flag parsing thuần.\n\nimport { join } from \"node:path\";\nimport { basename } from \"node:path\";\nimport { confirm, input, select } from \"@inquirer/prompts\";\nimport { addTeamPackSubmoduleWithRetryOnNetworkFail } from \"../lib/add-team-pack-submodule-with-retry-on-network-fail.js\";\nimport { appendAuditEntry } from \"../lib/audit-log-appender.js\";\nimport { createGithubRemoteFromFolder } from \"../lib/create-github-remote-from-folder.js\";\nimport {\n CreateWorkspaceRemoteError,\n createWorkspaceRemoteViaGh,\n linkExistingRemoteToWorkspace,\n} from \"../lib/create-workspace-remote-via-gh.js\";\nimport { ensureDir, pathExists } from \"../lib/filesystem-helpers.js\";\nimport { ensureGitHubReady } from \"../lib/git-auth-and-install-orchestrator.js\";\nimport { git } from \"../lib/git-operations.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport {\n appendGitignoreEntries,\n createClaudeDirTree,\n installGitHook,\n writeProjectKnowledgeFiles,\n writeProjectSettings,\n writeRootClaudeMd,\n} from \"../lib/project-tree-scaffolder.js\";\nimport {\n UserAbortedRecoveryError,\n promptRetryOrSkip,\n} from \"../lib/prompt-recovery-action-on-failure.js\";\nimport { runAiSetupPhase } from \"../lib/run-ai-setup-phase.js\";\nimport { runGitnexusSetupPhase } from \"../lib/run-gitnexus-setup-phase.js\";\nimport { setupDefaultFeaturesOnInit } from \"../lib/setup-default-features-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 type { RepoVisibility } from \"../lib/validate-repo-name-and-visibility.js\";\nimport { buildScaffoldVariables } from \"./init-scaffold-variable-builders.js\";\nimport { maybeCommitWorkspace, printInitSuccessBox } from \"./init-success-rendering-and-helpers.js\";\nimport type { InitOptions, ProjectStatus } from \"./init.js\";\n// runSecretsSetupWizard import removed v1.20.0 — AI setup phase auto-handles direnv.\n\n// Check origin remote của folder; nếu chưa có thì hỏi tạo (default Có) → gh repo create.\n// Trả về URL remote nếu có, undefined nếu user từ chối (caller fallback local path).\nexport async function getOrCreateOriginRemote(\n folderPath: string,\n opts: InitOptions,\n): Promise<string | undefined> {\n const remotes = await git(folderPath).getRemotes(true);\n const origin = remotes.find((r) => r.name === \"origin\");\n if (origin?.refs.push) {\n log.success(`Folder đã có remote origin: ${origin.refs.push}`);\n return origin.refs.push;\n }\n\n const shouldCreate =\n opts.createRemote ??\n (await confirm({\n message: \"Folder chưa có remote. Tạo GitHub repo ngay để share team?\",\n default: true,\n }));\n if (!shouldCreate) {\n log.warn(\"Tiếp tục với local path. Workspace chỉ chạy được trên máy bạn.\");\n return undefined;\n }\n\n await ensureGitHubReady();\n const visibility = (opts.repoVisibility ??\n (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 RepoVisibility;\n const repoName = await input({\n message: \"Tên repo:\",\n default: basename(folderPath),\n });\n const urls = createGithubRemoteFromFolder({\n folder: folderPath,\n name: repoName,\n visibility,\n org: opts.repoOrg,\n });\n return urls.sshUrl;\n}\n\n// Scaffold workspace dùng cho flow 1 + flow 2: workspace = parent của src/.\n// src/ là submodule trỏ tới remote URL hoặc local path.\nexport async function scaffoldWorkspaceWithSrcSubmodule(args: {\n workspacePath: string;\n workspaceName: string;\n srcRemoteUrl: string;\n teamOwner: string;\n description: string;\n packVersion?: string;\n packLatest?: boolean; // v1.10.0: pull HEAD main branch thay vì tag\n autoYes?: boolean;\n skipCommit?: boolean;\n createWorkspaceRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n skipTeamPack?: boolean;\n flow: ProjectStatus;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n ssoEmail?: string;\n}): Promise<void> {\n await ensureDir(args.workspacePath);\n await git(args.workspacePath).init();\n\n const sp = spinner(\n args.skipTeamPack ? \"Add submodule src/...\" : \"Add submodule src/ + team-ai-pack...\",\n );\n try {\n await git(args.workspacePath).subModule([\"add\", args.srcRemoteUrl, \"src\"]);\n let pinnedTag = \"HEAD\";\n if (!args.skipTeamPack) {\n // v1.2.6: Stop spinner trước pre-flight access check để menu prompt không bị spinner đè.\n sp.stop();\n const result = await addTeamPackSubmoduleWithRetryOnNetworkFail(\n args.workspacePath,\n args.packVersion,\n args.ssoEmail,\n args.packLatest === true && !args.packVersion, // v1.10.0\n );\n pinnedTag = result.pinnedTag ?? \"HEAD\";\n sp.succeed(`Pin team-ai-pack vào ${pinnedTag}`);\n } else {\n sp.succeed(\"Skip team-ai-pack (--skip-team-pack)\");\n }\n\n await finalizeWorkspaceScaffold({\n workspacePath: args.workspacePath,\n workspaceName: args.workspaceName,\n teamOwner: args.teamOwner,\n description: args.description,\n packVersion: pinnedTag,\n autoYes: args.autoYes,\n skipCommit: args.skipCommit,\n createWorkspaceRemote: args.createWorkspaceRemote,\n repoVisibility: args.repoVisibility,\n repoOrg: args.repoOrg,\n flow: args.flow,\n aiSkip: args.aiSkip,\n gitnexusSkip: args.gitnexusSkip,\n });\n } catch (err) {\n sp.fail(\"Init workspace thất bại\");\n throw err;\n }\n}\n\n// Common scaffold step sau khi src/ + team-ai-pack đã add xong.\nexport async function finalizeWorkspaceScaffold(args: {\n workspacePath: string;\n workspaceName: string;\n teamOwner: string;\n description: string;\n packVersion: string;\n autoYes?: boolean;\n skipCommit?: boolean;\n createWorkspaceRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n flow: ProjectStatus;\n aiSkip?: boolean;\n gitnexusSkip?: boolean;\n}): Promise<void> {\n // Mode \"client\" được dùng trong scaffold-variable builder cũ — giữ giá trị\n // này để compatible với template hiện tại. Sẽ rename trong release sau.\n const vars = buildScaffoldVariables({\n projectName: args.workspaceName,\n projectDescription: args.description,\n teamOwner: args.teamOwner,\n packVersion: args.packVersion,\n mode: \"client\",\n });\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 appendGitignoreEntries(args.workspacePath);\n await ensureDir(join(args.workspacePath, \"notes\"));\n await ensureDir(join(args.workspacePath, \"scripts\"));\n\n await installGitHook(join(args.workspacePath, \".git\"), \"post-merge\");\n await installGitHook(join(args.workspacePath, \".git\", \"modules\", \"src\"), \"pre-push\");\n log.success(\"Cài post-merge (workspace) + pre-push (src/)\");\n\n // v1.6.2: Auto-sync pack content vào convention paths Claude Code scan.\n // Pack submodule đã add vào .claude/pack/ ở bước trước → giờ tạo symlinks\n // + merge hooks pack vào settings.json để Claude Code load skills/agents/commands/hooks.\n // Trước v1.6.2 user phải chạy thêm `avatar sync` thủ công → bug UX. Giờ auto.\n await autoSyncPackOnInit(args.workspacePath, args.autoYes);\n\n await appendAuditEntry(\"init\", `flow=${args.flow},workspace=${args.workspaceName}`);\n await maybeCommitWorkspace(args.workspacePath, args.skipCommit);\n await maybeCreateWorkspaceRemote(args);\n\n let aiResult: Awaited<ReturnType<typeof runAiSetupPhase>> | null = null;\n if (args.aiSkip) {\n log.dim(\"Bỏ qua AI setup (--ai-skip). Setup sau qua: avatar ai setup\");\n } else {\n aiResult = await runAiSetupPhase({ workspacePath: args.workspacePath });\n }\n\n // v1.4.0 Phase 10: GitNexus setup. Auto-skip nếu --ai-skip (logical chain —\n // wiki cần API key từ AI setup phase). Hoặc explicit --gitnexus-skip.\n let gitnexusResult: Awaited<ReturnType<typeof runGitnexusSetupPhase>> | null = null;\n const skipGitnexus = args.aiSkip || args.gitnexusSkip;\n if (skipGitnexus) {\n if (args.gitnexusSkip) {\n log.dim(\"Bỏ qua GitNexus setup (--gitnexus-skip). Setup sau: avatar gitnexus install\");\n } else {\n log.dim(\"Bỏ qua GitNexus setup (auto-skip do --ai-skip).\");\n }\n } else {\n gitnexusResult = await runGitnexusSetupPhase({ workspacePath: args.workspacePath });\n }\n\n // v1.4.0: Re-render CLAUDE.md với gitnexusReady=true nếu GitNexus install OK.\n // Lý do: writeRootClaudeMd chạy sớm trong scaffold (trước GitNexus phase) → cần re-write\n // để add section \"🧠 CODEBASE INTELLIGENCE\" conditional dựa vào kết quả.\n if (gitnexusResult?.ok) {\n const updatedVars = buildScaffoldVariables({\n projectName: args.workspaceName,\n projectDescription: args.description,\n teamOwner: args.teamOwner,\n packVersion: args.packVersion,\n mode: \"client\",\n gitnexusReady: true,\n });\n await writeRootClaudeMd(args.workspacePath, updatedVars);\n log.dim(\"Updated CLAUDE.md với GitNexus section\");\n }\n\n await printInitSuccessBox(args.workspacePath, args.flow, aiResult, gitnexusResult);\n}\n\n// v1.6.2: Auto-sync pack content vào convention paths Claude Code scan.\n// Trước v1.6.2 user phải chạy thêm `avatar sync` thủ công → bug UX nghiêm trọng:\n// /avatar:* commands + skills + agents từ pack KHÔNG load được dù pack đã clone.\n//\n// Flow:\n// 1. Skip nếu pack submodule chưa tồn tại (vd --skip-team-pack hoặc clone fail)\n// 2. syncAllMountDirs: tạo symlinks .claude/{skills,agents,commands,hooks,workflows,scripts,knowledge}\n// → .claude/pack/<dir>/\n// 3. mergePackSettingsIntoProjectSettings: merge pack/templates/settings.json.tpl\n// (hooks + statusLine) vào .claude/settings.json\n// 4. Fail-soft: lỗi không block init (workspace vẫn usable + user có thể rerun\n// `avatar sync` thủ công), chỉ log warn.\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 submodule không tồn tại (skip auto-sync). Có thể 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 merge)...\");\n\n try {\n // Step 1: Symlink farm.\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 (pack thiếu dir)` : \"\"}`,\n );\n\n // Step 2: Settings merge (hooks + statusLine từ pack template).\n const mergeResult = await mergePackSettingsIntoProjectSettings(workspacePath);\n switch (mergeResult.action) {\n case \"merged\":\n log.success(` ✓ settings.json merged (${mergeResult.changes.join(\"; \")})`);\n break;\n case \"no-change\":\n log.dim(\" - settings.json đã sync, không cần thay đổi.\");\n break;\n case \"no-pack-template\":\n log.dim(\" - Pack không có templates/settings.json.tpl, skip merge.\");\n break;\n }\n\n // Step 3: Default features (vd prompt-scoring). Hỏi y/n (default yes); CI → auto yes.\n // Merge hook feature SAU settings merge để hook feature append cạnh scout-block.\n await setupDefaultFeaturesOnInit(workspacePath, { autoYes });\n\n // Step 4: Auto-migrate stale plaintext secrets từ existing settings.json\n // sang .envrc (silent, no prompt). v1.20.0: AI setup phase (run-ai-setup-phase)\n // đã store key trực tiếp vào .envrc qua storeProviderKeyViaSecretsSubsystem.\n // Bước này chỉ catch user upgrade từ pre-v1.20.0 workspace có sẵn key\n // plaintext trong settings.json từ trước.\n try {\n const { detectPlaintextSecretsInSettings, migrateSecretsFromSettingsToEnvrc } = await import(\n \"../lib/migrate-anthropic-key-from-settings-json-to-envrc.js\"\n );\n const detect = await detectPlaintextSecretsInSettings(workspacePath);\n if (detect.keys.length > 0) {\n log.info(\n `Auto-migrating ${detect.keys.length} plaintext secrets từ settings.json → .envrc...`,\n );\n await migrateSecretsFromSettingsToEnvrc(workspacePath);\n }\n } catch (err) {\n log.warn(`Auto-migrate secrets fail: ${err instanceof Error ? err.message : err}.`);\n }\n } catch (err) {\n // Fail-soft: workspace vẫn usable, user rerun `avatar sync` thủ công nếu cần.\n log.warn(\n `Auto-sync pack fail: ${err instanceof Error ? err.message : err}. Chạy \\`avatar sync\\` thủ công để retry.`,\n );\n }\n}\n\n// Hỏi/tạo remote GitHub cho workspace root. Chỉ chạy được khi đã commit\n// (gh repo create --push cần ít nhất 1 commit). Nếu user skip commit hoặc\n// từ chối tạo, workspace local-only — vẫn dùng được nhưng không share team.\nexport async function maybeCreateWorkspaceRemote(args: {\n workspacePath: string;\n workspaceName: string;\n autoYes?: boolean;\n skipCommit?: boolean;\n createWorkspaceRemote?: boolean;\n repoVisibility?: string;\n repoOrg?: string;\n}): Promise<void> {\n // Skip nếu chưa có commit (gh repo create --push sẽ fail).\n if (args.skipCommit) {\n log.dim(\"Skip workspace remote (chưa commit). Setup sau qua: gh repo create ...\");\n return;\n }\n\n // Resolve từ flag hoặc prompt.\n let shouldCreate = args.createWorkspaceRemote;\n if (shouldCreate === undefined) {\n if (args.autoYes) return; // CI mode: default skip để không hỏi\n shouldCreate = await confirm({\n message: \"Tạo remote GitHub cho workspace để share team? (Avatar state)\",\n default: false,\n });\n }\n if (!shouldCreate) return;\n\n const visibility = ((args.repoVisibility as \"private\" | \"public\" | undefined) ??\n (args.autoYes\n ? \"private\"\n : await select({\n message: \"Workspace visibility?\",\n choices: [\n { name: \"private (mặc định, an toàn)\", value: \"private\" as const },\n { name: \"public\", value: \"public\" as const },\n ],\n }))) as \"private\" | \"public\";\n\n // Loop retry — user có thể switch gh account / sửa --repo-org / skip / abort.\n while (true) {\n try {\n await createWorkspaceRemoteViaGh({\n workspacePath: args.workspacePath,\n workspaceName: args.workspaceName,\n visibility,\n org: args.repoOrg,\n });\n return; // Success → exit loop.\n } catch (err) {\n // v1.2.10: Branch xử lý reason code structured.\n // \"repo-exists\" → offer extra option \"Dùng remote đã có\".\n if (err instanceof CreateWorkspaceRemoteError && err.reason === \"repo-exists\") {\n const fullName = err.fullName;\n const reuseAction = (await select({\n message: `Repo '${fullName}' đã tồn tại trên GitHub. Cách xử lý?`,\n choices: [\n {\n name: \"Dùng remote đã có (link workspace local vào repo này)\",\n value: \"reuse\" as const,\n },\n {\n name: \"Nhập tên workspace khác (tạo repo mới)\",\n value: \"rename\" as const,\n },\n { name: \"Bỏ qua (workspace local-only)\", value: \"skip\" as const },\n { name: \"Tạm ngưng init\", value: \"abort\" as const },\n ],\n })) as \"reuse\" | \"rename\" | \"skip\" | \"abort\";\n\n if (reuseAction === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước tạo workspace remote.\");\n }\n if (reuseAction === \"skip\") {\n log.warn(\"Workspace vẫn sẵn sàng local-only. Setup remote sau khi cần.\");\n return;\n }\n if (reuseAction === \"reuse\") {\n // Sync function (spawnSync internally) — không cần await dù codebase async-heavy.\n linkExistingRemoteToWorkspace({\n workspacePath: args.workspacePath,\n fullName,\n });\n return;\n }\n // rename → prompt new name, loop tiếp với name mới.\n const newName = await input({\n message: \"Tên workspace mới (sẽ tạo repo new):\",\n validate: (v) => (v.trim().length > 0 ? true : \"Tên không được rỗng\"),\n });\n args.workspaceName = newName.trim();\n continue; // Loop tiếp tạo với name mới.\n }\n\n // Reason khác → generic retry/skip menu.\n const action = await promptRetryOrSkip({\n taskName: \"Tạo workspace remote trên GitHub\",\n reason: err instanceof Error ? err.message : String(err),\n allowSkip: true, // Workspace remote OPTIONAL — skip OK, workspace local vẫn dùng được.\n hint: \"Tip: sai org? Pass --repo-org=<your-gh-user>. Hoặc switch gh: gh auth login.\",\n });\n if (action === \"abort\") {\n throw new UserAbortedRecoveryError(\"User abort tại bước tạo workspace remote.\");\n }\n if (action === \"skip\") {\n log.warn(\"Workspace vẫn sẵn sàng local-only. Setup remote sau khi cần.\");\n return;\n }\n // retry → loop tiếp.\n }\n }\n}\n","// Tạo remote GitHub cho workspace + push initial commit. Khác với\n// create-github-remote-from-folder (dành cho src/): hàm này dành cho workspace\n// root (đã commit Avatar state) và setup tracking branch.\n//\n// Workspace remote là OPTIONAL: chỉ cần khi team muốn share Avatar state\n// (knowledge, hooks, settings) qua git. Workspace local-only vẫn dùng được cho\n// 1 dev cá nhân, nhưng không sync được cross-machine.\n//\n// v1.2.3 fix Bug B: validate `org` vs gh authenticated user TRƯỚC khi spawn\n// `gh repo create`. Nếu org != ghUser AND user không phải member org → fail-fast\n// với hint actionable.\n//\n// v1.2.10: Pre-flight check \"repo đã tồn tại\" + capture stderr full + throw\n// structured error code để caller offer \"Dùng remote đã có\" option.\nimport { spawnSync } from \"node:child_process\";\nimport { ensureGitHubReady } from \"./git-auth-and-install-orchestrator.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 CreateWorkspaceRemoteInput {\n workspacePath: string;\n workspaceName: string;\n visibility: RepoVisibility;\n org?: string;\n}\n\nexport interface CreateWorkspaceRemoteOutput {\n sshUrl: string;\n httpsUrl: string;\n}\n\n// v1.2.10: Structured error với reason code để caller branch recovery menu.\nexport type WorkspaceRemoteFailReason =\n | \"repo-exists\" // Repo cùng tên đã có trên GitHub — offer \"dùng remote đã có\"\n | \"no-permission\" // gh user không quyền tạo dưới namespace\n | \"name-invalid\" // Name vi phạm GitHub naming rules\n | \"network\" // DNS / connectivity\n | \"unknown\";\n\nexport class CreateWorkspaceRemoteError extends Error {\n reason: WorkspaceRemoteFailReason;\n fullName: string;\n stderr?: string;\n constructor(\n reason: WorkspaceRemoteFailReason,\n fullName: string,\n message: string,\n stderr?: string,\n ) {\n super(message);\n this.name = \"CreateWorkspaceRemoteError\";\n this.reason = reason;\n this.fullName = fullName;\n this.stderr = stderr;\n }\n}\n\n// Classify gh repo create stderr → reason code chuẩn hóa.\nfunction classifyGhCreateError(stderr: string): WorkspaceRemoteFailReason {\n const text = stderr.toLowerCase();\n if (\n text.includes(\"name already exists\") ||\n text.includes(\"already exists on this account\") ||\n text.includes(\"repository already exists\")\n ) {\n return \"repo-exists\";\n }\n if (\n text.includes(\"403\") ||\n text.includes(\"permission\") ||\n text.includes(\"not authorized\") ||\n text.includes(\"forbidden\")\n ) {\n return \"no-permission\";\n }\n if (text.includes(\"invalid\") && text.includes(\"name\")) {\n return \"name-invalid\";\n }\n if (\n text.includes(\"could not resolve\") ||\n text.includes(\"network\") ||\n text.includes(\"connection refused\")\n ) {\n return \"network\";\n }\n return \"unknown\";\n}\n\n// Check repo có tồn tại trên GitHub không (silent — chỉ check status).\n// Trả về true nếu repo tồn tại (user có thể access).\nfunction repoExistsOnGitHub(fullName: string): boolean {\n const r = spawnSync(\"gh\", [\"repo\", \"view\", fullName, \"--json\", \"name\"], {\n stdio: \"ignore\",\n });\n return r.status === 0;\n}\n\n// Check user (gh-authenticated) có quyền tạo repo dưới namespace `org` không.\n// Org = chính ghUser → OK (personal repo).\n// Org != ghUser → check membership trong org đó (cần là member).\nfunction canCreateInNamespace(\n org: string,\n ghUser: string,\n): {\n ok: boolean;\n reason?: string;\n} {\n // Same identity → luôn OK (tạo repo personal).\n if (org.toLowerCase() === ghUser.toLowerCase()) return { ok: true };\n\n // Check user có là member của org không qua gh api.\n // GET /orgs/{org}/members/{username} → 204 = member, 404 = không.\n const r = spawnSync(\"gh\", [\"api\", `orgs/${org}/members/${ghUser}`, \"--silent\"], {\n stdio: \"ignore\",\n });\n if (r.status === 0) return { ok: true };\n\n // Check xem org có tồn tại không — phân biệt \"org không tồn tại\" vs \"user không là member\".\n const orgCheck = spawnSync(\"gh\", [\"api\", `orgs/${org}`, \"--silent\"], { stdio: \"ignore\" });\n if (orgCheck.status !== 0) {\n // Org không tồn tại — có thể là user account khác (vd \"xgirl2510-ops\" thực ra là user).\n const userCheck = spawnSync(\"gh\", [\"api\", `users/${org}`, \"--silent\"], { stdio: \"ignore\" });\n if (userCheck.status === 0) {\n return {\n ok: false,\n reason: `'${org}' là personal account khác (không phải org). Bạn (${ghUser}) không thể tạo repo dưới account của user khác. Pass --repo-org=${ghUser} hoặc switch gh: gh auth login`,\n };\n }\n return {\n ok: false,\n reason: `'${org}' không tồn tại trên GitHub. Check chính tả hoặc tạo org trước.`,\n };\n }\n\n // Org tồn tại nhưng user không là member.\n return {\n ok: false,\n reason: `'${ghUser}' không phải member của org '${org}'. Liên hệ admin org để được invite, hoặc pass --repo-org=${ghUser} tạo dưới personal account.`,\n };\n}\n\n// Idempotent: nếu workspace đã có remote `origin`, skip. Nếu repo trên GitHub\n// đã tồn tại nhưng workspace chưa có remote, thử add remote + push.\nexport async function createWorkspaceRemoteViaGh(\n input: CreateWorkspaceRemoteInput,\n): Promise<CreateWorkspaceRemoteOutput> {\n validateRepoName(input.workspaceName);\n validateRepoVisibility(input.visibility);\n\n await ensureGitHubReady();\n const ghUser = resolveGithubUsernameDefault();\n const org = input.org ?? ghUser;\n\n // Pre-flight validate namespace permission — fail-fast với hint clear.\n const namespaceCheck = canCreateInNamespace(org, ghUser);\n if (!namespaceCheck.ok) {\n throw new Error(`Không thể tạo repo dưới '${org}/': ${namespaceCheck.reason}`);\n }\n\n const fullName = `${org}/${input.workspaceName}`;\n\n // v1.2.10: Pre-flight check repo đã tồn tại chưa — detect sớm trước khi\n // spawn gh repo create. Tránh fail mơ hồ với stderr không capture được.\n if (repoExistsOnGitHub(fullName)) {\n throw new CreateWorkspaceRemoteError(\n \"repo-exists\",\n fullName,\n `Repo '${fullName}' đã tồn tại trên GitHub. Có thể bạn đã init workspace này trước đó.`,\n );\n }\n\n log.info(`Tạo GitHub repo cho workspace: ${fullName} (${input.visibility})...`);\n\n // gh repo create --source=workspace --remote=origin --push tạo repo + add\n // remote `origin` + push branch hiện tại (main). Auto-track origin/main.\n // v1.2.10: Capture cả stdout + stderr để dump khi fail (trước đây stdio:inherit\n // nuốt context, user không biết lý do).\n const r = spawnSync(\n \"gh\",\n [\n \"repo\",\n \"create\",\n fullName,\n `--${input.visibility}`,\n \"--source\",\n input.workspacePath,\n \"--remote\",\n \"origin\",\n \"--push\",\n ],\n { stdio: [\"ignore\", \"pipe\", \"pipe\"], encoding: \"utf8\" },\n );\n\n if (r.status !== 0) {\n const stderr = (r.stderr || \"\").trim();\n const stdout = (r.stdout || \"\").trim();\n const combined = [stderr, stdout].filter(Boolean).join(\"\\n\");\n const reason = classifyGhCreateError(combined);\n\n // In stderr ra terminal để user thấy raw error từ gh CLI.\n if (combined) {\n process.stderr.write(`\\n${combined}\\n\\n`);\n }\n\n throw new CreateWorkspaceRemoteError(\n reason,\n fullName,\n `Tạo workspace remote thất bại (exit ${r.status}): ${reason}.`,\n combined,\n );\n }\n\n const sshUrl = `git@github.com:${fullName}.git`;\n const httpsUrl = `https://github.com/${fullName}.git`;\n log.success(`Workspace remote: ${sshUrl}`);\n return { sshUrl, httpsUrl };\n}\n\n// v1.2.10: Helper hook existing remote vào workspace local (không tạo mới).\n// Dùng khi user chọn \"Dùng remote đã có\" trong recovery menu.\nexport function linkExistingRemoteToWorkspace(args: {\n workspacePath: string;\n fullName: string;\n}): CreateWorkspaceRemoteOutput {\n const sshUrl = `git@github.com:${args.fullName}.git`;\n const httpsUrl = `https://github.com/${args.fullName}.git`;\n\n // Add remote origin (idempotent — nếu đã có thì set-url).\n const addResult = spawnSync(\n \"git\",\n [\"-C\", args.workspacePath, \"remote\", \"add\", \"origin\", sshUrl],\n {\n encoding: \"utf8\",\n stdio: [\"ignore\", \"pipe\", \"pipe\"],\n },\n );\n if (addResult.status !== 0) {\n // Có thể remote origin đã tồn tại — set-url override.\n spawnSync(\"git\", [\"-C\", args.workspacePath, \"remote\", \"set-url\", \"origin\", sshUrl], {\n stdio: \"ignore\",\n });\n }\n log.success(`Linked existing remote: ${sshUrl}`);\n return { sshUrl, httpsUrl };\n}\n","// setup-default-features-on-init.ts\n//\n// Bước init: hỏi user có bật các default feature (đọc từ pack features/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 <feature> (y/n)\" default yes mỗi feature.\n// - Non-interactive (autoYes / CI): tự động yes (giữ default bật), không hỏi.\n// - Fail-mềm: pack chưa có features/ → defaults rỗng → no-op im lặng.\n\nimport { confirm } from \"@inquirer/prompts\";\nimport { readDefaultFeatures } from \"./discover-pack-features-and-defaults.js\";\nimport { enableFeatureByName } from \"./feature-enable-disable-orchestrator.js\";\nimport { log } from \"./terminal-logger.js\";\n\n// Tên hiển thị thân thiện cho prompt (fallback = tên feature).\nconst DISPLAY_NAME: Record<string, string> = {\n \"prompt-scoring\": \"prompt scoring\",\n};\n\nexport async function setupDefaultFeaturesOnInit(\n workspacePath: string,\n opts: { autoYes?: boolean } = {},\n): Promise<void> {\n const defaults = await readDefaultFeatures(workspacePath);\n if (defaults.length === 0) {\n return; // pack chưa có features/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 feature '${name}' (user opt-out).`);\n continue;\n }\n // silent: feature default có thể chưa có trong pack cũ → không spam warning.\n await enableFeatureByName(workspacePath, name, { silent: true });\n }\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","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","// 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","// 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 <subcommand>` — group lệnh quản lý team-ai-pack submodule.\n// Subcommand đầu tiên: `status` — hiển thị tag đang pin + tag mới nhất từ remote\n// để member biết có cần `avatar sync` không.\nimport { join } from \"node:path\";\nimport boxen from \"boxen\";\nimport type { Command } from \"commander\";\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport { git, isGitRepo, listTags } from \"../lib/git-operations.js\";\nimport { pickLatestStableSemVerTag } from \"../lib/pick-latest-stable-semver-tag.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 submodule (status, ...)\");\n\n pack\n .command(\"status\")\n .description(\"Hiển thị tag pack đang pin (default offline; --fetch để check remote)\")\n .option(\"--json\", \"Output JSON cho script\")\n // v1.13.0 perf: Default OFFLINE. `git fetch --tags origin` tốn 1-5s trên mạng VN.\n // `avatar pack status` được gọi thường xuyên (mỗi terminal mở repo) → không nên\n // block 5s mặc định. User opt-in bằng `--fetch` khi muốn so sánh với remote.\n .option(\"--fetch\", \"Fetch remote tags trước khi so sánh (chậm thêm 1-5s)\")\n .action(async (opts: { json?: boolean; fetch?: boolean }) => {\n try {\n const snap = await gatherPackStatus(process.cwd(), opts.fetch === true);\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 currentTag: string | null; // null khi chưa cài\n latestTag: string | null; // null khi không fetch được hoặc không có tag\n upToDate: boolean | null; // null khi chưa biết (không fetch)\n fetched: boolean;\n}\n\nasync function gatherPackStatus(cwd: string, doFetch: boolean): Promise<PackStatus> {\n const packDir = join(cwd, PACK_RELATIVE_PATH);\n\n if (!(await pathExists(packDir)) || !(await isGitRepo(packDir))) {\n return {\n installed: false,\n currentTag: null,\n latestTag: null,\n upToDate: null,\n fetched: false,\n };\n }\n\n const currentTag = await readPinnedPackVersion(cwd).catch(() => null);\n\n let fetched = false;\n if (doFetch) {\n // Fetch tags từ origin để biết tag mới nhất. Silent fail nếu offline.\n try {\n await git(packDir).fetch([\"--tags\", \"origin\"]);\n fetched = true;\n } catch {\n fetched = false;\n }\n }\n\n const allTags = await listTags(packDir).catch(() => [] as string[]);\n const latestTag = pickLatestStableSemVerTag(allTags);\n\n const upToDate = currentTag && latestTag ? currentTag === latestTag : null;\n\n return {\n installed: true,\n currentTag,\n latestTag,\n upToDate,\n fetched,\n };\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.currentTag ?? chalk.yellow(\"(unknown)\");\n const latest = s.latestTag ?? chalk.dim(s.fetched ? \"(no tags)\" : \"(không fetch)\");\n\n let verdict: string;\n if (s.upToDate === true) {\n verdict = chalk.green(\"✓ Đang dùng tag mới nhất\");\n } else if (s.upToDate === false) {\n verdict = `${chalk.yellow(\"⚠ Có version mới\")} ${chalk.dim(\"→ avatar sync\")}`;\n } else {\n verdict = chalk.dim(\"(không so sánh được)\");\n }\n\n const lines = [\n `${chalk.bold(\"team-ai-pack status\")}`,\n \"─\".repeat(48),\n `${chalk.dim(\"Tag hiện tại:\")} ${current}`,\n `${chalk.dim(\"Tag mới nhất:\")} ${latest}`,\n `${chalk.dim(\"Trạng thái:\")} ${verdict}`,\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 secrets` — per-project secrets via direnv .envrc (v1.19.0).\n//\n// Subcommands:\n// setup Interactive wizard: detect/install direnv → migrate from\n// settings.json → prompt new key if needed\n// set <NAME> Set/update one secret (interactive hidden prompt)\n// get <NAME> Print secret value to stdout (masked on TTY, raw when piped)\n// list List Avatar-managed secret names (values masked)\n// rm <NAME> Remove one secret\n// check Health: .envrc exists? direnv installed? hook in zshrc?\n// Anthropic key validates?\n// migrate Explicit: move secrets from settings.json → .envrc\n//\n// Storage: .envrc Avatar-managed block (chmod 600, gitignored).\n// Original M09 spec used OS keychain; v1.19.0 pivot to direnv per-project.\nimport { confirm, password } from \"@inquirer/prompts\";\nimport type { Command } from \"commander\";\nimport { detectDirenvStatus } from \"../lib/detect-direnv-installation-status.js\";\nimport {\n ensureDirenvShellHookInZshrc,\n installDirenv,\n} from \"../lib/install-direnv-via-package-manager.js\";\nimport {\n detectPlaintextSecretsInSettings,\n migrateSecretsFromSettingsToEnvrc,\n} from \"../lib/migrate-anthropic-key-from-settings-json-to-envrc.js\";\nimport { resolveAvatarWorkspaceRootFromCwd } from \"../lib/resolve-avatar-workspace-root-from-cwd.js\";\nimport {\n fetchAnthropicModels,\n maskAnthropicKey,\n} from \"../lib/setup-anthropic-api-key-and-model.js\";\nimport { log } from \"../lib/terminal-logger.js\";\nimport {\n envrcPath,\n readAvatarEnvrcVars,\n writeEnvrcWithSecrets,\n} from \"../lib/write-envrc-with-per-project-secrets.js\";\n\nasync function resolveWorkspaceOrExit(): Promise<string> {\n const root = resolveAvatarWorkspaceRootFromCwd(process.cwd());\n if (!root) {\n log.error(\"Không tìm thấy Avatar workspace. Chạy 'avatar init' trước, hoặc cd vào workspace.\");\n process.exit(1);\n }\n return root;\n}\n\nfunction maskValue(value: string): string {\n if (value.length <= 8) return \"****\";\n return `${value.slice(0, 4)}...${value.slice(-4)}`;\n}\n\n// ---------------------------------------------------------------------------\n// secrets setup — interactive wizard\n// ---------------------------------------------------------------------------\n\nexport async function runSecretsSetupWizard(workspace: string): Promise<void> {\n log.info(`Workspace: ${workspace}`);\n\n // 1. direnv detection\n const direnv = await detectDirenvStatus();\n if (!direnv.installed) {\n log.warn(\"direnv chưa cài. Avatar secrets dùng direnv để auto-load .envrc khi cd vào project.\");\n const wantInstall = await confirm({\n message: \"Cài direnv ngay? (qua brew/apt/dnf/pacman tùy OS)\",\n default: true,\n });\n if (wantInstall) {\n log.info(\"Đang cài direnv...\");\n const result = await installDirenv();\n if (result.success) {\n log.success(`✓ ${result.message}`);\n } else {\n log.warn(`! ${result.message}`);\n log.info(`Manual install: ${result.manualInstructionsUrl}`);\n log.info(\"Tiếp tục — .envrc sẽ ghi nhưng cần direnv để auto-load.\");\n }\n }\n } else {\n log.success(`✓ direnv ${direnv.version ?? \"(installed)\"}`);\n }\n\n // 2. Shell hook\n if (!direnv.shellHookInZshrc) {\n log.warn(\"~/.zshrc chưa có direnv shell hook → .envrc sẽ không tự load.\");\n const wantHook = await confirm({\n message: 'Add eval \"$(direnv hook zsh)\" vào ~/.zshrc?',\n default: true,\n });\n if (wantHook) {\n const r = ensureDirenvShellHookInZshrc();\n if (r.modified) {\n log.success(`✓ Added direnv hook to ${r.zshrcPath}`);\n log.info(\"Reload shell: 'source ~/.zshrc' hoặc mở terminal mới\");\n } else {\n log.dim(\"Skip — hook đã tồn tại hoặc không write được\");\n }\n }\n } else {\n log.dim(\"✓ direnv shell hook đã trong ~/.zshrc\");\n }\n\n // 3. Migrate plaintext secrets from settings.json\n const detect = await detectPlaintextSecretsInSettings(workspace);\n if (detect.keys.length > 0) {\n log.warn(\n `Phát hiện ${detect.keys.length} secret plaintext trong .claude/settings.json: ${detect.keys.join(\", \")}`,\n );\n log.warn(\"Risk: leak nếu file commit lên git.\");\n const wantMigrate = await confirm({\n message: \"Migrate sang .envrc (direnv) ngay?\",\n default: true,\n });\n if (wantMigrate) {\n const r = await migrateSecretsFromSettingsToEnvrc(workspace);\n log.success(`✓ Migrated ${r.migratedKeys.length} secrets: ${r.migratedKeys.join(\", \")}`);\n if (r.settingsJsonBackupPath) {\n log.dim(` settings.json backup: ${r.settingsJsonBackupPath}`);\n }\n }\n }\n\n // 4. Anthropic key prompt nếu chưa có\n const existingVars = readAvatarEnvrcVars(workspace);\n if (!existingVars.has(\"ANTHROPIC_API_KEY\")) {\n const wantPrompt = await confirm({\n message: \"Set ANTHROPIC_API_KEY cho project này ngay?\",\n default: true,\n });\n if (wantPrompt) {\n await setOneSecret(workspace, \"ANTHROPIC_API_KEY\", { validateAnthropic: true });\n }\n }\n\n log.success(\"✓ Secrets setup done.\");\n log.dim(` .envrc: ${envrcPath(workspace)}`);\n log.dim(\" View: avatar secrets list\");\n}\n\nasync function setupAction(): Promise<void> {\n const workspace = await resolveWorkspaceOrExit();\n await runSecretsSetupWizard(workspace);\n}\n\n// ---------------------------------------------------------------------------\n// secrets set <NAME>\n// ---------------------------------------------------------------------------\n\nasync function setOneSecret(\n workspace: string,\n name: string,\n opts: { validateAnthropic?: boolean } = {},\n): Promise<void> {\n const value = await password({\n message: `Nhập value cho ${name} (input ẩn):`,\n mask: \"*\",\n validate: (v) => (v.trim().length > 0 ? true : \"Value bắt buộc\"),\n });\n\n if (opts.validateAnthropic && name === \"ANTHROPIC_API_KEY\") {\n log.info(`Verify key (${maskAnthropicKey(value)}) qua Anthropic /v1/models...`);\n try {\n const models = await fetchAnthropicModels(value);\n log.success(`✓ Key valid (${models.length} models available)`);\n } catch (err) {\n log.error(`Key không valid: ${err instanceof Error ? err.message : err}`);\n const proceed = await confirm({\n message: \"Vẫn ghi key (bỏ qua validation)?\",\n default: false,\n });\n if (!proceed) return;\n }\n }\n\n const r = await writeEnvrcWithSecrets(workspace, { [name]: value });\n log.success(`✓ Set ${name} (.envrc: ${r.varsBefore} → ${r.varsAfter} vars)`);\n if (r.direnvAllowOk === true) {\n log.dim(\" direnv allow ok — sẽ auto-load lần cd kế tiếp\");\n } else if (r.direnvAllowOk === null) {\n log.warn(\n \" direnv missing → file ghi OK nhưng không auto-load. Cài direnv hoặc source thủ công.\",\n );\n } else {\n log.warn(\" direnv allow failed — chạy 'direnv allow' thủ công trong workspace\");\n }\n}\n\nasync function setAction(name: string): Promise<void> {\n const workspace = await resolveWorkspaceOrExit();\n await setOneSecret(workspace, name, {\n validateAnthropic: name === \"ANTHROPIC_API_KEY\",\n });\n}\n\n// ---------------------------------------------------------------------------\n// secrets get <NAME>\n// ---------------------------------------------------------------------------\n\nasync function getAction(name: string): Promise<void> {\n const workspace = await resolveWorkspaceOrExit();\n const vars = readAvatarEnvrcVars(workspace);\n const value = vars.get(name);\n if (value === undefined) {\n log.error(`Secret '${name}' không tồn tại trong .envrc.`);\n process.exit(1);\n }\n if (process.stdout.isTTY) {\n console.log(maskValue(value));\n log.dim(`(masked. Để in raw: 'avatar secrets get ${name} | cat')`);\n } else {\n console.log(value);\n }\n}\n\n// ---------------------------------------------------------------------------\n// secrets list\n// ---------------------------------------------------------------------------\n\nasync function listAction(): Promise<void> {\n const workspace = await resolveWorkspaceOrExit();\n const vars = readAvatarEnvrcVars(workspace);\n if (vars.size === 0) {\n log.info(\"Chưa có secret nào trong .envrc Avatar block.\");\n log.dim(\"Set: avatar secrets set <NAME>\");\n return;\n }\n log.info(`${vars.size} secrets in ${envrcPath(workspace)}:`);\n for (const [key, value] of [...vars.entries()].sort()) {\n console.log(` ${key.padEnd(30)} = ${maskValue(value)}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// secrets rm <NAME>\n// ---------------------------------------------------------------------------\n\nasync function rmAction(name: string): Promise<void> {\n const workspace = await resolveWorkspaceOrExit();\n const vars = readAvatarEnvrcVars(workspace);\n if (!vars.has(name)) {\n log.warn(`Secret '${name}' không tồn tại — no-op.`);\n return;\n }\n const ok = await confirm({\n message: `Xóa secret '${name}' khỏi .envrc?`,\n default: false,\n });\n if (!ok) {\n log.dim(\"Hủy.\");\n return;\n }\n const r = await writeEnvrcWithSecrets(workspace, { [name]: null });\n log.success(`✓ Removed ${name} (.envrc: ${r.varsBefore} → ${r.varsAfter} vars)`);\n}\n\n// ---------------------------------------------------------------------------\n// secrets check\n// ---------------------------------------------------------------------------\n\nasync function checkAction(): Promise<void> {\n const workspace = await resolveWorkspaceOrExit();\n log.info(`Workspace: ${workspace}\\n`);\n\n const direnv = await detectDirenvStatus();\n log.info(`direnv installed: ${direnv.installed ? `✓ ${direnv.version ?? \"\"}` : \"✗\"}`);\n log.info(`direnv shell hook (~/.zshrc): ${direnv.shellHookInZshrc ? \"✓\" : \"✗\"}`);\n\n const path = envrcPath(workspace);\n const vars = readAvatarEnvrcVars(workspace);\n log.info(\n `.envrc: ${vars.size > 0 ? `✓ ${vars.size} secrets at ${path}` : \"✗ (no Avatar block)\"}`,\n );\n\n const detect = await detectPlaintextSecretsInSettings(workspace);\n if (detect.keys.length > 0) {\n log.warn(\n `⚠ Plaintext secrets in settings.json: ${detect.keys.join(\", \")} — run 'avatar secrets migrate'`,\n );\n }\n\n const anthropicKey = vars.get(\"ANTHROPIC_API_KEY\");\n if (anthropicKey) {\n log.info(`Verifying ANTHROPIC_API_KEY (${maskAnthropicKey(anthropicKey)})...`);\n try {\n const models = await fetchAnthropicModels(anthropicKey);\n log.success(`✓ Anthropic key valid (${models.length} models)`);\n } catch (err) {\n log.error(`✗ Anthropic key invalid: ${err instanceof Error ? err.message : err}`);\n }\n }\n}\n\n// ---------------------------------------------------------------------------\n// secrets migrate\n// ---------------------------------------------------------------------------\n\nasync function migrateAction(): Promise<void> {\n const workspace = await resolveWorkspaceOrExit();\n const detect = await detectPlaintextSecretsInSettings(workspace);\n if (detect.keys.length === 0) {\n log.info(\"Không có plaintext secret trong settings.json — nothing to migrate.\");\n return;\n }\n log.warn(`Migrating ${detect.keys.length} secrets: ${detect.keys.join(\", \")}`);\n const r = await migrateSecretsFromSettingsToEnvrc(workspace);\n log.success(`✓ Migrated ${r.migratedKeys.length} secrets to .envrc`);\n if (r.settingsJsonBackupPath) {\n log.dim(` Backup: ${r.settingsJsonBackupPath}`);\n }\n}\n\n// ---------------------------------------------------------------------------\n// Command registration\n// ---------------------------------------------------------------------------\n\nexport function registerSecretsCommand(program: Command): void {\n const secrets = program\n .command(\"secrets\")\n .description(\"Quản lý per-project secrets via direnv .envrc (v1.19.0)\");\n\n const wrap = (fn: () => Promise<void>) => async () => {\n try {\n await fn();\n } catch (err) {\n log.error(`secrets failed: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n };\n\n const wrapName = (fn: (name: string) => Promise<void>) => async (name: string) => {\n try {\n await fn(name);\n } catch (err) {\n log.error(`secrets failed: ${err instanceof Error ? err.message : err}`);\n process.exit(1);\n }\n };\n\n secrets\n .command(\"setup\")\n .description(\"Interactive wizard: install direnv + migrate + prompt new key\")\n .action(wrap(setupAction));\n\n secrets\n .command(\"set <NAME>\")\n .description(\"Set/update one secret in .envrc (hidden input prompt)\")\n .action(wrapName(setAction));\n\n secrets\n .command(\"get <NAME>\")\n .description(\"Print one secret value (masked on TTY, raw when piped)\")\n .action(wrapName(getAction));\n\n secrets\n .command(\"list\")\n .description(\"List Avatar-managed secret names (values masked)\")\n .action(wrap(listAction));\n\n secrets\n .command(\"rm <NAME>\")\n .description(\"Remove one secret from .envrc\")\n .action(wrapName(rmAction));\n\n secrets\n .command(\"check\")\n .description(\"Health: direnv installed, .envrc exists, Anthropic key validates\")\n .action(wrap(checkAction));\n\n secrets\n .command(\"migrate\")\n .description(\"Migrate plaintext secrets from settings.json → .envrc\")\n .action(wrap(migrateAction));\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 { discoverEnabledAndAvailableFeatures } from \"../lib/discover-pack-features-and-defaults.js\";\nimport { pathExists, readText } from \"../lib/filesystem-helpers.js\";\nimport { isGitRepo } from \"../lib/git-operations.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 featuresEnabled: string[];\n featuresAvailableCount: 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 featuresEnabled: [],\n featuresAvailableCount: 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, features] = await Promise.all([\n (async (): Promise<string | null> => {\n const isPackRepo = await isGitRepo(join(claudeRoot, \"pack\"));\n if (!isPackRepo) 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 discoverEnabledAndAvailableFeatures(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 featuresEnabled: features.enabled,\n featuresAvailableCount: features.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 formatFeatures(s: StatusSnapshot): string {\n if (s.featuresEnabled.length > 0) {\n return `${s.featuresEnabled.join(\", \")} (${s.featuresEnabled.length}/${s.featuresAvailableCount} available)`;\n }\n if (s.featuresAvailableCount > 0) {\n return `none enabled (${s.featuresAvailableCount} available — avatar feature 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(\"Features:\")} ${formatFeatures(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 [--force] [--version <tag>] [--dry-run]` — Milestone 08.\n//\n// Pulls latest team-ai-pack into .claude/pack/ and creates symlink farm so\n// Claude Code sees skills/agents/commands/hooks/workflows/scripts at\n// .claude/<dir> by symlink to .claude/pack/<dir>.\n//\n// Flow:\n// 1. Locate project root (cwd with .claude/pack/ submodule)\n// 2. git fetch --tags in submodule\n// 3. Determine target tag (--version or latest)\n// 4. --dry-run? show preview and exit\n// 5. checkout target tag in submodule\n// 6. syncAllMountDirs() → create/update symlinks\n// 7. Report results, warn on conflicts\nimport { join } from \"node:path\";\n\nimport type { Command } from \"commander\";\n\nimport { pathExists } from \"../lib/filesystem-helpers.js\";\nimport {\n checkoutBranchHeadInSubmodule,\n checkoutTagInSubmodule,\n currentCommitSha,\n git,\n listTags,\n} from \"../lib/git-operations.js\";\nimport { mergePackSettingsIntoProjectSettings } from \"../lib/merge-pack-settings-into-project-settings.js\";\nimport { pickLatestStableSemVerTag } from \"../lib/pick-latest-stable-semver-tag.js\";\nimport { buildSyncPreview } from \"../lib/preview-team-pack-sync-changes-for-dry-run.js\";\nimport { reapplyEnabledFeaturesAfterSync } from \"../lib/reapply-enabled-features-after-sync.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\";\n\ninterface SyncOptions {\n force?: boolean;\n version?: string;\n dryRun?: boolean;\n latest?: boolean; // v1.8.0: pull HEAD của main branch thay vì tag SemVer\n}\n\n// v1.8.0: Default branch để follow khi --latest. Hardcode \"main\" vì team-ai-pack\n// dùng main. Future: có thể đọc từ .ck.json nếu cần.\nconst DEFAULT_PACK_BRANCH = \"main\";\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(packDir))) {\n log.error(\n `team-ai-pack submodule chưa được khởi tạo ở ${TEAM_PACK_RELATIVE_PATH}/.\\n Chạy 'avatar init' để add submodule, hoặc 'git submodule update --init' nếu đã clone repo.`,\n );\n process.exit(1);\n }\n\n // Fetch latest tags from origin before resolving target version\n try {\n await git(packDir).fetch([\"--tags\", \"origin\"]);\n } catch (err) {\n log.warn(\n `Không fetch được tags từ origin (${err instanceof Error ? err.message : err}). Sẽ dùng tag local hiện có.`,\n );\n }\n\n // v1.8.0: --latest mode → pull HEAD của main branch (bleeding-edge).\n // Bypass SemVer tag picker. Conflict với --version → ưu tiên --version (explicit).\n const useLatestMode = opts.latest === true && !opts.version;\n\n // v1.7.1: Pick latest tag bằng SemVer-aware parser thay vì alphabetical sort.\n // - Skip pre-release tags (v0.5.0-rc1, -beta) → stable only mặc định\n // - Handle v0.10.0 > v0.4.0 đúng (alphabetical sort sai)\n // User muốn pin pre-release → pass --version explicit.\n const allTags = await listTags(packDir);\n let targetVersion: string;\n if (useLatestMode) {\n targetVersion = `${DEFAULT_PACK_BRANCH} (HEAD)`;\n } else {\n const picked = opts.version ?? pickLatestStableSemVerTag(allTags);\n if (!picked) {\n log.error(\n `Không tìm thấy stable SemVer tag (vMAJOR.MINOR.PATCH) trong team-ai-pack submodule.\\n Tags hiện có: ${allTags.length > 0 ? allTags.join(\", \") : \"(none)\"}\\n Pass --version <tag> rõ ràng, hoặc dùng --latest để pull HEAD branch ${DEFAULT_PACK_BRANCH}.`,\n );\n process.exit(1);\n }\n targetVersion = picked;\n }\n\n if (opts.dryRun) {\n const preview = await buildSyncPreview(packDir, claudeDir, targetVersion);\n log.info(`Pack version hiện tại: ${preview.currentVersion}`);\n log.info(`Target version: ${preview.targetVersion}`);\n if (preview.commitsBehind.length === 0) {\n log.info(\"Đã ở target version, không có commit mới.\");\n } else {\n log.info(`Commits cần pull (${preview.commitsBehind.length}):`);\n for (const c of preview.commitsBehind.slice(0, 20)) {\n console.log(` ${c}`);\n }\n if (preview.commitsBehind.length > 20) {\n console.log(` ... và ${preview.commitsBehind.length - 20} commits khác`);\n }\n }\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 thay đổi. Bỏ --dry-run để thực thi.\");\n return;\n }\n\n // Apply: checkout target (tag hoặc branch HEAD) then create symlinks.\n // v1.8.0: --latest → checkout HEAD của main branch (bleeding-edge mode).\n if (useLatestMode) {\n log.info(`Pulling HEAD của branch ${DEFAULT_PACK_BRANCH} (bleeding-edge mode)...`);\n await checkoutBranchHeadInSubmodule(TEAM_PACK_RELATIVE_PATH, DEFAULT_PACK_BRANCH, projectRoot);\n const sha = await currentCommitSha(packDir);\n log.dim(` HEAD = ${sha.slice(0, 7)}`);\n log.warn(\n \"⚠ --latest mode: workspace pin vào commit floating, không reproducible. \" +\n \"Chuyển về tag stable: avatar sync (no flag).\",\n );\n } else {\n log.info(`Checking out ${targetVersion} trong submodule...`);\n await checkoutTagInSubmodule(TEAM_PACK_RELATIVE_PATH, targetVersion, projectRoot);\n }\n\n log.info(\"Creating symlink farm...\");\n const results = await syncAllMountDirs(packDir, claudeDir, opts.force === true);\n\n reportResults(results, opts.force === true);\n\n // Merge pack settings.json template vào project settings.json để activate hooks/statusLine.\n // Fail-soft: lỗi merger không block sync (symlinks đã xong). Log warn để user biết.\n log.info(\"Merging pack settings.json template into project settings.json...\");\n try {\n const mergeResult = await mergePackSettingsIntoProjectSettings(projectRoot);\n switch (mergeResult.action) {\n case \"merged\":\n log.success(\n ` ✓ settings.json merged (${mergeResult.changes.join(\"; \")}). Backup: ${mergeResult.backupPath ?? \"n/a\"}`,\n );\n break;\n case \"no-change\":\n log.info(\" - settings.json đã sync với pack, không có thay đổi.\");\n break;\n case \"no-pack-template\":\n log.dim(\" - Pack không có templates/settings.json.tpl, skip merge.\");\n break;\n }\n } catch (err) {\n log.warn(\n ` ! Merge settings.json fail: ${err instanceof Error ? err.message : err}. Symlinks đã tạo OK, hooks có thể chưa active. Manual merge nếu cần.`,\n );\n }\n\n // Re-apply feature đang bật (vd prompt-scoring) — merge tpl ở trên có thể đã\n // ghi đè settings → hook feature mất nếu không re-apply. Fail-soft.\n try {\n await reapplyEnabledFeaturesAfterSync(projectRoot);\n } catch (err) {\n log.warn(\n ` ! Re-apply features fail: ${err instanceof Error ? err.message : err}. Chạy 'avatar feature enable <name>' thủ công nếu cần.`,\n );\n }\n\n log.success(`Synced team-ai-pack to ${targetVersion}.`);\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(\n ` ! ${r.dir} → CONFLICT: existing real dir. ` +\n `Dùng --force để backup + override (sẽ giữ data ở ${r.dir}.backup-<timestamp>/).`,\n );\n break;\n }\n }\n const conflicts = results.filter((r) => r.action === \"skipped-conflict\").length;\n if (conflicts > 0 && !force) {\n log.warn(\n `${conflicts} mount dir(s) bị skip do conflict. Chạy lại với --force nếu muốn override (backup tự động).`,\n );\n }\n}\n\nexport function registerSyncCommand(program: Command): void {\n program\n .command(\"sync\")\n .description(\"Pull team-ai-pack mới nhất + tạo symlink farm vào .claude/\")\n .option(\"--force\", \"Override .claude/<dir>/ nếu là real dir (backup trước)\")\n .option(\"--version <tag>\", \"Pin vào version cụ thể (vd: v0.2.0)\")\n .option(\"--latest\", \"Bleeding-edge: pull HEAD của main branch (bỏ qua tag SemVer)\")\n .option(\"--dry-run\", \"Hiển thị preview, không apply thay đổi\")\n .action(syncAction);\n}\n","// Preview what `avatar sync` would do, without modifying filesystem.\n//\n// Used by --dry-run flag. Compares current pinned version vs target version,\n// lists changed files via git log, reports which mount dirs need linking.\nimport { join } from \"node:path\";\n\nimport { pathExists } from \"./filesystem-helpers.js\";\nimport { currentCommitSha, git, listTags, tagAtHead } from \"./git-operations.js\";\nimport { pickLatestStableSemVerTag } from \"./pick-latest-stable-semver-tag.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 commitsBehind: 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 // Cannot use fs.lstat result easily here without import; rely on simple check\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\n// Compute the diff between current pinned version and target. Returns one-line\n// commit summaries (subject only), empty array if already at target.\nasync function listCommitsBetween(\n packDir: string,\n fromSha: string,\n toRef: string,\n): Promise<string[]> {\n try {\n const result = await git(packDir).raw([\"log\", \"--oneline\", `${fromSha}..${toRef}`]);\n return result\n .split(\"\\n\")\n .map((l) => l.trim())\n .filter((l) => l.length > 0);\n } catch {\n return [];\n }\n}\n\nexport async function buildSyncPreview(\n packDir: string,\n claudeDir: string,\n targetVersion?: string,\n): Promise<SyncPreview> {\n // v1.7.1: currentVersion ưu tiên tag tại HEAD, fallback SHA. targetVersion\n // dùng SemVer-aware picker (skip pre-release, sort numeric).\n const currentTagOrNull = await tagAtHead(packDir);\n const currentSha = await currentCommitSha(packDir);\n const currentVersion = currentTagOrNull ?? currentSha.slice(0, 7);\n\n const allTags = await listTags(packDir);\n const target = targetVersion ?? pickLatestStableSemVerTag(allTags) ?? \"HEAD\";\n\n const commits = await listCommitsBetween(packDir, currentSha, target);\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 commitsBehind: commits,\n mountDirStatuses: mountStatuses,\n };\n}\n","// reapply-enabled-features-after-sync.ts\n//\n// Sau khi sync merge lại settings.json.tpl (có thể đổi/ghi đè settings), re-apply\n// mọi feature đang enabled=true để hook feature KHÔNG bị mất. Không có bước này,\n// feature \"biến mất\" sau mỗi `avatar sync`.\n//\n// Idempotent: enable lại không nhân đôi entry (dedupe trong enableFeature lo).\n// Fail-mềm: feature trong state nhưng pack đã bỏ manifest → enableFeatureByName\n// trả false (warn), không crash sync.\n\nimport { enableFeatureByName } from \"./feature-enable-disable-orchestrator.js\";\nimport { listEnabledFeatures } from \"./feature-state-store.js\";\nimport { log } from \"./terminal-logger.js\";\n\nexport async function reapplyEnabledFeaturesAfterSync(workspacePath: string): Promise<void> {\n const enabled = await listEnabledFeatures(workspacePath);\n if (enabled.length === 0) {\n return;\n }\n log.info(`Re-apply ${enabled.length} feature(s) đang bật vào settings.json...`);\n for (const name of enabled) {\n await enableFeatureByName(workspacePath, name);\n }\n}\n","// `avatar tools {list,install,remove}` — Commands 10/11/12 spec (Chapter 12 v4).\n// Lifecycle management for system dependencies (git, gh, node, uv, docker) and\n// MCP servers (gitnexus, context7, serena, github, filesystem, playwright).\n// Registry source: team-ai-pack/tools/registry.yaml.\nimport type { Command } from \"commander\";\nimport { notImplementedYet } from \"../lib/not-implemented-stub.js\";\n\nexport function registerToolsCommand(program: Command): void {\n const tools = program.command(\"tools\").description(\"Quản lý system tools + MCP servers (M09)\");\n\n tools\n .command(\"list\")\n .description(\"Liệt kê tool đã cài / còn thiếu\")\n .option(\"--installed\", \"Chỉ liệt kê tool đã cài\")\n .option(\"--missing\", \"Chỉ liệt kê tool còn thiếu\")\n .option(\"--json\", \"Output JSON cho script\")\n .action(notImplementedYet(\"tools list\", \"Milestone 09\"));\n\n tools\n .command(\"install [tool-ids...]\")\n .description(\"Cài tool và đăng ký vào ~/.claude.json\")\n .option(\"--all-recommended\", \"Cài mọi MCP được recommend cho project type\")\n .option(\"--verify\", \"Chạy MCP thử để verify (mất ~30s/tool)\")\n .option(\"--no-secrets\", \"Skip prompt secrets, set sau qua 'avatar secrets'\")\n .action(notImplementedYet(\"tools install\", \"Milestone 09\"));\n\n tools\n .command(\"remove <tool-id>\")\n .description(\"Gỡ tool khỏi ~/.claude.json (optional uninstall binary)\")\n .option(\"--keep-secrets\", \"Không xóa secrets khỏi keychain\")\n .option(\"--keep-binary\", \"Không uninstall npm global binary\")\n .action(notImplementedYet(\"tools remove\", \"Milestone 09\"));\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":["constants","fs","dirname","join","relative","pathExists","path","ensureDir","readText","readJson","writeTextAtomic","content","mode","tmp","writeJsonAtomic","data","init_filesystem_helpers","__esmMin","spawnSync","chmodSync","existsSync","readFileSync","renameSync","writeFileSync","join","envrcPath","workspacePath","ENVRC_FILENAME","extractAvatarBlock","content","beginIdx","AVATAR_BLOCK_BEGIN","endIdx","AVATAR_BLOCK_END","parseAvatarBlockVars","block","vars","re","m","key","rawValue","value","escapeForDoubleQuotes","buildAvatarBlock","lines","AVATAR_BANNER","keys","writeEnvrcWithSecrets","updates","path","existing","oldBlock","existingVars","varsBefore","newBlock","newContent","hadExistingNonAvatarContent","tmpPath","direnvAllowOk","readAvatarEnvrcVars","init_write_envrc_with_per_project_secrets","__esmMin","migrate_anthropic_key_from_settings_json_to_envrc_exports","__export","MIGRATABLE_SECRET_KEYS","detectPlaintextSecretsInSettings","migrateSecretsFromSettingsToEnvrc","fs","join","settingsJsonPath","workspacePath","SETTINGS_JSON_REL","backupName","originalPath","d","stamp","path","pathExists","settings","readJson","env","found","key","keys","settingsPath","updates","value","writeEnvrcWithSecrets","backupPath","writeJsonAtomic","init_migrate_anthropic_key_from_settings_json_to_envrc","__esmMin","init_filesystem_helpers","init_write_envrc_with_per_project_secrets","Command","init_filesystem_helpers","fs","join","confirm","existsSync","readFileSync","dirname","join","MAX_WALKUP_LEVELS","isAvatarWorkspace","dir","hasClaudeDir","hasClaudeMd","hasSrcDir","gitmodulesPath","content","resolveAvatarWorkspaceRootFromCwd","startDir","current","i","parent","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","ensureAuditLogPermissions","fs","AUDIT_LOG_PATH","appendAuditEntry","action","detail","ensureAvatarHome","entry","line","spawnSync","chalk","ora","log","m","spinner","text","spinnerWithElapsed","prefix","startMs","sp","formatElapsed","sec","s","interval","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","platform","detectHostPlatform","p","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeClaudeBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeClaudeVersion","cachedInfo","detectClaudeCodeInstallation","path","invalidateClaudeCodeInstallationCache","spawnSync","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","password","select","ANTHROPIC_BASE_URL","ANTHROPIC_API_VERSION","FETCH_TIMEOUT_MS","maskAnthropicKey","key","validateAnthropicKeyFormat","trimmed","promptAnthropicKeyHidden","password","fetchAnthropicModels","apiKey","controller","timer","res","models","m","id","err","promptAnthropicModelChoice","only","log","sorted","a","b","score","lower","select","setupAnthropicApiKeyAndModel","model","password","select","GEMINI_BASE_URL","FETCH_TIMEOUT_MS","maskGeminiKey","key","validateGeminiKeyFormat","trimmed","promptGeminiKeyHidden","password","fetchGeminiModels","apiKey","controller","timeout","url","resp","body","m","err","filterPreferredGeminiModels","allModels","preferred","id","promptGeminiModelChoice","models","log","select","setupGeminiApiKeyAndModel","filtered","model","input","password","select","DEFAULT_BASE_URL","FETCH_TIMEOUT_MS","maskApiKey","key","promptApiKeyHidden","password","v","promptBaseUrl","defaultUrl","input","fetchAvailableModels","baseUrl","apiKey","controller","timer","res","models","m","id","err","vpnHint","errMsg","promptModelChoice","claudeAliases","only","log","choiceList","select","setupLLMLiteApiKeyAndModel","model","password","select","OPENAI_BASE_URL","FETCH_TIMEOUT_MS","maskOpenAIKey","key","validateOpenAIKeyFormat","trimmed","promptOpenAIKeyHidden","password","fetchOpenAIModels","apiKey","controller","timeout","resp","body","m","err","filterPreferredCodingModels","allModels","preferred","id","lower","promptOpenAIModelChoice","models","log","select","setupOpenAIApiKeyAndModel","filtered","model","fs","join","spawnSync","existsSync","readFileSync","homedir","join","tryRun","cmd","args","result","detectDirenvStatus","version","binary","checkShellHookInZshrc","which","zshrc","content","spawnSync","appendFileSync","existsSync","readFileSync","homedir","platform","join","MANUAL_URL","commandExists","cmd","runInstall","args","result","err","installDirenv","os","r","ensureDirenvShellHookInZshrc","zshrcPath","hookLine","hookMarker","existing","block","init_write_envrc_with_per_project_secrets","CLAUDE_ENV_REL","writeClaudeEnvFallback","workspacePath","secrets","envPath","join","fs","existing","lines","ours","filtered","line","m","key","value","escaped","newContent","i","arr","storeProviderKeyViaSecretsSubsystem","direnv","detectDirenvStatus","installedNow","addedHook","log","installResult","installDirenv","fallbackPath","hookResult","ensureDirenvShellHookInZshrc","writeResult","writeEnvrcWithSecrets","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","_o","_g","applyLLMLite","apiKey","baseUrl","skipApiKey","applyAnthropic","applyCodex","applyGemini","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","storeResult","storeProviderKeyViaSecretsSubsystem","anthropicConfig","setupAnthropicApiKeyAndModel","codexConfig","setupOpenAIApiKeyAndModel","geminiConfig","setupGeminiApiKeyAndModel","err","message","spawnSync","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","input","spawnSync","existsSync","join","assertAvatarWorkspaceRoot","cwd","srcGit","join","workspaceGit","claudeDir","existsSync","gitExec","args","r","spawnSync","stderr","isDirty","commitSrc","workspaceRoot","opts","srcPath","log","sha","pushed","commitWorkspace","executeCommitWithTargetSelection","result","srcOutcome","wsOutcome","registerCommitCommand","program","opts","runCommitSrc","message","input","v","result","executeCommitWithTargetSelection","log","err","spawnSync","fs","join","boxen","join","init_filesystem_helpers","fs","join","init_filesystem_helpers","fs","path","join","isStatusLineCommandResolvable","workspacePath","command","match","filePath","log","fullPath","join","wsResolved","path","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","fs","writeJsonAtomic","featureManifestPath","workspacePath","name","join","readFeatureManifest","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","enableFeature","manifest","merged","changes","mHooks","userHooks","outHooks","touched","totalMigrated","event","featEntries","existing","migratedUser","droppedCount","seen","toAdd","e","featDeny","userDeny","unionDeny","unionDedupe","disableFeature","_h","_p","rest","removed","entries","featKeys","kept","keptDeny","d","_d","permRest","newPerms","init_filesystem_helpers","join","FEATURE_STATE_RELATIVE_PATH","emptyState","stateFilePath","workspacePath","readFeatureState","path","pathExists","readJson","writeFeatureState","state","writeJsonAtomic","setFeatureState","name","entry","listEnabledFeatures","init_filesystem_helpers","manifestHooksPresent","settings","manifest","mHooks","sHooks","event","featEntries","present","presentCommands","e","h","entry","checkEnabledFeaturesHealth","cwd","enabled","listEnabledFeatures","settingsPath","join","pathExists","readJson","checks","name","readFeatureManifest","enableFeature","init_filesystem_helpers","init_filesystem_helpers","join","simpleGit","git","cwd","isGitRepo","pathExists","addSubmodule","repoUrl","destPath","cwd","git","checkoutTagInSubmodule","submodulePath","tag","submoduleCwd","join","checkoutBranchHeadInSubmodule","branch","listTags","tagAtHead","currentCommitSha","init_filesystem_helpers","fs","join","init_filesystem_helpers","existsSync","dirname","join","fileURLToPath","TEMPLATE_PATTERN","renderTemplate","source","variables","match","key","value","HERE","dirname","fileURLToPath","PACKAGE_ROOT","findPackageRoot","TEMPLATES_ROOT","join","HOOKS_ROOT","startDir","dir","existsSync","parent","loadTemplate","name","readText","renderTemplateByName","variables","source","renderTemplate","loadHook","backupIfExists","path","pathExists","ts","basePath","backupPath","counter","fs","writeWithBackup","content","mode","backup","writeTextAtomic","CLAUDE_SUBDIRS","createClaudeDirTree","projectRoot","claudeRoot","join","ensureDir","sub","dir","writeProjectKnowledgeFiles","_projectRoot","_vars","writeRootClaudeMd","vars","renderTemplateByName","writeProjectSettings","appendGitignoreEntries","tpl","marker","existing","separator","installGitHook","gitDir","hookName","loadHook","hooksDir","dest","registerDoctorCommand","program","opts","checks","runChecks","renderChecks","applyFixes","err","log","cwd","nodeVer","major","minor","n","nodeOk","config","readUserConfig","isTokenExpired","packPath","join","claudeMdPath","hookPath","gitRepo","hasPack","hasClaudeMd","hasHook","isGitRepo","pathExists","installGitHook","gitignorePath","gitignoreContent","gitignoreOk","fs","settingsGitignored","pythonCheck","spawnSync","python3Check","hasPython","hasPython3","settingsPath","settingsRaw","settings","match","refFile","fullPath","fileExists","which","hasClaudeCli","featureChecks","checkEnabledFeaturesHealth","lines","chalk","passed","issues","fixable","c","icon","boxen","count","resolve","checkbox","confirm","fs","join","init_filesystem_helpers","PACK_FEATURES_REL","packFeaturesDir","workspacePath","join","listAvailableFeatures","dir","pathExists","entries","fs","names","entry","readDefaultFeatures","path","readJson","discoverEnabledAndAvailableFeatures","available","enabled","listEnabledFeatures","buildAddChoices","available","enabled","enabledSet","name","buildRemoveChoices","resolveNonInteractiveSelection","candidates","spawnSync","warnIfRuntimeMissing","runtime","probe","spawnSync","log","logApplyResult","name","result","enableFeatureByName","workspacePath","opts","manifest","readFeatureManifest","enableFeature","setFeatureState","disableFeatureByName","prevVersion","readFeatureState","disableFeature","resolveWorkspace","opts","resolve","selectFeatures","candidates","preChecked","verb","resolveNonInteractiveSelection","choices","buildAddChoices","buildRemoveChoices","checkbox","runAdd","ws","available","enabled","discoverEnabledAndAvailableFeatures","log","selected","name","enableFeatureByName","runRemove","confirm","disableFeatureByName","runList","listAvailableFeatures","state","readFeatureState","names","isAvailable","entry","version","registerFeatureCommand","program","feat","init_filesystem_helpers","spawnSync","fs","join","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","spawnSync","stdout","runGitnexusAnalyze","workspacePath","metaPath","join","existsSync","confirm","boxen","spawnSync","VERSION_PROBE_TIMEOUT_MS","SEMVER_REGEX","probeGitnexusBinaryPath","probeCmd","detectHostPlatform","result","spawnSync","out","probeGitnexusVersion","cachedInfo","detectGitnexusInstallation","path","invalidateGitnexusInstallationCache","spawnSync","NPM_INSTALL_TIMEOUT_MS","GITNEXUS_PACKAGE","InstallGitnexusError","reason","message","exitCode","classifyNpmFailure","stderrSample","stderr","installGitnexusViaNpm","log","result","spawnSync","invalidateGitnexusInstallationCache","probe","detectGitnexusInstallation","input","select","UserAbortedRecoveryError","message","promptRetryOrSkip","args","log","choices","select","init_filesystem_helpers","fs","homedir","join","MCP_FILE_MODE","EXPECTED_GITNEXUS_ENTRY","getMcpServersPath","join","homedir","isEntryEqual","a","b","backupExistingFile","path","ts","backupPath","fs","registerGitnexusMcpServer","existing","fileExisted","pathExists","readJson","err","existingEntry","log","backup","merged","writeJsonAtomic","spawnSync","existsSync","join","confirm","REASONING_PATTERNS","isReasoningModel","modelName","pattern","init_filesystem_helpers","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","anthropicKey","llmliteToken","confirmWikiGeneration","model","confirm","tailLines","text","n","runGitnexusWikiConditional","creds","log","reasoningMode","isReasoningModel","writeGitnexusConfigForWikiRun","args","sp","spinnerWithElapsed","result","spawnSync","reason","stderr","stdout","wikiPath","existsSync","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","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","select","chalk","BANNER_LINES","GRADIENT_STOPS","lerpChannel","a","b","t","gradientAt","scaled","lo","hi","localT","renderAvatarBanner","opts","colored","line","idx","r","g","printAvatarBanner","spawnSync","input","select","spawnSync","fs","basename","join","confirm","select","spawnSync","RepoAlreadyExistsError","fullName","executeGhRepoCreate","input","args","r","spawnSync","resolveGithubUsernameDefault","r","REPO_NAME_REGEX","InvalidRepoNameError","name","validateRepoName","validateRepoVisibility","v","createGithubRemoteFromFolder","input","validateRepoName","validateRepoVisibility","org","resolveGithubUsernameDefault","log","urls","executeGhRepoCreate","init_filesystem_helpers","backupTimestamp","d","backupExistingDotGit","folderPath","gitDir","join","pathExists","backupName","backupPath","fs","log","reinitGitInFolder","r1","spawnSync","r3","resetFolderGitAndCreateNewRemoteUnderCurrentUser","opts","folderName","basename","repoName","confirm","visibility","select","createGithubRemoteFromFolder","spawnSync","TIMEOUT_MS","classifyRemoteError","stderr","text","tryVerifyGitRemoteAccessible","url","r","spawnSync","TIMEOUT_MS","err","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","readdirSync","select","simpleGit","existsSync","statSync","join","checkFolderHasGit","folderPath","gitPath","stat","simpleGit","INITIAL_COMMIT_MESSAGE","createInitialGitCommit","folderPath","g","status","existsSync","join","SIGNATURES","detectFolderTechStack","folderPath","matched","stack","files","f","readFileSync","dirname","join","fileURLToPath","__dirname","CANDIDATE_DIRS","AVATAR_MARKER_START","AVATAR_MARKER_END","readTemplate","stack","dir","composeGitignoreContent","stacks","sections","s","existsSync","readFileSync","writeFileSync","join","writeOrMergeGitignore","folderPath","avatarBlock","path","join","existsSync","writeFileSync","existing","readFileSync","startIdx","AVATAR_MARKER_START","endIdx","AVATAR_MARKER_END","before","after","InitAbortedByUserError","message","detectFolderGitState","folderPath","checkFolderHasGit","simpleGit","readdirSync","e","promptBootstrapStrategy","state","opts","select","stashUserChanges","g","stashName","status","log","restoreStash","err","getCurrentBranch","branch","writeAvatarGitignore","stacks","detectFolderTechStack","writeOrMergeGitignore","composeGitignoreContent","executeBootstrapWithStrategy","strategy","stashed","createInitialGitCommit","originalBranch","safeBootstrapGitInFolder","appendAuditEntry","join","spawnSync","confirm","select","boxen","parseRepoSlugFromGitUrl","url","checkRepoAccess","repoSlug","spawnSync","getCurrentGhUser","r","triggerGhAuthLoginInteractive","log","copyInfoToClipboardWithConsent","info","confirm","clipboardy","err","printAccessWarningBox","ghUser","ssoEmail","lines","chalk","boxen","buildAccessRequestInfo","ensureTeamPackAccessWithRetry","args","initialGhUser","action","select","finalUser","SEMVER_REGEX","parseSemVerTag","tag","match","major","minor","patch","prerelease","pickLatestStableSemVerTag","tags","includePrerelease","parsed","t","a","b","ORG_DEFAULT","GITHUB_URL_ALLOWLIST","InvalidTeamPackUrlError","url","assertAllowedUrl","resolveTeamPackRepoUrl","override","TEAM_PACK_REPO_URL","resolveTeamPackRepoUrl","TEAM_PACK_RELATIVE_PATH","TeamPackAccessAbortedError","message","DEFAULT_PACK_BRANCH","addTeamPackSubmodule","projectRoot","tag","ssoEmail","latest","url","repoSlug","parseRepoSlugFromGitUrl","ensureTeamPackAccessWithRetry","addSubmodule","err","msg","log","checkoutTagInSubmodule","checkoutBranchHeadInSubmodule","submoduleDir","join","allTags","listTags","target","pickLatestStableSemVerTag","readPinnedPackVersion","submoduleRoot","tagAtHead","currentCommitSha","basename","join","resolve","input","select","spawnSync","select","isSshPermissionError","message","text","triggerGhAuthLoginInteractive","log","r","spawnSync","openGithubSshKeysPage","handleSshPermissionError","select","addTeamPackSubmoduleWithRetryOnNetworkFail","projectRoot","tag","ssoEmail","latest","addTeamPackSubmodule","err","TeamPackAccessAbortedError","action","UserAbortedRecoveryError","promptRetryOrSkip","init_filesystem_helpers","spawnSync","checkGhCliAuthStatus","r","spawnSync","hasBinary","name","platform","detectHostPlatform","spawnSync","detectPackageManager","candidates","pm","spawnSync","INSTALL_COMMANDS","installGhCliViaPackageManager","pm","spec","log","r","spawnSync","spawnSync","setupGitCredentialViaGh","spawnSync","log","spawnSync","triggerGhCliAuthLogin","log","r","spawnSync","ensureGitHubReady","remoteUrl","checkGhCliAuthStatus","log","pm","detectPackageManager","promptRetryOrSkip","UserAbortedRecoveryError","installGhCliViaPackageManager","err","triggerGhCliAuthLogin","setupGitCredentialViaGh","result","tryVerifyGitRemoteAccessible","handleRemoteAccessFailureWithAccountSwitch","parseBootstrapStrategyOpts","opts","valid","readFileSync","dirname","resolve","fileURLToPath","cachedVersion","readCliVersion","here","i","candidate","raw","pkg","inferWorkspaceName","repoUrl","baseName","buildGitnexusSection","gitnexusReady","buildScaffoldVariables","args","readCliVersion","init_filesystem_helpers","join","relative","resolve","input","select","boxen","boxen","PACK_COMMAND_CHEATSHEET","formatPackCommandsCheatsheetBox","maxCmdWidth","e","header","chalk","subheader","lines","footer","content","boxen","init_filesystem_helpers","readdir","join","isEmptyOrMissing","path","pathExists","readdir","e","findAlternativeWorkspaceName","parent","desiredName","maxAttempts","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","maybeCommitWorkspace","workspacePath","skipCommit","g","git","formatAiStatusLine","aiResult","chalk","modelPart","formatGitnexusStatusLine","result","parts","printInitSuccessBox","rootPath","flow","gitnexusResult","lines","relative","boxen","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","formatPackCommandsCheatsheetBox","join","basename","confirm","input","select","spawnSync","CreateWorkspaceRemoteError","reason","fullName","message","stderr","classifyGhCreateError","text","repoExistsOnGitHub","spawnSync","canCreateInNamespace","org","ghUser","createWorkspaceRemoteViaGh","input","validateRepoName","validateRepoVisibility","ensureGitHubReady","resolveGithubUsernameDefault","namespaceCheck","log","r","stdout","combined","sshUrl","httpsUrl","linkExistingRemoteToWorkspace","args","init_filesystem_helpers","confirm","DISPLAY_NAME","setupDefaultFeaturesOnInit","workspacePath","opts","defaults","readDefaultFeatures","name","display","wantEnable","confirm","log","enableFeatureByName","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","getOrCreateOriginRemote","folderPath","opts","origin","git","r","log","confirm","ensureGitHubReady","visibility","select","repoName","input","basename","createGithubRemoteFromFolder","scaffoldWorkspaceWithSrcSubmodule","args","ensureDir","sp","spinner","pinnedTag","addTeamPackSubmoduleWithRetryOnNetworkFail","finalizeWorkspaceScaffold","err","vars","buildScaffoldVariables","createClaudeDirTree","writeProjectKnowledgeFiles","writeRootClaudeMd","writeProjectSettings","appendGitignoreEntries","join","installGitHook","autoSyncPackOnInit","appendAuditEntry","maybeCommitWorkspace","maybeCreateWorkspaceRemote","aiResult","runAiSetupPhase","gitnexusResult","runGitnexusSetupPhase","updatedVars","printInitSuccessBox","workspacePath","autoYes","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","claudeDir","results","syncAllMountDirs","created","missing","mergeResult","mergePackSettingsIntoProjectSettings","setupDefaultFeaturesOnInit","detectPlaintextSecretsInSettings","migrateSecretsFromSettingsToEnvrc","detect","shouldCreate","createWorkspaceRemoteViaGh","CreateWorkspaceRemoteError","fullName","reuseAction","UserAbortedRecoveryError","linkExistingRemoteToWorkspace","newName","v","action","promptRetryOrSkip","runInitFromExistingRemote","opts","ownerEmail","initialRemoteUrl","input","v","resolvedRemoteUrl","ensureGitHubReady","remoteUrl","teamOwner","promptTeamOwner","inferredName","inferWorkspaceName","workspaceName","workspaceParent","resolve","workspacePath","resolveWorkspacePath","scaffoldWorkspaceWithSrcSubmodule","runInitFromExistingFolder","folderPath","safeBootstrapGitInFolder","parseBootstrapStrategyOpts","getOrCreateOriginRemote","verify","tryVerifyGitRemoteAccessible","log","handleRemoteAccessFailureWithAccountSwitch","basename","runInitFromScratch","projectName","visibility","select","srcPath","join","ensureDir","urls","createGithubRemoteFromFolder","git","sp","spinner","pinnedTag","addTeamPackSubmoduleWithRetryOnNetworkFail","finalizeWorkspaceScaffold","err","boxen","open","GOOGLE_CLIENT_ID","GOOGLE_CLIENT_SECRET","HOSTED_DOMAIN","SCOPES","DEVICE_CODE_URL","TOKEN_URL","REVOKE_URL","requestDeviceCode","body","res","text","pollForToken","deviceCode","errorCode","decodeIdToken","idToken","parts","payload","base64","json","VALID_ISSUERS","CLOCK_SKEW_SECONDS","verifyIdTokenClaims","claims","nowSec","ageSec","verifyHostedDomain","buildUserConfig","token","expiresAt","revokeToken","token","body","REVOKE_URL","buildVerificationUrl","response","url","HOSTED_DOMAIN","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","registerInitCommand","program","opts","runInit","err","InitAbortedByUserError","log","TeamPackAccessAbortedError","RemoteAccessAbortedError","UserAbortedRecoveryError","printAvatarBanner","userConfig","readUserConfig","isTokenExpired","runLogin","promptRetryOrSkip","promptProjectStatus","runInitFromExistingRemote","runInitFromExistingFolder","runInitFromScratch","select","notImplementedYet","commandName","milestone","chalk","registerMcpRunCommand","program","notImplementedYet","init_filesystem_helpers","join","boxen","PACK_RELATIVE_PATH","registerPackCommand","program","opts","snap","gatherPackStatus","renderPackStatus","err","log","cwd","doFetch","packDir","join","pathExists","isGitRepo","currentTag","readPinnedPackVersion","fetched","git","allTags","listTags","latestTag","pickLatestStableSemVerTag","s","lines","chalk","boxen","current","latest","verdict","registerRestoreCommand","program","notImplementedYet","registerReviewCommand","program","notImplementedYet","registerScanCommand","program","notImplementedYet","confirm","password","init_migrate_anthropic_key_from_settings_json_to_envrc","init_write_envrc_with_per_project_secrets","resolveWorkspaceOrExit","root","resolveAvatarWorkspaceRootFromCwd","log","maskValue","value","runSecretsSetupWizard","workspace","direnv","detectDirenvStatus","confirm","result","installDirenv","r","ensureDirenvShellHookInZshrc","detect","detectPlaintextSecretsInSettings","migrateSecretsFromSettingsToEnvrc","readAvatarEnvrcVars","setOneSecret","envrcPath","setupAction","name","opts","password","v","maskAnthropicKey","models","fetchAnthropicModels","err","writeEnvrcWithSecrets","setAction","getAction","listAction","vars","key","rmAction","checkAction","path","anthropicKey","migrateAction","registerSecretsCommand","program","secrets","wrap","fn","wrapName","fs","join","boxen","init_filesystem_helpers","init_filesystem_helpers","fs","join","BACKUP_DIR_NAME","listBackups","projectRoot","dir","join","BACKUP_DIR_NAME","pathExists","fs","e","registerStatusCommand","program","opts","snapshot","gatherStatus","renderStatusBox","err","log","cwd","projectName","claudeRoot","join","pathExists","readCliVersion","pendingDir","packVersion","pendingCount","backupCount","techStackSummary","features","isGitRepo","readPinnedPackVersion","fs","n","listBackups","b","readTechStackFirstLine","discoverEnabledAndAvailableFeatures","techStackPath","readText","l","formatFeatures","s","lines","chalk","boxen","init_filesystem_helpers","join","init_filesystem_helpers","join","inspectMountDir","packDir","claudeDir","dir","source","join","dest","pathExists","fs","listCommitsBetween","fromSha","toRef","git","l","buildSyncPreview","targetVersion","currentTagOrNull","tagAtHead","currentSha","currentCommitSha","currentVersion","allTags","listTags","target","pickLatestStableSemVerTag","commits","mountStatuses","TEAM_PACK_MOUNT_DIRS","reapplyEnabledFeaturesAfterSync","workspacePath","enabled","listEnabledFeatures","log","name","enableFeatureByName","DEFAULT_PACK_BRANCH","syncAction","opts","projectRoot","claudeDir","join","packDir","TEAM_PACK_RELATIVE_PATH","pathExists","log","git","err","useLatestMode","allTags","listTags","targetVersion","picked","pickLatestStableSemVerTag","preview","buildSyncPreview","c","m","checkoutBranchHeadInSubmodule","sha","currentCommitSha","checkoutTagInSubmodule","results","syncAllMountDirs","reportResults","mergeResult","mergePackSettingsIntoProjectSettings","reapplyEnabledFeaturesAfterSync","force","r","conflicts","registerSyncCommand","program","registerToolsCommand","program","tools","notImplementedYet","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","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","registerCommitCommand","registerToolsCommand","registerSecretsCommand","registerMcpRunCommand","registerAiCommand","registerGitnexusCommand","registerFeatureCommand","registerPackCommand","registerUninstallCommand","err","msg"]}
|