freddie 0.0.41

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 (307) hide show
  1. package/AGENTS.md +180 -0
  2. package/CHANGELOG.md +32 -0
  3. package/README.md +130 -0
  4. package/bin/freddie.js +116 -0
  5. package/package.json +59 -0
  6. package/skills/creative/README.md +3 -0
  7. package/skills/creative/architecture-diagram/SKILL.md +52 -0
  8. package/skills/creative/ascii-video/SKILL.md +60 -0
  9. package/skills/creative/concept-diagrams/SKILL.md +65 -0
  10. package/skills/data/README.md +3 -0
  11. package/skills/data/etl-pipelines/SKILL.md +60 -0
  12. package/skills/data/sql-explainer/SKILL.md +60 -0
  13. package/skills/ops/README.md +3 -0
  14. package/skills/ops/incident-response/SKILL.md +74 -0
  15. package/skills/ops/log-triage/SKILL.md +79 -0
  16. package/skills/planning/README.md +3 -0
  17. package/skills/planning/okr-drafter/SKILL.md +60 -0
  18. package/skills/planning/weekly-review/SKILL.md +64 -0
  19. package/skills/software-development/README.md +3 -0
  20. package/skills/software-development/code-review/SKILL.md +70 -0
  21. package/skills/software-development/rfc-writer/SKILL.md +68 -0
  22. package/skills/software-development/systematic-debugging/SKILL.md +80 -0
  23. package/src/acp/auth.js +21 -0
  24. package/src/acp/entry.js +2 -0
  25. package/src/acp/events.js +10 -0
  26. package/src/acp/main.js +8 -0
  27. package/src/acp/permissions.js +29 -0
  28. package/src/acp/server.js +84 -0
  29. package/src/acp/session.js +26 -0
  30. package/src/acp/tools.js +17 -0
  31. package/src/agent/account_usage.js +19 -0
  32. package/src/agent/acptoapi-bridge.js +80 -0
  33. package/src/agent/anthropic_adapter.js +10 -0
  34. package/src/agent/auxiliary_client.js +20 -0
  35. package/src/agent/bedrock_adapter.js +11 -0
  36. package/src/agent/codex_responses_adapter.js +10 -0
  37. package/src/agent/compress/compressor.js +55 -0
  38. package/src/agent/compress/fallback.js +14 -0
  39. package/src/agent/compress/index.js +6 -0
  40. package/src/agent/compress/policy.js +47 -0
  41. package/src/agent/compress/prompt.js +46 -0
  42. package/src/agent/compress/prune.js +16 -0
  43. package/src/agent/compress/tokens.js +31 -0
  44. package/src/agent/context_references.js +40 -0
  45. package/src/agent/copilot_acp_client.js +6 -0
  46. package/src/agent/credential_pool.js +30 -0
  47. package/src/agent/credential_sources.js +18 -0
  48. package/src/agent/curator.js +5 -0
  49. package/src/agent/display.js +23 -0
  50. package/src/agent/error_classifier.js +15 -0
  51. package/src/agent/file_safety.js +9 -0
  52. package/src/agent/gemini_cloudcode_adapter.js +9 -0
  53. package/src/agent/gemini_native_adapter.js +11 -0
  54. package/src/agent/gemini_schema.js +19 -0
  55. package/src/agent/google_code_assist.js +8 -0
  56. package/src/agent/google_oauth.js +21 -0
  57. package/src/agent/image_gen_provider.js +8 -0
  58. package/src/agent/image_gen_registry.js +6 -0
  59. package/src/agent/image_routing.js +13 -0
  60. package/src/agent/insights.js +9 -0
  61. package/src/agent/llm_resolver.js +21 -0
  62. package/src/agent/lmstudio_reasoning.js +13 -0
  63. package/src/agent/machine.js +102 -0
  64. package/src/agent/manual_compression_feedback.js +5 -0
  65. package/src/agent/memory_manager.js +14 -0
  66. package/src/agent/memory_provider.js +1 -0
  67. package/src/agent/model_metadata.js +28 -0
  68. package/src/agent/models_dev.js +13 -0
  69. package/src/agent/moonshot_schema.js +11 -0
  70. package/src/agent/oauth_endpoints.js +79 -0
  71. package/src/agent/onboarding.js +16 -0
  72. package/src/agent/pi-bridge.js +37 -0
  73. package/src/agent/prompt_builder.js +12 -0
  74. package/src/agent/prompt_caching.js +24 -0
  75. package/src/agent/rate_limit_tracker.js +12 -0
  76. package/src/agent/redact.js +25 -0
  77. package/src/agent/retry_utils.js +17 -0
  78. package/src/agent/shell_hooks.js +16 -0
  79. package/src/agent/skill_commands.js +16 -0
  80. package/src/agent/skill_preprocessing.js +12 -0
  81. package/src/agent/skill_utils.js +14 -0
  82. package/src/agent/subdirectory_hints.js +17 -0
  83. package/src/agent/title_generator.js +13 -0
  84. package/src/agent/trajectory.js +9 -0
  85. package/src/agent/usage_pricing.js +16 -0
  86. package/src/auth.js +84 -0
  87. package/src/batch.js +32 -0
  88. package/src/cli/auth_commands.js +17 -0
  89. package/src/cli/azure_detect.js +9 -0
  90. package/src/cli/backup.js +17 -0
  91. package/src/cli/banner.js +13 -0
  92. package/src/cli/browser_connect.js +11 -0
  93. package/src/cli/callbacks.js +5 -0
  94. package/src/cli/claw.js +8 -0
  95. package/src/cli/cli_output.js +19 -0
  96. package/src/cli/clipboard.js +24 -0
  97. package/src/cli/codex_models.js +8 -0
  98. package/src/cli/colors.js +13 -0
  99. package/src/cli/completer.js +98 -0
  100. package/src/cli/completion.js +21 -0
  101. package/src/cli/copilot_auth.js +9 -0
  102. package/src/cli/curator_cli.js +5 -0
  103. package/src/cli/curses.js +15 -0
  104. package/src/cli/debug.js +6 -0
  105. package/src/cli/default_soul.js +20 -0
  106. package/src/cli/dingtalk_auth.js +12 -0
  107. package/src/cli/doctor.js +15 -0
  108. package/src/cli/dump.js +11 -0
  109. package/src/cli/env_loader.js +25 -0
  110. package/src/cli/fallback_cmd.js +9 -0
  111. package/src/cli/gateway_cli.js +17 -0
  112. package/src/cli/hooks.js +9 -0
  113. package/src/cli/interactive.js +61 -0
  114. package/src/cli/logs.js +32 -0
  115. package/src/cli/main.js +7 -0
  116. package/src/cli/mcp_config.js +9 -0
  117. package/src/cli/memory_setup.js +12 -0
  118. package/src/cli/model_catalog.js +23 -0
  119. package/src/cli/model_normalize.js +12 -0
  120. package/src/cli/model_switch.js +11 -0
  121. package/src/cli/models.js +13 -0
  122. package/src/cli/nous_subscription.js +12 -0
  123. package/src/cli/oneshot.js +6 -0
  124. package/src/cli/pairing.js +21 -0
  125. package/src/cli/platforms.js +14 -0
  126. package/src/cli/plugins.js +4 -0
  127. package/src/cli/plugins_cmd.js +21 -0
  128. package/src/cli/profiles_cli.js +6 -0
  129. package/src/cli/providers.js +18 -0
  130. package/src/cli/pty_bridge.js +16 -0
  131. package/src/cli/relaunch.js +7 -0
  132. package/src/cli/runtime_provider.js +9 -0
  133. package/src/cli/setup.js +131 -0
  134. package/src/cli/skills_config.js +6 -0
  135. package/src/cli/skills_hub.js +8 -0
  136. package/src/cli/slack_cli.js +17 -0
  137. package/src/cli/status.js +10 -0
  138. package/src/cli/timeouts.js +5 -0
  139. package/src/cli/tips.js +14 -0
  140. package/src/cli/tools_config.js +15 -0
  141. package/src/cli/uninstall.js +8 -0
  142. package/src/cli/vercel_auth.js +13 -0
  143. package/src/cli/voice.js +6 -0
  144. package/src/cli/web_server.js +13 -0
  145. package/src/cli/webhook.js +12 -0
  146. package/src/commands/profile.js +72 -0
  147. package/src/commands/registry.js +94 -0
  148. package/src/config.js +125 -0
  149. package/src/context/engine.js +42 -0
  150. package/src/cron/cron-parse.js +27 -0
  151. package/src/cron/scheduler.js +63 -0
  152. package/src/db.js +178 -0
  153. package/src/gateway/base.js +13 -0
  154. package/src/gateway/builtin_hooks/boot.js +5 -0
  155. package/src/gateway/builtin_hooks/broadcast.js +3 -0
  156. package/src/gateway/builtin_hooks/deny.js +6 -0
  157. package/src/gateway/builtin_hooks/index.js +17 -0
  158. package/src/gateway/builtin_hooks/presence.js +4 -0
  159. package/src/gateway/builtin_hooks/routing.js +7 -0
  160. package/src/gateway/helpers.js +27 -0
  161. package/src/gateway/platforms/api_server.js +21 -0
  162. package/src/gateway/platforms/bluebubbles.js +32 -0
  163. package/src/gateway/platforms/dingtalk.js +32 -0
  164. package/src/gateway/platforms/discord.js +24 -0
  165. package/src/gateway/platforms/email.js +51 -0
  166. package/src/gateway/platforms/feishu.js +32 -0
  167. package/src/gateway/platforms/feishu_comment.js +12 -0
  168. package/src/gateway/platforms/feishu_comment_rules.js +11 -0
  169. package/src/gateway/platforms/homeassistant.js +32 -0
  170. package/src/gateway/platforms/matrix.js +40 -0
  171. package/src/gateway/platforms/mattermost.js +29 -0
  172. package/src/gateway/platforms/qqbot.js +32 -0
  173. package/src/gateway/platforms/signal.js +33 -0
  174. package/src/gateway/platforms/slack.js +34 -0
  175. package/src/gateway/platforms/sms.js +34 -0
  176. package/src/gateway/platforms/telegram.js +38 -0
  177. package/src/gateway/platforms/telegram_network.js +17 -0
  178. package/src/gateway/platforms/webhook.js +19 -0
  179. package/src/gateway/platforms/wecom.js +32 -0
  180. package/src/gateway/platforms/wecom_callback.js +15 -0
  181. package/src/gateway/platforms/wecom_crypto.js +16 -0
  182. package/src/gateway/platforms/weixin.js +32 -0
  183. package/src/gateway/platforms/whatsapp.js +40 -0
  184. package/src/gateway/platforms/yuanbao.js +9 -0
  185. package/src/gateway/platforms/yuanbao_media.js +5 -0
  186. package/src/gateway/platforms/yuanbao_proto.js +9 -0
  187. package/src/gateway/platforms/yuanbao_sticker.js +6 -0
  188. package/src/gateway/run.js +42 -0
  189. package/src/gateway/service.js +143 -0
  190. package/src/home.js +44 -0
  191. package/src/index.js +47 -0
  192. package/src/mcp/server.js +49 -0
  193. package/src/observability/debug.js +31 -0
  194. package/src/observability/log.js +38 -0
  195. package/src/plugins/achievements/index.js +9 -0
  196. package/src/plugins/cockpit/index.js +8 -0
  197. package/src/plugins/context_engine/index.js +13 -0
  198. package/src/plugins/disk_cleanup/index.js +22 -0
  199. package/src/plugins/google_meet/index.js +19 -0
  200. package/src/plugins/image_gen/index.js +5 -0
  201. package/src/plugins/manager.js +66 -0
  202. package/src/plugins/memory/_index.js +8 -0
  203. package/src/plugins/memory/byterover.js +25 -0
  204. package/src/plugins/memory/hindsight.js +25 -0
  205. package/src/plugins/memory/holographic.js +31 -0
  206. package/src/plugins/memory/honcho.js +25 -0
  207. package/src/plugins/memory/mem0.js +25 -0
  208. package/src/plugins/memory/openviking.js +25 -0
  209. package/src/plugins/memory/provider.js +35 -0
  210. package/src/plugins/memory/retaindb.js +25 -0
  211. package/src/plugins/memory/supermemory.js +25 -0
  212. package/src/plugins/observability/index.js +18 -0
  213. package/src/plugins/platforms/index.js +20 -0
  214. package/src/plugins/spotify/index.js +22 -0
  215. package/src/rl/atropos.js +22 -0
  216. package/src/rl/cli.js +18 -0
  217. package/src/sessions.js +84 -0
  218. package/src/skills/index.js +49 -0
  219. package/src/skin/engine.js +81 -0
  220. package/src/swe/runner.js +26 -0
  221. package/src/time.js +25 -0
  222. package/src/tools/ansi_strip.js +8 -0
  223. package/src/tools/approval.js +15 -0
  224. package/src/tools/bash.js +35 -0
  225. package/src/tools/binary_extensions.js +22 -0
  226. package/src/tools/browser.js +48 -0
  227. package/src/tools/budget_config.js +13 -0
  228. package/src/tools/checkpoint.js +29 -0
  229. package/src/tools/clarify.js +15 -0
  230. package/src/tools/code_execution.js +27 -0
  231. package/src/tools/credential_files.js +16 -0
  232. package/src/tools/cronjob.js +16 -0
  233. package/src/tools/debug_helpers.js +9 -0
  234. package/src/tools/delegate.js +28 -0
  235. package/src/tools/discord_tool.js +13 -0
  236. package/src/tools/edit.js +31 -0
  237. package/src/tools/env_passthrough.js +15 -0
  238. package/src/tools/environments/base.js +26 -0
  239. package/src/tools/environments/daytona.js +48 -0
  240. package/src/tools/environments/docker.js +14 -0
  241. package/src/tools/environments/file_sync.js +60 -0
  242. package/src/tools/environments/index.js +36 -0
  243. package/src/tools/environments/local.js +31 -0
  244. package/src/tools/environments/modal.js +33 -0
  245. package/src/tools/environments/singularity.js +38 -0
  246. package/src/tools/environments/ssh.js +14 -0
  247. package/src/tools/environments/vercel_sandbox.js +47 -0
  248. package/src/tools/feishu_doc.js +15 -0
  249. package/src/tools/feishu_drive.js +14 -0
  250. package/src/tools/file_operations.js +17 -0
  251. package/src/tools/file_state.js +16 -0
  252. package/src/tools/file_tools.js +23 -0
  253. package/src/tools/fuzzy_match.js +8 -0
  254. package/src/tools/grep.js +51 -0
  255. package/src/tools/homeassistant_tool.js +15 -0
  256. package/src/tools/image_gen.js +33 -0
  257. package/src/tools/interrupt.js +18 -0
  258. package/src/tools/managed_tool_gateway.js +11 -0
  259. package/src/tools/mcp_oauth.js +21 -0
  260. package/src/tools/mcp_oauth_manager.js +20 -0
  261. package/src/tools/mcp_tool.js +36 -0
  262. package/src/tools/memory.js +66 -0
  263. package/src/tools/mixture_of_agents.js +14 -0
  264. package/src/tools/neutts_synth.js +13 -0
  265. package/src/tools/openrouter_client.js +13 -0
  266. package/src/tools/osv_check.js +11 -0
  267. package/src/tools/patch_parser.js +42 -0
  268. package/src/tools/path_security.js +16 -0
  269. package/src/tools/process_registry.js +17 -0
  270. package/src/tools/read.js +26 -0
  271. package/src/tools/registry.js +54 -0
  272. package/src/tools/rl_training.js +13 -0
  273. package/src/tools/schema_sanitizer.js +18 -0
  274. package/src/tools/send_message.js +32 -0
  275. package/src/tools/session_search.js +23 -0
  276. package/src/tools/skill_manager.js +17 -0
  277. package/src/tools/skill_usage.js +20 -0
  278. package/src/tools/skills_guard.js +17 -0
  279. package/src/tools/skills_hub.js +31 -0
  280. package/src/tools/skills_index.js +14 -0
  281. package/src/tools/skills_sync.js +19 -0
  282. package/src/tools/skills_tool.js +11 -0
  283. package/src/tools/slash_confirm.js +16 -0
  284. package/src/tools/terminal.js +29 -0
  285. package/src/tools/tirith_security.js +25 -0
  286. package/src/tools/todo.js +54 -0
  287. package/src/tools/tool_backend_helpers.js +26 -0
  288. package/src/tools/tool_output_limits.js +15 -0
  289. package/src/tools/tool_result_storage.js +20 -0
  290. package/src/tools/transcription.js +19 -0
  291. package/src/tools/tts.js +19 -0
  292. package/src/tools/url_safety.js +15 -0
  293. package/src/tools/vision.js +18 -0
  294. package/src/tools/voice_mode.js +10 -0
  295. package/src/tools/web_search.js +37 -0
  296. package/src/tools/web_tools.js +18 -0
  297. package/src/tools/website_policy.js +14 -0
  298. package/src/tools/write.js +25 -0
  299. package/src/tools/xai_http.js +13 -0
  300. package/src/tools/yuanbao_tools.js +13 -0
  301. package/src/toolset_distributions.js +18 -0
  302. package/src/toolsets.js +26 -0
  303. package/src/tui/index.js +26 -0
  304. package/src/utils.js +54 -0
  305. package/src/web/app.js +547 -0
  306. package/src/web/index.html +167 -0
  307. package/src/web/server.js +109 -0
@@ -0,0 +1,21 @@
1
+ import { COMMAND_REGISTRY } from '../commands/registry.js'
2
+ import { listSkills } from '../skills/index.js'
3
+ export function completeSlash(prefix) {
4
+ const p = prefix.replace(/^\//, '').toLowerCase()
5
+ const out = new Set()
6
+ for (const c of COMMAND_REGISTRY) {
7
+ if (c.name.startsWith(p)) out.add('/' + c.name)
8
+ for (const a of c.aliases) if (a.startsWith(p)) out.add('/' + a)
9
+ }
10
+ return [...out].sort()
11
+ }
12
+ export function completeSkill(prefix) {
13
+ const p = String(prefix).toLowerCase()
14
+ return listSkills().filter(s => s.name.toLowerCase().startsWith(p)).map(s => s.name).sort()
15
+ }
16
+ export function complete(line) {
17
+ if (line.startsWith('/')) return completeSlash(line)
18
+ const m = line.match(/@skill\/([\w-]*)$/)
19
+ if (m) return completeSkill(m[1]).map(s => '@skill/' + s)
20
+ return []
21
+ }
@@ -0,0 +1,9 @@
1
+ import { getAuthStore } from '../auth.js'
2
+ const KEY = 'COPILOT_TOKEN'
3
+ export async function getCopilotToken() {
4
+ if (process.env.COPILOT_TOKEN) return { source: 'env', value: process.env.COPILOT_TOKEN }
5
+ const stored = await getAuthStore().getCredential(KEY)
6
+ return stored?.value ? { source: 'auth-store', value: stored.value } : { source: 'none', value: null }
7
+ }
8
+ export async function setCopilotToken(token) { return await getAuthStore().setCredential(KEY, token) }
9
+ export async function clearCopilotToken() { return await getAuthStore().deleteCredential(KEY) }
@@ -0,0 +1,5 @@
1
+ import { add, list, clear } from '../agent/curator.js'
2
+ export const CURATOR_KINDS = ['favourite-prompt', 'pinned-session', 'shortcut', 'snippet']
3
+ export function curatorAdd(kind, key, value) { return add(kind, key, value) }
4
+ export function curatorList(kind) { return list(kind) }
5
+ export function curatorClear(kind) { return clear(kind) }
@@ -0,0 +1,15 @@
1
+ let _blessed = null
2
+ async function probe() { if (_blessed !== null) return _blessed; try { _blessed = await import('blessed') } catch { _blessed = false } return _blessed }
3
+
4
+ export async function launchCurses({ output } = {}) {
5
+ const b = await probe()
6
+ if (!b) return { error: 'blessed not installed; run: npm install blessed' }
7
+ const blessed = b.default || b
8
+ const screen = blessed.screen({ smartCSR: true, title: 'freddie' })
9
+ const log = blessed.log({ top: 0, left: 0, width: '100%', height: '90%', scrollable: true, alwaysScroll: true })
10
+ const input = blessed.textbox({ bottom: 0, left: 0, width: '100%', height: 3, inputOnFocus: true })
11
+ screen.append(log); screen.append(input)
12
+ screen.key(['C-c', 'q'], () => process.exit(0))
13
+ screen.render()
14
+ return { screen, log, input, render: () => screen.render(), close: () => screen.destroy() }
15
+ }
@@ -0,0 +1,6 @@
1
+ import { listDebug, snapshot, snapshotAll } from '../observability/debug.js'
2
+ let _verbose = Boolean(process.env.FREDDIE_DEBUG)
3
+ export function isVerbose() { return _verbose }
4
+ export function setVerbose(v) { _verbose = Boolean(v); process.env.FREDDIE_DEBUG = v ? '1' : '' }
5
+ export function dprint(...args) { if (_verbose) console.error('[debug]', ...args) }
6
+ export function dumpDebug(name) { return name ? snapshot(name) : { subsystems: listDebug(), all: snapshotAll() } }
@@ -0,0 +1,20 @@
1
+ import { getActiveSkin, setActiveSkin } from '../skin/engine.js'
2
+ import { saveConfigValue } from '../config.js'
3
+ export const SOULS = {
4
+ classic: { skin: 'default', persona: 'You are Freddie, a thoughtful collaborator.' },
5
+ ares: { skin: 'ares', persona: 'You are Ares: direct, forceful, action-oriented.' },
6
+ mono: { skin: 'mono', persona: 'You are a minimal monochrome assistant.' },
7
+ slate: { skin: 'slate', persona: 'You are a calm, precise developer assistant.' },
8
+ }
9
+ export function listSouls() { return Object.keys(SOULS) }
10
+ export function getSoul(name) { return SOULS[name] || SOULS.classic }
11
+ export function applySoul(name) {
12
+ const soul = getSoul(name)
13
+ setActiveSkin(soul.skin)
14
+ saveConfigValue('agent.persona', soul.persona)
15
+ return soul
16
+ }
17
+ export function activeSoul() {
18
+ const skinName = getActiveSkin().name
19
+ return Object.entries(SOULS).find(([_, s]) => s.skin === skinName)?.[0] || 'classic'
20
+ }
@@ -0,0 +1,12 @@
1
+ import { getAuthStore } from '../auth.js'
2
+ const KEY = 'DINGTALK_ACCESS_TOKEN'
3
+ export async function getDingtalkToken() {
4
+ if (process.env.DINGTALK_ACCESS_TOKEN) return { source: 'env', value: process.env.DINGTALK_ACCESS_TOKEN }
5
+ const stored = await getAuthStore().getCredential(KEY)
6
+ return stored?.value ? { source: 'auth-store', value: stored.value } : { source: 'none', value: null }
7
+ }
8
+ export async function setDingtalkToken(token) { return await getAuthStore().setCredential(KEY, token) }
9
+ export async function fetchTokenFromAppKey({ appKey, appSecret }) {
10
+ const r = await fetch('https://oapi.dingtalk.com/gettoken?appkey=' + encodeURIComponent(appKey) + '&appsecret=' + encodeURIComponent(appSecret))
11
+ return await r.json()
12
+ }
@@ -0,0 +1,15 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { spawnSync } from 'node:child_process'
4
+ import { getFophHome } from '../home.js'
5
+ const CHECKS = [
6
+ { name: 'freddie-home', run: () => fs.existsSync(getFophHome()) ? { ok: true } : { ok: false, fix: 'mkdir -p ' + getFophHome() } },
7
+ { name: 'node-version', run: () => { const v = process.versions.node; const major = Number(v.split('.')[0]); return major >= 20 ? { ok: true, value: v } : { ok: false, fix: 'install node >=20', value: v } } },
8
+ { name: 'better-sqlite3', run: () => { try { require.resolve('better-sqlite3'); return { ok: true } } catch { return { ok: false, fix: 'npm install' } } } },
9
+ { name: 'gh-cli', run: () => { const r = spawnSync('gh', ['--version'], { encoding: 'utf8' }); return r.status === 0 ? { ok: true, value: r.stdout.split('\n')[0] } : { ok: false, fix: 'install gh CLI' } } },
10
+ { name: 'git', run: () => { const r = spawnSync('git', ['--version'], { encoding: 'utf8' }); return r.status === 0 ? { ok: true, value: r.stdout.trim() } : { ok: false, fix: 'install git' } } },
11
+ { name: 'config-file', run: () => { const p = path.join(getFophHome(), 'config.yaml'); return fs.existsSync(p) ? { ok: true } : { ok: false, fix: 'freddie setup' } } },
12
+ ]
13
+ export function runDoctor() {
14
+ return CHECKS.map(c => { try { return { name: c.name, ...c.run() } } catch (e) { return { name: c.name, ok: false, error: String(e.message || e) } } })
15
+ }
@@ -0,0 +1,11 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { getFophHome } from '../home.js'
4
+ import { listSessions, getMessages } from '../sessions.js'
5
+ import { loadConfig } from '../config.js'
6
+ export async function dumpAll(outFile = null) {
7
+ const out = { ts: Date.now(), foph_home: getFophHome(), config: loadConfig(), sessions: listSessions(1000).map(s => ({ ...s, messages: getMessages(s.id) })) }
8
+ const json = JSON.stringify(out, null, 2)
9
+ if (outFile) { fs.mkdirSync(path.dirname(outFile), { recursive: true }); fs.writeFileSync(outFile, json, 'utf8'); return { written: outFile, bytes: json.length } }
10
+ return out
11
+ }
@@ -0,0 +1,25 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { getFophHome } from '../home.js'
4
+ const RE = /^([A-Za-z_][A-Za-z0-9_]*)=(.*)$/
5
+ function parse(text) {
6
+ const out = {}
7
+ for (const line of String(text).split('\n')) {
8
+ const t = line.trim()
9
+ if (!t || t.startsWith('#')) continue
10
+ const m = t.match(RE)
11
+ if (m) out[m[1]] = m[2].replace(/^["']|["']$/g, '')
12
+ }
13
+ return out
14
+ }
15
+ export function loadEnvFile(file = null) {
16
+ const candidates = file ? [file] : [path.join(getFophHome(), '.env'), path.join(process.cwd(), '.env')]
17
+ const merged = {}
18
+ for (const f of candidates) if (fs.existsSync(f)) Object.assign(merged, parse(fs.readFileSync(f, 'utf8')))
19
+ return merged
20
+ }
21
+ export function applyEnvFile(file = null) {
22
+ const env = loadEnvFile(file)
23
+ for (const [k, v] of Object.entries(env)) if (!(k in process.env)) process.env[k] = v
24
+ return Object.keys(env).length
25
+ }
@@ -0,0 +1,9 @@
1
+ import { resolveCommand, COMMAND_REGISTRY } from '../commands/registry.js'
2
+ import { fuzzyMatch } from '../utils.js'
3
+ export function suggest(input) {
4
+ const slash = input.replace(/^\//, '').split(/\s+/)[0]
5
+ if (resolveCommand(input)) return null
6
+ const scored = COMMAND_REGISTRY.map(c => ({ name: c.name, score: fuzzyMatch(slash, c.name) })).filter(s => s.score > 0)
7
+ scored.sort((a, b) => b.score - a.score)
8
+ return scored.slice(0, 3).map(s => '/' + s.name)
9
+ }
@@ -0,0 +1,17 @@
1
+ import { Gateway } from '../gateway/run.js'
2
+ import { WebhookAdapter } from '../gateway/platforms/webhook.js'
3
+ import { ApiServerAdapter } from '../gateway/platforms/api_server.js'
4
+ import { registerBuiltinHooks } from '../gateway/builtin_hooks/index.js'
5
+ let _gateway = null
6
+ export async function startGateway({ port = 0, hooks = true } = {}) {
7
+ if (_gateway) return _gateway
8
+ const wh = new WebhookAdapter({ port })
9
+ const api = new ApiServerAdapter({ port: 0 })
10
+ const gw = new Gateway({ platforms: { webhook: wh, api_server: api } })
11
+ if (hooks) registerBuiltinHooks(gw)
12
+ await gw.start()
13
+ _gateway = { gw, webhook: wh, api }
14
+ return { webhookPort: wh.port, apiPort: api.port }
15
+ }
16
+ export async function stopGateway() { if (_gateway) { await _gateway.gw.stop(); _gateway = null } return { stopped: true } }
17
+ export function gatewayStatus() { return _gateway ? { running: true, webhookPort: _gateway.webhook.port, apiPort: _gateway.api.port } : { running: false } }
@@ -0,0 +1,9 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { getFophHome } from '../home.js'
4
+ function file() { return path.join(getFophHome(), 'hooks.json') }
5
+ export function loadHooks() { try { return JSON.parse(fs.readFileSync(file(), 'utf8')) } catch { return { pre_command: [], post_command: [], pre_tool: [], post_tool: [] } } }
6
+ export function saveHooks(h) { fs.writeFileSync(file(), JSON.stringify(h, null, 2), 'utf8') }
7
+ export function addHook(stage, command) { const h = loadHooks(); (h[stage] = h[stage] || []).push(command); saveHooks(h); return h }
8
+ export function removeHook(stage, idx) { const h = loadHooks(); h[stage] = (h[stage] || []).filter((_, i) => i !== idx); saveHooks(h); return h }
9
+ export function listHooks() { return loadHooks() }
@@ -0,0 +1,61 @@
1
+ import readline from 'node:readline'
2
+ import { runTurn } from '../agent/machine.js'
3
+ import { resolveCommand, COMMAND_REGISTRY, COMMANDS_BY_CATEGORY } from '../commands/registry.js'
4
+ import { getActiveSkin } from '../skin/engine.js'
5
+ import { createSession, appendMessage } from '../sessions.js'
6
+ import { listAllProfiles, switchProfile } from '../commands/profile.js'
7
+
8
+ const HANDLERS = {
9
+ help: () => {
10
+ const out = []
11
+ for (const [cat, cmds] of Object.entries(COMMANDS_BY_CATEGORY)) {
12
+ out.push(`\n# ${cat}`)
13
+ for (const c of cmds) out.push(` /${c.name}${c.args_hint ? ' ' + c.args_hint : ''}\t${c.description}`)
14
+ }
15
+ return out.join('\n')
16
+ },
17
+ quit: (state) => { state.exit = true; return 'bye.' },
18
+ profile: (_s, args) => {
19
+ if (!args[0] || args[0] === 'list') return listAllProfiles().join('\n')
20
+ if (args[0] === 'switch' && args[1]) { switchProfile(args[1]); return 'switched: ' + args[1] }
21
+ return 'usage: /profile [list|switch <name>]'
22
+ },
23
+ sessions: () => 'Use the CLI: freddie sessions',
24
+ clear: (state) => { state.messages = []; return 'cleared.' },
25
+ }
26
+
27
+ export async function interactive({ callLLM, input = process.stdin, output = process.stdout } = {}) {
28
+ const skin = getActiveSkin()
29
+ const state = { messages: [], session: createSession({ platform: 'cli' }), exit: false }
30
+ output.write(`${skin.branding.welcome}\n`)
31
+ const rl = readline.createInterface({ input, output, terminal: input.isTTY })
32
+ const prompt = () => { if (!state.exit) rl.setPrompt(skin.branding.prompt_symbol); rl.prompt() }
33
+ rl.on('line', async (raw) => {
34
+ const line = raw.trim()
35
+ if (!line) return prompt()
36
+ if (line.startsWith('/')) {
37
+ const parts = line.slice(1).split(/\s+/)
38
+ const name = resolveCommand('/' + parts[0])
39
+ const handler = HANDLERS[name]
40
+ if (!handler) { output.write(`unknown command: /${parts[0]}\n`); return prompt() }
41
+ output.write(handler(state, parts.slice(1)) + '\n')
42
+ if (state.exit) rl.close()
43
+ else prompt()
44
+ return
45
+ }
46
+ appendMessage(state.session, { role: 'user', content: line })
47
+ try {
48
+ const out = await runTurn({ prompt: line, messages: state.messages, callLLM, timeoutMs: 60000 })
49
+ state.messages = out.messages
50
+ const reply = out.result || out.error || '(no response)'
51
+ output.write(`${skin.branding.response_label}${reply}\n`)
52
+ appendMessage(state.session, { role: 'assistant', content: reply })
53
+ } catch (e) {
54
+ output.write(`error: ${e.message}\n`)
55
+ }
56
+ prompt()
57
+ })
58
+ rl.on('close', () => {})
59
+ prompt()
60
+ return new Promise(resolve => rl.on('close', resolve))
61
+ }
@@ -0,0 +1,32 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { getFophHome } from '../home.js'
4
+ export function listLogFiles() {
5
+ const dir = path.join(getFophHome(), 'logs')
6
+ if (!fs.existsSync(dir)) return []
7
+ return fs.readdirSync(dir).filter(f => f.endsWith('.log')).map(f => f.replace(/\.log$/, ''))
8
+ }
9
+ export function tail(subsystem, { max = 100, level = null } = {}) {
10
+ const file = path.join(getFophHome(), 'logs', subsystem + '.log')
11
+ if (!fs.existsSync(file)) return []
12
+ const lines = fs.readFileSync(file, 'utf8').trim().split('\n').filter(Boolean)
13
+ let parsed = lines.map(l => { try { return JSON.parse(l) } catch { return { raw: l } } })
14
+ if (level) { const SEV = { debug: 10, info: 20, warning: 30, error: 40 }; const min = SEV[level] || 20; parsed = parsed.filter(r => (SEV[r.severity] || 0) >= min) }
15
+ return parsed.slice(-max)
16
+ }
17
+ export async function followLog(subsystem, onLine) {
18
+ const file = path.join(getFophHome(), 'logs', subsystem + '.log')
19
+ let pos = fs.existsSync(file) ? fs.statSync(file).size : 0
20
+ const watcher = fs.watch(path.dirname(file), (_, name) => {
21
+ if (name !== subsystem + '.log') return
22
+ const stat = fs.statSync(file)
23
+ if (stat.size <= pos) { pos = 0; return }
24
+ const buf = Buffer.alloc(stat.size - pos)
25
+ const fd = fs.openSync(file, 'r')
26
+ fs.readSync(fd, buf, 0, buf.length, pos)
27
+ fs.closeSync(fd)
28
+ pos = stat.size
29
+ for (const l of buf.toString('utf8').split('\n').filter(Boolean)) try { onLine(JSON.parse(l)) } catch { onLine({ raw: l }) }
30
+ })
31
+ return () => watcher.close()
32
+ }
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander'
2
+ export function buildMainProgram() {
3
+ const program = new Command()
4
+ program.name('freddie').version('0.5.0').description('Freddie — JS rebuild of hermes-agent')
5
+ return program
6
+ }
7
+ export { buildMainProgram as createCli }
@@ -0,0 +1,9 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { getFophHome } from '../home.js'
4
+ function file() { return path.join(getFophHome(), 'mcp.json') }
5
+ export function loadMcpConfig() { try { return JSON.parse(fs.readFileSync(file(), 'utf8')) } catch { return { servers: {} } } }
6
+ export function saveMcpConfig(cfg) { fs.writeFileSync(file(), JSON.stringify(cfg, null, 2), 'utf8') }
7
+ export function addServer(name, { command, args = [], env = {} }) { const c = loadMcpConfig(); c.servers[name] = { command, args, env }; saveMcpConfig(c); return c.servers[name] }
8
+ export function removeServer(name) { const c = loadMcpConfig(); delete c.servers[name]; saveMcpConfig(c); return name }
9
+ export function listServers() { return Object.entries(loadMcpConfig().servers).map(([n, s]) => ({ name: n, ...s })) }
@@ -0,0 +1,12 @@
1
+ import { saveConfigValue } from '../config.js'
2
+ import { getAuthStore } from '../auth.js'
3
+ import { listMemoryProviders } from '../plugins/memory/provider.js'
4
+ export const PROVIDER_ENV = { honcho: 'HONCHO_API_KEY', mem0: 'MEM0_API_KEY', supermemory: 'SUPERMEMORY_API_KEY', byterover: 'BYTEROVER_API_KEY', hindsight: 'HINDSIGHT_API_KEY', openviking: 'OPENVIKING_API_KEY', retaindb: 'RETAINDB_API_KEY' }
5
+ export function listProviders() { return listMemoryProviders() }
6
+ export async function configureProvider(name, apiKey, options = {}) {
7
+ if (!listMemoryProviders().includes(name)) throw new Error('unknown memory provider: ' + name)
8
+ saveConfigValue('memory.provider', name)
9
+ saveConfigValue('memory.options', options)
10
+ if (apiKey && PROVIDER_ENV[name]) await getAuthStore().setCredential(PROVIDER_ENV[name], apiKey)
11
+ return { configured: name, hasKey: Boolean(apiKey) }
12
+ }
@@ -0,0 +1,23 @@
1
+ import { getModelContextLength } from '../agent/model_metadata.js'
2
+ import { priceFor } from '../agent/usage_pricing.js'
3
+ const CATALOG = [
4
+ { provider: 'anthropic', id: 'claude-opus-4-7', tags: ['flagship'] },
5
+ { provider: 'anthropic', id: 'claude-sonnet-4-6', tags: ['default'] },
6
+ { provider: 'anthropic', id: 'claude-haiku-4-5', tags: ['fast'] },
7
+ { provider: 'openai', id: 'gpt-5', tags: ['flagship'] },
8
+ { provider: 'openai', id: 'gpt-5-mini', tags: ['fast'] },
9
+ { provider: 'openai', id: 'gpt-4o', tags: ['vision'] },
10
+ { provider: 'openai', id: 'o3', tags: ['reasoning'] },
11
+ { provider: 'google', id: 'gemini-2.5-pro', tags: ['flagship', 'long-context'] },
12
+ { provider: 'google', id: 'gemini-2.5-flash', tags: ['fast'] },
13
+ { provider: 'xai', id: 'grok-4', tags: ['flagship'] },
14
+ { provider: 'deepseek', id: 'deepseek-v3', tags: ['cheap'] },
15
+ { provider: 'groq', id: 'llama-3.3-70b', tags: ['fast'] },
16
+ ]
17
+ export function listCatalog({ provider } = {}) {
18
+ return CATALOG.filter(m => !provider || m.provider === provider).map(m => ({ ...m, contextLength: getModelContextLength(m.id), pricing: priceFor(m.id) }))
19
+ }
20
+ export function findInCatalog(id) {
21
+ const m = CATALOG.find(x => x.id === id || id.startsWith(x.id))
22
+ return m ? { ...m, contextLength: getModelContextLength(m.id), pricing: priceFor(m.id) } : null
23
+ }
@@ -0,0 +1,12 @@
1
+ const ALIASES = {
2
+ sonnet: 'claude-sonnet-4-6', opus: 'claude-opus-4-7', haiku: 'claude-haiku-4-5',
3
+ gpt5: 'gpt-5', gpt5mini: 'gpt-5-mini', '4o': 'gpt-4o', '4omini': 'gpt-4o-mini',
4
+ flash: 'gemini-2.5-flash', pro: 'gemini-2.5-pro',
5
+ grok: 'grok-4', llama: 'llama-3.3-70b', dsv3: 'deepseek-v3',
6
+ }
7
+ export function normalizeModel(input) {
8
+ if (!input) return null
9
+ const lower = String(input).trim().toLowerCase().replace(/[^a-z0-9-]/g, '')
10
+ return ALIASES[lower] || input
11
+ }
12
+ export function listAliases() { return { ...ALIASES } }
@@ -0,0 +1,11 @@
1
+ import { saveConfigValue, getConfigValue } from '../config.js'
2
+ import { normalizeModel } from './model_normalize.js'
3
+ import { findInCatalog } from './model_catalog.js'
4
+ export function activeModel() { return getConfigValue('agent.model') }
5
+ export function switchModel(input) {
6
+ const id = normalizeModel(input)
7
+ const cat = findInCatalog(id)
8
+ saveConfigValue('agent.model', id)
9
+ if (cat?.provider) saveConfigValue('agent.provider', cat.provider)
10
+ return { model: id, provider: cat?.provider, contextLength: cat?.contextLength }
11
+ }
@@ -0,0 +1,13 @@
1
+ import { listCatalog, findInCatalog } from './model_catalog.js'
2
+ import { switchModel, activeModel } from './model_switch.js'
3
+ export const ACTIONS = {
4
+ list: ({ provider }) => ({ models: listCatalog({ provider }) }),
5
+ get: ({ id }) => ({ model: findInCatalog(id) }),
6
+ use: ({ id }) => switchModel(id),
7
+ active: () => ({ active: activeModel() }),
8
+ }
9
+ export async function modelsSubcommand(action = 'list', args = {}) {
10
+ const fn = ACTIONS[action]
11
+ if (!fn) return { error: 'unknown action: ' + action, valid: Object.keys(ACTIONS) }
12
+ return fn(args)
13
+ }
@@ -0,0 +1,12 @@
1
+ import { getAuthStore } from '../auth.js'
2
+ const KEY = 'NOUS_API_KEY'
3
+ export async function nousStatus() {
4
+ const k = process.env.NOUS_API_KEY || (await getAuthStore().getCredential(KEY))?.value
5
+ if (!k) return { active: false, hint: 'freddie nous-subscription set <key>' }
6
+ try {
7
+ const r = await fetch('https://api.nousresearch.com/v1/me', { headers: { authorization: 'Bearer ' + k } })
8
+ return { active: r.ok, status: r.status }
9
+ } catch (e) { return { active: false, error: String(e.message || e) } }
10
+ }
11
+ export async function setKey(key) { return await getAuthStore().setCredential(KEY, key) }
12
+ export async function clearKey() { return await getAuthStore().deleteCredential(KEY) }
@@ -0,0 +1,6 @@
1
+ import { runTurn } from '../agent/machine.js'
2
+ export async function oneshot({ prompt, model = null, callLLM = null, timeoutMs = 60000 } = {}) {
3
+ if (!prompt) throw new Error('prompt required')
4
+ const out = await runTurn({ prompt, model, callLLM, timeoutMs })
5
+ return { result: out.result || '', error: out.error || null, iterations: out.iterations }
6
+ }
@@ -0,0 +1,21 @@
1
+ import crypto from 'node:crypto'
2
+ import { getAuthStore } from '../auth.js'
3
+ const _pending = new Map()
4
+ export function generatePairCode() {
5
+ const code = crypto.randomBytes(3).toString('hex').toUpperCase()
6
+ const secret = crypto.randomBytes(32).toString('hex')
7
+ _pending.set(code, { secret, ts: Date.now() })
8
+ setTimeout(() => _pending.delete(code), 5 * 60_000)
9
+ return { code, secret }
10
+ }
11
+ export async function completePairing(code, deviceName) {
12
+ const p = _pending.get(code)
13
+ if (!p) return { error: 'invalid or expired code' }
14
+ _pending.delete(code)
15
+ await getAuthStore().setCredential('paired-device:' + deviceName, p.secret)
16
+ return { paired: deviceName }
17
+ }
18
+ export async function listPaired() {
19
+ const all = await getAuthStore().listCredentials()
20
+ return all.filter(n => n.startsWith('paired-device:')).map(n => n.replace('paired-device:', ''))
21
+ }
@@ -0,0 +1,14 @@
1
+ import fs from 'node:fs'
2
+ import path from 'node:path'
3
+ import { fileURLToPath } from 'node:url'
4
+ const __dirname = path.dirname(fileURLToPath(import.meta.url))
5
+ export async function listPlatforms() {
6
+ const dir = path.resolve(__dirname, '../gateway/platforms')
7
+ const out = []
8
+ for (const f of fs.readdirSync(dir).filter(f => f.endsWith('.js'))) {
9
+ const mod = await import('file://' + path.join(dir, f).replace(/\\/g, '/'))
10
+ const cls = Object.values(mod).find(v => typeof v === 'function' && /Adapter$/.test(v.name))
11
+ if (cls) { try { const inst = new cls({}); out.push({ name: f.replace(/\.js$/, ''), requiresEnv: inst.getRequiredEnv?.() || [] }) } catch { out.push({ name: f.replace(/\.js$/, ''), requiresEnv: [] }) } }
12
+ }
13
+ return out
14
+ }
@@ -0,0 +1,4 @@
1
+ import { pluginManager } from '../plugins/manager.js'
2
+ export async function listPluginsInstalled() { await pluginManager.discoverPlugins(); return pluginManager.plugins.map(p => ({ name: p.name, dir: p.dir || null })) }
3
+ export function listHooks() { return Object.fromEntries(Object.entries(pluginManager.hooks).map(([k, v]) => [k, v.length])) }
4
+ export function listCliCommands() { return pluginManager.cliCommands || [] }
@@ -0,0 +1,21 @@
1
+ import { listPluginsInstalled, listHooks, listCliCommands } from './plugins.js'
2
+ import fs from 'node:fs'
3
+ import path from 'node:path'
4
+ import { getFophHome } from '../home.js'
5
+ export async function pluginsSubcommand(action = 'list', { name, body } = {}) {
6
+ if (action === 'list') return { plugins: await listPluginsInstalled(), hooks: listHooks(), cliCommands: listCliCommands().length }
7
+ if (action === 'install') {
8
+ if (!name || !body) return { error: 'name + body required' }
9
+ const dir = path.join(getFophHome(), 'plugins', name)
10
+ fs.mkdirSync(dir, { recursive: true })
11
+ fs.writeFileSync(path.join(dir, 'package.json'), JSON.stringify({ name, main: 'index.js' }, null, 2))
12
+ fs.writeFileSync(path.join(dir, 'index.js'), body, 'utf8')
13
+ return { installed: dir }
14
+ }
15
+ if (action === 'uninstall') {
16
+ const dir = path.join(getFophHome(), 'plugins', name)
17
+ if (fs.existsSync(dir)) { fs.rmSync(dir, { recursive: true, force: true }); return { uninstalled: name } }
18
+ return { error: 'not found' }
19
+ }
20
+ return { error: 'unknown action' }
21
+ }
@@ -0,0 +1,6 @@
1
+ export { createProfile, deleteProfile, switchProfile, listAllProfiles } from '../commands/profile.js'
2
+ import { listAllProfiles } from '../commands/profile.js'
3
+ import { applyProfileOverride, getProfilesRoot, listProfiles } from '../home.js'
4
+ export function activeProfile() { return process.env.FREDDIE_PROFILE || 'default' }
5
+ export function listAll() { return { active: activeProfile(), all: listAllProfiles(), root: getProfilesRoot() } }
6
+ export function setActive(name) { applyProfileOverride(name); return { active: name || 'default' } }
@@ -0,0 +1,18 @@
1
+ import { resolveKey, listProviders as srcList } from '../agent/credential_sources.js'
2
+ import { saveConfigValue, getConfigValue } from '../config.js'
3
+ import { getAuthStore } from '../auth.js'
4
+ export function listProviders() { return srcList() }
5
+ export async function status() {
6
+ const out = []
7
+ for (const p of listProviders()) {
8
+ const k = await resolveKey(p)
9
+ out.push({ provider: p, source: k.source, configured: k.value != null })
10
+ }
11
+ return { providers: out, active: getConfigValue('agent.provider') }
12
+ }
13
+ export async function setKey(provider, key) {
14
+ const env = (provider.toUpperCase() + '_API_KEY')
15
+ await getAuthStore().setCredential(env, key)
16
+ return { provider, stored: env }
17
+ }
18
+ export function setActive(provider) { saveConfigValue('agent.provider', provider); return { active: provider } }
@@ -0,0 +1,16 @@
1
+ import { spawn } from 'node:child_process'
2
+ const _ptys = new Map()
3
+ export function openPty({ shell = process.platform === 'win32' ? 'cmd' : process.env.SHELL || 'sh', cwd = process.cwd(), env = process.env } = {}) {
4
+ const id = 'pty-' + Date.now() + '-' + Math.random().toString(36).slice(2, 6)
5
+ const child = spawn(shell, [], { cwd, env, stdio: ['pipe', 'pipe', 'pipe'] })
6
+ const buf = { stdout: '', stderr: '' }
7
+ child.stdout?.on('data', d => buf.stdout += d.toString())
8
+ child.stderr?.on('data', d => buf.stderr += d.toString())
9
+ _ptys.set(id, { child, buf })
10
+ child.on('exit', () => _ptys.delete(id))
11
+ return id
12
+ }
13
+ export function ptyWrite(id, data) { const p = _ptys.get(id); if (!p) return { error: 'unknown' }; p.child.stdin?.write(data); return { ok: true } }
14
+ export function ptyRead(id) { const p = _ptys.get(id); if (!p) return { error: 'unknown' }; const out = { ...p.buf }; p.buf.stdout = ''; p.buf.stderr = ''; return out }
15
+ export function ptyClose(id) { const p = _ptys.get(id); if (!p) return { error: 'unknown' }; try { p.child.kill('SIGTERM') } catch {}; _ptys.delete(id); return { closed: id } }
16
+ export function ptyList() { return [..._ptys.keys()] }
@@ -0,0 +1,7 @@
1
+ export function relaunch({ argv = process.argv } = {}) {
2
+ const { spawn } = require('node:child_process')
3
+ const child = spawn(argv[0], argv.slice(1), { detached: true, stdio: 'ignore', env: process.env })
4
+ child.unref()
5
+ setTimeout(() => process.exit(0), 100)
6
+ return { pid: child.pid }
7
+ }
@@ -0,0 +1,9 @@
1
+ import { getConfigValue } from '../config.js'
2
+ import { resolveKey } from '../agent/credential_sources.js'
3
+ export async function activeRuntime() {
4
+ const provider = getConfigValue('agent.provider', 'anthropic')
5
+ const model = getConfigValue('agent.model', '')
6
+ const key = await resolveKey(provider)
7
+ return { provider, model, hasKey: key.value != null, keySource: key.source }
8
+ }
9
+ export const KNOWN_RUNTIMES = ['anthropic', 'openai', 'google', 'xai', 'groq', 'openrouter', 'deepseek', 'mistral', 'lmstudio', 'ollama']