one-code-extension 0.1.4 → 0.3.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 (300) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +222 -177
  3. package/agents/explore.md +9 -6
  4. package/agents/general-purpose.md +1 -1
  5. package/agents/plan.md +6 -4
  6. package/docs/guide/README.md +52 -0
  7. package/docs/guide/bring-your-claude-code-setup.md +128 -0
  8. package/docs/guide/configuration.md +173 -0
  9. package/docs/guide/differences-from-claude-code.md +152 -0
  10. package/docs/guide/doctor.md +88 -0
  11. package/docs/guide/hooks.md +111 -0
  12. package/docs/guide/installation.md +126 -0
  13. package/docs/guide/permissions-modes-and-auto-mode.md +452 -0
  14. package/docs/guide/providers-and-models.md +196 -0
  15. package/docs/guide/reference.md +226 -0
  16. package/docs/guide/sessions-and-context.md +192 -0
  17. package/docs/guide/skills-plugins-and-mcp.md +178 -0
  18. package/docs/guide/subagents-and-workflows.md +195 -0
  19. package/docs/guide/tasks-and-background-work.md +113 -0
  20. package/docs/guide/terminal-interface.md +157 -0
  21. package/docs/guide/tools.md +177 -0
  22. package/docs/guide/troubleshooting.md +177 -0
  23. package/extensions/ask-user/index.ts +65 -71
  24. package/extensions/ask-user/questions.ts +15 -28
  25. package/extensions/ask-user/widget.ts +533 -0
  26. package/extensions/auto-mode/actions.ts +9 -26
  27. package/extensions/auto-mode/classifier-prompt.ts +127 -0
  28. package/extensions/auto-mode/classifier.ts +305 -227
  29. package/extensions/auto-mode/config.ts +279 -77
  30. package/extensions/auto-mode/defaults.ts +17 -60
  31. package/extensions/auto-mode/instructions.ts +2 -1
  32. package/extensions/auto-mode/model-picker.ts +53 -31
  33. package/extensions/auto-mode/model-select.ts +153 -82
  34. package/extensions/auto-mode/paths.ts +13 -1
  35. package/extensions/auto-mode/pause.ts +8 -0
  36. package/extensions/auto-mode/prompt.ts +205 -238
  37. package/extensions/auto-mode/recoverability.ts +229 -0
  38. package/extensions/auto-mode/rules.ts +92 -75
  39. package/extensions/auto-mode/safety-floor.ts +43 -12
  40. package/extensions/auto-mode/sensitive.ts +11 -0
  41. package/extensions/auto-mode/setup-run.ts +245 -0
  42. package/extensions/auto-mode/setup.ts +235 -0
  43. package/extensions/auto-mode/shell-analysis.ts +332 -26
  44. package/extensions/auto-mode/transcript.ts +102 -0
  45. package/extensions/background/index.ts +256 -43
  46. package/extensions/background/monitor-batch.ts +58 -0
  47. package/extensions/background/registry.ts +9 -0
  48. package/extensions/background/wakeup.ts +45 -0
  49. package/extensions/bash/background.ts +47 -22
  50. package/extensions/bash/guards.ts +229 -0
  51. package/extensions/bash/index.ts +81 -19
  52. package/extensions/branding/index.ts +44 -18
  53. package/extensions/branding/startup.ts +23 -8
  54. package/extensions/claude-compat/index.ts +17 -2
  55. package/extensions/claude-context/index.ts +41 -12
  56. package/extensions/clear/index.ts +27 -0
  57. package/extensions/compaction/fit.ts +121 -0
  58. package/extensions/compaction/index.ts +176 -71
  59. package/extensions/compaction/prompt.ts +21 -1
  60. package/extensions/context-budget/budget.ts +61 -0
  61. package/extensions/context-budget/index.ts +66 -0
  62. package/extensions/context-management/index.ts +40 -16
  63. package/extensions/doctor/build.ts +226 -0
  64. package/extensions/doctor/cli.ts +99 -0
  65. package/extensions/doctor/compat.ts +532 -0
  66. package/extensions/doctor/dependencies.ts +186 -0
  67. package/extensions/doctor/fix-prompt.ts +125 -0
  68. package/extensions/doctor/index.ts +308 -0
  69. package/extensions/doctor/models.ts +231 -0
  70. package/extensions/doctor/presets.ts +208 -0
  71. package/extensions/doctor/report.ts +189 -0
  72. package/extensions/doctor/update-lookup.ts +46 -0
  73. package/extensions/doctor/viewer.ts +124 -0
  74. package/extensions/effort/index.ts +41 -4
  75. package/extensions/effort/slider.ts +33 -2
  76. package/extensions/file-tracker/index.ts +143 -20
  77. package/extensions/file-tracker/replay.ts +46 -0
  78. package/extensions/file-tracker/tracker.ts +51 -4
  79. package/extensions/footer/footer-line.ts +207 -0
  80. package/extensions/footer/index.ts +152 -0
  81. package/extensions/footer/pr.ts +53 -0
  82. package/extensions/hooks/executor.ts +57 -20
  83. package/extensions/hooks/formatter-notice.ts +76 -0
  84. package/extensions/hooks/index.ts +314 -73
  85. package/extensions/hooks/matcher.ts +35 -1
  86. package/extensions/hooks/plugin-hooks.ts +3 -3
  87. package/extensions/hooks/protocol.ts +58 -0
  88. package/extensions/hooks/subagent-bridge.ts +77 -0
  89. package/extensions/hooks/trust.ts +15 -4
  90. package/extensions/init/index.ts +26 -0
  91. package/extensions/init/prompt.ts +40 -0
  92. package/extensions/interrupted/index.ts +35 -0
  93. package/extensions/interrupted/line.ts +9 -0
  94. package/extensions/lib/abort.ts +15 -0
  95. package/extensions/lib/agent-loader.ts +183 -0
  96. package/extensions/lib/anthropic-payload.ts +15 -0
  97. package/extensions/lib/atomic-write.ts +56 -0
  98. package/extensions/lib/bridge-watch.ts +20 -0
  99. package/extensions/lib/cache-retention.ts +60 -0
  100. package/extensions/lib/capability-index.ts +466 -0
  101. package/extensions/lib/claude-context.ts +318 -27
  102. package/extensions/lib/claude-settings.ts +64 -0
  103. package/extensions/lib/consent-preview.ts +30 -0
  104. package/extensions/lib/deferred.ts +144 -13
  105. package/extensions/lib/favorites.ts +43 -0
  106. package/extensions/lib/fetch-timeout.ts +15 -0
  107. package/extensions/lib/frontmatter.ts +31 -0
  108. package/extensions/lib/git.ts +44 -2
  109. package/extensions/lib/hook-gate.ts +90 -0
  110. package/extensions/lib/interrupt.ts +94 -0
  111. package/extensions/lib/mcp-overrides.ts +61 -0
  112. package/extensions/lib/mcp-share.ts +25 -0
  113. package/extensions/lib/mcp-status.ts +34 -0
  114. package/extensions/lib/memory.ts +41 -6
  115. package/extensions/lib/model-facts.generated.json +2480 -0
  116. package/extensions/lib/model-facts.ts +162 -0
  117. package/extensions/lib/model-policy.ts +145 -189
  118. package/extensions/lib/model-tier.ts +480 -64
  119. package/extensions/lib/model-unusable.ts +37 -0
  120. package/extensions/lib/notifications.ts +320 -1
  121. package/extensions/lib/one-code-settings.ts +63 -0
  122. package/extensions/lib/original-command.ts +71 -0
  123. package/extensions/lib/package-version.ts +31 -0
  124. package/extensions/lib/paths.ts +39 -4
  125. package/extensions/lib/permission-gate.ts +192 -0
  126. package/extensions/lib/persisted-output.ts +18 -0
  127. package/extensions/lib/pi-ai-estimate.ts +160 -0
  128. package/extensions/lib/pi-version.ts +1 -1
  129. package/extensions/lib/plan-mode-channels.ts +11 -0
  130. package/extensions/lib/plugin-overrides.ts +24 -0
  131. package/extensions/lib/plugin-root.ts +44 -0
  132. package/extensions/lib/plugins.ts +233 -54
  133. package/extensions/lib/prefix-warm-gate.ts +157 -0
  134. package/extensions/lib/process-tree.ts +56 -0
  135. package/extensions/lib/reminders.ts +321 -68
  136. package/extensions/lib/scratchpad.ts +11 -8
  137. package/extensions/lib/session-lifecycle.ts +28 -0
  138. package/extensions/lib/settings-channels.ts +14 -0
  139. package/extensions/lib/shell-tasks.ts +54 -0
  140. package/extensions/lib/skill-overrides.ts +86 -0
  141. package/extensions/lib/skill-scan.ts +110 -0
  142. package/extensions/lib/text-width.ts +267 -0
  143. package/extensions/lib/tui-render.ts +281 -54
  144. package/extensions/lib/usage-bus.ts +75 -0
  145. package/extensions/lib/usage-tracker.ts +67 -0
  146. package/extensions/lib/worktree-isolation.ts +170 -0
  147. package/extensions/lsp/client.ts +69 -12
  148. package/extensions/lsp/format.ts +12 -2
  149. package/extensions/lsp/index.ts +273 -84
  150. package/extensions/lsp/install-hints.ts +43 -0
  151. package/extensions/lsp/keep-alive.ts +19 -0
  152. package/extensions/lsp/plugin-servers.ts +331 -0
  153. package/extensions/lsp/watcher.ts +157 -0
  154. package/extensions/mcp/announce.ts +99 -0
  155. package/extensions/mcp/client.ts +181 -22
  156. package/extensions/mcp/config.ts +55 -24
  157. package/extensions/mcp/index.ts +586 -86
  158. package/extensions/mcp/oauth/browser.ts +26 -0
  159. package/extensions/mcp/oauth/callback.ts +104 -0
  160. package/extensions/mcp/oauth/flow.ts +68 -0
  161. package/extensions/mcp/oauth/provider.ts +147 -0
  162. package/extensions/mcp/oauth/store.ts +86 -0
  163. package/extensions/mcp/panel/keys.ts +34 -0
  164. package/extensions/mcp/panel/model.ts +62 -0
  165. package/extensions/mcp/panel/render.ts +141 -0
  166. package/extensions/mcp/panel/state.ts +108 -0
  167. package/extensions/mcp/schema.ts +83 -6
  168. package/extensions/mcp/trust.ts +340 -0
  169. package/extensions/memory/entries.ts +163 -0
  170. package/extensions/memory/index.ts +119 -2
  171. package/extensions/memory/open-external.ts +97 -0
  172. package/extensions/memory/panel.ts +124 -0
  173. package/extensions/model-default/index.ts +74 -0
  174. package/extensions/notebook/index.ts +36 -18
  175. package/extensions/notebook/notebook.ts +110 -17
  176. package/extensions/permissions/index.ts +1012 -247
  177. package/extensions/permissions/matcher.ts +488 -57
  178. package/extensions/permissions/modes.ts +55 -7
  179. package/extensions/permissions/project-trust.ts +81 -0
  180. package/extensions/permissions/protected-paths.ts +16 -3
  181. package/extensions/permissions/rule-prose.ts +79 -0
  182. package/extensions/permissions/session-grant.ts +91 -0
  183. package/extensions/permissions/settings.ts +112 -33
  184. package/extensions/permissions/subagent-gate.ts +57 -0
  185. package/extensions/plan-mode/index.ts +143 -33
  186. package/extensions/plan-mode/reminder.ts +16 -19
  187. package/extensions/plan-mode/viewer.ts +40 -31
  188. package/extensions/plugins/counts.ts +68 -0
  189. package/extensions/plugins/index.ts +496 -32
  190. package/extensions/plugins/install/install.ts +167 -0
  191. package/extensions/plugins/install/paths.ts +23 -0
  192. package/extensions/plugins/install/registry.ts +72 -0
  193. package/extensions/plugins/marketplace/git.ts +103 -0
  194. package/extensions/plugins/marketplace/official.ts +36 -0
  195. package/extensions/plugins/marketplace/parse.ts +94 -0
  196. package/extensions/plugins/marketplace/registry.ts +60 -0
  197. package/extensions/plugins/marketplace/sync.ts +146 -0
  198. package/extensions/plugins/marketplace/types.ts +128 -0
  199. package/extensions/plugins/panel/keys.ts +52 -0
  200. package/extensions/plugins/panel/render.ts +315 -0
  201. package/extensions/plugins/panel/rows.ts +230 -0
  202. package/extensions/plugins/panel/state.ts +333 -0
  203. package/extensions/recap/index.ts +215 -0
  204. package/extensions/recap/prompt.ts +44 -0
  205. package/extensions/recap/scheduler.ts +87 -0
  206. package/extensions/search-tools/index.ts +0 -0
  207. package/extensions/search-tools/tools.ts +7 -5
  208. package/extensions/skill/index.ts +360 -34
  209. package/extensions/skill/invoke.ts +185 -0
  210. package/extensions/skill/listing.ts +21 -0
  211. package/extensions/skill/panel/keys.ts +41 -0
  212. package/extensions/skill/panel/render.ts +91 -0
  213. package/extensions/skill/panel/state.ts +115 -0
  214. package/extensions/spinner/index.ts +110 -0
  215. package/extensions/spinner/line.ts +51 -0
  216. package/extensions/spinner/verbs.ts +201 -0
  217. package/extensions/subagents/agents.ts +88 -44
  218. package/extensions/subagents/default-model.ts +62 -32
  219. package/extensions/subagents/delegation-steer.ts +23 -0
  220. package/extensions/subagents/hand-back-review.ts +67 -0
  221. package/extensions/subagents/index.ts +1545 -346
  222. package/extensions/subagents/live-runs.ts +293 -0
  223. package/extensions/subagents/model-select.ts +252 -82
  224. package/extensions/subagents/outcome.ts +90 -0
  225. package/extensions/subagents/panel-keys.ts +41 -0
  226. package/extensions/subagents/panel-render.ts +357 -0
  227. package/extensions/subagents/panel-widget.ts +330 -0
  228. package/extensions/subagents/pending-claim.ts +45 -0
  229. package/extensions/subagents/prose.ts +105 -0
  230. package/extensions/subagents/runner.ts +674 -0
  231. package/extensions/subagents/runs.ts +68 -1
  232. package/extensions/subagents/send-to-main-tool.ts +55 -0
  233. package/extensions/subagents/session-turns.ts +138 -0
  234. package/extensions/subagents/shell-panel.ts +198 -0
  235. package/extensions/subagents/usage.ts +16 -4
  236. package/extensions/subagents/worktree.ts +13 -0
  237. package/extensions/system-prompt/environment.ts +5 -3
  238. package/extensions/system-prompt/git-status.ts +93 -0
  239. package/extensions/system-prompt/index.ts +23 -1
  240. package/extensions/system-prompt/template.ts +34 -4
  241. package/extensions/system-prompt/tiers/common.ts +31 -6
  242. package/extensions/system-prompt/tiers/low.ts +13 -3
  243. package/extensions/system-prompt/tiers/mid.ts +21 -8
  244. package/extensions/system-reminder/index.ts +53 -9
  245. package/extensions/tasks/index.ts +65 -16
  246. package/extensions/tasks/store.ts +76 -3
  247. package/extensions/tool-search/announce.ts +42 -0
  248. package/extensions/tool-search/index.ts +136 -13
  249. package/extensions/turn-duration/index.ts +60 -0
  250. package/extensions/turn-duration/line.ts +22 -0
  251. package/extensions/turn-duration/span.ts +41 -0
  252. package/extensions/turn-duration/verbs.ts +24 -0
  253. package/extensions/web/backends.ts +421 -0
  254. package/extensions/web/index.ts +178 -21
  255. package/extensions/web-fetch/index.ts +74 -18
  256. package/extensions/web-fetch/summarize.ts +1 -1
  257. package/extensions/workflow/agent-session.ts +182 -112
  258. package/extensions/workflow/globals.ts +129 -106
  259. package/extensions/workflow/index.ts +343 -28
  260. package/extensions/workflow/records.ts +118 -0
  261. package/extensions/workflow/run-manager.ts +80 -31
  262. package/extensions/workflow/saved-workflows.ts +2 -1
  263. package/extensions/workflow/script-worker.mjs +110 -0
  264. package/extensions/workflow/types.ts +74 -8
  265. package/extensions/workflow/viewer.ts +562 -0
  266. package/extensions/workflow/vm-runtime.ts +118 -22
  267. package/extensions/workflow/widget.ts +126 -18
  268. package/extensions/worktree/guards.ts +240 -0
  269. package/extensions/worktree/index.ts +30 -5
  270. package/extensions/worktree/rewrite.ts +32 -16
  271. package/package.json +20 -10
  272. package/skills/code-review/SKILL.md +165 -0
  273. package/skills/fewer-permission-prompts/SKILL.md +61 -0
  274. package/skills/security-review/SKILL.md +178 -0
  275. package/skills/simplify/SKILL.md +70 -0
  276. package/themes/{one-code-light.json → onecode-light.json} +6 -1
  277. package/themes/{one-code.json → onecode.json} +6 -1
  278. package/types/app.d.ts +8 -1
  279. package/types/community-packages.d.ts +45 -2
  280. package/docs/decisions/auto-mode.md +0 -526
  281. package/docs/decisions/branding.md +0 -127
  282. package/docs/decisions/code-review-remediation.md +0 -106
  283. package/docs/decisions/compaction.md +0 -140
  284. package/docs/decisions/distribution.md +0 -168
  285. package/docs/decisions/hooks.md +0 -64
  286. package/docs/decisions/lsp.md +0 -58
  287. package/docs/decisions/mcp.md +0 -104
  288. package/docs/decisions/memory-state.md +0 -170
  289. package/docs/decisions/model-policy.md +0 -126
  290. package/docs/decisions/modes.md +0 -178
  291. package/docs/decisions/skills-plugins.md +0 -56
  292. package/docs/decisions/subagents-workflows.md +0 -225
  293. package/docs/decisions/system-prompt.md +0 -98
  294. package/docs/decisions/tools.md +0 -407
  295. package/docs/decisions/tui.md +0 -217
  296. package/docs/decisions.md +0 -148
  297. package/extensions/subagents/child.ts +0 -414
  298. package/extensions/subagents/rpc-turns.ts +0 -123
  299. package/extensions/todo/index.ts +0 -133
  300. package/extensions/workflow/permission-gate.ts +0 -71
@@ -0,0 +1,226 @@
1
+ /**
2
+ * Assemble the doctor report from gathered inputs (pure).
3
+ *
4
+ * Section order is the order a first-session user needs answers in: is the
5
+ * install sound, is any provider ready, which model does each role get, what
6
+ * would the presets change, what of my Claude Code setup was picked up, are my
7
+ * MCP servers reachable, what external programs are missing — then the issues.
8
+ */
9
+
10
+ import { dirname, join } from "node:path";
11
+ import { fileURLToPath } from "node:url";
12
+ import type { Api, Model } from "@earendil-works/pi-ai";
13
+ import { extensionVersion } from "../lib/package-version.ts";
14
+ import { piVersionWarning, TESTED_PI_MAX_EXCLUSIVE, TESTED_PI_MIN } from "../lib/pi-version.ts";
15
+ import { modelSpec } from "../lib/model-policy.ts";
16
+ import { readJsonFile } from "../lib/atomic-write.ts";
17
+ import { oneCodeSettingsPath } from "../lib/one-code-settings.ts";
18
+ import { webSearchSettingsFrom } from "../web/backends.ts";
19
+ import { collectCompat, importedConfigSection, mcpSection } from "./compat.ts";
20
+ import { checkDependencies, dependenciesSection } from "./dependencies.ts";
21
+ import { collectModelFacts, modelsSection } from "./models.ts";
22
+ import { computePresets, presetsSection } from "./presets.ts";
23
+ import {
24
+ countNoun,
25
+ type DoctorEnvironment,
26
+ type DoctorReport,
27
+ type Finding,
28
+ type RegistryView,
29
+ REPORT_TITLE,
30
+ type ReportLine,
31
+ type ReportSection,
32
+ type SessionView,
33
+ shortenHome,
34
+ } from "./report.ts";
35
+
36
+ export const BUNDLED_AGENTS_DIR = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "agents");
37
+
38
+ /** The environment variables a first-time user is most likely to already have; hinted when nothing is configured. */
39
+ export const COMMON_KEY_ENV_VARS = ["ANTHROPIC_API_KEY", "OPENAI_API_KEY", "GEMINI_API_KEY", "OPENROUTER_API_KEY", "OPENCODE_API_KEY"];
40
+
41
+ const AUTH_SOURCE_LABEL: Record<string, string> = {
42
+ stored: "key saved by /login",
43
+ environment: "environment variable",
44
+ runtime: "provided by an extension",
45
+ fallback: "default credentials",
46
+ models_json_key: "key in models.json",
47
+ models_json_command: "key command in models.json",
48
+ };
49
+
50
+ export function installationSection(env: DoctorEnvironment, findings: Finding[]): ReportSection {
51
+ const lines: ReportLine[] = [];
52
+ const pi = env.piVersion ? ` · pi ${env.piVersion}` : "";
53
+ lines.push({
54
+ text: env.install === "app" ? `Running: the bundled onecode app ${env.oneCodeVersion}${pi}` : `Running: one-code-extension ${env.oneCodeVersion} on your own pi${pi}`,
55
+ level: "ok",
56
+ });
57
+ lines.push({ text: `Node ${env.nodeVersion} · ${env.platform}-${env.arch}`, level: "dim" });
58
+ lines.push({ text: `pi agent dir: ${shortenHome(env.agentDir, env.home)} (auth, models, sessions, pi settings)`, level: "dim" });
59
+ lines.push({ text: `One Code state: ${shortenHome(env.stateDir, env.home)} (own settings, plans)`, level: "dim" });
60
+ const claudeDir = env.env.CLAUDE_CONFIG_DIR ? `${env.env.CLAUDE_CONFIG_DIR} (CLAUDE_CONFIG_DIR)` : shortenHome(`${env.home}/.claude`, env.home);
61
+ lines.push({ text: `Claude Code config read from: ${claudeDir} (never written)`, level: "dim" });
62
+
63
+ const versionWarning = piVersionWarning(env.piVersion);
64
+ if (versionWarning) {
65
+ lines.push({ text: `pi ${env.piVersion} is outside the tested range ${TESTED_PI_MIN} – <${TESTED_PI_MAX_EXCLUSIVE}`, level: "warn" });
66
+ findings.push({ level: "warn", text: versionWarning, fix: env.install === "app" ? "Upgrade One Code (it pins a tested pi)." : "Update one-code-extension, or pin pi inside the tested range." });
67
+ }
68
+
69
+ if (env.latest) {
70
+ switch (env.latest.status) {
71
+ case "current":
72
+ lines.push({ text: `Updates: up to date (${env.oneCodeVersion} is the latest release)`, level: "ok" });
73
+ break;
74
+ case "behind":
75
+ lines.push({
76
+ text: `Updates: ${env.latest.version} is available (you have ${env.oneCodeVersion}) — ${env.install === "app" ? "npm install -g @one-ai/one-code" : "pi update"}`,
77
+ level: "warn",
78
+ });
79
+ break;
80
+ case "skipped":
81
+ lines.push({ text: `Updates: check skipped (${env.latest.reason ?? "offline"})`, level: "dim" });
82
+ break;
83
+ default:
84
+ lines.push({ text: `Updates: could not check the registry${env.latest.reason ? ` (${env.latest.reason})` : ""}`, level: "dim" });
85
+ }
86
+ }
87
+ return { title: "Installation", lines };
88
+ }
89
+
90
+ export interface ProviderSummary {
91
+ provider: string;
92
+ name: string;
93
+ ready: boolean;
94
+ source?: string;
95
+ models: number;
96
+ available: number;
97
+ }
98
+
99
+ export function summarizeProviders(registry: RegistryView): ProviderSummary[] {
100
+ const byProvider = new Map<string, { models: number; available: number }>();
101
+ for (const model of registry.all) {
102
+ const entry = byProvider.get(model.provider) ?? { models: 0, available: 0 };
103
+ entry.models++;
104
+ byProvider.set(model.provider, entry);
105
+ }
106
+ for (const model of registry.available) {
107
+ const entry = byProvider.get(model.provider) ?? { models: 0, available: 0 };
108
+ entry.available++;
109
+ byProvider.set(model.provider, entry);
110
+ }
111
+ const out: ProviderSummary[] = [];
112
+ for (const [provider, counts] of byProvider) {
113
+ let status: { configured: boolean; source?: string; label?: string } = { configured: false };
114
+ try {
115
+ status = registry.authStatus(provider);
116
+ } catch {
117
+ // a provider the registry cannot answer for counts as not configured
118
+ }
119
+ const ready = status.configured || counts.available > 0;
120
+ const source = status.source ? AUTH_SOURCE_LABEL[status.source] ?? status.source : undefined;
121
+ out.push({
122
+ provider,
123
+ name: registry.displayName?.(provider) ?? provider,
124
+ ready,
125
+ source: source && status.label && status.source === "environment" ? `${source} ${status.label}` : source,
126
+ models: counts.models,
127
+ available: counts.available,
128
+ });
129
+ }
130
+ return out.sort((a, b) => Number(b.ready) - Number(a.ready) || a.provider.localeCompare(b.provider));
131
+ }
132
+
133
+ export function providersSection(registry: RegistryView, findings: Finding[]): { section: ReportSection; readyCount: number } {
134
+ const providers = summarizeProviders(registry);
135
+ const ready = providers.filter((p) => p.ready);
136
+ const lines: ReportLine[] = [];
137
+ for (const provider of ready) {
138
+ const models = provider.available || provider.models;
139
+ lines.push({
140
+ text: `${provider.name} (${provider.provider}): ready — ${provider.source ?? "credentials configured"} · ${countNoun(models, "model")}`,
141
+ level: "ok",
142
+ });
143
+ }
144
+ const rest = providers.length - ready.length;
145
+ if (ready.length === 0) {
146
+ lines.push({ text: `No provider has credentials (${rest} known providers)`, level: "error" });
147
+ findings.push({
148
+ level: "error",
149
+ text: "No model provider is configured, so no session can send a request yet.",
150
+ fix: `Run /login inside One Code, or export a key such as ${COMMON_KEY_ENV_VARS.slice(0, 2).join(" or ")} before launching. Free options: OpenCode Zen (deepseek-v4-flash-free) and OpenRouter's :free models.`,
151
+ });
152
+ } else if (rest > 0) {
153
+ lines.push({ text: `${countNoun(rest, "more provider")} without credentials — /login to add one, or set its API-key environment variable`, level: "dim" });
154
+ }
155
+ return { section: { title: "Providers", lines }, readyCount: ready.length };
156
+ }
157
+
158
+ export interface BuildInput {
159
+ env: DoctorEnvironment;
160
+ registry: RegistryView;
161
+ session: SessionView;
162
+ }
163
+
164
+ export function buildDoctorReport({ env, registry, session }: BuildInput): DoctorReport {
165
+ const findings: Finding[] = [];
166
+ const sections: ReportSection[] = [];
167
+
168
+ sections.push(installationSection(env, findings));
169
+ const providers = providersSection(registry, findings);
170
+ sections.push(providers.section);
171
+
172
+ const facts = collectModelFacts(registry.available, session, env.home, env.env);
173
+ sections.push(modelsSection(facts, session, findings));
174
+
175
+ const presets = computePresets(registry.available, session.model);
176
+ sections.push(presetsSection(presets, session.model));
177
+
178
+ const compat = collectCompat({ cwd: env.cwd, home: env.home, agentDir: env.agentDir, env: env.env, bundledAgentsDir: BUNDLED_AGENTS_DIR });
179
+ findings.push(...compat.findings);
180
+ sections.push(importedConfigSection(compat, env.home));
181
+ sections.push(mcpSection(compat, session, env.home));
182
+
183
+ const webSearchSettings = webSearchSettingsFrom(readJsonFile<{ webSearch?: unknown }>(oneCodeSettingsPath(env.home, env.env))?.webSearch);
184
+ const deps = checkDependencies({ cwd: env.cwd, env: env.env, platform: env.platform, mcpServers: compat.mcp.servers, sessionModel: session.model, webSearchSettings });
185
+ findings.push(...deps.findings);
186
+ sections.push(dependenciesSection(deps));
187
+
188
+ // Errors first, then warnings, in discovery order within each.
189
+ findings.sort((a, b) => Number(b.level === "error") - Number(a.level === "error"));
190
+ const ready = providers.readyCount > 0 && session.model !== undefined && !findings.some((f) => f.level === "error");
191
+
192
+ return {
193
+ title: REPORT_TITLE,
194
+ summary: summaryText({ ready, providersReady: providers.readyCount, main: session.model, subagent: facts.subagent.model, classifier: facts.classifier.model, files: compat.files.filter((f) => f.exists).length, contextFiles: compat.contextFiles.length, findings }),
195
+ sections,
196
+ findings,
197
+ ready,
198
+ };
199
+ }
200
+
201
+ function summaryText(input: {
202
+ ready: boolean;
203
+ providersReady: number;
204
+ main?: Model<Api>;
205
+ subagent?: Model<Api>;
206
+ classifier?: Model<Api>;
207
+ files: number;
208
+ contextFiles: number;
209
+ findings: Finding[];
210
+ }): string {
211
+ const errors = input.findings.filter((f) => f.level === "error").length;
212
+ const warnings = input.findings.length - errors;
213
+ const config = `${countNoun(input.files, "settings file")} and ${countNoun(input.contextFiles, "instruction file")} are in use.`;
214
+ if (input.providersReady === 0 || !input.main) {
215
+ return `Not ready: no model provider has credentials, so nothing can run yet. Connect one with /login (or an API-key environment variable), then rerun /doctor. ${config}`;
216
+ }
217
+ const roles = `Main model ${modelSpec(input.main)}${input.subagent ? `, subagents on ${modelSpec(input.subagent)}` : ""}${input.classifier ? `, auto-mode classifier ${modelSpec(input.classifier)}` : ""}.`;
218
+ if (!input.ready) return `${countNoun(errors, "problem")} need${errors === 1 ? "s" : ""} attention (listed at the bottom). ${roles} ${config}`;
219
+ const tail = warnings ? `${countNoun(warnings, "warning")} below; none blocks a session.` : "No issues found.";
220
+ return `Ready. ${roles} ${config} ${tail}`;
221
+ }
222
+
223
+ /** The extension's own version — the app overrides it with CC_VERSION (lockstep releases). */
224
+ export function oneCodeVersion(env: NodeJS.ProcessEnv = process.env): string {
225
+ return env.CC_VERSION ?? extensionVersion();
226
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * `onecode doctor` — the report without a session, Claude Code's `claude doctor`.
3
+ *
4
+ * The bundled app (app/bin.mjs) loads this file through pi's own TypeScript
5
+ * loader (jiti) and calls `runDoctorCli` before pi's `main()` would ever run,
6
+ * so it works on the machine the doctor exists for: one with no provider yet,
7
+ * where pi's session bootstrap has nothing to start. Reads pi's auth and model
8
+ * files through pi's own `ModelRuntime`/`ModelRegistry` (no network) and pi's
9
+ * saved default model through `SettingsManager`; everything else is the same
10
+ * `buildDoctorReport` the `/doctor` command uses.
11
+ */
12
+
13
+ import { join } from "node:path";
14
+ import { ModelRegistry, ModelRuntime, SettingsManager } from "@earendil-works/pi-coding-agent";
15
+ import { oneCodeStateDir } from "../lib/paths.ts";
16
+ import { loadPermissionSettings } from "../permissions/settings.ts";
17
+ import { buildDoctorReport } from "./build.ts";
18
+ import { type DoctorReport, renderDoctorText, type SessionView } from "./report.ts";
19
+ import { lookupLatestVersion } from "./update-lookup.ts";
20
+
21
+ export interface DoctorCliOptions {
22
+ agentDir: string;
23
+ cwd: string;
24
+ home: string;
25
+ env: NodeJS.ProcessEnv;
26
+ version: string;
27
+ install: "app" | "pi-package";
28
+ piVersion?: string;
29
+ /** Report width; defaults to the terminal's columns, else 100. */
30
+ columns?: number;
31
+ /** Skip the registry version lookup (tests). */
32
+ network?: boolean;
33
+ }
34
+
35
+ /** Build the report headlessly. Exported separately so tests can inspect the structure. */
36
+ export async function collectDoctorCliReport(options: DoctorCliOptions): Promise<DoctorReport> {
37
+ const { agentDir, cwd, home, env } = options;
38
+ // The registry lookup (network, up to 3s) overlaps the local runtime load.
39
+ const latestPending = options.network === false ? undefined : lookupLatestVersion({ install: options.install, current: options.version, env });
40
+ const runtime = await ModelRuntime.create({
41
+ authPath: join(agentDir, "auth.json"),
42
+ modelsPath: join(agentDir, "models.json"),
43
+ allowModelNetwork: false,
44
+ });
45
+ const registry = new ModelRegistry(runtime);
46
+ const settings = SettingsManager.create(cwd, agentDir);
47
+
48
+ const available = registry.getAvailable();
49
+ const defaultProvider = settings.getDefaultProvider();
50
+ const defaultModel = settings.getDefaultModel();
51
+ const saved = defaultProvider && defaultModel ? registry.find(defaultProvider, defaultModel) : undefined;
52
+ const session: SessionView = saved
53
+ ? { model: saved, modelSource: "default-setting" }
54
+ : available[0]
55
+ ? { model: available[0], modelSource: "first-available" }
56
+ : { modelSource: "none" };
57
+ session.thinkingLevel = settings.getDefaultThinkingLevel();
58
+ const permissions = loadPermissionSettings(cwd, home);
59
+ session.permission = {
60
+ mode: permissions.defaultMode ?? "auto",
61
+ source: permissions.defaultMode ? "permissions.defaultMode in settings" : "One Code's default",
62
+ };
63
+
64
+ const latest = await latestPending;
65
+ return buildDoctorReport({
66
+ env: {
67
+ cwd,
68
+ home,
69
+ agentDir,
70
+ stateDir: oneCodeStateDir(env, home),
71
+ env,
72
+ platform: process.platform,
73
+ arch: process.arch,
74
+ nodeVersion: process.versions.node,
75
+ oneCodeVersion: options.version,
76
+ install: options.install,
77
+ piVersion: options.piVersion,
78
+ latest,
79
+ },
80
+ registry: {
81
+ all: registry.getAll(),
82
+ available,
83
+ authStatus: (provider) => registry.getProviderAuthStatus(provider),
84
+ displayName: (provider) => registry.getProviderDisplayName(provider),
85
+ },
86
+ session,
87
+ });
88
+ }
89
+
90
+ /** Print the report; exit code 1 when an error-level finding means a session cannot work yet. */
91
+ export async function runDoctorCli(options: DoctorCliOptions, write: (text: string) => void = (text) => process.stdout.write(text)): Promise<number> {
92
+ const report = await collectDoctorCliReport(options);
93
+ const width = options.columns ?? process.stdout.columns ?? 100;
94
+ write(`${renderDoctorText(report, Math.max(60, Math.min(width, 120)))}\n\n`);
95
+ const where = options.install === "app" ? "an onecode session" : "a pi session";
96
+ write(`For a full setup checkup that can also fix issues, run /doctor inside ${where}.\n`);
97
+ write("Inside a session, /doctor report shows this report alone.\n");
98
+ return report.findings.some((finding) => finding.level === "error") ? 1 : 0;
99
+ }