hoomanjs 1.29.1 → 1.30.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.
Files changed (270) hide show
  1. package/README.md +216 -69
  2. package/dist/acp/acp-agent.d.ts +5 -5
  3. package/dist/acp/acp-agent.js +55 -54
  4. package/dist/acp/acp-agent.js.map +1 -1
  5. package/dist/acp/approvals.d.ts +3 -3
  6. package/dist/acp/approvals.js +73 -115
  7. package/dist/acp/approvals.js.map +1 -1
  8. package/dist/acp/sessions/options.js +11 -25
  9. package/dist/acp/sessions/options.js.map +1 -1
  10. package/dist/acp/utils/tool-kind.js +1 -5
  11. package/dist/acp/utils/tool-kind.js.map +1 -1
  12. package/dist/chat/app.d.ts +5 -1
  13. package/dist/chat/app.js +322 -202
  14. package/dist/chat/app.js.map +1 -1
  15. package/dist/chat/approvals.d.ts +4 -3
  16. package/dist/chat/approvals.js +11 -45
  17. package/dist/chat/approvals.js.map +1 -1
  18. package/dist/chat/components/ChatMessage.d.ts +1 -2
  19. package/dist/chat/components/ChatMessage.js +4 -5
  20. package/dist/chat/components/ChatMessage.js.map +1 -1
  21. package/dist/chat/components/EmptyChatBanner.js +2 -3
  22. package/dist/chat/components/EmptyChatBanner.js.map +1 -1
  23. package/dist/chat/components/StatusBar.js +6 -2
  24. package/dist/chat/components/StatusBar.js.map +1 -1
  25. package/dist/chat/components/ThoughtEvent.d.ts +6 -0
  26. package/dist/chat/components/ThoughtEvent.js +30 -0
  27. package/dist/chat/components/ThoughtEvent.js.map +1 -0
  28. package/dist/chat/components/ToolEvent.js +1 -1
  29. package/dist/chat/components/ToolEvent.js.map +1 -1
  30. package/dist/chat/components/Transcript.d.ts +7 -2
  31. package/dist/chat/components/Transcript.js +6 -2
  32. package/dist/chat/components/Transcript.js.map +1 -1
  33. package/dist/chat/components/markdown/BlockRenderer.js +7 -7
  34. package/dist/chat/components/markdown/BlockRenderer.js.map +1 -1
  35. package/dist/chat/components/markdown/MarkdownMessage.js +1 -1
  36. package/dist/chat/components/markdown/MarkdownMessage.js.map +1 -1
  37. package/dist/chat/components/shared.js +2 -0
  38. package/dist/chat/components/shared.js.map +1 -1
  39. package/dist/chat/index.d.ts +4 -0
  40. package/dist/chat/index.js +17 -3
  41. package/dist/chat/index.js.map +1 -1
  42. package/dist/chat/steering.d.ts +20 -0
  43. package/dist/chat/steering.js +67 -0
  44. package/dist/chat/steering.js.map +1 -0
  45. package/dist/chat/types.d.ts +5 -1
  46. package/dist/cli.js +91 -13
  47. package/dist/cli.js.map +1 -1
  48. package/dist/configure/app.d.ts +1 -1
  49. package/dist/configure/app.js +581 -303
  50. package/dist/configure/app.js.map +1 -1
  51. package/dist/configure/components/MenuScreen.d.ts +4 -2
  52. package/dist/configure/components/MenuScreen.js +33 -6
  53. package/dist/configure/components/MenuScreen.js.map +1 -1
  54. package/dist/configure/components/SelectMenuItem.d.ts +1 -0
  55. package/dist/configure/components/SelectMenuItem.js +19 -10
  56. package/dist/configure/components/SelectMenuItem.js.map +1 -1
  57. package/dist/configure/index.js +4 -2
  58. package/dist/configure/index.js.map +1 -1
  59. package/dist/configure/types.d.ts +14 -12
  60. package/dist/configure/utils.d.ts +2 -0
  61. package/dist/configure/utils.js +21 -2
  62. package/dist/configure/utils.js.map +1 -1
  63. package/dist/core/agent/index.d.ts +3 -3
  64. package/dist/core/agent/index.js +20 -25
  65. package/dist/core/agent/index.js.map +1 -1
  66. package/dist/core/agent/mode-aware-tool-registry.js +2 -1
  67. package/dist/core/agent/mode-aware-tool-registry.js.map +1 -1
  68. package/dist/core/approvals/intervention.d.ts +34 -0
  69. package/dist/core/approvals/intervention.js +94 -0
  70. package/dist/core/approvals/intervention.js.map +1 -0
  71. package/dist/core/config.d.ts +49 -27
  72. package/dist/core/config.js +85 -25
  73. package/dist/core/config.js.map +1 -1
  74. package/dist/core/context/index.d.ts +11 -2
  75. package/dist/core/context/index.js +54 -4
  76. package/dist/core/context/index.js.map +1 -1
  77. package/dist/core/context/model-extractor.d.ts +13 -0
  78. package/dist/core/context/model-extractor.js +98 -0
  79. package/dist/core/context/model-extractor.js.map +1 -0
  80. package/dist/core/index.d.ts +6 -2
  81. package/dist/core/index.js +6 -1
  82. package/dist/core/index.js.map +1 -1
  83. package/dist/core/mcp/config.d.ts +28 -0
  84. package/dist/core/mcp/index.d.ts +5 -3
  85. package/dist/core/mcp/index.js +4 -2
  86. package/dist/core/mcp/index.js.map +1 -1
  87. package/dist/core/mcp/manager.d.ts +14 -1
  88. package/dist/core/mcp/manager.js +76 -4
  89. package/dist/core/mcp/manager.js.map +1 -1
  90. package/dist/core/mcp/oauth/callback-server.d.ts +16 -0
  91. package/dist/core/mcp/oauth/callback-server.js +134 -0
  92. package/dist/core/mcp/oauth/callback-server.js.map +1 -0
  93. package/dist/core/mcp/oauth/identity.d.ts +9 -0
  94. package/dist/core/mcp/oauth/identity.js +31 -0
  95. package/dist/core/mcp/oauth/identity.js.map +1 -0
  96. package/dist/core/mcp/oauth/index.d.ts +11 -0
  97. package/dist/core/mcp/oauth/index.js +15 -0
  98. package/dist/core/mcp/oauth/index.js.map +1 -0
  99. package/dist/core/mcp/oauth/provider.d.ts +43 -0
  100. package/dist/core/mcp/oauth/provider.js +203 -0
  101. package/dist/core/mcp/oauth/provider.js.map +1 -0
  102. package/dist/core/mcp/oauth/service.d.ts +29 -0
  103. package/dist/core/mcp/oauth/service.js +139 -0
  104. package/dist/core/mcp/oauth/service.js.map +1 -0
  105. package/dist/core/mcp/oauth/store.d.ts +14 -0
  106. package/dist/core/mcp/oauth/store.js +86 -0
  107. package/dist/core/mcp/oauth/store.js.map +1 -0
  108. package/dist/core/mcp/oauth/types.d.ts +87 -0
  109. package/dist/core/mcp/oauth/types.js +45 -0
  110. package/dist/core/mcp/oauth/types.js.map +1 -0
  111. package/dist/core/mcp/types.d.ts +56 -0
  112. package/dist/core/mcp/types.js +3 -0
  113. package/dist/core/mcp/types.js.map +1 -1
  114. package/dist/core/memory/file-store.d.ts +24 -0
  115. package/dist/core/memory/file-store.js +151 -0
  116. package/dist/core/memory/file-store.js.map +1 -0
  117. package/dist/core/memory/index.d.ts +2 -5
  118. package/dist/core/memory/index.js +2 -3
  119. package/dist/core/memory/index.js.map +1 -1
  120. package/dist/core/memory/runtime.d.ts +4 -0
  121. package/dist/core/memory/runtime.js +37 -0
  122. package/dist/core/memory/runtime.js.map +1 -0
  123. package/dist/core/models/anthropic.js +24 -1
  124. package/dist/core/models/anthropic.js.map +1 -1
  125. package/dist/core/models/moonshot.js +33 -1
  126. package/dist/core/models/moonshot.js.map +1 -1
  127. package/dist/core/modes/definitions.d.ts +19 -0
  128. package/dist/core/modes/definitions.js +99 -0
  129. package/dist/core/modes/definitions.js.map +1 -0
  130. package/dist/core/modes/index.d.ts +3 -0
  131. package/dist/core/modes/index.js +4 -0
  132. package/dist/core/modes/index.js.map +1 -0
  133. package/dist/core/modes/registry.d.ts +9 -0
  134. package/dist/core/modes/registry.js +57 -0
  135. package/dist/core/modes/registry.js.map +1 -0
  136. package/dist/core/modes/schema.d.ts +5 -0
  137. package/dist/core/modes/schema.js +6 -0
  138. package/dist/core/modes/schema.js.map +1 -0
  139. package/dist/core/prompts/agents/research.md +8 -8
  140. package/dist/core/prompts/bundled.d.ts +3 -0
  141. package/dist/core/prompts/bundled.js +14 -0
  142. package/dist/core/prompts/bundled.js.map +1 -0
  143. package/dist/core/prompts/environment.d.ts +0 -1
  144. package/dist/core/prompts/environment.js +0 -2
  145. package/dist/core/prompts/environment.js.map +1 -1
  146. package/dist/core/prompts/index.d.ts +1 -4
  147. package/dist/core/prompts/index.js +1 -7
  148. package/dist/core/prompts/index.js.map +1 -1
  149. package/dist/core/prompts/modes/agent.md +3 -0
  150. package/dist/core/prompts/modes/ask.md +5 -3
  151. package/dist/core/prompts/modes/plan.md +2 -2
  152. package/dist/core/prompts/session-mode-appendix.d.ts +3 -9
  153. package/dist/core/prompts/session-mode-appendix.js +32 -69
  154. package/dist/core/prompts/session-mode-appendix.js.map +1 -1
  155. package/dist/core/prompts/static/environment.md +2 -2
  156. package/dist/core/prompts/static/init.md +25 -0
  157. package/dist/core/prompts/static/memory.md +9 -123
  158. package/dist/core/prompts/static/planning.md +1 -1
  159. package/dist/core/prompts/static/skills.md +6 -5
  160. package/dist/core/prompts/static/subagents.md +2 -2
  161. package/dist/core/prompts/static/web-search.md +2 -1
  162. package/dist/core/prompts/system.js +8 -22
  163. package/dist/core/prompts/system.js.map +1 -1
  164. package/dist/core/skills/built-in/hooman-config/SKILL.md +105 -32
  165. package/dist/core/skills/built-in/hooman-skills/SKILL.md +1 -1
  166. package/dist/core/skills/index.d.ts +1 -0
  167. package/dist/core/skills/index.js +1 -0
  168. package/dist/core/skills/index.js.map +1 -1
  169. package/dist/core/skills/plugin.d.ts +7 -0
  170. package/dist/core/skills/plugin.js +40 -0
  171. package/dist/core/skills/plugin.js.map +1 -0
  172. package/dist/core/state/agent-app-state.d.ts +1 -1
  173. package/dist/core/state/agent-app-state.js +1 -1
  174. package/dist/core/state/session-mode.d.ts +2 -9
  175. package/dist/core/state/session-mode.js +4 -12
  176. package/dist/core/state/session-mode.js.map +1 -1
  177. package/dist/core/state/tool-approvals.d.ts +4 -8
  178. package/dist/core/state/tool-approvals.js +14 -76
  179. package/dist/core/state/tool-approvals.js.map +1 -1
  180. package/dist/core/subagents/index.d.ts +3 -0
  181. package/dist/core/subagents/index.js +4 -0
  182. package/dist/core/subagents/index.js.map +1 -0
  183. package/dist/core/subagents/research.d.ts +16 -0
  184. package/dist/core/subagents/research.js +58 -0
  185. package/dist/core/subagents/research.js.map +1 -0
  186. package/dist/core/{agents → subagents}/runner.d.ts +6 -5
  187. package/dist/core/{agents → subagents}/runner.js +8 -10
  188. package/dist/core/subagents/runner.js.map +1 -0
  189. package/dist/core/{agents/tools.d.ts → subagents/tool.d.ts} +6 -6
  190. package/dist/core/{agents/tools.js → subagents/tool.js} +14 -16
  191. package/dist/core/subagents/tool.js.map +1 -0
  192. package/dist/core/tools/plan.js +2 -6
  193. package/dist/core/tools/plan.js.map +1 -1
  194. package/dist/core/tools/time.js +1 -1
  195. package/dist/core/tools/time.js.map +1 -1
  196. package/dist/core/utils/browser.d.ts +1 -0
  197. package/dist/core/utils/browser.js +25 -0
  198. package/dist/core/utils/browser.js.map +1 -0
  199. package/dist/core/utils/paths.d.ts +2 -4
  200. package/dist/core/utils/paths.js +2 -4
  201. package/dist/core/utils/paths.js.map +1 -1
  202. package/dist/daemon/approvals.d.ts +2 -2
  203. package/dist/daemon/approvals.js +51 -56
  204. package/dist/daemon/approvals.js.map +1 -1
  205. package/dist/daemon/index.js +9 -6
  206. package/dist/daemon/index.js.map +1 -1
  207. package/dist/exec/approvals.d.ts +2 -4
  208. package/dist/exec/approvals.js +16 -48
  209. package/dist/exec/approvals.js.map +1 -1
  210. package/dist/index.d.ts +13 -18
  211. package/dist/index.js +9 -11
  212. package/dist/index.js.map +1 -1
  213. package/package.json +5 -18
  214. package/dist/chat/components/ScrollView.d.ts +0 -106
  215. package/dist/chat/components/ScrollView.js +0 -80
  216. package/dist/chat/components/ScrollView.js.map +0 -1
  217. package/dist/chat/components/TranscriptViewport.d.ts +0 -9
  218. package/dist/chat/components/TranscriptViewport.js +0 -124
  219. package/dist/chat/components/TranscriptViewport.js.map +0 -1
  220. package/dist/core/agents/definitions.d.ts +0 -12
  221. package/dist/core/agents/definitions.js +0 -20
  222. package/dist/core/agents/definitions.js.map +0 -1
  223. package/dist/core/agents/index.d.ts +0 -4
  224. package/dist/core/agents/index.js +0 -5
  225. package/dist/core/agents/index.js.map +0 -1
  226. package/dist/core/agents/registry.d.ts +0 -5
  227. package/dist/core/agents/registry.js +0 -84
  228. package/dist/core/agents/registry.js.map +0 -1
  229. package/dist/core/agents/runner.js.map +0 -1
  230. package/dist/core/agents/tools.js.map +0 -1
  231. package/dist/core/inference/embedder.d.ts +0 -26
  232. package/dist/core/inference/embedder.js +0 -85
  233. package/dist/core/inference/embedder.js.map +0 -1
  234. package/dist/core/inference/index.d.ts +0 -12
  235. package/dist/core/inference/index.js +0 -20
  236. package/dist/core/inference/index.js.map +0 -1
  237. package/dist/core/inference/loader.d.ts +0 -8
  238. package/dist/core/inference/loader.js +0 -85
  239. package/dist/core/inference/loader.js.map +0 -1
  240. package/dist/core/memory/brain.d.ts +0 -24
  241. package/dist/core/memory/brain.js +0 -133
  242. package/dist/core/memory/brain.js.map +0 -1
  243. package/dist/core/memory/database.d.ts +0 -4
  244. package/dist/core/memory/database.js +0 -87
  245. package/dist/core/memory/database.js.map +0 -1
  246. package/dist/core/memory/tools.d.ts +0 -16
  247. package/dist/core/memory/tools.js +0 -105
  248. package/dist/core/memory/tools.js.map +0 -1
  249. package/dist/core/memory/types.d.ts +0 -11
  250. package/dist/core/memory/types.js +0 -2
  251. package/dist/core/memory/types.js.map +0 -1
  252. package/dist/core/prompts/skills.d.ts +0 -15
  253. package/dist/core/prompts/skills.js +0 -92
  254. package/dist/core/prompts/skills.js.map +0 -1
  255. package/dist/core/prompts/static/wiki.md +0 -25
  256. package/dist/core/wiki/converters.d.ts +0 -12
  257. package/dist/core/wiki/converters.js +0 -73
  258. package/dist/core/wiki/converters.js.map +0 -1
  259. package/dist/core/wiki/database.d.ts +0 -39
  260. package/dist/core/wiki/database.js +0 -179
  261. package/dist/core/wiki/database.js.map +0 -1
  262. package/dist/core/wiki/index.d.ts +0 -7
  263. package/dist/core/wiki/index.js +0 -5
  264. package/dist/core/wiki/index.js.map +0 -1
  265. package/dist/core/wiki/storage.d.ts +0 -37
  266. package/dist/core/wiki/storage.js +0 -197
  267. package/dist/core/wiki/storage.js.map +0 -1
  268. package/dist/core/wiki/tools.d.ts +0 -5
  269. package/dist/core/wiki/tools.js +0 -32
  270. package/dist/core/wiki/tools.js.map +0 -1
@@ -1,18 +1,17 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
2
  import { useCallback, useEffect, useMemo, useState } from "react";
3
3
  import { existsSync, readFileSync, writeFileSync } from "node:fs";
4
- import path from "node:path";
5
4
  import { Box, Text, useApp, useInput } from "ink";
6
5
  import { LlmProvider, } from "../core/config.js";
6
+ import { McpOAuthConfigSchema, } from "../core/mcp/oauth/types.js";
7
7
  import { McpTransportSchema, } from "../core/mcp/types.js";
8
- import { Storage } from "../core/wiki/storage.js";
9
8
  import { basePath, configJsonPath, instructionsMdPath, mcpJsonPath, skillsPath, } from "../core/utils/paths.js";
10
9
  import { BusyScreen } from "./components/BusyScreen.js";
11
10
  import { HomeScreen } from "./components/HomeScreen.js";
12
11
  import { MenuScreen } from "./components/MenuScreen.js";
13
12
  import { PromptForm } from "./components/PromptForm.js";
14
13
  import { openFileInEditor } from "./open-in-editor.js";
15
- import { DEFAULT_INSTRUCTIONS, compactJson, folderNameForSkill, normalizeOptional, noticeColor, parseNumber, parseObjectRecord, maskSensitiveParamsForDisplay, parseStringArray, parseStringRecord, transportSummary, truncate, } from "./utils.js";
14
+ import { DEFAULT_INSTRUCTIONS, compactJson, folderNameForSkill, paramsPreview, normalizeOptional, noticeColor, parseOptionalBoolean, parseNumber, parseObjectRecord, maskSensitiveParamsForDisplay, parseStringArray, parseStringRecord, transportSummary, truncate, } from "./utils.js";
16
15
  const PROMPT_LABELS = {
17
16
  behaviour: "Behaviour",
18
17
  communication: "Communication",
@@ -26,10 +25,39 @@ const SEARCH_PROVIDER_LABELS = {
26
25
  serper: "Serper",
27
26
  tavily: "Tavily",
28
27
  };
28
+ const SUPPORTED_PROVIDER_TYPES = [
29
+ LlmProvider.Anthropic,
30
+ LlmProvider.Bedrock,
31
+ LlmProvider.Google,
32
+ LlmProvider.Groq,
33
+ LlmProvider.Moonshot,
34
+ LlmProvider.Ollama,
35
+ LlmProvider.OpenAI,
36
+ LlmProvider.Xai,
37
+ ];
38
+ function providerParamsTemplate(provider) {
39
+ switch (provider) {
40
+ case LlmProvider.Anthropic:
41
+ return { apiKey: "" };
42
+ case LlmProvider.Bedrock:
43
+ return { region: "us-west-2" };
44
+ case LlmProvider.Google:
45
+ return { apiKey: "" };
46
+ case LlmProvider.Groq:
47
+ return { apiKey: "" };
48
+ case LlmProvider.Moonshot:
49
+ return { apiKey: "" };
50
+ case LlmProvider.Ollama:
51
+ return {};
52
+ case LlmProvider.OpenAI:
53
+ return { apiKey: "" };
54
+ case LlmProvider.Xai:
55
+ return { apiKey: "" };
56
+ }
57
+ }
29
58
  /** On/off display for tool rows (`Tool • Yes` / `Tool • No`). */
30
59
  const yesNo = (on) => (on ? "Yes" : "No");
31
- const WIKI_DOCS_PAGE_SIZE = 15;
32
- export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
60
+ export function ConfigureApp({ config, mcpConfig, mcpManager, skills, onExit, }) {
33
61
  const { exit } = useApp();
34
62
  const [screen, setScreen] = useState({ kind: "home" });
35
63
  const [prompt, setPrompt] = useState(null);
@@ -38,13 +66,7 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
38
66
  const [revision, setRevision] = useState(0);
39
67
  const [installedSkills, setInstalledSkills] = useState([]);
40
68
  const [searchResults, setSearchResults] = useState([]);
41
- const [wikiDocsList, setWikiDocsList] = useState({
42
- status: "idle",
43
- });
44
- const [wikiDocsNonce, setWikiDocsNonce] = useState(0);
45
- const bumpWikiDocs = useCallback(() => {
46
- setWikiDocsNonce((n) => n + 1);
47
- }, []);
69
+ const [mcpAuthStatuses, setMcpAuthStatuses] = useState({});
48
70
  const refresh = useCallback(() => {
49
71
  setRevision((value) => value + 1);
50
72
  }, []);
@@ -72,38 +94,37 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
72
94
  useEffect(() => {
73
95
  void refreshSkills();
74
96
  }, [refreshSkills]);
75
- const wikiDocsPage = screen.kind === "config-wiki-docs" ? screen.page : null;
76
97
  useEffect(() => {
77
- if (wikiDocsPage == null) {
78
- setWikiDocsList({ status: "idle" });
79
- return;
80
- }
81
98
  let cancelled = false;
82
- setWikiDocsList({ status: "loading", page: wikiDocsPage });
83
- void (async () => {
99
+ const loadStatuses = async () => {
84
100
  try {
85
- const storage = Storage.create();
86
- await storage.warmup();
87
- const result = await storage.list(wikiDocsPage, WIKI_DOCS_PAGE_SIZE);
88
- await storage.close();
89
- if (!cancelled) {
90
- setWikiDocsList({ status: "ready", page: wikiDocsPage, result });
101
+ const rows = await mcpManager.listAuthStatuses();
102
+ if (cancelled) {
103
+ return;
91
104
  }
105
+ setMcpAuthStatuses(Object.fromEntries(rows.map((row) => [row.name, row.status])));
92
106
  }
93
- catch (error) {
107
+ catch {
94
108
  if (!cancelled) {
95
- setWikiDocsList({
96
- status: "error",
97
- page: wikiDocsPage,
98
- message: error instanceof Error ? error.message : String(error),
99
- });
109
+ setMcpAuthStatuses({});
100
110
  }
101
111
  }
102
- })();
112
+ };
113
+ void loadStatuses();
103
114
  return () => {
104
115
  cancelled = true;
105
116
  };
106
- }, [wikiDocsPage, wikiDocsNonce]);
117
+ }, [mcpManager, revision]);
118
+ const configData = useMemo(() => ({
119
+ name: config.name,
120
+ providers: config.providers,
121
+ llms: config.llms,
122
+ search: config.search,
123
+ prompts: config.prompts,
124
+ tools: config.tools,
125
+ compaction: config.compaction,
126
+ }), [config, revision]);
127
+ const mcpServers = useMemo(() => mcpConfig.list(), [mcpConfig, revision]);
107
128
  useInput((input, key) => {
108
129
  if (key.ctrl && input.toLowerCase() === "c") {
109
130
  onExit();
@@ -130,34 +151,31 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
130
151
  setScreen({ kind: "config-llm-edit", name: screen.name });
131
152
  return;
132
153
  }
133
- if (screen.kind === "config-wiki-delete-confirm") {
134
- setScreen({ kind: "config-wiki-docs", page: screen.page });
135
- return;
136
- }
137
- if (screen.kind === "config-wiki-docs") {
138
- setScreen({ kind: "config-wiki" });
139
- return;
140
- }
141
- if (screen.kind === "config-wiki") {
142
- setScreen({ kind: "config-tools" });
154
+ if (screen.kind === "config-provider-delete-confirm") {
155
+ setScreen({ kind: "config-provider-edit", name: screen.name });
143
156
  return;
144
157
  }
145
158
  if (screen.kind !== "home") {
146
159
  setScreen({ kind: "home" });
147
160
  }
148
161
  }, { isActive: true });
149
- const configData = useMemo(() => ({
150
- name: config.name,
151
- llms: config.llms,
152
- search: config.search,
153
- prompts: config.prompts,
154
- tools: config.tools,
155
- compaction: config.compaction,
156
- }), [config, revision]);
157
- const mcpServers = useMemo(() => mcpConfig.list(), [mcpConfig, revision]);
158
162
  const setSuccess = useCallback((text) => {
159
163
  setNotice({ kind: "success", text });
160
164
  }, []);
165
+ const authenticateMcpServer = useCallback(async (name) => {
166
+ await runTask(`Authenticating MCP server "${name}"...`, async () => {
167
+ await mcpManager.authenticate(name);
168
+ refresh();
169
+ setSuccess(`Authenticated MCP server "${name}".`);
170
+ });
171
+ }, [mcpManager, refresh, runTask, setSuccess]);
172
+ const logoutMcpServer = useCallback(async (name) => {
173
+ await runTask(`Logging out MCP server "${name}"...`, async () => {
174
+ await mcpManager.logout(name);
175
+ refresh();
176
+ setSuccess(`Cleared OAuth credentials for "${name}".`);
177
+ });
178
+ }, [mcpManager, refresh, runTask, setSuccess]);
161
179
  const updateConfig = useCallback((partial, message) => {
162
180
  config.update(partial);
163
181
  refresh();
@@ -171,7 +189,7 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
171
189
  {
172
190
  name,
173
191
  options: {
174
- provider: LlmProvider.Ollama,
192
+ provider: config.providers[0]?.name ?? LlmProvider.Ollama,
175
193
  model: "gemma4:e4b",
176
194
  params: {},
177
195
  },
@@ -179,6 +197,39 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
179
197
  },
180
198
  ], [config]);
181
199
  const removeLlm = useCallback((name) => config.llms.filter((m) => m.name !== name), [config]);
200
+ const patchProvider = useCallback((name, patch) => config.providers.map((provider) => provider.name === name
201
+ ? {
202
+ ...provider,
203
+ options: {
204
+ ...provider.options,
205
+ ...patch,
206
+ params: patch.params ?? provider.options.params,
207
+ },
208
+ }
209
+ : provider), [config]);
210
+ const renameProvider = useCallback((oldName, newName) => ({
211
+ providers: config.providers.map((provider) => provider.name === oldName ? { ...provider, name: newName } : provider),
212
+ llms: config.llms.map((llm) => llm.options.provider === oldName
213
+ ? {
214
+ ...llm,
215
+ options: {
216
+ ...llm.options,
217
+ provider: newName,
218
+ },
219
+ }
220
+ : llm),
221
+ }), [config]);
222
+ const addProvider = useCallback((name) => [
223
+ ...config.providers,
224
+ {
225
+ name,
226
+ options: {
227
+ provider: LlmProvider.Ollama,
228
+ params: providerParamsTemplate(LlmProvider.Ollama),
229
+ },
230
+ },
231
+ ], [config]);
232
+ const removeProvider = useCallback((name) => config.providers.filter((provider) => provider.name !== name), [config]);
182
233
  const promptValue = useCallback((state) => {
183
234
  setPrompt(state);
184
235
  }, []);
@@ -260,6 +311,192 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
260
311
  }, [mcpConfig, promptValue, refresh, setSuccess]);
261
312
  const promptForRemote = useCallback((name, type, initial) => {
262
313
  const mode = initial ? "Edit" : "Add";
314
+ const persistRemote = (url, headers, oauth) => {
315
+ const transport = McpTransportSchema.parse({
316
+ type,
317
+ url,
318
+ ...(headers && Object.keys(headers).length > 0 ? { headers } : {}),
319
+ ...(oauth ? { oauth } : {}),
320
+ });
321
+ if (initial) {
322
+ mcpConfig.update(name, transport);
323
+ setSuccess(`Updated MCP server "${name}".`);
324
+ }
325
+ else {
326
+ mcpConfig.add(name, transport);
327
+ setSuccess(`Added MCP server "${name}".`);
328
+ }
329
+ setPrompt(null);
330
+ setScreen({ kind: "mcp" });
331
+ refresh();
332
+ };
333
+ const promptForOAuthDetails = (url, headers, oauthEnabled) => {
334
+ if (!oauthEnabled) {
335
+ persistRemote(url, headers, undefined);
336
+ return;
337
+ }
338
+ promptValue({
339
+ title: `${mode} ${type} server`,
340
+ label: "OAuth client ID (optional)",
341
+ initialValue: initial?.oauth?.clientId ?? "",
342
+ placeholder: "client-id",
343
+ onSubmit: async (clientIdValue) => {
344
+ promptValue({
345
+ title: `${mode} ${type} server`,
346
+ label: "OAuth client secret (optional)",
347
+ initialValue: initial?.oauth?.clientSecret ?? "",
348
+ placeholder: "secret",
349
+ note: "Stored in mcp.json only if you enter a value.",
350
+ onSubmit: async (clientSecretValue) => {
351
+ promptValue({
352
+ title: `${mode} ${type} server`,
353
+ label: "OAuth scopes (optional)",
354
+ initialValue: initial?.oauth?.scopes
355
+ ? compactJson(initial.oauth.scopes)
356
+ : "",
357
+ placeholder: '["read","write"]',
358
+ onSubmit: async (scopesValue) => {
359
+ promptValue({
360
+ title: `${mode} ${type} server`,
361
+ label: "OAuth audiences (optional)",
362
+ initialValue: initial?.oauth?.audiences
363
+ ? compactJson(initial.oauth.audiences)
364
+ : "",
365
+ placeholder: '["https://api.example.com"]',
366
+ onSubmit: async (audiencesValue) => {
367
+ promptValue({
368
+ title: `${mode} ${type} server`,
369
+ label: "OAuth callback port (optional)",
370
+ initialValue: initial?.oauth?.callbackPort !== undefined
371
+ ? String(initial.oauth.callbackPort)
372
+ : "",
373
+ placeholder: "19876",
374
+ onSubmit: async (callbackPortValue) => {
375
+ promptValue({
376
+ title: `${mode} ${type} server`,
377
+ label: "OAuth redirect URI (optional)",
378
+ initialValue: initial?.oauth?.redirectUri ?? "",
379
+ placeholder: "http://127.0.0.1:19876/mcp/oauth/callback",
380
+ onSubmit: async (redirectUriValue) => {
381
+ promptValue({
382
+ title: `${mode} ${type} server`,
383
+ label: "OAuth issuer (optional)",
384
+ initialValue: initial?.oauth?.issuer ?? "",
385
+ placeholder: "https://auth.example.com",
386
+ onSubmit: async (issuerValue) => {
387
+ promptValue({
388
+ title: `${mode} ${type} server`,
389
+ label: "OAuth authorization URL override (optional)",
390
+ initialValue: initial?.oauth?.authorizationUrl ?? "",
391
+ placeholder: "https://auth.example.com/authorize",
392
+ onSubmit: async (authorizationUrlValue) => {
393
+ promptValue({
394
+ title: `${mode} ${type} server`,
395
+ label: "OAuth token URL override (optional)",
396
+ initialValue: initial?.oauth?.tokenUrl ?? "",
397
+ placeholder: "https://auth.example.com/token",
398
+ onSubmit: async (tokenUrlValue) => {
399
+ promptValue({
400
+ title: `${mode} ${type} server`,
401
+ label: "OAuth registration URL override (optional)",
402
+ initialValue: initial?.oauth
403
+ ?.registrationUrl ?? "",
404
+ placeholder: "https://auth.example.com/register",
405
+ onSubmit: async (registrationUrlValue) => {
406
+ promptValue({
407
+ title: `${mode} ${type} server`,
408
+ label: "OAuth token param name (optional)",
409
+ initialValue: initial?.oauth
410
+ ?.tokenParamName ?? "",
411
+ placeholder: "access_token",
412
+ onSubmit: async (tokenParamNameValue) => {
413
+ const scopes = parseStringArray(scopesValue, "OAuth scopes");
414
+ const audiences = parseStringArray(audiencesValue, "OAuth audiences");
415
+ const callbackPort = normalizeOptional(callbackPortValue) !== undefined
416
+ ? parseNumber(callbackPortValue, "OAuth callback port", {
417
+ integer: true,
418
+ min: 1,
419
+ max: 65535,
420
+ })
421
+ : undefined;
422
+ const oauth = McpOAuthConfigSchema.parse({
423
+ enabled: true,
424
+ ...(normalizeOptional(clientIdValue)
425
+ ? {
426
+ clientId: normalizeOptional(clientIdValue),
427
+ }
428
+ : {}),
429
+ ...(normalizeOptional(clientSecretValue)
430
+ ? {
431
+ clientSecret: normalizeOptional(clientSecretValue),
432
+ }
433
+ : {}),
434
+ ...(scopes.length > 0
435
+ ? { scopes }
436
+ : {}),
437
+ ...(audiences.length >
438
+ 0
439
+ ? { audiences }
440
+ : {}),
441
+ ...(callbackPort !==
442
+ undefined
443
+ ? { callbackPort }
444
+ : {}),
445
+ ...(normalizeOptional(redirectUriValue)
446
+ ? {
447
+ redirectUri: normalizeOptional(redirectUriValue),
448
+ }
449
+ : {}),
450
+ ...(normalizeOptional(issuerValue)
451
+ ? {
452
+ issuer: normalizeOptional(issuerValue),
453
+ }
454
+ : {}),
455
+ ...(normalizeOptional(authorizationUrlValue)
456
+ ? {
457
+ authorizationUrl: normalizeOptional(authorizationUrlValue),
458
+ }
459
+ : {}),
460
+ ...(normalizeOptional(tokenUrlValue)
461
+ ? {
462
+ tokenUrl: normalizeOptional(tokenUrlValue),
463
+ }
464
+ : {}),
465
+ ...(normalizeOptional(registrationUrlValue)
466
+ ? {
467
+ registrationUrl: normalizeOptional(registrationUrlValue),
468
+ }
469
+ : {}),
470
+ ...(normalizeOptional(tokenParamNameValue)
471
+ ? {
472
+ tokenParamName: normalizeOptional(tokenParamNameValue),
473
+ }
474
+ : {}),
475
+ });
476
+ persistRemote(url, headers, oauth);
477
+ },
478
+ });
479
+ },
480
+ });
481
+ },
482
+ });
483
+ },
484
+ });
485
+ },
486
+ });
487
+ },
488
+ });
489
+ },
490
+ });
491
+ },
492
+ });
493
+ },
494
+ });
495
+ },
496
+ });
497
+ },
498
+ });
499
+ };
263
500
  promptValue({
264
501
  title: `${mode} ${type} server`,
265
502
  label: "URL",
@@ -277,24 +514,17 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
277
514
  placeholder: '{"Authorization":"Bearer ..."}',
278
515
  onSubmit: async (headersValue) => {
279
516
  const headers = parseStringRecord(headersValue, "Headers");
280
- const transport = McpTransportSchema.parse({
281
- type,
282
- url,
283
- ...(headers && Object.keys(headers).length > 0
284
- ? { headers }
285
- : {}),
517
+ promptValue({
518
+ title: `${mode} ${type} server`,
519
+ label: "Enable OAuth? (yes/no)",
520
+ initialValue: initial?.oauth ? "yes" : "no",
521
+ placeholder: "no",
522
+ note: "Choose yes for servers that use OAuth 2.0/2.1 or dynamic client registration.",
523
+ onSubmit: async (oauthEnabledValue) => {
524
+ const oauthEnabled = parseOptionalBoolean(oauthEnabledValue, "Enable OAuth");
525
+ promptForOAuthDetails(url, headers, oauthEnabled);
526
+ },
286
527
  });
287
- if (initial) {
288
- mcpConfig.update(name, transport);
289
- setSuccess(`Updated MCP server "${name}".`);
290
- }
291
- else {
292
- mcpConfig.add(name, transport);
293
- setSuccess(`Added MCP server "${name}".`);
294
- }
295
- setPrompt(null);
296
- setScreen({ kind: "mcp" });
297
- refresh();
298
528
  },
299
529
  });
300
530
  },
@@ -318,10 +548,24 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
318
548
  },
319
549
  });
320
550
  }, [promptForRemote, promptForStdio, promptValue]);
551
+ const llmSummary = useCallback((entry) => {
552
+ const compactModelId = (model) => {
553
+ if (model.length <= 23) {
554
+ return model;
555
+ }
556
+ return `${model.slice(0, 10)}...${model.slice(-10)}`;
557
+ };
558
+ const resolved = config.resolveLlm(entry.name);
559
+ if (!resolved) {
560
+ return `${entry.options.provider}/${compactModelId(entry.options.model)}`;
561
+ }
562
+ return `${entry.options.provider} -> ${resolved.options.provider}/${compactModelId(resolved.options.model)}`;
563
+ }, [config]);
564
+ const providerUsageCount = useCallback((name) => config.llms.filter((llm) => llm.options.provider === name).length, [config]);
321
565
  const renderHome = () => {
322
566
  const items = [
323
567
  {
324
- label: `Configuration • ${config.llm.provider}/${config.llm.model}`,
568
+ label: `Inference • ${llmSummary(config.llms.find((m) => m.default) ?? config.llms[0])}`,
325
569
  value: () => setScreen({ kind: "config" }),
326
570
  },
327
571
  {
@@ -392,6 +636,10 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
392
636
  })(),
393
637
  value: () => setScreen({ kind: "config-llms" }),
394
638
  },
639
+ {
640
+ label: `Providers • ${config.providers.length} configured`,
641
+ value: () => setScreen({ kind: "config-providers" }),
642
+ },
395
643
  {
396
644
  label: `Prompts • ${enabledPrompts}/${totalPrompts} enabled`,
397
645
  value: () => setScreen({ kind: "config-prompts" }),
@@ -526,24 +774,194 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
526
774
  },
527
775
  },
528
776
  {
529
- label: `Memory tool • ${yesNo(configData.tools.memory.enabled)}`,
530
- value: () => setScreen({ kind: "config-ltm" }),
777
+ label: `Subagents tool • ${yesNo(configData.tools.agents.enabled)}`,
778
+ value: () => {
779
+ updateConfig({
780
+ tools: {
781
+ ...config.tools,
782
+ agents: {
783
+ ...config.tools.agents,
784
+ enabled: !configData.tools.agents.enabled,
785
+ },
786
+ },
787
+ }, `Subagents tool ${configData.tools.agents.enabled ? "disabled" : "enabled"}.`);
788
+ setScreen({ kind: "config-tools" });
789
+ },
790
+ },
791
+ {
792
+ label: "Back",
793
+ value: () => setScreen({ kind: "config" }),
531
794
  },
795
+ ];
796
+ return (_jsx(MenuScreen, { title: "Tools", description: "Enable, disable, and configure built-in tools.", items: items }));
797
+ };
798
+ const renderProvidersMenu = () => {
799
+ const providerItems = config.providers.map((provider) => ({
800
+ key: `provider:${provider.name}`,
801
+ label: `${provider.name} • ${provider.options.provider} • ${providerUsageCount(provider.name)} model(s)`,
802
+ boldSubstring: provider.name,
803
+ value: () => setScreen({ kind: "config-provider-edit", name: provider.name }),
804
+ }));
805
+ const items = [
532
806
  {
533
- label: `Wiki tool • ${yesNo(configData.tools.wiki.enabled)}`,
534
- value: () => setScreen({ kind: "config-wiki" }),
807
+ label: "Add provider",
808
+ value: () => promptValue({
809
+ title: "Add a new provider",
810
+ label: "Name",
811
+ placeholder: "openai-prod",
812
+ onSubmit: async (value) => {
813
+ const name = value.trim();
814
+ if (!name) {
815
+ throw new Error("Name is required.");
816
+ }
817
+ if (config.providers.some((provider) => provider.name === name)) {
818
+ throw new Error(`A provider named "${name}" already exists.`);
819
+ }
820
+ updateConfig({ providers: addProvider(name) }, `Added provider "${name}" with an Ollama scaffold.`);
821
+ setPrompt(null);
822
+ setScreen({ kind: "config-provider-edit", name });
823
+ },
824
+ }),
535
825
  },
826
+ ...providerItems,
536
827
  {
537
828
  label: "Back",
538
829
  value: () => setScreen({ kind: "config" }),
539
830
  },
540
831
  ];
541
- return (_jsx(MenuScreen, { title: "Tools", description: "Enable, disable, and configure built-in tools.", items: items }));
832
+ return (_jsx(MenuScreen, { title: "Providers", description: "Configure reusable provider credentials and shared params.", items: items }));
833
+ };
834
+ const renderProviderEditMenu = () => {
835
+ if (screen.kind !== "config-provider-edit") {
836
+ return null;
837
+ }
838
+ const { name } = screen;
839
+ const entry = config.providers.find((provider) => provider.name === name);
840
+ if (!entry) {
841
+ return null;
842
+ }
843
+ const usageCount = providerUsageCount(entry.name);
844
+ const items = [
845
+ {
846
+ label: `Name • ${entry.name}`,
847
+ value: () => promptValue({
848
+ title: "Rename provider",
849
+ label: "Name",
850
+ initialValue: entry.name,
851
+ onSubmit: async (value) => {
852
+ const next = value.trim();
853
+ if (!next) {
854
+ throw new Error("Name is required.");
855
+ }
856
+ if (next === entry.name) {
857
+ setPrompt(null);
858
+ return;
859
+ }
860
+ if (config.providers.some((provider) => provider.name === next)) {
861
+ throw new Error(`A provider named "${next}" already exists.`);
862
+ }
863
+ updateConfig(renameProvider(entry.name, next), `Renamed provider "${entry.name}" to "${next}".`);
864
+ setPrompt(null);
865
+ setScreen({ kind: "config-provider-edit", name: next });
866
+ },
867
+ }),
868
+ },
869
+ {
870
+ label: `Type • ${entry.options.provider}`,
871
+ value: () => setScreen({ kind: "config-provider-type", name: entry.name }),
872
+ },
873
+ {
874
+ label: `Params • ${paramsPreview(entry.options.params)}`,
875
+ value: () => promptValue({
876
+ title: "Update provider params",
877
+ label: "Parameters",
878
+ initialValue: compactJson(entry.options.params),
879
+ placeholder: '{"apiKey":"..."}',
880
+ onSubmit: async (value) => {
881
+ const params = parseObjectRecord(value, "Provider params");
882
+ updateConfig({ providers: patchProvider(entry.name, { params }) }, "Updated provider params.");
883
+ setPrompt(null);
884
+ },
885
+ }),
886
+ },
887
+ ...(usageCount > 0
888
+ ? []
889
+ : [
890
+ {
891
+ label: `Delete "${entry.name}"`,
892
+ boldSubstring: entry.name,
893
+ value: () => setScreen({
894
+ kind: "config-provider-delete-confirm",
895
+ name: entry.name,
896
+ }),
897
+ },
898
+ ]),
899
+ {
900
+ label: "Back",
901
+ value: () => setScreen({ kind: "config-providers" }),
902
+ },
903
+ ];
904
+ return (_jsx(MenuScreen, { title: `Edit Provider • ${entry.name}`, description: usageCount > 0
905
+ ? `Used by ${usageCount} model(s). Rename updates references automatically; delete is disabled while in use.`
906
+ : "Edit shared provider settings or delete this provider.", items: items }));
907
+ };
908
+ const renderProviderTypeMenu = () => {
909
+ if (screen.kind !== "config-provider-type") {
910
+ return null;
911
+ }
912
+ const { name } = screen;
913
+ const entry = config.providers.find((provider) => provider.name === name);
914
+ if (!entry) {
915
+ return null;
916
+ }
917
+ const items = [
918
+ ...SUPPORTED_PROVIDER_TYPES.map((provider) => ({
919
+ label: provider === entry.options.provider
920
+ ? `${provider} • current`
921
+ : provider,
922
+ value: () => {
923
+ updateConfig({
924
+ providers: patchProvider(entry.name, {
925
+ provider,
926
+ params: providerParamsTemplate(provider),
927
+ }),
928
+ }, `Updated provider type for "${entry.name}" to "${provider}" and scaffolded params.`);
929
+ setScreen({ kind: "config-provider-edit", name: entry.name });
930
+ },
931
+ })),
932
+ {
933
+ label: "Back",
934
+ value: () => setScreen({ kind: "config-provider-edit", name: entry.name }),
935
+ },
936
+ ];
937
+ return (_jsx(MenuScreen, { title: `Choose Provider Type • ${entry.name}`, description: "Pick which runtime provider this shared config targets.", items: items }));
938
+ };
939
+ const renderProviderDeleteConfirm = () => {
940
+ if (screen.kind !== "config-provider-delete-confirm") {
941
+ return null;
942
+ }
943
+ const { name } = screen;
944
+ const items = [
945
+ {
946
+ key: `provider-del-cancel:${name}`,
947
+ label: "No — keep provider",
948
+ value: () => setScreen({ kind: "config-provider-edit", name }),
949
+ },
950
+ {
951
+ key: `provider-del-confirm:${name}`,
952
+ label: "Yes — remove provider",
953
+ value: () => {
954
+ updateConfig({ providers: removeProvider(name) }, `Deleted provider "${name}".`);
955
+ setScreen({ kind: "config-providers" });
956
+ },
957
+ },
958
+ ];
959
+ return (_jsx(MenuScreen, { title: "Delete provider?", description: `Remove "${name}" from the configured providers?`, items: items }));
542
960
  };
543
961
  const renderLlmsMenu = () => {
544
962
  const llmItems = config.llms.map((m) => ({
545
963
  key: `llm:${m.name}`,
546
- label: `${m.name} • ${m.options.provider}/${m.options.model}${m.default ? " • default" : ""}`,
964
+ label: `${m.name} • ${llmSummary(m)}${m.default ? " • default" : ""}`,
547
965
  boldSubstring: m.name,
548
966
  value: () => setScreen({ kind: "config-llm-edit", name: m.name }),
549
967
  }));
@@ -562,6 +980,9 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
562
980
  if (config.llms.some((m) => m.name === name)) {
563
981
  throw new Error(`An LLM named "${name}" already exists.`);
564
982
  }
983
+ if (config.providers.length === 0) {
984
+ throw new Error("Add at least one provider first so the model can reference it.");
985
+ }
565
986
  updateConfig({ llms: addLlm(name) }, `Added LLM "${name}".`);
566
987
  setPrompt(null);
567
988
  setScreen({ kind: "config-llm-edit", name });
@@ -633,7 +1054,7 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
633
1054
  }),
634
1055
  },
635
1056
  {
636
- label: `Params • ${truncate(compactJson(maskSensitiveParamsForDisplay(entry.options.params)))}`,
1057
+ label: `Params • ${paramsPreview(entry.options.params)}`,
637
1058
  value: () => promptValue({
638
1059
  title: "Update LLM params",
639
1060
  label: "Parameters",
@@ -688,12 +1109,12 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
688
1109
  return null;
689
1110
  }
690
1111
  const items = [
691
- ...Object.values(LlmProvider).map((provider) => ({
692
- label: provider === entry.options.provider
693
- ? `${provider} • current`
694
- : provider,
1112
+ ...config.providers.map((provider) => ({
1113
+ label: provider.name === entry.options.provider
1114
+ ? `${provider.name} • current`
1115
+ : `${provider.name} • ${provider.options.provider}`,
695
1116
  value: () => {
696
- updateConfig({ llms: patchLlm(entry.name, { provider }) }, `Updated provider for "${entry.name}" to "${provider}".`);
1117
+ updateConfig({ llms: patchLlm(entry.name, { provider: provider.name }) }, `Updated provider for "${entry.name}" to "${provider.name}".`);
697
1118
  setScreen({ kind: "config-llm-edit", name: entry.name });
698
1119
  },
699
1120
  })),
@@ -702,7 +1123,7 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
702
1123
  value: () => setScreen({ kind: "config-llm-edit", name: entry.name }),
703
1124
  },
704
1125
  ];
705
- return (_jsx(MenuScreen, { title: `Choose Provider • ${entry.name}`, description: "Pick which model provider to use for this LLM.", items: items }));
1126
+ return (_jsx(MenuScreen, { title: `Choose Provider • ${entry.name}`, description: "Pick which shared provider config this LLM should use.", items: items }));
706
1127
  };
707
1128
  const renderLlmDeleteConfirm = () => {
708
1129
  if (screen.kind !== "config-llm-delete-confirm") {
@@ -828,209 +1249,18 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
828
1249
  ];
829
1250
  return (_jsx(MenuScreen, { title: "Search", description: "Configure web search provider and credentials.", items: items }));
830
1251
  };
831
- const renderLtmConfigMenu = () => {
832
- const items = [
833
- {
834
- label: `Enabled • ${yesNo(configData.tools.memory.enabled)}`,
835
- value: () => {
836
- updateConfig({
837
- tools: {
838
- ...config.tools,
839
- memory: {
840
- ...config.tools.memory,
841
- enabled: !configData.tools.memory.enabled,
842
- },
843
- },
844
- }, `Memory tool ${configData.tools.memory.enabled ? "disabled" : "enabled"}.`);
845
- setScreen({ kind: "config-ltm" });
846
- },
847
- },
848
- {
849
- label: "Back",
850
- value: () => setScreen({ kind: "config-tools" }),
851
- },
852
- ];
853
- return (_jsx(MenuScreen, { title: "Memory tool", description: "Vectors live in ~/.hooman/memory.sqlite (sqlite-vec). Embedding model is fixed in code (DEFAULT_EMBED_MODEL).", items: items }));
854
- };
855
- const renderWikiConfigMenu = () => {
856
- const items = [
857
- {
858
- label: `Tool • ${yesNo(configData.tools.wiki.enabled)}`,
859
- value: () => {
860
- updateConfig({
861
- tools: {
862
- ...config.tools,
863
- wiki: {
864
- ...config.tools.wiki,
865
- enabled: !configData.tools.wiki.enabled,
866
- },
867
- },
868
- }, `Wiki tool ${configData.tools.wiki.enabled ? "disabled" : "enabled"}.`);
869
- setScreen({ kind: "config-wiki" });
870
- },
871
- },
872
- {
873
- label: "Manage documents",
874
- value: () => setScreen({ kind: "config-wiki-docs", page: 1 }),
875
- },
876
- {
877
- label: "Back",
878
- value: () => setScreen({ kind: "config-tools" }),
879
- },
880
- ];
881
- return (_jsx(MenuScreen, { title: "Tool", description: "Turn wiki_search on or off for the agent, then open Manage documents to add or remove PDF/DOCX files under ~/.hooman/wiki. Embedding may download the model on first use.", items: items }));
882
- };
883
- const renderWikiDocsMenu = () => {
884
- if (screen.kind !== "config-wiki-docs") {
885
- return null;
886
- }
887
- const { page } = screen;
888
- if (wikiDocsList.status === "loading" || wikiDocsList.status === "idle") {
889
- return _jsx(BusyScreen, { message: "Loading documents\u2026" });
890
- }
891
- if (wikiDocsList.status === "error") {
892
- return (_jsx(MenuScreen, { title: "Manage documents", description: `Could not load index: ${wikiDocsList.message}`, items: [
893
- {
894
- label: "Retry",
895
- value: () => bumpWikiDocs(),
896
- },
897
- {
898
- label: "Back",
899
- value: () => setScreen({ kind: "config-wiki" }),
900
- },
901
- ] }));
902
- }
903
- if (wikiDocsList.page !== page) {
904
- return _jsx(BusyScreen, { message: "Loading documents\u2026" });
905
- }
906
- const { result } = wikiDocsList;
907
- const totalPages = Math.max(1, Math.ceil(result.total / WIKI_DOCS_PAGE_SIZE));
908
- const hasPrev = page > 1;
909
- const hasNext = page < totalPages;
910
- const formatUpdated = (ms) => {
911
- try {
912
- return new Date(ms).toLocaleString(undefined, {
913
- dateStyle: "short",
914
- timeStyle: "short",
915
- });
916
- }
917
- catch {
918
- return String(ms);
919
- }
920
- };
921
- const docItems = result.items.map((doc) => {
922
- const summary = truncate(`${doc.file_name} • ${formatUpdated(doc.updated_at_ms)} • ${doc.original_mime_type}`, 96);
923
- return {
924
- key: `wiki-remove:${doc.doc_id}`,
925
- label: `Remove • ${summary}`,
926
- boldSubstring: doc.file_name,
927
- value: () => setScreen({
928
- kind: "config-wiki-delete-confirm",
929
- docId: doc.doc_id,
930
- displayName: doc.file_name,
931
- page,
932
- }),
933
- };
934
- });
935
- const items = [
936
- {
937
- label: "Add document (file path)",
938
- value: () => promptValue({
939
- title: "Index a file into the wiki",
940
- label: "Absolute path",
941
- placeholder: "/path/to/document.pdf",
942
- note: "Only PDF and DOCX are supported (PDF needs Java 11+ for OpenDataLoader).",
943
- onSubmit: async (value) => {
944
- const raw = value.trim();
945
- if (!raw) {
946
- throw new Error("Path is required.");
947
- }
948
- const abs = path.resolve(raw);
949
- if (!existsSync(abs)) {
950
- throw new Error(`File not found: ${abs}`);
951
- }
952
- setPrompt(null);
953
- await runTask(`Indexing "${path.basename(abs)}"…`, async () => {
954
- const storage = Storage.create();
955
- try {
956
- await storage.warmup();
957
- await storage.add({ filePath: abs });
958
- }
959
- finally {
960
- await storage.close();
961
- }
962
- bumpWikiDocs();
963
- setSuccess(`Indexed "${path.basename(abs)}".`);
964
- });
965
- setScreen({ kind: "config-wiki-docs", page: 1 });
966
- },
967
- }),
968
- },
969
- ...docItems,
970
- ...(hasPrev
971
- ? [
972
- {
973
- label: "Previous page",
974
- value: () => setScreen({ kind: "config-wiki-docs", page: page - 1 }),
975
- },
976
- ]
977
- : []),
978
- ...(hasNext
979
- ? [
980
- {
981
- label: "Next page",
982
- value: () => setScreen({ kind: "config-wiki-docs", page: page + 1 }),
983
- },
984
- ]
985
- : []),
986
- {
987
- label: "Back",
988
- value: () => setScreen({ kind: "config-wiki" }),
989
- },
990
- ];
991
- return (_jsx(MenuScreen, { title: `Manage documents · page ${page} of ${totalPages}`, description: result.items.length === 0
992
- ? "No documents yet. Add a PDF or DOCX path below (PDF needs Java 11+ on PATH)."
993
- : `${result.total} document(s) total · ${result.items.length} on this page.`, items: items }));
994
- };
995
- const renderWikiDeleteConfirm = () => {
996
- if (screen.kind !== "config-wiki-delete-confirm") {
997
- return null;
998
- }
999
- const { docId, displayName, page } = screen;
1000
- const items = [
1001
- {
1002
- key: `wiki-del-cancel:${docId}`,
1003
- label: "No — keep document",
1004
- value: () => setScreen({ kind: "config-wiki-docs", page }),
1005
- },
1006
- {
1007
- key: `wiki-del-confirm:${docId}`,
1008
- label: "Yes — remove from index",
1009
- value: () => void runTask(`Removing "${displayName}"…`, async () => {
1010
- const storage = Storage.create();
1011
- try {
1012
- await storage.warmup();
1013
- const ok = await storage.remove(docId);
1014
- if (!ok) {
1015
- throw new Error("Document was already removed.");
1016
- }
1017
- }
1018
- finally {
1019
- await storage.close();
1020
- }
1021
- bumpWikiDocs();
1022
- setSuccess(`Removed "${displayName}" from the wiki index.`);
1023
- setScreen({ kind: "config-wiki-docs", page });
1024
- }),
1025
- },
1026
- ];
1027
- return (_jsx(MenuScreen, { title: "Remove wiki document?", description: `Drop "${displayName}" from the index and delete its stored copies under ~/.hooman/wiki? This cannot be undone.`, items: items }));
1028
- };
1029
1252
  const renderMcpMenu = () => {
1030
- const serverItems = mcpServers.flatMap((server) => [
1031
- {
1032
- label: `Edit ${server.name} • ${transportSummary(server.transport)}`,
1253
+ const serverItems = mcpServers.map((server) => {
1254
+ const oauthStatus = mcpAuthStatuses[server.name];
1255
+ return {
1256
+ key: `mcp-server:${server.name}`,
1257
+ label: `Edit ${server.name} • ${formatMcpServerLabel(server.transport, oauthStatus)}`,
1033
1258
  boldSubstring: server.name,
1259
+ oauthStatus: oauthStatus === "authenticated" ||
1260
+ oauthStatus === "expired" ||
1261
+ oauthStatus === "unauthenticated"
1262
+ ? oauthStatus
1263
+ : undefined,
1034
1264
  value: () => {
1035
1265
  if (server.transport.type === "stdio") {
1036
1266
  promptForStdio(server.name, server.transport);
@@ -1039,13 +1269,8 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
1039
1269
  promptForRemote(server.name, server.transport.type, server.transport);
1040
1270
  }
1041
1271
  },
1042
- },
1043
- {
1044
- label: `Delete ${server.name}`,
1045
- boldSubstring: server.name,
1046
- value: () => setScreen({ kind: "mcp-delete-confirm", name: server.name }),
1047
- },
1048
- ]);
1272
+ };
1273
+ });
1049
1274
  const items = [
1050
1275
  {
1051
1276
  label: "Add stdio server",
@@ -1073,7 +1298,29 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
1073
1298
  value: () => setScreen({ kind: "home" }),
1074
1299
  },
1075
1300
  ];
1076
- return (_jsx(MenuScreen, { title: "MCP Servers", description: "Add, edit, or remove named MCP transports from ~/.hooman/mcp.json.", items: items }));
1301
+ return (_jsx(MenuScreen, { title: "MCP Servers", description: "Add, edit, or remove named MCP transports from ~/.hooman/mcp.json.", items: items, footerHint: (item) => formatMcpFooterHint(item, mcpServers, mcpAuthStatuses), onShortcut: async (input, item) => {
1302
+ const server = findMcpServerFromMenuItem(item, mcpServers);
1303
+ if (!server) {
1304
+ return;
1305
+ }
1306
+ const status = mcpAuthStatuses[server.name];
1307
+ const key = input.toLowerCase();
1308
+ if (key === "d") {
1309
+ setScreen({ kind: "mcp-delete-confirm", name: server.name });
1310
+ return;
1311
+ }
1312
+ if (key === "r" &&
1313
+ server.transport.type !== "stdio" &&
1314
+ status !== "unsupported") {
1315
+ await authenticateMcpServer(server.name);
1316
+ return;
1317
+ }
1318
+ if (key === "l" &&
1319
+ server.transport.type !== "stdio" &&
1320
+ (status === "authenticated" || status === "expired")) {
1321
+ await logoutMcpServer(server.name);
1322
+ }
1323
+ } }));
1077
1324
  };
1078
1325
  const renderSkillsMenu = () => {
1079
1326
  const skillItems = installedSkills.map((skill) => {
@@ -1094,7 +1341,7 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
1094
1341
  value: () => promptValue({
1095
1342
  title: "Search skills catalog",
1096
1343
  label: "Query",
1097
- placeholder: "memory, github, playwright...",
1344
+ placeholder: "github, playwright, slack...",
1098
1345
  onSubmit: async (value) => {
1099
1346
  const query = value.trim();
1100
1347
  if (query.length < 2) {
@@ -1239,6 +1486,14 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
1239
1486
  return renderHome();
1240
1487
  case "config":
1241
1488
  return renderConfigMenu();
1489
+ case "config-providers":
1490
+ return renderProvidersMenu();
1491
+ case "config-provider-edit":
1492
+ return renderProviderEditMenu();
1493
+ case "config-provider-type":
1494
+ return renderProviderTypeMenu();
1495
+ case "config-provider-delete-confirm":
1496
+ return renderProviderDeleteConfirm();
1242
1497
  case "config-llms":
1243
1498
  return renderLlmsMenu();
1244
1499
  case "config-llm-edit":
@@ -1255,14 +1510,6 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
1255
1510
  return renderSearchConfigMenu();
1256
1511
  case "config-search-provider":
1257
1512
  return renderSearchProviderMenu();
1258
- case "config-ltm":
1259
- return renderLtmConfigMenu();
1260
- case "config-wiki":
1261
- return renderWikiConfigMenu();
1262
- case "config-wiki-docs":
1263
- return renderWikiDocsMenu();
1264
- case "config-wiki-delete-confirm":
1265
- return renderWikiDeleteConfirm();
1266
1513
  case "mcp":
1267
1514
  return renderMcpMenu();
1268
1515
  case "mcp-delete-confirm":
@@ -1277,6 +1524,37 @@ export function ConfigureApp({ config, mcpConfig, skills, onExit, }) {
1277
1524
  return null;
1278
1525
  }
1279
1526
  })();
1280
- return (_jsxs(Box, { flexDirection: "column", width: "100%", paddingX: 1, children: [notice ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: noticeColor(notice.kind), children: notice.text }) })) : null, _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: `config: ${config.llm.provider}/${config.llm.model} • mcp: ${mcpServers.length} • skills: ${installedSkills.length}` }) }), body] }));
1527
+ return (_jsxs(Box, { flexDirection: "column", width: "100%", paddingX: 1, children: [notice ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: noticeColor(notice.kind), children: notice.text }) })) : null, _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: `inference: ${llmSummary(config.llms.find((m) => m.default) ?? config.llms[0])} • mcp: ${mcpServers.length} • skills: ${installedSkills.length}` }) }), body] }));
1528
+ }
1529
+ function formatMcpServerLabel(transport, status) {
1530
+ const summary = transportSummary(transport);
1531
+ if ((status === "expired" || status === "unauthenticated") &&
1532
+ summary.endsWith(" • oauth")) {
1533
+ return `${summary.slice(0, -" • oauth".length)} • oauth needed`;
1534
+ }
1535
+ return summary;
1536
+ }
1537
+ function findMcpServerFromMenuItem(item, servers) {
1538
+ if (!item?.key?.startsWith("mcp-server:")) {
1539
+ return null;
1540
+ }
1541
+ const serverName = item.key.slice("mcp-server:".length);
1542
+ return servers.find((server) => server.name === serverName) ?? null;
1543
+ }
1544
+ function formatMcpFooterHint(item, servers, statuses) {
1545
+ const server = findMcpServerFromMenuItem(item, servers);
1546
+ const status = server ? statuses[server.name] : undefined;
1547
+ const parts = ["enter: edit"];
1548
+ if (server && server.transport.type !== "stdio" && status !== "unsupported") {
1549
+ parts.push(status === "authenticated" ? "r: re-auth" : "r: authenticate");
1550
+ if (status === "authenticated" || status === "expired") {
1551
+ parts.push("l: logout");
1552
+ }
1553
+ }
1554
+ if (server) {
1555
+ parts.push("d: delete");
1556
+ }
1557
+ parts.push("esc: back", "ctrl+c: exit");
1558
+ return parts.join(" | ");
1281
1559
  }
1282
1560
  //# sourceMappingURL=app.js.map