@phi-code-admin/phi-code 0.95.0 → 0.96.1

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.
Files changed (36) hide show
  1. package/CHANGELOG.md +84 -0
  2. package/dist/cli/args.d.ts +1 -1
  3. package/dist/cli/args.d.ts.map +1 -1
  4. package/dist/cli/args.js +43 -1
  5. package/dist/cli/args.js.map +1 -1
  6. package/dist/core/api-key-store.d.ts +20 -4
  7. package/dist/core/api-key-store.d.ts.map +1 -1
  8. package/dist/core/api-key-store.js +37 -7
  9. package/dist/core/api-key-store.js.map +1 -1
  10. package/dist/core/compaction/compaction.d.ts +16 -0
  11. package/dist/core/compaction/compaction.d.ts.map +1 -1
  12. package/dist/core/compaction/compaction.js +43 -19
  13. package/dist/core/compaction/compaction.js.map +1 -1
  14. package/dist/core/json-utils.d.ts +11 -0
  15. package/dist/core/json-utils.d.ts.map +1 -0
  16. package/dist/core/json-utils.js +15 -0
  17. package/dist/core/json-utils.js.map +1 -0
  18. package/dist/core/model-registry.d.ts +6 -1
  19. package/dist/core/model-registry.d.ts.map +1 -1
  20. package/dist/core/model-registry.js +10 -9
  21. package/dist/core/model-registry.js.map +1 -1
  22. package/dist/migrations.d.ts.map +1 -1
  23. package/dist/migrations.js +2 -2
  24. package/dist/migrations.js.map +1 -1
  25. package/extensions/phi/benchmark.ts +32 -28
  26. package/extensions/phi/memory.ts +1 -1
  27. package/extensions/phi/models.ts +4 -3
  28. package/extensions/phi/orchestrator.ts +198 -12
  29. package/extensions/phi/providers/candidate-fanout.ts +180 -0
  30. package/extensions/phi/providers/debug-build-commands.ts +27 -0
  31. package/extensions/phi/providers/escalation.ts +16 -0
  32. package/extensions/phi/providers/explore-fanout.ts +1 -1
  33. package/extensions/phi/providers/telemetry.ts +66 -0
  34. package/extensions/phi/providers/test-discovery.ts +119 -0
  35. package/extensions/phi/providers/triage.ts +15 -0
  36. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAaH;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAoDhD;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CA+CnD;AA+ID;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C/E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAC3C,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAOA","sourcesContent":["/**\n * One-time migrations that run on startup.\n */\n\nimport chalk from \"chalk\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport { basename, dirname, join } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir, getBinDir } from \"./config.js\";\nimport { migrateKeybindingsConfig } from \"./core/keybindings.js\";\n\nconst MIGRATION_GUIDE_URL =\n\t\"https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration\";\nconst EXTENSIONS_DOC_URL =\n\t\"https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/docs/extensions.md\";\n\n/**\n * Migrate legacy oauth.json and settings.json apiKeys to auth.json.\n *\n * @returns Array of provider names that were migrated\n */\nexport function migrateAuthToAuthJson(): string[] {\n\tconst agentDir = getAgentDir();\n\tconst authPath = join(agentDir, \"auth.json\");\n\tconst oauthPath = join(agentDir, \"oauth.json\");\n\tconst settingsPath = join(agentDir, \"settings.json\");\n\n\t// Skip if auth.json already exists\n\tif (existsSync(authPath)) return [];\n\n\tconst migrated: Record<string, unknown> = {};\n\tconst providers: string[] = [];\n\n\t// Migrate oauth.json\n\tif (existsSync(oauthPath)) {\n\t\ttry {\n\t\t\tconst oauth = JSON.parse(readFileSync(oauthPath, \"utf-8\"));\n\t\t\tfor (const [provider, cred] of Object.entries(oauth)) {\n\t\t\t\tmigrated[provider] = { type: \"oauth\", ...(cred as object) };\n\t\t\t\tproviders.push(provider);\n\t\t\t}\n\t\t\trenameSync(oauthPath, `${oauthPath}.migrated`);\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\t// Migrate settings.json apiKeys\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst settings = JSON.parse(content);\n\t\t\tif (settings.apiKeys && typeof settings.apiKeys === \"object\") {\n\t\t\t\tfor (const [provider, key] of Object.entries(settings.apiKeys)) {\n\t\t\t\t\tif (!migrated[provider] && typeof key === \"string\") {\n\t\t\t\t\t\tmigrated[provider] = { type: \"api_key\", key };\n\t\t\t\t\t\tproviders.push(provider);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete settings.apiKeys;\n\t\t\t\twriteFileSync(settingsPath, JSON.stringify(settings, null, 2));\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\tif (Object.keys(migrated).length > 0) {\n\t\tmkdirSync(dirname(authPath), { recursive: true });\n\t\twriteFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });\n\t}\n\n\treturn providers;\n}\n\n/**\n * Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.\n *\n * Bug in v0.30.0: Sessions were saved to ~/.pi/agent/ instead of\n * ~/.pi/agent/sessions/<encoded-cwd>/. This migration moves them\n * to the correct location based on the cwd in their session header.\n *\n * See: https://github.com/earendil-works/pi-mono/issues/320\n */\nexport function migrateSessionsFromAgentRoot(): void {\n\tconst agentDir = getAgentDir();\n\n\t// Find all .jsonl files directly in agentDir (not in subdirectories)\n\tlet files: string[];\n\ttry {\n\t\tfiles = readdirSync(agentDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(agentDir, f));\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (files.length === 0) return;\n\n\tfor (const file of files) {\n\t\ttry {\n\t\t\t// Read first line to get session header\n\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\tconst firstLine = content.split(\"\\n\")[0];\n\t\t\tif (!firstLine?.trim()) continue;\n\n\t\t\tconst header = JSON.parse(firstLine);\n\t\t\tif (header.type !== \"session\" || !header.cwd) continue;\n\n\t\t\tconst cwd: string = header.cwd;\n\n\t\t\t// Compute the correct session directory (same encoding as session-manager.ts)\n\t\t\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\t\t\tconst correctDir = join(agentDir, \"sessions\", safePath);\n\n\t\t\t// Create directory if needed\n\t\t\tif (!existsSync(correctDir)) {\n\t\t\t\tmkdirSync(correctDir, { recursive: true });\n\t\t\t}\n\n\t\t\t// Move the file (basename handles both separators per platform)\n\t\t\tconst fileName = basename(file);\n\t\t\tconst newPath = join(correctDir, fileName);\n\n\t\t\tif (existsSync(newPath)) continue; // Skip if target exists\n\n\t\t\trenameSync(file, newPath);\n\t\t} catch {\n\t\t\t// Skip files that can't be migrated\n\t\t}\n\t}\n}\n\n/**\n * Migrate commands/ to prompts/ if needed.\n * Works for both regular directories and symlinks.\n */\nfunction migrateCommandsToPrompts(baseDir: string, label: string): boolean {\n\tconst commandsDir = join(baseDir, \"commands\");\n\tconst promptsDir = join(baseDir, \"prompts\");\n\n\tif (existsSync(commandsDir) && !existsSync(promptsDir)) {\n\t\ttry {\n\t\t\trenameSync(commandsDir, promptsDir);\n\t\t\tconsole.log(chalk.green(`Migrated ${label} commands/ → prompts/`));\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tconsole.log(\n\t\t\t\tchalk.yellow(\n\t\t\t\t\t`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction migrateKeybindingsConfigFile(): void {\n\tconst configPath = join(getAgentDir(), \"keybindings.json\");\n\tif (!existsSync(configPath)) return;\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn;\n\t\t}\n\t\tconst { config, migrated } = migrateKeybindingsConfig(parsed as Record<string, unknown>);\n\t\tif (!migrated) return;\n\t\twriteFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf-8\");\n\t} catch {\n\t\t// Ignore malformed files during migration\n\t}\n}\n\n/**\n * Move fd/rg binaries from tools/ to bin/ if they exist.\n */\nfunction migrateToolsToBin(): void {\n\tconst agentDir = getAgentDir();\n\tconst toolsDir = join(agentDir, \"tools\");\n\tconst binDir = getBinDir();\n\n\tif (!existsSync(toolsDir)) return;\n\n\tconst binaries = [\"fd\", \"rg\", \"fd.exe\", \"rg.exe\"];\n\tlet movedAny = false;\n\n\tfor (const bin of binaries) {\n\t\tconst oldPath = join(toolsDir, bin);\n\t\tconst newPath = join(binDir, bin);\n\n\t\tif (existsSync(oldPath)) {\n\t\t\tif (!existsSync(binDir)) {\n\t\t\t\tmkdirSync(binDir, { recursive: true });\n\t\t\t}\n\t\t\tif (!existsSync(newPath)) {\n\t\t\t\ttry {\n\t\t\t\t\trenameSync(oldPath, newPath);\n\t\t\t\t\tmovedAny = true;\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Target exists, just delete the old one\n\t\t\t\ttry {\n\t\t\t\t\trmSync?.(oldPath, { force: true });\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (movedAny) {\n\t\tconsole.log(chalk.green(`Migrated managed binaries tools/ → bin/`));\n\t}\n}\n\n/**\n * Check for deprecated hooks/ and tools/ directories.\n * Note: tools/ may contain fd/rg binaries extracted by pi, so only warn if it has other files.\n */\nfunction checkDeprecatedExtensionDirs(baseDir: string, label: string): string[] {\n\tconst hooksDir = join(baseDir, \"hooks\");\n\tconst toolsDir = join(baseDir, \"tools\");\n\tconst warnings: string[] = [];\n\n\tif (existsSync(hooksDir)) {\n\t\twarnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);\n\t}\n\n\tif (existsSync(toolsDir)) {\n\t\t// Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)\n\t\ttry {\n\t\t\tconst entries = readdirSync(toolsDir);\n\t\t\tconst customTools = entries.filter((e) => {\n\t\t\t\tconst lower = e.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tlower !== \"fd\" && lower !== \"rg\" && lower !== \"fd.exe\" && lower !== \"rg.exe\" && !e.startsWith(\".\") // Ignore .DS_Store and other hidden files\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (customTools.length > 0) {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read errors\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\n/**\n * Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.\n */\nfunction migrateExtensionSystem(cwd: string): string[] {\n\tconst agentDir = getAgentDir();\n\tconst projectDir = join(cwd, CONFIG_DIR_NAME);\n\n\t// Migrate commands/ to prompts/\n\tmigrateCommandsToPrompts(agentDir, \"Global\");\n\tmigrateCommandsToPrompts(projectDir, \"Project\");\n\n\t// Check for deprecated directories\n\tconst warnings = [\n\t\t...checkDeprecatedExtensionDirs(agentDir, \"Global\"),\n\t\t...checkDeprecatedExtensionDirs(projectDir, \"Project\"),\n\t];\n\n\treturn warnings;\n}\n\n/**\n * Print deprecation warnings and wait for keypress.\n */\nexport async function showDeprecationWarnings(warnings: string[]): Promise<void> {\n\tif (warnings.length === 0) return;\n\n\tfor (const warning of warnings) {\n\t\tconsole.log(chalk.yellow(`Warning: ${warning}`));\n\t}\n\tconsole.log(chalk.yellow(`\\nMove your extensions to the extensions/ directory.`));\n\tconsole.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));\n\tconsole.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));\n\tconsole.log(chalk.dim(`\\nPress any key to continue...`));\n\n\t// If stdin is not a TTY (pipe/CI), do not enable raw mode and continue.\n\tif (!process.stdin.isTTY) {\n\t\tconsole.log();\n\t\treturn;\n\t}\n\n\tawait new Promise<void>((resolve) => {\n\t\tlet done = false;\n\t\tconst onData = () => finish();\n\t\tconst onSigint = () => finish(true);\n\n\t\t// Restore stdin state on every exit path (keypress or SIGINT).\n\t\tconst finish = (interrupted = false) => {\n\t\t\tif (done) return;\n\t\t\tdone = true;\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tprocess.removeListener(\"SIGINT\", onSigint);\n\t\t\tprocess.stdin.setRawMode?.(false);\n\t\t\tprocess.stdin.pause();\n\t\t\tif (interrupted) {\n\t\t\t\tconsole.log();\n\t\t\t\tprocess.exit(130);\n\t\t\t}\n\t\t\tresolve();\n\t\t};\n\n\t\tprocess.stdin.setRawMode?.(true);\n\t\tprocess.stdin.resume();\n\t\tprocess.stdin.once(\"data\", onData);\n\t\tprocess.once(\"SIGINT\", onSigint);\n\t});\n\tconsole.log();\n}\n\n/**\n * Run all migrations. Called once on startup.\n *\n * @returns Object with migration results and deprecation warnings\n */\nexport function runMigrations(cwd: string): {\n\tmigratedAuthProviders: string[];\n\tdeprecationWarnings: string[];\n} {\n\tconst migratedAuthProviders = migrateAuthToAuthJson();\n\tmigrateSessionsFromAgentRoot();\n\tmigrateToolsToBin();\n\tmigrateKeybindingsConfigFile();\n\tconst deprecationWarnings = migrateExtensionSystem(cwd);\n\treturn { migratedAuthProviders, deprecationWarnings };\n}\n"]}
1
+ {"version":3,"file":"migrations.d.ts","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"AAAA;;GAEG;AAWH;;;;GAIG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAoDhD;AAED;;;;;;;;GAQG;AACH,wBAAgB,4BAA4B,IAAI,IAAI,CA+CnD;AA+ID;;GAEG;AACH,wBAAsB,uBAAuB,CAAC,QAAQ,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CA2C/E;AAED;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG;IAC3C,qBAAqB,EAAE,MAAM,EAAE,CAAC;IAChC,mBAAmB,EAAE,MAAM,EAAE,CAAC;CAC9B,CAOA","sourcesContent":["/**\n * One-time migrations that run on startup.\n */\n\nimport chalk from \"chalk\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport { basename, dirname, join } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir, getBinDir } from \"./config.js\";\nimport { migrateKeybindingsConfig } from \"./core/keybindings.js\";\n\nconst MIGRATION_GUIDE_URL = \"https://github.com/uglyswap/phi-code/blob/main/packages/coding-agent/CHANGELOG.md\";\nconst EXTENSIONS_DOC_URL = \"https://github.com/uglyswap/phi-code/blob/main/packages/coding-agent/docs/extensions.md\";\n\n/**\n * Migrate legacy oauth.json and settings.json apiKeys to auth.json.\n *\n * @returns Array of provider names that were migrated\n */\nexport function migrateAuthToAuthJson(): string[] {\n\tconst agentDir = getAgentDir();\n\tconst authPath = join(agentDir, \"auth.json\");\n\tconst oauthPath = join(agentDir, \"oauth.json\");\n\tconst settingsPath = join(agentDir, \"settings.json\");\n\n\t// Skip if auth.json already exists\n\tif (existsSync(authPath)) return [];\n\n\tconst migrated: Record<string, unknown> = {};\n\tconst providers: string[] = [];\n\n\t// Migrate oauth.json\n\tif (existsSync(oauthPath)) {\n\t\ttry {\n\t\t\tconst oauth = JSON.parse(readFileSync(oauthPath, \"utf-8\"));\n\t\t\tfor (const [provider, cred] of Object.entries(oauth)) {\n\t\t\t\tmigrated[provider] = { type: \"oauth\", ...(cred as object) };\n\t\t\t\tproviders.push(provider);\n\t\t\t}\n\t\t\trenameSync(oauthPath, `${oauthPath}.migrated`);\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\t// Migrate settings.json apiKeys\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst settings = JSON.parse(content);\n\t\t\tif (settings.apiKeys && typeof settings.apiKeys === \"object\") {\n\t\t\t\tfor (const [provider, key] of Object.entries(settings.apiKeys)) {\n\t\t\t\t\tif (!migrated[provider] && typeof key === \"string\") {\n\t\t\t\t\t\tmigrated[provider] = { type: \"api_key\", key };\n\t\t\t\t\t\tproviders.push(provider);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete settings.apiKeys;\n\t\t\t\twriteFileSync(settingsPath, JSON.stringify(settings, null, 2));\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\tif (Object.keys(migrated).length > 0) {\n\t\tmkdirSync(dirname(authPath), { recursive: true });\n\t\twriteFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });\n\t}\n\n\treturn providers;\n}\n\n/**\n * Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.\n *\n * Bug in v0.30.0: Sessions were saved to ~/.pi/agent/ instead of\n * ~/.pi/agent/sessions/<encoded-cwd>/. This migration moves them\n * to the correct location based on the cwd in their session header.\n *\n * See: https://github.com/earendil-works/pi-mono/issues/320\n */\nexport function migrateSessionsFromAgentRoot(): void {\n\tconst agentDir = getAgentDir();\n\n\t// Find all .jsonl files directly in agentDir (not in subdirectories)\n\tlet files: string[];\n\ttry {\n\t\tfiles = readdirSync(agentDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(agentDir, f));\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (files.length === 0) return;\n\n\tfor (const file of files) {\n\t\ttry {\n\t\t\t// Read first line to get session header\n\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\tconst firstLine = content.split(\"\\n\")[0];\n\t\t\tif (!firstLine?.trim()) continue;\n\n\t\t\tconst header = JSON.parse(firstLine);\n\t\t\tif (header.type !== \"session\" || !header.cwd) continue;\n\n\t\t\tconst cwd: string = header.cwd;\n\n\t\t\t// Compute the correct session directory (same encoding as session-manager.ts)\n\t\t\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\t\t\tconst correctDir = join(agentDir, \"sessions\", safePath);\n\n\t\t\t// Create directory if needed\n\t\t\tif (!existsSync(correctDir)) {\n\t\t\t\tmkdirSync(correctDir, { recursive: true });\n\t\t\t}\n\n\t\t\t// Move the file (basename handles both separators per platform)\n\t\t\tconst fileName = basename(file);\n\t\t\tconst newPath = join(correctDir, fileName);\n\n\t\t\tif (existsSync(newPath)) continue; // Skip if target exists\n\n\t\t\trenameSync(file, newPath);\n\t\t} catch {\n\t\t\t// Skip files that can't be migrated\n\t\t}\n\t}\n}\n\n/**\n * Migrate commands/ to prompts/ if needed.\n * Works for both regular directories and symlinks.\n */\nfunction migrateCommandsToPrompts(baseDir: string, label: string): boolean {\n\tconst commandsDir = join(baseDir, \"commands\");\n\tconst promptsDir = join(baseDir, \"prompts\");\n\n\tif (existsSync(commandsDir) && !existsSync(promptsDir)) {\n\t\ttry {\n\t\t\trenameSync(commandsDir, promptsDir);\n\t\t\tconsole.log(chalk.green(`Migrated ${label} commands/ → prompts/`));\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tconsole.log(\n\t\t\t\tchalk.yellow(\n\t\t\t\t\t`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction migrateKeybindingsConfigFile(): void {\n\tconst configPath = join(getAgentDir(), \"keybindings.json\");\n\tif (!existsSync(configPath)) return;\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn;\n\t\t}\n\t\tconst { config, migrated } = migrateKeybindingsConfig(parsed as Record<string, unknown>);\n\t\tif (!migrated) return;\n\t\twriteFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf-8\");\n\t} catch {\n\t\t// Ignore malformed files during migration\n\t}\n}\n\n/**\n * Move fd/rg binaries from tools/ to bin/ if they exist.\n */\nfunction migrateToolsToBin(): void {\n\tconst agentDir = getAgentDir();\n\tconst toolsDir = join(agentDir, \"tools\");\n\tconst binDir = getBinDir();\n\n\tif (!existsSync(toolsDir)) return;\n\n\tconst binaries = [\"fd\", \"rg\", \"fd.exe\", \"rg.exe\"];\n\tlet movedAny = false;\n\n\tfor (const bin of binaries) {\n\t\tconst oldPath = join(toolsDir, bin);\n\t\tconst newPath = join(binDir, bin);\n\n\t\tif (existsSync(oldPath)) {\n\t\t\tif (!existsSync(binDir)) {\n\t\t\t\tmkdirSync(binDir, { recursive: true });\n\t\t\t}\n\t\t\tif (!existsSync(newPath)) {\n\t\t\t\ttry {\n\t\t\t\t\trenameSync(oldPath, newPath);\n\t\t\t\t\tmovedAny = true;\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Target exists, just delete the old one\n\t\t\t\ttry {\n\t\t\t\t\trmSync?.(oldPath, { force: true });\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (movedAny) {\n\t\tconsole.log(chalk.green(`Migrated managed binaries tools/ → bin/`));\n\t}\n}\n\n/**\n * Check for deprecated hooks/ and tools/ directories.\n * Note: tools/ may contain fd/rg binaries extracted by pi, so only warn if it has other files.\n */\nfunction checkDeprecatedExtensionDirs(baseDir: string, label: string): string[] {\n\tconst hooksDir = join(baseDir, \"hooks\");\n\tconst toolsDir = join(baseDir, \"tools\");\n\tconst warnings: string[] = [];\n\n\tif (existsSync(hooksDir)) {\n\t\twarnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);\n\t}\n\n\tif (existsSync(toolsDir)) {\n\t\t// Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)\n\t\ttry {\n\t\t\tconst entries = readdirSync(toolsDir);\n\t\t\tconst customTools = entries.filter((e) => {\n\t\t\t\tconst lower = e.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tlower !== \"fd\" && lower !== \"rg\" && lower !== \"fd.exe\" && lower !== \"rg.exe\" && !e.startsWith(\".\") // Ignore .DS_Store and other hidden files\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (customTools.length > 0) {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read errors\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\n/**\n * Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.\n */\nfunction migrateExtensionSystem(cwd: string): string[] {\n\tconst agentDir = getAgentDir();\n\tconst projectDir = join(cwd, CONFIG_DIR_NAME);\n\n\t// Migrate commands/ to prompts/\n\tmigrateCommandsToPrompts(agentDir, \"Global\");\n\tmigrateCommandsToPrompts(projectDir, \"Project\");\n\n\t// Check for deprecated directories\n\tconst warnings = [\n\t\t...checkDeprecatedExtensionDirs(agentDir, \"Global\"),\n\t\t...checkDeprecatedExtensionDirs(projectDir, \"Project\"),\n\t];\n\n\treturn warnings;\n}\n\n/**\n * Print deprecation warnings and wait for keypress.\n */\nexport async function showDeprecationWarnings(warnings: string[]): Promise<void> {\n\tif (warnings.length === 0) return;\n\n\tfor (const warning of warnings) {\n\t\tconsole.log(chalk.yellow(`Warning: ${warning}`));\n\t}\n\tconsole.log(chalk.yellow(`\\nMove your extensions to the extensions/ directory.`));\n\tconsole.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));\n\tconsole.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));\n\tconsole.log(chalk.dim(`\\nPress any key to continue...`));\n\n\t// If stdin is not a TTY (pipe/CI), do not enable raw mode and continue.\n\tif (!process.stdin.isTTY) {\n\t\tconsole.log();\n\t\treturn;\n\t}\n\n\tawait new Promise<void>((resolve) => {\n\t\tlet done = false;\n\t\tconst onData = () => finish();\n\t\tconst onSigint = () => finish(true);\n\n\t\t// Restore stdin state on every exit path (keypress or SIGINT).\n\t\tconst finish = (interrupted = false) => {\n\t\t\tif (done) return;\n\t\t\tdone = true;\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tprocess.removeListener(\"SIGINT\", onSigint);\n\t\t\tprocess.stdin.setRawMode?.(false);\n\t\t\tprocess.stdin.pause();\n\t\t\tif (interrupted) {\n\t\t\t\tconsole.log();\n\t\t\t\tprocess.exit(130);\n\t\t\t}\n\t\t\tresolve();\n\t\t};\n\n\t\tprocess.stdin.setRawMode?.(true);\n\t\tprocess.stdin.resume();\n\t\tprocess.stdin.once(\"data\", onData);\n\t\tprocess.once(\"SIGINT\", onSigint);\n\t});\n\tconsole.log();\n}\n\n/**\n * Run all migrations. Called once on startup.\n *\n * @returns Object with migration results and deprecation warnings\n */\nexport function runMigrations(cwd: string): {\n\tmigratedAuthProviders: string[];\n\tdeprecationWarnings: string[];\n} {\n\tconst migratedAuthProviders = migrateAuthToAuthJson();\n\tmigrateSessionsFromAgentRoot();\n\tmigrateToolsToBin();\n\tmigrateKeybindingsConfigFile();\n\tconst deprecationWarnings = migrateExtensionSystem(cwd);\n\treturn { migratedAuthProviders, deprecationWarnings };\n}\n"]}
@@ -6,8 +6,8 @@ import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, w
6
6
  import { basename, dirname, join } from "path";
7
7
  import { CONFIG_DIR_NAME, getAgentDir, getBinDir } from "./config.js";
8
8
  import { migrateKeybindingsConfig } from "./core/keybindings.js";
9
- const MIGRATION_GUIDE_URL = "https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration";
10
- const EXTENSIONS_DOC_URL = "https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/docs/extensions.md";
9
+ const MIGRATION_GUIDE_URL = "https://github.com/uglyswap/phi-code/blob/main/packages/coding-agent/CHANGELOG.md";
10
+ const EXTENSIONS_DOC_URL = "https://github.com/uglyswap/phi-code/blob/main/packages/coding-agent/docs/extensions.md";
11
11
  /**
12
12
  * Migrate legacy oauth.json and settings.json apiKeys to auth.json.
13
13
  *
@@ -1 +1 @@
1
- {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One-time migrations that run on startup.\n */\n\nimport chalk from \"chalk\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport { basename, dirname, join } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir, getBinDir } from \"./config.js\";\nimport { migrateKeybindingsConfig } from \"./core/keybindings.js\";\n\nconst MIGRATION_GUIDE_URL =\n\t\"https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/CHANGELOG.md#extensions-migration\";\nconst EXTENSIONS_DOC_URL =\n\t\"https://github.com/earendil-works/pi-mono/blob/main/packages/coding-agent/docs/extensions.md\";\n\n/**\n * Migrate legacy oauth.json and settings.json apiKeys to auth.json.\n *\n * @returns Array of provider names that were migrated\n */\nexport function migrateAuthToAuthJson(): string[] {\n\tconst agentDir = getAgentDir();\n\tconst authPath = join(agentDir, \"auth.json\");\n\tconst oauthPath = join(agentDir, \"oauth.json\");\n\tconst settingsPath = join(agentDir, \"settings.json\");\n\n\t// Skip if auth.json already exists\n\tif (existsSync(authPath)) return [];\n\n\tconst migrated: Record<string, unknown> = {};\n\tconst providers: string[] = [];\n\n\t// Migrate oauth.json\n\tif (existsSync(oauthPath)) {\n\t\ttry {\n\t\t\tconst oauth = JSON.parse(readFileSync(oauthPath, \"utf-8\"));\n\t\t\tfor (const [provider, cred] of Object.entries(oauth)) {\n\t\t\t\tmigrated[provider] = { type: \"oauth\", ...(cred as object) };\n\t\t\t\tproviders.push(provider);\n\t\t\t}\n\t\t\trenameSync(oauthPath, `${oauthPath}.migrated`);\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\t// Migrate settings.json apiKeys\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst settings = JSON.parse(content);\n\t\t\tif (settings.apiKeys && typeof settings.apiKeys === \"object\") {\n\t\t\t\tfor (const [provider, key] of Object.entries(settings.apiKeys)) {\n\t\t\t\t\tif (!migrated[provider] && typeof key === \"string\") {\n\t\t\t\t\t\tmigrated[provider] = { type: \"api_key\", key };\n\t\t\t\t\t\tproviders.push(provider);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete settings.apiKeys;\n\t\t\t\twriteFileSync(settingsPath, JSON.stringify(settings, null, 2));\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\tif (Object.keys(migrated).length > 0) {\n\t\tmkdirSync(dirname(authPath), { recursive: true });\n\t\twriteFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });\n\t}\n\n\treturn providers;\n}\n\n/**\n * Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.\n *\n * Bug in v0.30.0: Sessions were saved to ~/.pi/agent/ instead of\n * ~/.pi/agent/sessions/<encoded-cwd>/. This migration moves them\n * to the correct location based on the cwd in their session header.\n *\n * See: https://github.com/earendil-works/pi-mono/issues/320\n */\nexport function migrateSessionsFromAgentRoot(): void {\n\tconst agentDir = getAgentDir();\n\n\t// Find all .jsonl files directly in agentDir (not in subdirectories)\n\tlet files: string[];\n\ttry {\n\t\tfiles = readdirSync(agentDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(agentDir, f));\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (files.length === 0) return;\n\n\tfor (const file of files) {\n\t\ttry {\n\t\t\t// Read first line to get session header\n\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\tconst firstLine = content.split(\"\\n\")[0];\n\t\t\tif (!firstLine?.trim()) continue;\n\n\t\t\tconst header = JSON.parse(firstLine);\n\t\t\tif (header.type !== \"session\" || !header.cwd) continue;\n\n\t\t\tconst cwd: string = header.cwd;\n\n\t\t\t// Compute the correct session directory (same encoding as session-manager.ts)\n\t\t\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\t\t\tconst correctDir = join(agentDir, \"sessions\", safePath);\n\n\t\t\t// Create directory if needed\n\t\t\tif (!existsSync(correctDir)) {\n\t\t\t\tmkdirSync(correctDir, { recursive: true });\n\t\t\t}\n\n\t\t\t// Move the file (basename handles both separators per platform)\n\t\t\tconst fileName = basename(file);\n\t\t\tconst newPath = join(correctDir, fileName);\n\n\t\t\tif (existsSync(newPath)) continue; // Skip if target exists\n\n\t\t\trenameSync(file, newPath);\n\t\t} catch {\n\t\t\t// Skip files that can't be migrated\n\t\t}\n\t}\n}\n\n/**\n * Migrate commands/ to prompts/ if needed.\n * Works for both regular directories and symlinks.\n */\nfunction migrateCommandsToPrompts(baseDir: string, label: string): boolean {\n\tconst commandsDir = join(baseDir, \"commands\");\n\tconst promptsDir = join(baseDir, \"prompts\");\n\n\tif (existsSync(commandsDir) && !existsSync(promptsDir)) {\n\t\ttry {\n\t\t\trenameSync(commandsDir, promptsDir);\n\t\t\tconsole.log(chalk.green(`Migrated ${label} commands/ → prompts/`));\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tconsole.log(\n\t\t\t\tchalk.yellow(\n\t\t\t\t\t`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction migrateKeybindingsConfigFile(): void {\n\tconst configPath = join(getAgentDir(), \"keybindings.json\");\n\tif (!existsSync(configPath)) return;\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn;\n\t\t}\n\t\tconst { config, migrated } = migrateKeybindingsConfig(parsed as Record<string, unknown>);\n\t\tif (!migrated) return;\n\t\twriteFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf-8\");\n\t} catch {\n\t\t// Ignore malformed files during migration\n\t}\n}\n\n/**\n * Move fd/rg binaries from tools/ to bin/ if they exist.\n */\nfunction migrateToolsToBin(): void {\n\tconst agentDir = getAgentDir();\n\tconst toolsDir = join(agentDir, \"tools\");\n\tconst binDir = getBinDir();\n\n\tif (!existsSync(toolsDir)) return;\n\n\tconst binaries = [\"fd\", \"rg\", \"fd.exe\", \"rg.exe\"];\n\tlet movedAny = false;\n\n\tfor (const bin of binaries) {\n\t\tconst oldPath = join(toolsDir, bin);\n\t\tconst newPath = join(binDir, bin);\n\n\t\tif (existsSync(oldPath)) {\n\t\t\tif (!existsSync(binDir)) {\n\t\t\t\tmkdirSync(binDir, { recursive: true });\n\t\t\t}\n\t\t\tif (!existsSync(newPath)) {\n\t\t\t\ttry {\n\t\t\t\t\trenameSync(oldPath, newPath);\n\t\t\t\t\tmovedAny = true;\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Target exists, just delete the old one\n\t\t\t\ttry {\n\t\t\t\t\trmSync?.(oldPath, { force: true });\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (movedAny) {\n\t\tconsole.log(chalk.green(`Migrated managed binaries tools/ → bin/`));\n\t}\n}\n\n/**\n * Check for deprecated hooks/ and tools/ directories.\n * Note: tools/ may contain fd/rg binaries extracted by pi, so only warn if it has other files.\n */\nfunction checkDeprecatedExtensionDirs(baseDir: string, label: string): string[] {\n\tconst hooksDir = join(baseDir, \"hooks\");\n\tconst toolsDir = join(baseDir, \"tools\");\n\tconst warnings: string[] = [];\n\n\tif (existsSync(hooksDir)) {\n\t\twarnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);\n\t}\n\n\tif (existsSync(toolsDir)) {\n\t\t// Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)\n\t\ttry {\n\t\t\tconst entries = readdirSync(toolsDir);\n\t\t\tconst customTools = entries.filter((e) => {\n\t\t\t\tconst lower = e.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tlower !== \"fd\" && lower !== \"rg\" && lower !== \"fd.exe\" && lower !== \"rg.exe\" && !e.startsWith(\".\") // Ignore .DS_Store and other hidden files\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (customTools.length > 0) {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read errors\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\n/**\n * Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.\n */\nfunction migrateExtensionSystem(cwd: string): string[] {\n\tconst agentDir = getAgentDir();\n\tconst projectDir = join(cwd, CONFIG_DIR_NAME);\n\n\t// Migrate commands/ to prompts/\n\tmigrateCommandsToPrompts(agentDir, \"Global\");\n\tmigrateCommandsToPrompts(projectDir, \"Project\");\n\n\t// Check for deprecated directories\n\tconst warnings = [\n\t\t...checkDeprecatedExtensionDirs(agentDir, \"Global\"),\n\t\t...checkDeprecatedExtensionDirs(projectDir, \"Project\"),\n\t];\n\n\treturn warnings;\n}\n\n/**\n * Print deprecation warnings and wait for keypress.\n */\nexport async function showDeprecationWarnings(warnings: string[]): Promise<void> {\n\tif (warnings.length === 0) return;\n\n\tfor (const warning of warnings) {\n\t\tconsole.log(chalk.yellow(`Warning: ${warning}`));\n\t}\n\tconsole.log(chalk.yellow(`\\nMove your extensions to the extensions/ directory.`));\n\tconsole.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));\n\tconsole.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));\n\tconsole.log(chalk.dim(`\\nPress any key to continue...`));\n\n\t// If stdin is not a TTY (pipe/CI), do not enable raw mode and continue.\n\tif (!process.stdin.isTTY) {\n\t\tconsole.log();\n\t\treturn;\n\t}\n\n\tawait new Promise<void>((resolve) => {\n\t\tlet done = false;\n\t\tconst onData = () => finish();\n\t\tconst onSigint = () => finish(true);\n\n\t\t// Restore stdin state on every exit path (keypress or SIGINT).\n\t\tconst finish = (interrupted = false) => {\n\t\t\tif (done) return;\n\t\t\tdone = true;\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tprocess.removeListener(\"SIGINT\", onSigint);\n\t\t\tprocess.stdin.setRawMode?.(false);\n\t\t\tprocess.stdin.pause();\n\t\t\tif (interrupted) {\n\t\t\t\tconsole.log();\n\t\t\t\tprocess.exit(130);\n\t\t\t}\n\t\t\tresolve();\n\t\t};\n\n\t\tprocess.stdin.setRawMode?.(true);\n\t\tprocess.stdin.resume();\n\t\tprocess.stdin.once(\"data\", onData);\n\t\tprocess.once(\"SIGINT\", onSigint);\n\t});\n\tconsole.log();\n}\n\n/**\n * Run all migrations. Called once on startup.\n *\n * @returns Object with migration results and deprecation warnings\n */\nexport function runMigrations(cwd: string): {\n\tmigratedAuthProviders: string[];\n\tdeprecationWarnings: string[];\n} {\n\tconst migratedAuthProviders = migrateAuthToAuthJson();\n\tmigrateSessionsFromAgentRoot();\n\tmigrateToolsToBin();\n\tmigrateKeybindingsConfigFile();\n\tconst deprecationWarnings = migrateExtensionSystem(cwd);\n\treturn { migratedAuthProviders, deprecationWarnings };\n}\n"]}
1
+ {"version":3,"file":"migrations.js","sourceRoot":"","sources":["../src/migrations.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * One-time migrations that run on startup.\n */\n\nimport chalk from \"chalk\";\nimport { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync } from \"fs\";\nimport { basename, dirname, join } from \"path\";\nimport { CONFIG_DIR_NAME, getAgentDir, getBinDir } from \"./config.js\";\nimport { migrateKeybindingsConfig } from \"./core/keybindings.js\";\n\nconst MIGRATION_GUIDE_URL = \"https://github.com/uglyswap/phi-code/blob/main/packages/coding-agent/CHANGELOG.md\";\nconst EXTENSIONS_DOC_URL = \"https://github.com/uglyswap/phi-code/blob/main/packages/coding-agent/docs/extensions.md\";\n\n/**\n * Migrate legacy oauth.json and settings.json apiKeys to auth.json.\n *\n * @returns Array of provider names that were migrated\n */\nexport function migrateAuthToAuthJson(): string[] {\n\tconst agentDir = getAgentDir();\n\tconst authPath = join(agentDir, \"auth.json\");\n\tconst oauthPath = join(agentDir, \"oauth.json\");\n\tconst settingsPath = join(agentDir, \"settings.json\");\n\n\t// Skip if auth.json already exists\n\tif (existsSync(authPath)) return [];\n\n\tconst migrated: Record<string, unknown> = {};\n\tconst providers: string[] = [];\n\n\t// Migrate oauth.json\n\tif (existsSync(oauthPath)) {\n\t\ttry {\n\t\t\tconst oauth = JSON.parse(readFileSync(oauthPath, \"utf-8\"));\n\t\t\tfor (const [provider, cred] of Object.entries(oauth)) {\n\t\t\t\tmigrated[provider] = { type: \"oauth\", ...(cred as object) };\n\t\t\t\tproviders.push(provider);\n\t\t\t}\n\t\t\trenameSync(oauthPath, `${oauthPath}.migrated`);\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\t// Migrate settings.json apiKeys\n\tif (existsSync(settingsPath)) {\n\t\ttry {\n\t\t\tconst content = readFileSync(settingsPath, \"utf-8\");\n\t\t\tconst settings = JSON.parse(content);\n\t\t\tif (settings.apiKeys && typeof settings.apiKeys === \"object\") {\n\t\t\t\tfor (const [provider, key] of Object.entries(settings.apiKeys)) {\n\t\t\t\t\tif (!migrated[provider] && typeof key === \"string\") {\n\t\t\t\t\t\tmigrated[provider] = { type: \"api_key\", key };\n\t\t\t\t\t\tproviders.push(provider);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tdelete settings.apiKeys;\n\t\t\t\twriteFileSync(settingsPath, JSON.stringify(settings, null, 2));\n\t\t\t}\n\t\t} catch {\n\t\t\t// Skip on error\n\t\t}\n\t}\n\n\tif (Object.keys(migrated).length > 0) {\n\t\tmkdirSync(dirname(authPath), { recursive: true });\n\t\twriteFileSync(authPath, JSON.stringify(migrated, null, 2), { mode: 0o600 });\n\t}\n\n\treturn providers;\n}\n\n/**\n * Migrate sessions from ~/.pi/agent/*.jsonl to proper session directories.\n *\n * Bug in v0.30.0: Sessions were saved to ~/.pi/agent/ instead of\n * ~/.pi/agent/sessions/<encoded-cwd>/. This migration moves them\n * to the correct location based on the cwd in their session header.\n *\n * See: https://github.com/earendil-works/pi-mono/issues/320\n */\nexport function migrateSessionsFromAgentRoot(): void {\n\tconst agentDir = getAgentDir();\n\n\t// Find all .jsonl files directly in agentDir (not in subdirectories)\n\tlet files: string[];\n\ttry {\n\t\tfiles = readdirSync(agentDir)\n\t\t\t.filter((f) => f.endsWith(\".jsonl\"))\n\t\t\t.map((f) => join(agentDir, f));\n\t} catch {\n\t\treturn;\n\t}\n\n\tif (files.length === 0) return;\n\n\tfor (const file of files) {\n\t\ttry {\n\t\t\t// Read first line to get session header\n\t\t\tconst content = readFileSync(file, \"utf8\");\n\t\t\tconst firstLine = content.split(\"\\n\")[0];\n\t\t\tif (!firstLine?.trim()) continue;\n\n\t\t\tconst header = JSON.parse(firstLine);\n\t\t\tif (header.type !== \"session\" || !header.cwd) continue;\n\n\t\t\tconst cwd: string = header.cwd;\n\n\t\t\t// Compute the correct session directory (same encoding as session-manager.ts)\n\t\t\tconst safePath = `--${cwd.replace(/^[/\\\\]/, \"\").replace(/[/\\\\:]/g, \"-\")}--`;\n\t\t\tconst correctDir = join(agentDir, \"sessions\", safePath);\n\n\t\t\t// Create directory if needed\n\t\t\tif (!existsSync(correctDir)) {\n\t\t\t\tmkdirSync(correctDir, { recursive: true });\n\t\t\t}\n\n\t\t\t// Move the file (basename handles both separators per platform)\n\t\t\tconst fileName = basename(file);\n\t\t\tconst newPath = join(correctDir, fileName);\n\n\t\t\tif (existsSync(newPath)) continue; // Skip if target exists\n\n\t\t\trenameSync(file, newPath);\n\t\t} catch {\n\t\t\t// Skip files that can't be migrated\n\t\t}\n\t}\n}\n\n/**\n * Migrate commands/ to prompts/ if needed.\n * Works for both regular directories and symlinks.\n */\nfunction migrateCommandsToPrompts(baseDir: string, label: string): boolean {\n\tconst commandsDir = join(baseDir, \"commands\");\n\tconst promptsDir = join(baseDir, \"prompts\");\n\n\tif (existsSync(commandsDir) && !existsSync(promptsDir)) {\n\t\ttry {\n\t\t\trenameSync(commandsDir, promptsDir);\n\t\t\tconsole.log(chalk.green(`Migrated ${label} commands/ → prompts/`));\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tconsole.log(\n\t\t\t\tchalk.yellow(\n\t\t\t\t\t`Warning: Could not migrate ${label} commands/ to prompts/: ${err instanceof Error ? err.message : err}`,\n\t\t\t\t),\n\t\t\t);\n\t\t}\n\t}\n\treturn false;\n}\n\nfunction migrateKeybindingsConfigFile(): void {\n\tconst configPath = join(getAgentDir(), \"keybindings.json\");\n\tif (!existsSync(configPath)) return;\n\n\ttry {\n\t\tconst parsed = JSON.parse(readFileSync(configPath, \"utf-8\")) as unknown;\n\t\tif (typeof parsed !== \"object\" || parsed === null || Array.isArray(parsed)) {\n\t\t\treturn;\n\t\t}\n\t\tconst { config, migrated } = migrateKeybindingsConfig(parsed as Record<string, unknown>);\n\t\tif (!migrated) return;\n\t\twriteFileSync(configPath, `${JSON.stringify(config, null, 2)}\\n`, \"utf-8\");\n\t} catch {\n\t\t// Ignore malformed files during migration\n\t}\n}\n\n/**\n * Move fd/rg binaries from tools/ to bin/ if they exist.\n */\nfunction migrateToolsToBin(): void {\n\tconst agentDir = getAgentDir();\n\tconst toolsDir = join(agentDir, \"tools\");\n\tconst binDir = getBinDir();\n\n\tif (!existsSync(toolsDir)) return;\n\n\tconst binaries = [\"fd\", \"rg\", \"fd.exe\", \"rg.exe\"];\n\tlet movedAny = false;\n\n\tfor (const bin of binaries) {\n\t\tconst oldPath = join(toolsDir, bin);\n\t\tconst newPath = join(binDir, bin);\n\n\t\tif (existsSync(oldPath)) {\n\t\t\tif (!existsSync(binDir)) {\n\t\t\t\tmkdirSync(binDir, { recursive: true });\n\t\t\t}\n\t\t\tif (!existsSync(newPath)) {\n\t\t\t\ttry {\n\t\t\t\t\trenameSync(oldPath, newPath);\n\t\t\t\t\tmovedAny = true;\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore errors\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\t// Target exists, just delete the old one\n\t\t\t\ttry {\n\t\t\t\t\trmSync?.(oldPath, { force: true });\n\t\t\t\t} catch {\n\t\t\t\t\t// Ignore\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tif (movedAny) {\n\t\tconsole.log(chalk.green(`Migrated managed binaries tools/ → bin/`));\n\t}\n}\n\n/**\n * Check for deprecated hooks/ and tools/ directories.\n * Note: tools/ may contain fd/rg binaries extracted by pi, so only warn if it has other files.\n */\nfunction checkDeprecatedExtensionDirs(baseDir: string, label: string): string[] {\n\tconst hooksDir = join(baseDir, \"hooks\");\n\tconst toolsDir = join(baseDir, \"tools\");\n\tconst warnings: string[] = [];\n\n\tif (existsSync(hooksDir)) {\n\t\twarnings.push(`${label} hooks/ directory found. Hooks have been renamed to extensions.`);\n\t}\n\n\tif (existsSync(toolsDir)) {\n\t\t// Check if tools/ contains anything other than fd/rg (which are auto-extracted binaries)\n\t\ttry {\n\t\t\tconst entries = readdirSync(toolsDir);\n\t\t\tconst customTools = entries.filter((e) => {\n\t\t\t\tconst lower = e.toLowerCase();\n\t\t\t\treturn (\n\t\t\t\t\tlower !== \"fd\" && lower !== \"rg\" && lower !== \"fd.exe\" && lower !== \"rg.exe\" && !e.startsWith(\".\") // Ignore .DS_Store and other hidden files\n\t\t\t\t);\n\t\t\t});\n\t\t\tif (customTools.length > 0) {\n\t\t\t\twarnings.push(\n\t\t\t\t\t`${label} tools/ directory contains custom tools. Custom tools have been merged into extensions.`,\n\t\t\t\t);\n\t\t\t}\n\t\t} catch {\n\t\t\t// Ignore read errors\n\t\t}\n\t}\n\n\treturn warnings;\n}\n\n/**\n * Run extension system migrations (commands→prompts) and collect warnings about deprecated directories.\n */\nfunction migrateExtensionSystem(cwd: string): string[] {\n\tconst agentDir = getAgentDir();\n\tconst projectDir = join(cwd, CONFIG_DIR_NAME);\n\n\t// Migrate commands/ to prompts/\n\tmigrateCommandsToPrompts(agentDir, \"Global\");\n\tmigrateCommandsToPrompts(projectDir, \"Project\");\n\n\t// Check for deprecated directories\n\tconst warnings = [\n\t\t...checkDeprecatedExtensionDirs(agentDir, \"Global\"),\n\t\t...checkDeprecatedExtensionDirs(projectDir, \"Project\"),\n\t];\n\n\treturn warnings;\n}\n\n/**\n * Print deprecation warnings and wait for keypress.\n */\nexport async function showDeprecationWarnings(warnings: string[]): Promise<void> {\n\tif (warnings.length === 0) return;\n\n\tfor (const warning of warnings) {\n\t\tconsole.log(chalk.yellow(`Warning: ${warning}`));\n\t}\n\tconsole.log(chalk.yellow(`\\nMove your extensions to the extensions/ directory.`));\n\tconsole.log(chalk.yellow(`Migration guide: ${MIGRATION_GUIDE_URL}`));\n\tconsole.log(chalk.yellow(`Documentation: ${EXTENSIONS_DOC_URL}`));\n\tconsole.log(chalk.dim(`\\nPress any key to continue...`));\n\n\t// If stdin is not a TTY (pipe/CI), do not enable raw mode and continue.\n\tif (!process.stdin.isTTY) {\n\t\tconsole.log();\n\t\treturn;\n\t}\n\n\tawait new Promise<void>((resolve) => {\n\t\tlet done = false;\n\t\tconst onData = () => finish();\n\t\tconst onSigint = () => finish(true);\n\n\t\t// Restore stdin state on every exit path (keypress or SIGINT).\n\t\tconst finish = (interrupted = false) => {\n\t\t\tif (done) return;\n\t\t\tdone = true;\n\t\t\tprocess.stdin.removeListener(\"data\", onData);\n\t\t\tprocess.removeListener(\"SIGINT\", onSigint);\n\t\t\tprocess.stdin.setRawMode?.(false);\n\t\t\tprocess.stdin.pause();\n\t\t\tif (interrupted) {\n\t\t\t\tconsole.log();\n\t\t\t\tprocess.exit(130);\n\t\t\t}\n\t\t\tresolve();\n\t\t};\n\n\t\tprocess.stdin.setRawMode?.(true);\n\t\tprocess.stdin.resume();\n\t\tprocess.stdin.once(\"data\", onData);\n\t\tprocess.once(\"SIGINT\", onSigint);\n\t});\n\tconsole.log();\n}\n\n/**\n * Run all migrations. Called once on startup.\n *\n * @returns Object with migration results and deprecation warnings\n */\nexport function runMigrations(cwd: string): {\n\tmigratedAuthProviders: string[];\n\tdeprecationWarnings: string[];\n} {\n\tconst migratedAuthProviders = migrateAuthToAuthJson();\n\tmigrateSessionsFromAgentRoot();\n\tmigrateToolsToBin();\n\tmigrateKeybindingsConfigFile();\n\tconst deprecationWarnings = migrateExtensionSystem(cwd);\n\treturn { migratedAuthProviders, deprecationWarnings };\n}\n"]}
@@ -20,7 +20,7 @@
20
20
  import { access, mkdir, readFile, writeFile } from "node:fs/promises";
21
21
  import { homedir } from "node:os";
22
22
  import { join } from "node:path";
23
- import type { ExtensionAPI, ExtensionContext } from "phi-code";
23
+ import { type ExtensionAPI, type ExtensionContext, getApiKeyStore } from "phi-code";
24
24
 
25
25
  // ─── Types ───────────────────────────────────────────────────────────────
26
26
 
@@ -394,9 +394,19 @@ function getProviderConfigs(): ProviderConfig[] {
394
394
  async function getAvailableModels(): Promise<Array<{ id: string; provider: string; baseUrl: string; apiKey: string }>> {
395
395
  const models: Array<{ id: string; provider: string; baseUrl: string; apiKey: string }> = [];
396
396
 
397
- // 1. Cloud providers via env vars
397
+ // 1. Cloud providers via env vars, falling back to keys stored in
398
+ // models.json via /setup or /keys (the store resolves env-var names and
399
+ // "!cmd" values the same way the model registry does at request time).
400
+ const store = getApiKeyStore();
398
401
  for (const provider of getProviderConfigs()) {
399
- const apiKey = process.env[provider.envVar];
402
+ let apiKey = process.env[provider.envVar];
403
+ if (!apiKey) {
404
+ try {
405
+ apiKey = store.getKey(provider.name);
406
+ } catch {
407
+ /* unreadable models.json — env-only behavior */
408
+ }
409
+ }
400
410
  if (!apiKey) continue;
401
411
 
402
412
  for (const modelId of provider.models) {
@@ -409,33 +419,27 @@ async function getAvailableModels(): Promise<Array<{ id: string; provider: strin
409
419
  }
410
420
  }
411
421
 
412
- // 2. Local providers (LM Studio, Ollama) — auto-detect via models.json
413
- const { join } = await import("node:path");
414
- const { homedir } = await import("node:os");
415
- const { readFileSync, existsSync } = await import("node:fs");
416
-
417
- const modelsJsonPath = join(homedir(), ".phi", "agent", "models.json");
418
- if (existsSync(modelsJsonPath)) {
419
- try {
420
- const config = JSON.parse(readFileSync(modelsJsonPath, "utf-8"));
421
- if (config.providers) {
422
- for (const [id, providerConfig] of Object.entries<any>(config.providers)) {
423
- const baseUrl = providerConfig.baseUrl || "";
424
- const apiKey = providerConfig.apiKey || "local";
425
- if (providerConfig.models?.length > 0) {
426
- for (const m of providerConfig.models) {
427
- const modelId = typeof m === "string" ? m : m.id;
428
- // Skip if already added from env vars
429
- if (!models.some((existing) => existing.id === modelId && existing.baseUrl === baseUrl)) {
430
- models.push({ id: modelId, provider: id, baseUrl, apiKey });
431
- }
432
- }
433
- }
422
+ // 2. Local providers (LM Studio, Ollama) — auto-detect via models.json,
423
+ // read through ApiKeyStore so // comments and trailing commas are
424
+ // tolerated and PHI_AGENT_DIR overrides are honored.
425
+ try {
426
+ for (const id of store.listProviders()) {
427
+ const providerConfig = store.getProvider(id);
428
+ if (!providerConfig) continue;
429
+ const baseUrl = providerConfig.baseUrl || "";
430
+ const apiKey = store.getKey(id) || "local";
431
+ if (!providerConfig.models || providerConfig.models.length === 0) continue;
432
+ for (const m of providerConfig.models) {
433
+ const modelId = typeof m === "string" ? m : (m as { id?: string }).id;
434
+ if (!modelId) continue;
435
+ // Skip if already added from env vars
436
+ if (!models.some((existing) => existing.id === modelId && existing.baseUrl === baseUrl)) {
437
+ models.push({ id: modelId, provider: id, baseUrl, apiKey });
434
438
  }
435
439
  }
436
- } catch {
437
- /* ignore parse errors */
438
440
  }
441
+ } catch {
442
+ /* ignore unreadable models.json */
439
443
  }
440
444
 
441
445
  // 3. Try to detect LM Studio (port 1234) and Ollama (port 11434) directly
@@ -745,7 +749,7 @@ Scoring: S (80+), A (65+), B (50+), C (35+), D (<35)`,
745
749
  .map((p) => ` ${p.envVar}: ${process.env[p.envVar] ? "set but no models configured" : "not set"}`)
746
750
  .join("\n");
747
751
  ctx.ui.notify(
748
- `❌ No benchmarkable models found.\n\nProvider status:\n${hint}\n\nSet at least one API key with known models.`,
752
+ `❌ No benchmarkable models found.\n\nProvider status (env):\n${hint}\n\nSet at least one API key with known models (env var, /setup or /keys).`,
749
753
  "warning",
750
754
  );
751
755
  return;
@@ -689,7 +689,7 @@ violates project rules.
689
689
  const locations = [
690
690
  join(process.cwd(), "AGENTS.md"),
691
691
  join(process.cwd(), ".phi", "AGENTS.md"),
692
- join(sigmaMemory.config.memoryDir, "AGENTS.md"),
692
+ join(sigmaMemory.getConfig().memoryDir, "AGENTS.md"),
693
693
  ];
694
694
 
695
695
  for (const agentsPath of locations) {
@@ -159,10 +159,11 @@ async function refreshOne(
159
159
  resolvedApiKey?: string,
160
160
  ): Promise<RefreshOutcome> {
161
161
  const stored = store.getProvider(providerId);
162
- const storedKey =
163
- stored?.apiKey && !stored.apiKey.startsWith("$") && stored.apiKey !== "local" ? stored.apiKey : undefined;
164
- // Prefer the key stored in models.json, else the one resolved from
162
+ // Prefer the key stored in models.json (resolved through the store: env-var
163
+ // names and "!cmd" values yield a usable key, "local" is a sentinel, an
164
+ // unresolved "$NAME" yields undefined), else the one resolved from
165
165
  // auth.json/env by the model registry (providers set up via /auth only).
166
+ const storedKey = stored?.apiKey && stored.apiKey !== "local" ? store.getKey(providerId) : undefined;
166
167
  const apiKey = storedKey ?? resolvedApiKey;
167
168
 
168
169
  // OpenCode Go is a provider pair the generic fetchLiveModels path can't express