titan-agent 5.4.2 → 5.5.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/agent.js +9 -5
- package/dist/agent/agent.js.map +1 -1
- package/dist/agent/agentLoop.js +7 -3
- package/dist/agent/agentLoop.js.map +1 -1
- package/dist/agent/checkpoint.js +2 -2
- package/dist/agent/checkpoint.js.map +1 -1
- package/dist/agent/commandPost.js +3 -3
- package/dist/agent/commandPost.js.map +1 -1
- package/dist/agent/goalProposer.js +2 -2
- package/dist/agent/goalProposer.js.map +1 -1
- package/dist/agent/goals.js +3 -3
- package/dist/agent/goals.js.map +1 -1
- package/dist/agent/planner.js +4 -4
- package/dist/agent/planner.js.map +1 -1
- package/dist/agent/userProfile.js +2 -2
- package/dist/agent/userProfile.js.map +1 -1
- package/dist/cli/doctor.js +33 -0
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/onboard.js +4 -4
- package/dist/cli/onboard.js.map +1 -1
- package/dist/config/config.js +3 -3
- package/dist/config/config.js.map +1 -1
- package/dist/config/schema.js +8 -1
- package/dist/config/schema.js.map +1 -1
- package/dist/gateway/routes/adminRouter.js +500 -0
- package/dist/gateway/routes/adminRouter.js.map +1 -0
- package/dist/gateway/routes/agents.js +231 -0
- package/dist/gateway/routes/agents.js.map +1 -0
- package/dist/gateway/routes/agentsRouter.js +32 -0
- package/dist/gateway/routes/agentsRouter.js.map +1 -0
- package/dist/gateway/routes/checkpoints.js +41 -0
- package/dist/gateway/routes/checkpoints.js.map +1 -0
- package/dist/gateway/routes/commandPost.js +755 -0
- package/dist/gateway/routes/commandPost.js.map +1 -0
- package/dist/gateway/routes/companies.js +166 -0
- package/dist/gateway/routes/companies.js.map +1 -0
- package/dist/gateway/routes/files.js +295 -0
- package/dist/gateway/routes/files.js.map +1 -0
- package/dist/gateway/routes/hardwareRouter.js +151 -0
- package/dist/gateway/routes/hardwareRouter.js.map +1 -0
- package/dist/gateway/routes/mcpRouter.js +88 -0
- package/dist/gateway/routes/mcpRouter.js.map +1 -0
- package/dist/gateway/routes/mesh.js +464 -0
- package/dist/gateway/routes/mesh.js.map +1 -0
- package/dist/gateway/routes/metricsRouter.js +131 -0
- package/dist/gateway/routes/metricsRouter.js.map +1 -0
- package/dist/gateway/routes/organism.js +82 -0
- package/dist/gateway/routes/organism.js.map +1 -0
- package/dist/gateway/routes/paperclip.js +101 -0
- package/dist/gateway/routes/paperclip.js.map +1 -0
- package/dist/gateway/routes/sessions.js +227 -0
- package/dist/gateway/routes/sessions.js.map +1 -0
- package/dist/gateway/routes/skills.js +295 -0
- package/dist/gateway/routes/skills.js.map +1 -0
- package/dist/gateway/routes/socialRouter.js +145 -0
- package/dist/gateway/routes/socialRouter.js.map +1 -0
- package/dist/gateway/routes/systemRouter.js +220 -0
- package/dist/gateway/routes/systemRouter.js.map +1 -0
- package/dist/gateway/routes/teamsRecipes.js +297 -0
- package/dist/gateway/routes/teamsRecipes.js.map +1 -0
- package/dist/gateway/routes/tests.js +401 -0
- package/dist/gateway/routes/tests.js.map +1 -0
- package/dist/gateway/routes/traces.js +33 -0
- package/dist/gateway/routes/traces.js.map +1 -0
- package/dist/gateway/routes/voiceRouter.js +770 -0
- package/dist/gateway/routes/voiceRouter.js.map +1 -0
- package/dist/gateway/routes/watchRouter.js +131 -0
- package/dist/gateway/routes/watchRouter.js.map +1 -0
- package/dist/gateway/server.js +1179 -7379
- package/dist/gateway/server.js.map +1 -1
- package/dist/mcp/registry.js +2 -2
- package/dist/mcp/registry.js.map +1 -1
- package/dist/memory/episodic.js +2 -2
- package/dist/memory/episodic.js.map +1 -1
- package/dist/memory/learning.js +3 -3
- package/dist/memory/learning.js.map +1 -1
- package/dist/memory/memory.js +3 -3
- package/dist/memory/memory.js.map +1 -1
- package/dist/organism/drives.js +2 -2
- package/dist/organism/drives.js.map +1 -1
- package/dist/providers/errorTaxonomy.js +13 -0
- package/dist/providers/errorTaxonomy.js.map +1 -1
- package/dist/providers/ollama.js +3 -1
- package/dist/providers/ollama.js.map +1 -1
- package/dist/providers/openai_compat.js +4 -3
- package/dist/providers/openai_compat.js.map +1 -1
- package/dist/providers/router.js +13 -0
- package/dist/providers/router.js.map +1 -1
- package/dist/safety/killSwitch.js +2 -2
- package/dist/safety/killSwitch.js.map +1 -1
- package/dist/skills/builtin/agent_debate.js +2 -2
- package/dist/skills/builtin/agent_debate.js.map +1 -1
- package/dist/skills/builtin/apply_patch.js +3 -3
- package/dist/skills/builtin/apply_patch.js.map +1 -1
- package/dist/skills/builtin/shell.js +2 -2
- package/dist/skills/builtin/shell.js.map +1 -1
- package/dist/skills/builtin/voice_control.js +49 -0
- package/dist/skills/builtin/voice_control.js.map +1 -0
- package/dist/skills/builtin/widget_gallery.js +6 -1
- package/dist/skills/builtin/widget_gallery.js.map +1 -1
- package/dist/skills/registry.js +15 -4
- package/dist/skills/registry.js.map +1 -1
- package/dist/storage/JsonStorage.js +4 -4
- package/dist/storage/JsonStorage.js.map +1 -1
- package/dist/utils/constants.js +1 -1
- package/dist/utils/constants.js.map +1 -1
- package/dist/utils/helpers.js +3 -1
- package/dist/utils/helpers.js.map +1 -1
- package/dist/utils/lifecycle.js +86 -0
- package/dist/utils/lifecycle.js.map +1 -0
- package/dist/voice/bridge.js +136 -0
- package/dist/voice/bridge.js.map +1 -0
- package/docs/COO-MASTER-PLAN-2026-05-02.md +474 -0
- package/docs/HANDOFF/2026-04-29.md +141 -0
- package/docs/HANDOFF-2026-04-30.md +144 -0
- package/docs/HANDOFF-2026-05-03.md +114 -0
- package/docs/adr/2026-04-29-widget-pipeline-traceability.md +49 -0
- package/docs/agent-memory/README.md +45 -0
- package/docs/agent-memory/commands.md +100 -0
- package/docs/agent-memory/context-tree.md +101 -0
- package/docs/agent-memory/current-state.md +54 -0
- package/docs/agent-memory/decisions.md +78 -0
- package/docs/agent-memory/known-issues.md +76 -0
- package/docs/agent-memory/reflections.md +52 -0
- package/docs/agent-memory/skills-candidates.md +80 -0
- package/docs/superpowers/plans/2026-04-29-comprehensive-audit.md +256 -0
- package/docs/superpowers/plans/2026-04-29-comprehensive-test-plan.md +396 -0
- package/docs/superpowers/plans/2026-04-29-fix-all-prs.md +251 -0
- package/docs/superpowers/plans/2026-04-29-gitnexus-gap-remediation.md +969 -0
- package/package.json +5 -2
- package/ui/dist/assets/{AuditPanel-CM6Wg9hO.js → AuditPanel-VzSndmDN.js} +2 -2
- package/ui/dist/assets/{AutonomyPanel-CESx3ANg.js → AutonomyPanel-BiFouzAV.js} +2 -2
- package/ui/dist/assets/AutopilotPanel-fjOfM668.js +1 -0
- package/ui/dist/assets/{AutoresearchPanel-DR47NqT5.js → AutoresearchPanel-CVCxzAH3.js} +2 -2
- package/ui/dist/assets/BackupPanel-CHVTG--q.js +1 -0
- package/ui/dist/assets/{BrowserPanel-C15x9bLn.js → BrowserPanel-D5mvMKFU.js} +2 -2
- package/ui/dist/assets/CPActivity-B12mt35m.js +1 -0
- package/ui/dist/assets/CPAgentDetail-DsdShc-1.js +1 -0
- package/ui/dist/assets/CPAgents-j_7C-oQV.js +1 -0
- package/ui/dist/assets/CPApprovals-BShKSX9X.js +1 -0
- package/ui/dist/assets/CPCosts-CKPlhBDs.js +1 -0
- package/ui/dist/assets/CPDashboard-11c0nkxK.js +1 -0
- package/ui/dist/assets/CPFiles-BhLEOnXy.js +1 -0
- package/ui/dist/assets/CPGoals-Bi3t1b2P.js +1 -0
- package/ui/dist/assets/CPInbox-Bbr7khp6.js +11 -0
- package/ui/dist/assets/CPIssueDetail-DSdgNK8r.js +1 -0
- package/ui/dist/assets/CPIssues-DDEVKhX6.js +1 -0
- package/ui/dist/assets/CPLayout-DgPOfyGv.js +17 -0
- package/ui/dist/assets/CPOrg-Df73RrRJ.js +8 -0
- package/ui/dist/assets/CPRuns-ByioAz8w.js +1 -0
- package/ui/dist/assets/{CPSocial-nb-j7sOE.js → CPSocial-Dlnr_w1X.js} +2 -2
- package/ui/dist/assets/ChannelsPanel-DQjQCTK5.js +1 -0
- package/ui/dist/assets/CheckpointsPanel-C4vKjlAJ.js +1 -0
- package/ui/dist/assets/CommandPostHub-C9pp5Giq.js +24 -0
- package/ui/dist/assets/CronPanel-C6bzUfrD.js +1 -0
- package/ui/dist/assets/DaemonPanel-BA5Tb_UO.js +1 -0
- package/ui/dist/assets/{DataTable-B2Ma8hfi.js → DataTable-CH7IYJJh.js} +1 -1
- package/ui/dist/assets/{EmptyState-CcKyk5Yn.js → EmptyState-jU6yNDnF.js} +1 -1
- package/ui/dist/assets/{EvalHarnessPanel-BqtMc1ZM.js → EvalHarnessPanel-DnYqredY.js} +2 -2
- package/ui/dist/assets/EvalPanel-ChO7CD1r.js +1 -0
- package/ui/dist/assets/{FilesPanel-3QKvrWPo.js → FilesPanel-CaUkv2is.js} +2 -2
- package/ui/dist/assets/FleetPanel-DC_5uj0N.js +1 -0
- package/ui/dist/assets/{HomelabPanel-DhrjTX9m.js → HomelabPanel-CE5PGRpL.js} +2 -2
- package/ui/dist/assets/InfraView-C-uSlvb9.js +2 -0
- package/ui/dist/assets/InlineEditableField-BMQjiE6-.js +1 -0
- package/ui/dist/assets/Input-Bu_b3qmY.js +1 -0
- package/ui/dist/assets/IntegrationsPanel-DsYpAq43.js +1 -0
- package/ui/dist/assets/IntelligenceView-DUdIO1K7.js +2 -0
- package/ui/dist/assets/LearningPanel-UpQZC-mA.js +1 -0
- package/ui/dist/assets/LogsPanel-ClXJ4fcr.js +1 -0
- package/ui/dist/assets/McpPanel-JKgtIERQ.js +1 -0
- package/ui/dist/assets/{MemoryGraphPanel-Bzvjmzvk.js → MemoryGraphPanel-Bo2OrvA6.js} +2 -2
- package/ui/dist/assets/MemoryWikiPanel-BqJ1AmYm.js +11 -0
- package/ui/dist/assets/{MeshPanel-C3LJSlht.js → MeshPanel-BJVGYvwk.js} +2 -2
- package/ui/dist/assets/Modal-CAAooiZU.js +1 -0
- package/ui/dist/assets/NvidiaPanel-BtCg3G4w.js +1 -0
- package/ui/dist/assets/OrganismPanel-DgrTTzcF.js +1 -0
- package/ui/dist/assets/OverviewPanel-rVav1Hox.js +1 -0
- package/ui/dist/assets/{PageHeader-BimceqQo.js → PageHeader-CnZtP8ek.js} +1 -1
- package/ui/dist/assets/PaperclipPanel-C-FKdhiF.js +1 -0
- package/ui/dist/assets/{PersonasPanel-L1j78p6H.js → PersonasPanel-BmlxokfB.js} +1 -1
- package/ui/dist/assets/RecipesPanel-BNKKChis.js +1 -0
- package/ui/dist/assets/SecurityPanel-I7JRHiNy.js +1 -0
- package/ui/dist/assets/SelfImprovePanel-u9h0Lt3p.js +1 -0
- package/ui/dist/assets/{SelfProposalsPanel-lNmiDThB.js → SelfProposalsPanel-DKl9iBjM.js} +2 -2
- package/ui/dist/assets/SessionsPanel-BhRiWI_g.js +1 -0
- package/ui/dist/assets/{SessionsTab-JQbltWww.js → SessionsTab-Bk08wyeY.js} +1 -1
- package/ui/dist/assets/SettingsPanel-haLfmG2k.js +1 -0
- package/ui/dist/assets/SettingsView--gi3fxI8.js +2 -0
- package/ui/dist/assets/{SkeletonLoader-atQtpcF5.js → SkeletonLoader-B5v09EF_.js} +1 -1
- package/ui/dist/assets/{SkillsPanel-DlFs2ih7.js → SkillsPanel-BlAHFLcQ.js} +1 -1
- package/ui/dist/assets/SomaView-CExtS3zw.js +5 -0
- package/ui/dist/assets/{StatCard-DciE_Iqc.js → StatCard-BIsyMybM.js} +1 -1
- package/ui/dist/assets/{StatusBadge-BtfSPoW2.js → StatusBadge-D5nU7El8.js} +1 -1
- package/ui/dist/assets/Tabs-BBYZrBI8.js +1 -0
- package/ui/dist/assets/TeamsPanel-LPXJg823.js +1 -0
- package/ui/dist/assets/TelemetryPanel-EqpRBmOI.js +1 -0
- package/ui/dist/assets/TitanCanvas-BCbWnLMd.js +985 -0
- package/ui/dist/assets/ToolsView-CeP0Zz-N.js +2 -0
- package/ui/dist/assets/{Tooltip-70UK0E2I.js → Tooltip-BSO2XVpF.js} +1 -1
- package/ui/dist/assets/TraceViewer-BKI7o5B0.js +1 -0
- package/ui/dist/assets/TrainingPanel-c-RhjdE1.js +1 -0
- package/ui/dist/assets/VoiceOverlay-D-gc58b0.js +27 -0
- package/ui/dist/assets/VramPanel-C6xc7zgd.js +1 -0
- package/ui/dist/assets/{WatchView-C-sGFpVy.js → WatchView-dqBVCVH0.js} +1 -1
- package/ui/dist/assets/WorkTab-CBoLNrTM.js +1 -0
- package/ui/dist/assets/{WorkflowsPanel-CvgQU1xI.js → WorkflowsPanel-BAnSTOYe.js} +2 -2
- package/ui/dist/assets/approvalHeadline-DB9SgR-9.js +1 -0
- package/ui/dist/assets/{arrow-left-DwqHtJiU.js → arrow-left-5chqas7J.js} +1 -1
- package/ui/dist/assets/briefcase-D4vLzudp.js +6 -0
- package/ui/dist/assets/{chart-column-BtNO6sRy.js → chart-column-CdFlBpoP.js} +1 -1
- package/ui/dist/assets/check-Bpm1IONe.js +6 -0
- package/ui/dist/assets/chevron-down-D7OLjvuD.js +6 -0
- package/ui/dist/assets/chevron-right-aQEw2mUW.js +6 -0
- package/ui/dist/assets/chevron-up-C5g6pEj8.js +6 -0
- package/ui/dist/assets/{circle-check-big-DZRE_MbN.js → circle-check-big-fPhEdP88.js} +1 -1
- package/ui/dist/assets/clock-CTsgP_Sn.js +6 -0
- package/ui/dist/assets/{dollar-sign-aVG3a5eL.js → dollar-sign-CudFVYFc.js} +1 -1
- package/ui/dist/assets/{download-BxiWJU4G.js → download-DZRxDn67.js} +1 -1
- package/ui/dist/assets/external-link-BZ0y_Ahx.js +6 -0
- package/ui/dist/assets/{eye-off-CkgfFYhm.js → eye-off-BmJF0YYx.js} +1 -1
- package/ui/dist/assets/folder-DA43TRCm.js +11 -0
- package/ui/dist/assets/{funnel-PkLdxKyC.js → funnel-J3mULzrz.js} +1 -1
- package/ui/dist/assets/{git-branch-BM-Gw95X.js → git-branch-oHibJqDq.js} +1 -1
- package/ui/dist/assets/{index-D0RJ8701.css → index-BR0vfkIi.css} +1 -1
- package/ui/dist/assets/{index-CahJbWSR.js → index-DzwowwSI.js} +20 -20
- package/ui/dist/assets/{layers-BuGf4FIJ.js → layers-DsyEyu7z.js} +1 -1
- package/ui/dist/assets/{legacy-CR6o4t-y.js → legacy-8ITl64sV.js} +1 -1
- package/ui/dist/assets/{lightbulb-n8gc_XAL.js → lightbulb-C54Ske-p.js} +1 -1
- package/ui/dist/assets/list-todo-Cnd4rdoK.js +6 -0
- package/ui/dist/assets/loader-circle-1YOBsoQp.js +6 -0
- package/ui/dist/assets/network-DbGDAdrn.js +6 -0
- package/ui/dist/assets/{pause-DCV52koX.js → pause-CYhO_uQo.js} +1 -1
- package/ui/dist/assets/{play-CcJ9BnCh.js → play-DVY9c5Ck.js} +1 -1
- package/ui/dist/assets/{plug-CfWBXfCl.js → plug-BcXjlPUL.js} +1 -1
- package/ui/dist/assets/plus-Csu2v9GN.js +6 -0
- package/ui/dist/assets/{proxy-CzZDfLmm.js → proxy-DxS2_9D7.js} +1 -1
- package/ui/dist/assets/rotate-ccw-Co-_W04j.js +6 -0
- package/ui/dist/assets/save-Btx-kpoW.js +6 -0
- package/ui/dist/assets/search-0hXTwEZR.js +6 -0
- package/ui/dist/assets/send-TEpapzQR.js +6 -0
- package/ui/dist/assets/shield-check-DjBJXZUr.js +6 -0
- package/ui/dist/assets/{square-DJpUhlxi.js → square-OweUvjP-.js} +1 -1
- package/ui/dist/assets/{target-DWcmM_9m.js → target-BRW80Xer.js} +1 -1
- package/ui/dist/assets/terminal-BtiqJ628.js +16 -0
- package/ui/dist/assets/{toggle-right-YusFQ69L.js → toggle-right-CKtSrl28.js} +1 -1
- package/ui/dist/assets/{trash-2-CK7yQ55V.js → trash-2-DgWrHVax.js} +1 -1
- package/ui/dist/assets/{trending-up-DGjFyubC.js → trending-up-MpIrE4j6.js} +1 -1
- package/ui/dist/assets/{trophy-uQv_NgDB.js → trophy-CECuZNhX.js} +1 -1
- package/ui/dist/assets/users-dZgv4ePG.js +16 -0
- package/ui/dist/assets/wrench-CDz3xYve.js +11 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/AutopilotPanel-DtEet1hJ.js +0 -1
- package/ui/dist/assets/BackupPanel-BGP8p3l3.js +0 -1
- package/ui/dist/assets/CPAgents-DYUtPzSq.js +0 -1
- package/ui/dist/assets/CPDashboard-Bf0-SyCh.js +0 -6
- package/ui/dist/assets/CPFiles-CxgxjQcO.js +0 -1
- package/ui/dist/assets/CPGoals-BsmCMTvT.js +0 -1
- package/ui/dist/assets/CPInbox-tMSbmQ9H.js +0 -11
- package/ui/dist/assets/ChannelsPanel-DP5C2OKd.js +0 -1
- package/ui/dist/assets/CheckpointsPanel-DlranVLZ.js +0 -1
- package/ui/dist/assets/CommandPostHub-BgxIa4Ev.js +0 -29
- package/ui/dist/assets/CronPanel-LoT5yKwJ.js +0 -1
- package/ui/dist/assets/DaemonPanel-DBGMqaE_.js +0 -1
- package/ui/dist/assets/EvalPanel-Bc33j0pN.js +0 -1
- package/ui/dist/assets/FleetPanel-CSsXuQYj.js +0 -1
- package/ui/dist/assets/InfraView-CR6HyrL6.js +0 -2
- package/ui/dist/assets/InlineEditableField-CnvF-yFR.js +0 -1
- package/ui/dist/assets/Input-GTHp2Rkr.js +0 -1
- package/ui/dist/assets/IntegrationsPanel-CymCRE3T.js +0 -1
- package/ui/dist/assets/IntelligenceView-C1IHxJRC.js +0 -2
- package/ui/dist/assets/LearningPanel-DOCES3lH.js +0 -1
- package/ui/dist/assets/LogsPanel-BLnAqEaZ.js +0 -1
- package/ui/dist/assets/McpPanel-ChUzmr3z.js +0 -1
- package/ui/dist/assets/MemoryWikiPanel-Dwk3Aqwd.js +0 -11
- package/ui/dist/assets/NvidiaPanel-CeZK_-CV.js +0 -1
- package/ui/dist/assets/OrganismPanel-BB6YOiQV.js +0 -1
- package/ui/dist/assets/OverviewPanel-BmtBhQnv.js +0 -1
- package/ui/dist/assets/PaperclipPanel-C-brgwA3.js +0 -1
- package/ui/dist/assets/RecipesPanel-34lCfynJ.js +0 -1
- package/ui/dist/assets/SecurityPanel-CBTPWLj6.js +0 -1
- package/ui/dist/assets/SelfImprovePanel-BrPbFHhG.js +0 -1
- package/ui/dist/assets/SessionsPanel-DAEYIn83.js +0 -1
- package/ui/dist/assets/SettingsPanel-CzRROAYQ.js +0 -1
- package/ui/dist/assets/SettingsView-CN7ii2uw.js +0 -2
- package/ui/dist/assets/SomaView-Ba642Oqb.js +0 -5
- package/ui/dist/assets/TeamsPanel-DKQ5z2Qe.js +0 -1
- package/ui/dist/assets/TelemetryPanel-B6KAc55Q.js +0 -1
- package/ui/dist/assets/TitanCanvas-C-s0A-lv.js +0 -1092
- package/ui/dist/assets/ToolsView-Dei0KMP0.js +0 -2
- package/ui/dist/assets/TraceViewer-BniolyBx.js +0 -1
- package/ui/dist/assets/TrainingPanel-Bz4CTPGW.js +0 -1
- package/ui/dist/assets/VoiceOverlay-CmNCrLcd.js +0 -37
- package/ui/dist/assets/VramPanel-Xh_OtRDR.js +0 -1
- package/ui/dist/assets/WorkTab-BjLNmgIK.js +0 -1
package/dist/cli/doctor.js
CHANGED
|
@@ -141,6 +141,39 @@ async function runDoctor(options) {
|
|
|
141
141
|
});
|
|
142
142
|
}
|
|
143
143
|
}
|
|
144
|
+
const modelStr = config.agent.model || "";
|
|
145
|
+
const knownProviderPrefixes = [
|
|
146
|
+
"ollama/",
|
|
147
|
+
"anthropic/",
|
|
148
|
+
"openai/",
|
|
149
|
+
"google/",
|
|
150
|
+
"groq/",
|
|
151
|
+
"mistral/",
|
|
152
|
+
"openrouter/",
|
|
153
|
+
"xai/",
|
|
154
|
+
"together/",
|
|
155
|
+
"deepseek/",
|
|
156
|
+
"fireworks/",
|
|
157
|
+
"cerebras/",
|
|
158
|
+
"cohere/",
|
|
159
|
+
"perplexity/",
|
|
160
|
+
"azure/",
|
|
161
|
+
"openai_compat/"
|
|
162
|
+
];
|
|
163
|
+
const hasProviderPrefix = knownProviderPrefixes.some((p) => modelStr.startsWith(p));
|
|
164
|
+
if (!hasProviderPrefix && modelStr) {
|
|
165
|
+
checks.push({
|
|
166
|
+
name: "Model resolution",
|
|
167
|
+
status: "warn",
|
|
168
|
+
message: `"${modelStr}" has no provider prefix (e.g. ollama/${modelStr}). TITAN will try to auto-detect the provider but may fail at runtime. Add a provider prefix to avoid ambiguity.`
|
|
169
|
+
});
|
|
170
|
+
} else if (hasProviderPrefix) {
|
|
171
|
+
checks.push({
|
|
172
|
+
name: "Model resolution",
|
|
173
|
+
status: "pass",
|
|
174
|
+
message: `${modelStr} (provider prefix detected)`
|
|
175
|
+
});
|
|
176
|
+
}
|
|
144
177
|
} catch (error) {
|
|
145
178
|
checks.push({
|
|
146
179
|
name: "Config validation",
|
package/dist/cli/doctor.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/doctor.ts"],"sourcesContent":["/**\n * TITAN -- Doctor Diagnostic Tool\n * Checks system health, configuration, connectivity, and dependencies.\n * Supports --fix flag to auto-heal detected issues via selfHeal.ts.\n * Supports --json flag for machine-readable output.\n */\nimport chalk from 'chalk';\nimport { existsSync, readdirSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { loadConfig, configExists } from '../config/config.js';\nimport { TITAN_HOME, TITAN_CONFIG_PATH, TITAN_DB_PATH, TITAN_WORKSPACE, TITAN_VERSION, TITAN_LOGS_DIR } from '../utils/constants.js';\nimport { healthCheckAll } from '../providers/router.js';\nimport { auditSecurity } from '../security/sandbox.js';\nimport { getStallStats } from '../agent/stallDetector.js';\nimport {\n fixMissingTitanHome,\n fixMissingConfig,\n fixInvalidConfig,\n fixMissingWorkspace,\n fixBrokenChannelConfig,\n fixPermissions,\n fixStaleLogFiles,\n fixOrphanedSessions,\n type HealResult,\n} from './selfHeal.js';\n\ninterface CheckResult {\n name: string;\n status: 'pass' | 'warn' | 'fail';\n message: string;\n /** Key used to map to an auto-fix function */\n fixKey?: string;\n}\n\n/** Fetch weekly npm download count for a package */\nasync function fetchNpmDownloads(packageName: string): Promise<number | null> {\n try {\n const response = await fetch(`https://api.npmjs.org/downloads/point/last-week/${packageName}`, {\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) return null;\n const data = await response.json() as { downloads?: number };\n return data.downloads ?? null;\n } catch {\n return null;\n }\n}\n\nexport interface DoctorReport {\n version: string;\n timestamp: string;\n checks: CheckResult[];\n summary: { pass: number; warn: number; fail: number };\n npm?: { weeklyDownloads: number | null };\n fixes?: HealResult[];\n}\n\nexport async function runDoctor(options?: { fix?: boolean; json?: boolean; dryRun?: boolean }): Promise<DoctorReport> {\n const autoFix = options?.fix ?? false;\n const dryRun = options?.dryRun ?? false;\n const jsonOutput = options?.json ?? false;\n\n if (!jsonOutput) {\n console.log(chalk.cyan(`\\n🩺 TITAN Doctor v${TITAN_VERSION}\\n`));\n console.log(chalk.gray('Running diagnostics...\\n'));\n }\n\n const checks: CheckResult[] = [];\n const healResults: HealResult[] = [];\n\n // 1. Node.js version\n const nodeVersion = process.versions.node;\n const [major] = nodeVersion.split('.').map(Number);\n checks.push({\n name: 'Node.js version',\n status: major >= 20 ? 'pass' : major >= 18 ? 'warn' : 'fail',\n message: `v${nodeVersion} ${major >= 20 ? '(recommended)' : major >= 18 ? '(minimum, upgrade recommended)' : '(too old, need >= 20)'}`,\n });\n\n // 2. TITAN home directory\n checks.push({\n name: 'TITAN home directory',\n status: existsSync(TITAN_HOME) ? 'pass' : 'warn',\n message: existsSync(TITAN_HOME) ? TITAN_HOME : `Not found: ${TITAN_HOME} (run: titan onboard)`,\n fixKey: 'titanHome',\n });\n\n // 3. Configuration file\n checks.push({\n name: 'Configuration file',\n status: configExists() ? 'pass' : 'warn',\n message: configExists() ? TITAN_CONFIG_PATH : `Not found (run: titan onboard)`,\n fixKey: 'config',\n });\n\n // 4. Database (lazily created — not having one on a fresh install is normal)\n checks.push({\n name: 'Database',\n status: 'pass',\n message: existsSync(TITAN_DB_PATH) ? TITAN_DB_PATH : 'Will be created on first use (this is normal)',\n });\n\n // 5. Workspace\n checks.push({\n name: 'Workspace directory',\n status: existsSync(TITAN_WORKSPACE) ? 'pass' : 'warn',\n message: existsSync(TITAN_WORKSPACE) ? TITAN_WORKSPACE : `Not found (run: titan onboard)`,\n fixKey: 'workspace',\n });\n\n // 6. AI Provider connectivity\n if (configExists()) {\n if (!jsonOutput) console.log(chalk.gray(' Checking AI providers...'));\n try {\n const config = loadConfig();\n const providerHealth = await healthCheckAll();\n for (const [name, healthy] of Object.entries(providerHealth)) {\n let message = 'Reachable';\n if (!healthy) {\n // Provide actionable error messages for missing API keys\n const providerConfig = (config.providers as Record<string, Record<string, unknown>>)?.[name];\n const hasApiKey = !!(providerConfig?.apiKey);\n const envVarMap: Record<string, string> = {\n anthropic: 'ANTHROPIC_API_KEY', openai: 'OPENAI_API_KEY',\n google: 'GOOGLE_API_KEY', groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY', openrouter: 'OPENROUTER_API_KEY',\n xai: 'XAI_API_KEY', together: 'TOGETHER_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY', fireworks: 'FIREWORKS_API_KEY',\n cerebras: 'CEREBRAS_API_KEY', cohere: 'COHERE_API_KEY',\n perplexity: 'PERPLEXITY_API_KEY', azure: 'AZURE_OPENAI_API_KEY',\n };\n const envVar = envVarMap[name];\n const hasEnvVar = envVar ? !!process.env[envVar] : false;\n\n if (name === 'ollama') {\n const baseUrl = (providerConfig?.baseUrl as string) || 'http://localhost:11434';\n message = `Unreachable at ${baseUrl} — is Ollama running?`;\n } else if (!hasApiKey && !hasEnvVar) {\n message = envVar\n ? `No API key — set ${envVar} or add providers.${name}.apiKey to ~/.titan/titan.json`\n : `No API key — add providers.${name}.apiKey to ~/.titan/titan.json`;\n } else {\n message = 'API key set but provider unreachable — check key validity or network';\n }\n }\n checks.push({\n name: `Provider: ${name}`,\n status: healthy ? 'pass' : 'warn',\n message,\n });\n }\n } catch (error) {\n checks.push({\n name: 'AI Providers',\n status: 'warn',\n message: `Could not check: ${(error as Error).message}`,\n });\n }\n }\n\n // 7. Configuration validation\n if (configExists()) {\n try {\n const config = loadConfig();\n checks.push({\n name: 'Config validation',\n status: 'pass',\n message: `Model: ${config.agent.model}`,\n });\n\n // Check channel configuration\n for (const [channelName, channelConfig] of Object.entries(config.channels)) {\n if (channelConfig.enabled) {\n const hasToken = !!(channelConfig.token || channelConfig.apiKey);\n checks.push({\n name: `Channel: ${channelName}`,\n status: hasToken ? 'pass' : 'fail',\n message: hasToken ? 'Configured' : 'Enabled but no token set',\n fixKey: 'channels',\n });\n }\n }\n } catch (error) {\n checks.push({\n name: 'Config validation',\n status: 'fail',\n message: (error as Error).message,\n fixKey: 'invalidConfig',\n });\n }\n }\n\n // 8. Cloudflare Tunnel (when enabled)\n if (configExists()) {\n try {\n const cfg = loadConfig();\n if (cfg.tunnel?.enabled) {\n let tunnelAvailable = false;\n try {\n const { execSync } = await import('child_process');\n execSync('cloudflared --version', { stdio: 'ignore' });\n tunnelAvailable = true;\n } catch {\n // not installed\n }\n checks.push({\n name: 'Cloudflare Tunnel (cloudflared)',\n status: tunnelAvailable ? 'pass' : 'fail',\n message: tunnelAvailable ? 'cloudflared binary found' : 'cloudflared not installed (https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/)',\n });\n }\n } catch {\n // config load failed — handled elsewhere\n }\n }\n\n // 9. Security audit\n if (configExists()) {\n const securityIssues = auditSecurity();\n for (const issue of securityIssues) {\n checks.push({\n name: 'Security',\n status: issue.level === 'error' ? 'fail' : issue.level === 'warn' ? 'warn' : 'pass',\n message: issue.message,\n });\n }\n }\n\n // 9. Disk space\n try {\n const { execSync } = await import('child_process');\n const dfOutput = execSync('df -h / | tail -1', { encoding: 'utf-8' });\n const parts = dfOutput.trim().split(/\\s+/);\n const available = parts[3];\n const usePercent = parts.length >= 5 ? parseInt(parts[4], 10) : NaN;\n checks.push({\n name: 'Disk space',\n status: isNaN(usePercent) ? 'warn' : usePercent < 90 ? 'pass' : usePercent < 95 ? 'warn' : 'fail',\n message: isNaN(usePercent) ? 'Could not parse disk usage' : `${available} available (${parts[4]} used)`,\n });\n } catch {\n checks.push({ name: 'Disk space', status: 'warn', message: 'Could not check' });\n }\n\n // 10. Memory\n const memUsage = process.memoryUsage();\n const rssGB = (memUsage.rss / 1024 / 1024).toFixed(1);\n checks.push({\n name: 'Memory usage',\n status: 'pass',\n message: `${rssGB} MB RSS`,\n });\n\n // 11. Stall Detector Status\n const stallStatus = getStallStats();\n let stallStatusLevel: 'pass' | 'warn' | 'fail' = 'pass';\n let stallMessage = 'Healthy (0 active stalls)';\n\n // getStallStats returns an array of session stats\n const activeStalls = stallStatus.length;\n if (activeStalls > 0) {\n stallStatusLevel = activeStalls > 2 ? 'fail' : 'warn';\n stallMessage = `Detected ${activeStalls} stuck sessions.`;\n }\n checks.push({\n name: 'Agent Stall Status',\n status: stallStatusLevel,\n message: stallMessage,\n });\n\n // 12. Stale sessions check\n const sessionsDir = join(TITAN_HOME, 'sessions');\n if (existsSync(sessionsDir)) {\n try {\n const sessionFiles = readdirSync(sessionsDir);\n const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;\n let staleCount = 0;\n for (const file of sessionFiles) {\n const fullPath = join(sessionsDir, file);\n try {\n const stat = statSync(fullPath);\n if (stat.isFile() && stat.mtimeMs < oneDayAgo) {\n staleCount++;\n }\n } catch {\n // skip unreadable files\n }\n }\n checks.push({\n name: 'Stale sessions',\n status: staleCount > 0 ? 'warn' : 'pass',\n message: staleCount > 0 ? `${staleCount} session file(s) older than 24 hours` : 'No stale sessions',\n fixKey: 'staleSessions',\n });\n } catch {\n checks.push({ name: 'Stale sessions', status: 'warn', message: 'Could not read sessions directory' });\n }\n } else {\n checks.push({ name: 'Stale sessions', status: 'pass', message: 'No sessions directory' });\n }\n\n // 13. Log directory size check\n if (existsSync(TITAN_LOGS_DIR)) {\n try {\n const logFiles = readdirSync(TITAN_LOGS_DIR);\n let totalBytes = 0;\n for (const file of logFiles) {\n const fullPath = join(TITAN_LOGS_DIR, file);\n try {\n const stat = statSync(fullPath);\n if (stat.isFile()) {\n totalBytes += stat.size;\n }\n } catch {\n // skip unreadable files\n }\n }\n const totalMB = totalBytes / (1024 * 1024);\n checks.push({\n name: 'Log directory size',\n status: totalMB > 100 ? 'warn' : 'pass',\n message: totalMB > 100\n ? `${totalMB.toFixed(1)} MB (consider rotating logs)`\n : `${totalMB.toFixed(1)} MB`,\n fixKey: totalMB > 100 ? 'staleLogs' : undefined,\n });\n } catch {\n checks.push({ name: 'Log directory size', status: 'warn', message: 'Could not check log directory' });\n }\n } else {\n checks.push({ name: 'Log directory size', status: 'pass', message: 'No logs directory' });\n }\n\n // 14. Permissions check\n checks.push({\n name: 'TITAN home permissions',\n status: existsSync(TITAN_HOME) ? 'pass' : 'warn',\n message: existsSync(TITAN_HOME) ? 'Exists' : 'Cannot check (TITAN_HOME missing)',\n fixKey: 'permissions',\n });\n\n // 15. npm download count\n if (!jsonOutput) console.log(chalk.gray(' Checking npm downloads...'));\n const npmDownloads = await fetchNpmDownloads('titan-agent');\n if (npmDownloads !== null) {\n checks.push({\n name: 'npm weekly downloads',\n status: 'pass',\n message: `${npmDownloads.toLocaleString()} downloads/week (titan-agent)`,\n });\n } else {\n checks.push({\n name: 'npm weekly downloads',\n status: 'warn',\n message: 'Could not fetch npm download stats',\n });\n }\n\n // Auto-fix pass if --fix was specified\n if (autoFix) {\n if (dryRun) {\n if (!jsonOutput) console.log(chalk.cyan('\\n 🔧 Dry run — showing fixes that WOULD be applied:\\n'));\n } else {\n if (!jsonOutput) console.log(chalk.cyan('\\n 🔧 Running auto-fix...\\n'));\n }\n\n const issueChecks = checks.filter((c) => (c.status === 'warn' || c.status === 'fail') && c.fixKey);\n const fixKeysNeeded = new Set(issueChecks.map((c) => c.fixKey!));\n\n const fixMap: Record<string, () => HealResult> = {\n titanHome: fixMissingTitanHome,\n config: fixMissingConfig,\n invalidConfig: fixInvalidConfig,\n workspace: fixMissingWorkspace,\n channels: fixBrokenChannelConfig,\n permissions: fixPermissions,\n staleLogs: fixStaleLogFiles,\n staleSessions: fixOrphanedSessions,\n };\n\n const fixDescriptions: Record<string, string> = {\n titanHome: 'Create TITAN home directory',\n config: 'Create default configuration file',\n invalidConfig: 'Reset invalid configuration to defaults',\n workspace: 'Create workspace directory',\n channels: 'Disable misconfigured channels',\n permissions: 'Fix file permissions on TITAN home',\n staleLogs: 'Clean up stale log files',\n staleSessions: 'Remove orphaned session files',\n };\n\n if (dryRun) {\n for (const key of fixKeysNeeded) {\n if (fixMap[key]) {\n const description = fixDescriptions[key] ?? key;\n healResults.push({ action: key, success: true, message: `[dry-run] Would fix: ${description}` });\n if (!jsonOutput) {\n console.log(` ${chalk.yellow('⏭')} ${key}: ${chalk.gray(`Would fix: ${description}`)}`);\n }\n }\n }\n\n if (!jsonOutput) {\n console.log(chalk.cyan(`\\n 🔧 Dry run complete — ${fixKeysNeeded.size} fix(es) would be applied. Run without --dry-run to apply.`));\n }\n } else {\n for (const key of fixKeysNeeded) {\n const fixFn = fixMap[key];\n if (fixFn) {\n const result = fixFn();\n healResults.push(result);\n if (!jsonOutput) {\n const icon = result.success ? chalk.green('✅') : chalk.red('❌');\n console.log(` ${icon} ${result.action}: ${chalk.gray(result.message)}`);\n }\n }\n }\n\n if (!jsonOutput) {\n const fixedCount = healResults.filter((r) => r.success).length;\n const remainingCount = healResults.filter((r) => !r.success).length;\n console.log(chalk.cyan(`\\n 🔧 ${fixedCount} issues auto-fixed, ${remainingCount} remaining`));\n }\n }\n }\n\n const passCount = checks.filter((c) => c.status === 'pass').length;\n const warnCount = checks.filter((c) => c.status === 'warn').length;\n const failCount = checks.filter((c) => c.status === 'fail').length;\n\n // Build the report\n const report: DoctorReport = {\n version: TITAN_VERSION,\n timestamp: new Date().toISOString(),\n checks,\n summary: { pass: passCount, warn: warnCount, fail: failCount },\n npm: { weeklyDownloads: npmDownloads },\n };\n if (healResults.length > 0) {\n report.fixes = healResults;\n }\n\n // Output\n if (jsonOutput) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n // Print results\n console.log('');\n const statusIcons = { pass: chalk.green('✅'), warn: chalk.yellow('⚠️ '), fail: chalk.red('❌') };\n for (const check of checks) {\n console.log(` ${statusIcons[check.status]} ${chalk.white(check.name)}: ${chalk.gray(check.message)}`);\n }\n\n console.log(`\\n ${chalk.green(`${passCount} passed`)} | ${chalk.yellow(`${warnCount} warnings`)} | ${chalk.red(`${failCount} failed`)}`);\n\n if (failCount > 0) {\n console.log(chalk.red('\\n ⚠️ Some checks failed. Run `titan doctor --fix` or `titan onboard` to fix common issues.\\n'));\n } else if (warnCount > 0) {\n console.log(chalk.yellow('\\n ℹ️ Some warnings found. Run `titan doctor --fix` to auto-fix or review the items above.\\n'));\n } else {\n console.log(chalk.green('\\n 🎉 All checks passed! TITAN is healthy.\\n'));\n }\n }\n\n return report;\n}\n"],"mappings":";AAMA,OAAO,WAAW;AAClB,SAAS,YAAY,aAAa,gBAAgB;AAClD,SAAS,YAAY;AACrB,SAAS,YAAY,oBAAoB;AACzC,SAAS,YAAY,mBAAmB,eAAe,iBAAiB,eAAe,sBAAsB;AAC7G,SAAS,sBAAsB;AAC/B,SAAS,qBAAqB;AAC9B,SAAS,qBAAqB;AAC9B;AAAA,EACI;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAEG;AAWP,eAAe,kBAAkB,aAA6C;AAC1E,MAAI;AACA,UAAM,WAAW,MAAM,MAAM,mDAAmD,WAAW,IAAI;AAAA,MAC3F,QAAQ,YAAY,QAAQ,GAAI;AAAA,IACpC,CAAC;AACD,QAAI,CAAC,SAAS,GAAI,QAAO;AACzB,UAAM,OAAO,MAAM,SAAS,KAAK;AACjC,WAAO,KAAK,aAAa;AAAA,EAC7B,QAAQ;AACJ,WAAO;AAAA,EACX;AACJ;AAWA,eAAsB,UAAU,SAAsF;AAClH,QAAM,UAAU,SAAS,OAAO;AAChC,QAAM,SAAS,SAAS,UAAU;AAClC,QAAM,aAAa,SAAS,QAAQ;AAEpC,MAAI,CAAC,YAAY;AACb,YAAQ,IAAI,MAAM,KAAK;AAAA,0BAAsB,aAAa;AAAA,CAAI,CAAC;AAC/D,YAAQ,IAAI,MAAM,KAAK,0BAA0B,CAAC;AAAA,EACtD;AAEA,QAAM,SAAwB,CAAC;AAC/B,QAAM,cAA4B,CAAC;AAGnC,QAAM,cAAc,QAAQ,SAAS;AACrC,QAAM,CAAC,KAAK,IAAI,YAAY,MAAM,GAAG,EAAE,IAAI,MAAM;AACjD,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ,SAAS,KAAK,SAAS,SAAS,KAAK,SAAS;AAAA,IACtD,SAAS,IAAI,WAAW,IAAI,SAAS,KAAK,kBAAkB,SAAS,KAAK,mCAAmC,uBAAuB;AAAA,EACxI,CAAC;AAGD,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ,WAAW,UAAU,IAAI,SAAS;AAAA,IAC1C,SAAS,WAAW,UAAU,IAAI,aAAa,cAAc,UAAU;AAAA,IACvE,QAAQ;AAAA,EACZ,CAAC;AAGD,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ,aAAa,IAAI,SAAS;AAAA,IAClC,SAAS,aAAa,IAAI,oBAAoB;AAAA,IAC9C,QAAQ;AAAA,EACZ,CAAC;AAGD,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,WAAW,aAAa,IAAI,gBAAgB;AAAA,EACzD,CAAC;AAGD,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ,WAAW,eAAe,IAAI,SAAS;AAAA,IAC/C,SAAS,WAAW,eAAe,IAAI,kBAAkB;AAAA,IACzD,QAAQ;AAAA,EACZ,CAAC;AAGD,MAAI,aAAa,GAAG;AAChB,QAAI,CAAC,WAAY,SAAQ,IAAI,MAAM,KAAK,4BAA4B,CAAC;AACrE,QAAI;AACA,YAAM,SAAS,WAAW;AAC1B,YAAM,iBAAiB,MAAM,eAAe;AAC5C,iBAAW,CAAC,MAAM,OAAO,KAAK,OAAO,QAAQ,cAAc,GAAG;AAC1D,YAAI,UAAU;AACd,YAAI,CAAC,SAAS;AAEV,gBAAM,iBAAkB,OAAO,YAAwD,IAAI;AAC3F,gBAAM,YAAY,CAAC,CAAE,gBAAgB;AACrC,gBAAM,YAAoC;AAAA,YACtC,WAAW;AAAA,YAAqB,QAAQ;AAAA,YACxC,QAAQ;AAAA,YAAkB,MAAM;AAAA,YAChC,SAAS;AAAA,YAAmB,YAAY;AAAA,YACxC,KAAK;AAAA,YAAe,UAAU;AAAA,YAC9B,UAAU;AAAA,YAAoB,WAAW;AAAA,YACzC,UAAU;AAAA,YAAoB,QAAQ;AAAA,YACtC,YAAY;AAAA,YAAsB,OAAO;AAAA,UAC7C;AACA,gBAAM,SAAS,UAAU,IAAI;AAC7B,gBAAM,YAAY,SAAS,CAAC,CAAC,QAAQ,IAAI,MAAM,IAAI;AAEnD,cAAI,SAAS,UAAU;AACnB,kBAAM,UAAW,gBAAgB,WAAsB;AACvD,sBAAU,kBAAkB,OAAO;AAAA,UACvC,WAAW,CAAC,aAAa,CAAC,WAAW;AACjC,sBAAU,SACJ,yBAAoB,MAAM,qBAAqB,IAAI,mCACnD,mCAA8B,IAAI;AAAA,UAC5C,OAAO;AACH,sBAAU;AAAA,UACd;AAAA,QACJ;AACA,eAAO,KAAK;AAAA,UACR,MAAM,aAAa,IAAI;AAAA,UACvB,QAAQ,UAAU,SAAS;AAAA,UAC3B;AAAA,QACJ,CAAC;AAAA,MACL;AAAA,IACJ,SAAS,OAAO;AACZ,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,oBAAqB,MAAgB,OAAO;AAAA,MACzD,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,MAAI,aAAa,GAAG;AAChB,QAAI;AACA,YAAM,SAAS,WAAW;AAC1B,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAS,UAAU,OAAO,MAAM,KAAK;AAAA,MACzC,CAAC;AAGD,iBAAW,CAAC,aAAa,aAAa,KAAK,OAAO,QAAQ,OAAO,QAAQ,GAAG;AACxE,YAAI,cAAc,SAAS;AACvB,gBAAM,WAAW,CAAC,EAAE,cAAc,SAAS,cAAc;AACzD,iBAAO,KAAK;AAAA,YACR,MAAM,YAAY,WAAW;AAAA,YAC7B,QAAQ,WAAW,SAAS;AAAA,YAC5B,SAAS,WAAW,eAAe;AAAA,YACnC,QAAQ;AAAA,UACZ,CAAC;AAAA,QACL;AAAA,MACJ;AAAA,IACJ,SAAS,OAAO;AACZ,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,QAAQ;AAAA,QACR,SAAU,MAAgB;AAAA,QAC1B,QAAQ;AAAA,MACZ,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,MAAI,aAAa,GAAG;AAChB,QAAI;AACA,YAAM,MAAM,WAAW;AACvB,UAAI,IAAI,QAAQ,SAAS;AACrB,YAAI,kBAAkB;AACtB,YAAI;AACA,gBAAM,EAAE,SAAS,IAAI,MAAM,OAAO,eAAe;AACjD,mBAAS,yBAAyB,EAAE,OAAO,SAAS,CAAC;AACrD,4BAAkB;AAAA,QACtB,QAAQ;AAAA,QAER;AACA,eAAO,KAAK;AAAA,UACR,MAAM;AAAA,UACN,QAAQ,kBAAkB,SAAS;AAAA,UACnC,SAAS,kBAAkB,6BAA6B;AAAA,QAC5D,CAAC;AAAA,MACL;AAAA,IACJ,QAAQ;AAAA,IAER;AAAA,EACJ;AAGA,MAAI,aAAa,GAAG;AAChB,UAAM,iBAAiB,cAAc;AACrC,eAAW,SAAS,gBAAgB;AAChC,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,QAAQ,MAAM,UAAU,UAAU,SAAS,MAAM,UAAU,SAAS,SAAS;AAAA,QAC7E,SAAS,MAAM;AAAA,MACnB,CAAC;AAAA,IACL;AAAA,EACJ;AAGA,MAAI;AACA,UAAM,EAAE,SAAS,IAAI,MAAM,OAAO,eAAe;AACjD,UAAM,WAAW,SAAS,qBAAqB,EAAE,UAAU,QAAQ,CAAC;AACpE,UAAM,QAAQ,SAAS,KAAK,EAAE,MAAM,KAAK;AACzC,UAAM,YAAY,MAAM,CAAC;AACzB,UAAM,aAAa,MAAM,UAAU,IAAI,SAAS,MAAM,CAAC,GAAG,EAAE,IAAI;AAChE,WAAO,KAAK;AAAA,MACR,MAAM;AAAA,MACN,QAAQ,MAAM,UAAU,IAAI,SAAS,aAAa,KAAK,SAAS,aAAa,KAAK,SAAS;AAAA,MAC3F,SAAS,MAAM,UAAU,IAAI,+BAA+B,GAAG,SAAS,eAAe,MAAM,CAAC,CAAC;AAAA,IACnG,CAAC;AAAA,EACL,QAAQ;AACJ,WAAO,KAAK,EAAE,MAAM,cAAc,QAAQ,QAAQ,SAAS,kBAAkB,CAAC;AAAA,EAClF;AAGA,QAAM,WAAW,QAAQ,YAAY;AACrC,QAAM,SAAS,SAAS,MAAM,OAAO,MAAM,QAAQ,CAAC;AACpD,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS,GAAG,KAAK;AAAA,EACrB,CAAC;AAGD,QAAM,cAAc,cAAc;AAClC,MAAI,mBAA6C;AACjD,MAAI,eAAe;AAGnB,QAAM,eAAe,YAAY;AACjC,MAAI,eAAe,GAAG;AAClB,uBAAmB,eAAe,IAAI,SAAS;AAC/C,mBAAe,YAAY,YAAY;AAAA,EAC3C;AACA,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ;AAAA,IACR,SAAS;AAAA,EACb,CAAC;AAGD,QAAM,cAAc,KAAK,YAAY,UAAU;AAC/C,MAAI,WAAW,WAAW,GAAG;AACzB,QAAI;AACA,YAAM,eAAe,YAAY,WAAW;AAC5C,YAAM,YAAY,KAAK,IAAI,IAAI,KAAK,KAAK,KAAK;AAC9C,UAAI,aAAa;AACjB,iBAAW,QAAQ,cAAc;AAC7B,cAAM,WAAW,KAAK,aAAa,IAAI;AACvC,YAAI;AACA,gBAAM,OAAO,SAAS,QAAQ;AAC9B,cAAI,KAAK,OAAO,KAAK,KAAK,UAAU,WAAW;AAC3C;AAAA,UACJ;AAAA,QACJ,QAAQ;AAAA,QAER;AAAA,MACJ;AACA,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,QAAQ,aAAa,IAAI,SAAS;AAAA,QAClC,SAAS,aAAa,IAAI,GAAG,UAAU,yCAAyC;AAAA,QAChF,QAAQ;AAAA,MACZ,CAAC;AAAA,IACL,QAAQ;AACJ,aAAO,KAAK,EAAE,MAAM,kBAAkB,QAAQ,QAAQ,SAAS,oCAAoC,CAAC;AAAA,IACxG;AAAA,EACJ,OAAO;AACH,WAAO,KAAK,EAAE,MAAM,kBAAkB,QAAQ,QAAQ,SAAS,wBAAwB,CAAC;AAAA,EAC5F;AAGA,MAAI,WAAW,cAAc,GAAG;AAC5B,QAAI;AACA,YAAM,WAAW,YAAY,cAAc;AAC3C,UAAI,aAAa;AACjB,iBAAW,QAAQ,UAAU;AACzB,cAAM,WAAW,KAAK,gBAAgB,IAAI;AAC1C,YAAI;AACA,gBAAM,OAAO,SAAS,QAAQ;AAC9B,cAAI,KAAK,OAAO,GAAG;AACf,0BAAc,KAAK;AAAA,UACvB;AAAA,QACJ,QAAQ;AAAA,QAER;AAAA,MACJ;AACA,YAAM,UAAU,cAAc,OAAO;AACrC,aAAO,KAAK;AAAA,QACR,MAAM;AAAA,QACN,QAAQ,UAAU,MAAM,SAAS;AAAA,QACjC,SAAS,UAAU,MACb,GAAG,QAAQ,QAAQ,CAAC,CAAC,iCACrB,GAAG,QAAQ,QAAQ,CAAC,CAAC;AAAA,QAC3B,QAAQ,UAAU,MAAM,cAAc;AAAA,MAC1C,CAAC;AAAA,IACL,QAAQ;AACJ,aAAO,KAAK,EAAE,MAAM,sBAAsB,QAAQ,QAAQ,SAAS,gCAAgC,CAAC;AAAA,IACxG;AAAA,EACJ,OAAO;AACH,WAAO,KAAK,EAAE,MAAM,sBAAsB,QAAQ,QAAQ,SAAS,oBAAoB,CAAC;AAAA,EAC5F;AAGA,SAAO,KAAK;AAAA,IACR,MAAM;AAAA,IACN,QAAQ,WAAW,UAAU,IAAI,SAAS;AAAA,IAC1C,SAAS,WAAW,UAAU,IAAI,WAAW;AAAA,IAC7C,QAAQ;AAAA,EACZ,CAAC;AAGD,MAAI,CAAC,WAAY,SAAQ,IAAI,MAAM,KAAK,6BAA6B,CAAC;AACtE,QAAM,eAAe,MAAM,kBAAkB,aAAa;AAC1D,MAAI,iBAAiB,MAAM;AACvB,WAAO,KAAK;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS,GAAG,aAAa,eAAe,CAAC;AAAA,IAC7C,CAAC;AAAA,EACL,OAAO;AACH,WAAO,KAAK;AAAA,MACR,MAAM;AAAA,MACN,QAAQ;AAAA,MACR,SAAS;AAAA,IACb,CAAC;AAAA,EACL;AAGA,MAAI,SAAS;AACT,QAAI,QAAQ;AACR,UAAI,CAAC,WAAY,SAAQ,IAAI,MAAM,KAAK,qEAAyD,CAAC;AAAA,IACtG,OAAO;AACH,UAAI,CAAC,WAAY,SAAQ,IAAI,MAAM,KAAK,qCAA8B,CAAC;AAAA,IAC3E;AAEA,UAAM,cAAc,OAAO,OAAO,CAAC,OAAO,EAAE,WAAW,UAAU,EAAE,WAAW,WAAW,EAAE,MAAM;AACjG,UAAM,gBAAgB,IAAI,IAAI,YAAY,IAAI,CAAC,MAAM,EAAE,MAAO,CAAC;AAE/D,UAAM,SAA2C;AAAA,MAC7C,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,WAAW;AAAA,MACX,UAAU;AAAA,MACV,aAAa;AAAA,MACb,WAAW;AAAA,MACX,eAAe;AAAA,IACnB;AAEA,UAAM,kBAA0C;AAAA,MAC5C,WAAW;AAAA,MACX,QAAQ;AAAA,MACR,eAAe;AAAA,MACf,WAAW;AAAA,MACX,UAAU;AAAA,MACV,aAAa;AAAA,MACb,WAAW;AAAA,MACX,eAAe;AAAA,IACnB;AAEA,QAAI,QAAQ;AACR,iBAAW,OAAO,eAAe;AAC7B,YAAI,OAAO,GAAG,GAAG;AACb,gBAAM,cAAc,gBAAgB,GAAG,KAAK;AAC5C,sBAAY,KAAK,EAAE,QAAQ,KAAK,SAAS,MAAM,SAAS,wBAAwB,WAAW,GAAG,CAAC;AAC/F,cAAI,CAAC,YAAY;AACb,oBAAQ,IAAI,KAAK,MAAM,OAAO,QAAG,CAAC,KAAK,GAAG,KAAK,MAAM,KAAK,cAAc,WAAW,EAAE,CAAC,EAAE;AAAA,UAC5F;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,CAAC,YAAY;AACb,gBAAQ,IAAI,MAAM,KAAK;AAAA,sCAA6B,cAAc,IAAI,4DAA4D,CAAC;AAAA,MACvI;AAAA,IACJ,OAAO;AACH,iBAAW,OAAO,eAAe;AAC7B,cAAM,QAAQ,OAAO,GAAG;AACxB,YAAI,OAAO;AACP,gBAAM,SAAS,MAAM;AACrB,sBAAY,KAAK,MAAM;AACvB,cAAI,CAAC,YAAY;AACb,kBAAM,OAAO,OAAO,UAAU,MAAM,MAAM,QAAG,IAAI,MAAM,IAAI,QAAG;AAC9D,oBAAQ,IAAI,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,MAAM,KAAK,OAAO,OAAO,CAAC,EAAE;AAAA,UAC3E;AAAA,QACJ;AAAA,MACJ;AAEA,UAAI,CAAC,YAAY;AACb,cAAM,aAAa,YAAY,OAAO,CAAC,MAAM,EAAE,OAAO,EAAE;AACxD,cAAM,iBAAiB,YAAY,OAAO,CAAC,MAAM,CAAC,EAAE,OAAO,EAAE;AAC7D,gBAAQ,IAAI,MAAM,KAAK;AAAA,cAAU,UAAU,uBAAuB,cAAc,YAAY,CAAC;AAAA,MACjG;AAAA,IACJ;AAAA,EACJ;AAEA,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAC5D,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAC5D,QAAM,YAAY,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,MAAM,EAAE;AAG5D,QAAM,SAAuB;AAAA,IACzB,SAAS;AAAA,IACT,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IAClC;AAAA,IACA,SAAS,EAAE,MAAM,WAAW,MAAM,WAAW,MAAM,UAAU;AAAA,IAC7D,KAAK,EAAE,iBAAiB,aAAa;AAAA,EACzC;AACA,MAAI,YAAY,SAAS,GAAG;AACxB,WAAO,QAAQ;AAAA,EACnB;AAGA,MAAI,YAAY;AACZ,YAAQ,IAAI,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AAAA,EAC/C,OAAO;AAEH,YAAQ,IAAI,EAAE;AACd,UAAM,cAAc,EAAE,MAAM,MAAM,MAAM,QAAG,GAAG,MAAM,MAAM,OAAO,eAAK,GAAG,MAAM,MAAM,IAAI,QAAG,EAAE;AAC9F,eAAW,SAAS,QAAQ;AACxB,cAAQ,IAAI,KAAK,YAAY,MAAM,MAAM,CAAC,IAAI,MAAM,MAAM,MAAM,IAAI,CAAC,KAAK,MAAM,KAAK,MAAM,OAAO,CAAC,EAAE;AAAA,IACzG;AAEA,YAAQ,IAAI;AAAA,IAAO,MAAM,MAAM,GAAG,SAAS,SAAS,CAAC,MAAM,MAAM,OAAO,GAAG,SAAS,WAAW,CAAC,MAAM,MAAM,IAAI,GAAG,SAAS,SAAS,CAAC,EAAE;AAExI,QAAI,YAAY,GAAG;AACf,cAAQ,IAAI,MAAM,IAAI,2GAAiG,CAAC;AAAA,IAC5H,WAAW,YAAY,GAAG;AACtB,cAAQ,IAAI,MAAM,OAAO,0GAAgG,CAAC;AAAA,IAC9H,OAAO;AACH,cAAQ,IAAI,MAAM,MAAM,sDAA+C,CAAC;AAAA,IAC5E;AAAA,EACJ;AAEA,SAAO;AACX;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/doctor.ts"],"sourcesContent":["/**\n * TITAN -- Doctor Diagnostic Tool\n * Checks system health, configuration, connectivity, and dependencies.\n * Supports --fix flag to auto-heal detected issues via selfHeal.ts.\n * Supports --json flag for machine-readable output.\n */\nimport chalk from 'chalk';\nimport { existsSync, readdirSync, statSync } from 'fs';\nimport { join } from 'path';\nimport { loadConfig, configExists } from '../config/config.js';\nimport { TITAN_HOME, TITAN_CONFIG_PATH, TITAN_DB_PATH, TITAN_WORKSPACE, TITAN_VERSION, TITAN_LOGS_DIR } from '../utils/constants.js';\nimport { healthCheckAll } from '../providers/router.js';\nimport { auditSecurity } from '../security/sandbox.js';\nimport { getStallStats } from '../agent/stallDetector.js';\nimport {\n fixMissingTitanHome,\n fixMissingConfig,\n fixInvalidConfig,\n fixMissingWorkspace,\n fixBrokenChannelConfig,\n fixPermissions,\n fixStaleLogFiles,\n fixOrphanedSessions,\n type HealResult,\n} from './selfHeal.js';\n\ninterface CheckResult {\n name: string;\n status: 'pass' | 'warn' | 'fail';\n message: string;\n /** Key used to map to an auto-fix function */\n fixKey?: string;\n}\n\n/** Fetch weekly npm download count for a package */\nasync function fetchNpmDownloads(packageName: string): Promise<number | null> {\n try {\n const response = await fetch(`https://api.npmjs.org/downloads/point/last-week/${packageName}`, {\n signal: AbortSignal.timeout(5000),\n });\n if (!response.ok) return null;\n const data = await response.json() as { downloads?: number };\n return data.downloads ?? null;\n } catch {\n return null;\n }\n}\n\nexport interface DoctorReport {\n version: string;\n timestamp: string;\n checks: CheckResult[];\n summary: { pass: number; warn: number; fail: number };\n npm?: { weeklyDownloads: number | null };\n fixes?: HealResult[];\n}\n\nexport async function runDoctor(options?: { fix?: boolean; json?: boolean; dryRun?: boolean }): Promise<DoctorReport> {\n const autoFix = options?.fix ?? false;\n const dryRun = options?.dryRun ?? false;\n const jsonOutput = options?.json ?? false;\n\n if (!jsonOutput) {\n console.log(chalk.cyan(`\\n🩺 TITAN Doctor v${TITAN_VERSION}\\n`));\n console.log(chalk.gray('Running diagnostics...\\n'));\n }\n\n const checks: CheckResult[] = [];\n const healResults: HealResult[] = [];\n\n // 1. Node.js version\n const nodeVersion = process.versions.node;\n const [major] = nodeVersion.split('.').map(Number);\n checks.push({\n name: 'Node.js version',\n status: major >= 20 ? 'pass' : major >= 18 ? 'warn' : 'fail',\n message: `v${nodeVersion} ${major >= 20 ? '(recommended)' : major >= 18 ? '(minimum, upgrade recommended)' : '(too old, need >= 20)'}`,\n });\n\n // 2. TITAN home directory\n checks.push({\n name: 'TITAN home directory',\n status: existsSync(TITAN_HOME) ? 'pass' : 'warn',\n message: existsSync(TITAN_HOME) ? TITAN_HOME : `Not found: ${TITAN_HOME} (run: titan onboard)`,\n fixKey: 'titanHome',\n });\n\n // 3. Configuration file\n checks.push({\n name: 'Configuration file',\n status: configExists() ? 'pass' : 'warn',\n message: configExists() ? TITAN_CONFIG_PATH : `Not found (run: titan onboard)`,\n fixKey: 'config',\n });\n\n // 4. Database (lazily created — not having one on a fresh install is normal)\n checks.push({\n name: 'Database',\n status: 'pass',\n message: existsSync(TITAN_DB_PATH) ? TITAN_DB_PATH : 'Will be created on first use (this is normal)',\n });\n\n // 5. Workspace\n checks.push({\n name: 'Workspace directory',\n status: existsSync(TITAN_WORKSPACE) ? 'pass' : 'warn',\n message: existsSync(TITAN_WORKSPACE) ? TITAN_WORKSPACE : `Not found (run: titan onboard)`,\n fixKey: 'workspace',\n });\n\n // 6. AI Provider connectivity\n if (configExists()) {\n if (!jsonOutput) console.log(chalk.gray(' Checking AI providers...'));\n try {\n const config = loadConfig();\n const providerHealth = await healthCheckAll();\n for (const [name, healthy] of Object.entries(providerHealth)) {\n let message = 'Reachable';\n if (!healthy) {\n // Provide actionable error messages for missing API keys\n const providerConfig = (config.providers as Record<string, Record<string, unknown>>)?.[name];\n const hasApiKey = !!(providerConfig?.apiKey);\n const envVarMap: Record<string, string> = {\n anthropic: 'ANTHROPIC_API_KEY', openai: 'OPENAI_API_KEY',\n google: 'GOOGLE_API_KEY', groq: 'GROQ_API_KEY',\n mistral: 'MISTRAL_API_KEY', openrouter: 'OPENROUTER_API_KEY',\n xai: 'XAI_API_KEY', together: 'TOGETHER_API_KEY',\n deepseek: 'DEEPSEEK_API_KEY', fireworks: 'FIREWORKS_API_KEY',\n cerebras: 'CEREBRAS_API_KEY', cohere: 'COHERE_API_KEY',\n perplexity: 'PERPLEXITY_API_KEY', azure: 'AZURE_OPENAI_API_KEY',\n };\n const envVar = envVarMap[name];\n const hasEnvVar = envVar ? !!process.env[envVar] : false;\n\n if (name === 'ollama') {\n const baseUrl = (providerConfig?.baseUrl as string) || 'http://localhost:11434';\n message = `Unreachable at ${baseUrl} — is Ollama running?`;\n } else if (!hasApiKey && !hasEnvVar) {\n message = envVar\n ? `No API key — set ${envVar} or add providers.${name}.apiKey to ~/.titan/titan.json`\n : `No API key — add providers.${name}.apiKey to ~/.titan/titan.json`;\n } else {\n message = 'API key set but provider unreachable — check key validity or network';\n }\n }\n checks.push({\n name: `Provider: ${name}`,\n status: healthy ? 'pass' : 'warn',\n message,\n });\n }\n } catch (error) {\n checks.push({\n name: 'AI Providers',\n status: 'warn',\n message: `Could not check: ${(error as Error).message}`,\n });\n }\n }\n\n // 7. Configuration validation\n if (configExists()) {\n try {\n const config = loadConfig();\n checks.push({\n name: 'Config validation',\n status: 'pass',\n message: `Model: ${config.agent.model}`,\n });\n\n // Check channel configuration\n for (const [channelName, channelConfig] of Object.entries(config.channels)) {\n if (channelConfig.enabled) {\n const hasToken = !!(channelConfig.token || channelConfig.apiKey);\n checks.push({\n name: `Channel: ${channelName}`,\n status: hasToken ? 'pass' : 'fail',\n message: hasToken ? 'Configured' : 'Enabled but no token set',\n fixKey: 'channels',\n });\n }\n }\n\n // Check model resolution — does the configured model string look routable?\n const modelStr: string = config.agent.model || '';\n const knownProviderPrefixes = [\n 'ollama/', 'anthropic/', 'openai/', 'google/', 'groq/', 'mistral/',\n 'openrouter/', 'xai/', 'together/', 'deepseek/', 'fireworks/',\n 'cerebras/', 'cohere/', 'perplexity/', 'azure/', 'openai_compat/',\n ];\n const hasProviderPrefix = knownProviderPrefixes.some(p => modelStr.startsWith(p));\n if (!hasProviderPrefix && modelStr) {\n checks.push({\n name: 'Model resolution',\n status: 'warn',\n message: `\"${modelStr}\" has no provider prefix (e.g. ollama/${modelStr}). ` +\n `TITAN will try to auto-detect the provider but may fail at runtime. ` +\n `Add a provider prefix to avoid ambiguity.`,\n });\n } else if (hasProviderPrefix) {\n checks.push({\n name: 'Model resolution',\n status: 'pass',\n message: `${modelStr} (provider prefix detected)`,\n });\n }\n } catch (error) {\n checks.push({\n name: 'Config validation',\n status: 'fail',\n message: (error as Error).message,\n fixKey: 'invalidConfig',\n });\n }\n }\n\n // 8. Cloudflare Tunnel (when enabled)\n if (configExists()) {\n try {\n const cfg = loadConfig();\n if (cfg.tunnel?.enabled) {\n let tunnelAvailable = false;\n try {\n const { execSync } = await import('child_process');\n execSync('cloudflared --version', { stdio: 'ignore' });\n tunnelAvailable = true;\n } catch {\n // not installed\n }\n checks.push({\n name: 'Cloudflare Tunnel (cloudflared)',\n status: tunnelAvailable ? 'pass' : 'fail',\n message: tunnelAvailable ? 'cloudflared binary found' : 'cloudflared not installed (https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/downloads/)',\n });\n }\n } catch {\n // config load failed — handled elsewhere\n }\n }\n\n // 9. Security audit\n if (configExists()) {\n const securityIssues = auditSecurity();\n for (const issue of securityIssues) {\n checks.push({\n name: 'Security',\n status: issue.level === 'error' ? 'fail' : issue.level === 'warn' ? 'warn' : 'pass',\n message: issue.message,\n });\n }\n }\n\n // 9. Disk space\n try {\n const { execSync } = await import('child_process');\n const dfOutput = execSync('df -h / | tail -1', { encoding: 'utf-8' });\n const parts = dfOutput.trim().split(/\\s+/);\n const available = parts[3];\n const usePercent = parts.length >= 5 ? parseInt(parts[4], 10) : NaN;\n checks.push({\n name: 'Disk space',\n status: isNaN(usePercent) ? 'warn' : usePercent < 90 ? 'pass' : usePercent < 95 ? 'warn' : 'fail',\n message: isNaN(usePercent) ? 'Could not parse disk usage' : `${available} available (${parts[4]} used)`,\n });\n } catch {\n checks.push({ name: 'Disk space', status: 'warn', message: 'Could not check' });\n }\n\n // 10. Memory\n const memUsage = process.memoryUsage();\n const rssGB = (memUsage.rss / 1024 / 1024).toFixed(1);\n checks.push({\n name: 'Memory usage',\n status: 'pass',\n message: `${rssGB} MB RSS`,\n });\n\n // 11. Stall Detector Status\n const stallStatus = getStallStats();\n let stallStatusLevel: 'pass' | 'warn' | 'fail' = 'pass';\n let stallMessage = 'Healthy (0 active stalls)';\n\n // getStallStats returns an array of session stats\n const activeStalls = stallStatus.length;\n if (activeStalls > 0) {\n stallStatusLevel = activeStalls > 2 ? 'fail' : 'warn';\n stallMessage = `Detected ${activeStalls} stuck sessions.`;\n }\n checks.push({\n name: 'Agent Stall Status',\n status: stallStatusLevel,\n message: stallMessage,\n });\n\n // 12. Stale sessions check\n const sessionsDir = join(TITAN_HOME, 'sessions');\n if (existsSync(sessionsDir)) {\n try {\n const sessionFiles = readdirSync(sessionsDir);\n const oneDayAgo = Date.now() - 24 * 60 * 60 * 1000;\n let staleCount = 0;\n for (const file of sessionFiles) {\n const fullPath = join(sessionsDir, file);\n try {\n const stat = statSync(fullPath);\n if (stat.isFile() && stat.mtimeMs < oneDayAgo) {\n staleCount++;\n }\n } catch {\n // skip unreadable files\n }\n }\n checks.push({\n name: 'Stale sessions',\n status: staleCount > 0 ? 'warn' : 'pass',\n message: staleCount > 0 ? `${staleCount} session file(s) older than 24 hours` : 'No stale sessions',\n fixKey: 'staleSessions',\n });\n } catch {\n checks.push({ name: 'Stale sessions', status: 'warn', message: 'Could not read sessions directory' });\n }\n } else {\n checks.push({ name: 'Stale sessions', status: 'pass', message: 'No sessions directory' });\n }\n\n // 13. Log directory size check\n if (existsSync(TITAN_LOGS_DIR)) {\n try {\n const logFiles = readdirSync(TITAN_LOGS_DIR);\n let totalBytes = 0;\n for (const file of logFiles) {\n const fullPath = join(TITAN_LOGS_DIR, file);\n try {\n const stat = statSync(fullPath);\n if (stat.isFile()) {\n totalBytes += stat.size;\n }\n } catch {\n // skip unreadable files\n }\n }\n const totalMB = totalBytes / (1024 * 1024);\n checks.push({\n name: 'Log directory size',\n status: totalMB > 100 ? 'warn' : 'pass',\n message: totalMB > 100\n ? `${totalMB.toFixed(1)} MB (consider rotating logs)`\n : `${totalMB.toFixed(1)} MB`,\n fixKey: totalMB > 100 ? 'staleLogs' : undefined,\n });\n } catch {\n checks.push({ name: 'Log directory size', status: 'warn', message: 'Could not check log directory' });\n }\n } else {\n checks.push({ name: 'Log directory size', status: 'pass', message: 'No logs directory' });\n }\n\n // 14. Permissions check\n checks.push({\n name: 'TITAN home permissions',\n status: existsSync(TITAN_HOME) ? 'pass' : 'warn',\n message: existsSync(TITAN_HOME) ? 'Exists' : 'Cannot check (TITAN_HOME missing)',\n fixKey: 'permissions',\n });\n\n // 15. npm download count\n if (!jsonOutput) console.log(chalk.gray(' Checking npm downloads...'));\n const npmDownloads = await fetchNpmDownloads('titan-agent');\n if (npmDownloads !== null) {\n checks.push({\n name: 'npm weekly downloads',\n status: 'pass',\n message: `${npmDownloads.toLocaleString()} downloads/week (titan-agent)`,\n });\n } else {\n checks.push({\n name: 'npm weekly downloads',\n status: 'warn',\n message: 'Could not fetch npm download stats',\n });\n }\n\n // Auto-fix pass if --fix was specified\n if (autoFix) {\n if (dryRun) {\n if (!jsonOutput) console.log(chalk.cyan('\\n 🔧 Dry run — showing fixes that WOULD be applied:\\n'));\n } else {\n if (!jsonOutput) console.log(chalk.cyan('\\n 🔧 Running auto-fix...\\n'));\n }\n\n const issueChecks = checks.filter((c) => (c.status === 'warn' || c.status === 'fail') && c.fixKey);\n const fixKeysNeeded = new Set(issueChecks.map((c) => c.fixKey!));\n\n const fixMap: Record<string, () => HealResult> = {\n titanHome: fixMissingTitanHome,\n config: fixMissingConfig,\n invalidConfig: fixInvalidConfig,\n workspace: fixMissingWorkspace,\n channels: fixBrokenChannelConfig,\n permissions: fixPermissions,\n staleLogs: fixStaleLogFiles,\n staleSessions: fixOrphanedSessions,\n };\n\n const fixDescriptions: Record<string, string> = {\n titanHome: 'Create TITAN home directory',\n config: 'Create default configuration file',\n invalidConfig: 'Reset invalid configuration to defaults',\n workspace: 'Create workspace directory',\n channels: 'Disable misconfigured channels',\n permissions: 'Fix file permissions on TITAN home',\n staleLogs: 'Clean up stale log files',\n staleSessions: 'Remove orphaned session files',\n };\n\n if (dryRun) {\n for (const key of fixKeysNeeded) {\n if (fixMap[key]) {\n const description = fixDescriptions[key] ?? key;\n healResults.push({ action: key, success: true, message: `[dry-run] Would fix: ${description}` });\n if (!jsonOutput) {\n console.log(` ${chalk.yellow('⏭')} ${key}: ${chalk.gray(`Would fix: ${description}`)}`);\n }\n }\n }\n\n if (!jsonOutput) {\n console.log(chalk.cyan(`\\n 🔧 Dry run complete — ${fixKeysNeeded.size} fix(es) would be applied. Run without --dry-run to apply.`));\n }\n } else {\n for (const key of fixKeysNeeded) {\n const fixFn = fixMap[key];\n if (fixFn) {\n const result = fixFn();\n healResults.push(result);\n if (!jsonOutput) {\n const icon = result.success ? chalk.green('✅') : chalk.red('❌');\n console.log(` ${icon} ${result.action}: ${chalk.gray(result.message)}`);\n }\n }\n }\n\n if (!jsonOutput) {\n const fixedCount = healResults.filter((r) => r.success).length;\n const remainingCount = healResults.filter((r) => !r.success).length;\n console.log(chalk.cyan(`\\n 🔧 ${fixedCount} issues auto-fixed, ${remainingCount} remaining`));\n }\n }\n }\n\n const passCount = checks.filter((c) => c.status === 'pass').length;\n const warnCount = checks.filter((c) => c.status === 'warn').length;\n const failCount = checks.filter((c) => c.status === 'fail').length;\n\n // Build the report\n const report: DoctorReport = {\n version: TITAN_VERSION,\n timestamp: new Date().toISOString(),\n checks,\n summary: { pass: passCount, warn: warnCount, fail: failCount },\n npm: { weeklyDownloads: npmDownloads },\n };\n if (healResults.length > 0) {\n report.fixes = healResults;\n }\n\n // Output\n if (jsonOutput) {\n console.log(JSON.stringify(report, null, 2));\n } else {\n // Print results\n console.log('');\n const statusIcons = { pass: chalk.green('✅'), warn: chalk.yellow('⚠️ '), fail: chalk.red('❌') };\n for (const check of checks) {\n console.log(` ${statusIcons[check.status]} ${chalk.white(check.name)}: ${chalk.gray(check.message)}`);\n }\n\n console.log(`\\n ${chalk.green(`${passCount} passed`)} | ${chalk.yellow(`${warnCount} warnings`)} | ${chalk.red(`${failCount} failed`)}`);\n\n if (failCount > 0) {\n console.log(chalk.red('\\n ⚠️ Some checks failed. Run `titan doctor --fix` or `titan onboard` to fix common issues.\\n'));\n } else if (warnCount > 0) {\n console.log(chalk.yellow('\\n ℹ️ Some warnings found. Run `titan doctor --fix` to auto-fix or review the items above.\\n'));\n } else {\n console.log(chalk.green('\\n 🎉 All checks passed! TITAN is healthy.\\n'));\n }\n }\n\n return report;\n}\n"],"mappings":"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","names":[]}
|
package/dist/cli/onboard.js
CHANGED
|
@@ -4,7 +4,7 @@ import chalk from "chalk";
|
|
|
4
4
|
import { exec } from "child_process";
|
|
5
5
|
import { saveConfig, getDefaultConfig } from "../config/config.js";
|
|
6
6
|
import { TITAN_HOME, TITAN_WORKSPACE, TITAN_SKILLS_DIR, TITAN_CONFIG_PATH } from "../utils/constants.js";
|
|
7
|
-
import {
|
|
7
|
+
import { mkdirIfNotExists } from "../utils/helpers.js";
|
|
8
8
|
import { initMemory } from "../memory/memory.js";
|
|
9
9
|
import { loadProfile, saveProfile } from "../memory/relationship.js";
|
|
10
10
|
async function fetchOllamaModels(baseUrl) {
|
|
@@ -466,9 +466,9 @@ async function runOnboard(_installDaemon) {
|
|
|
466
466
|
});
|
|
467
467
|
config.logging.level = logLevel;
|
|
468
468
|
console.log(chalk.yellow("\n\u2500\u2500\u2500 Setting up workspace \u2500\u2500\u2500\n"));
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
469
|
+
mkdirIfNotExists(TITAN_HOME);
|
|
470
|
+
mkdirIfNotExists(TITAN_WORKSPACE);
|
|
471
|
+
mkdirIfNotExists(TITAN_SKILLS_DIR);
|
|
472
472
|
initMemory();
|
|
473
473
|
saveConfig(config);
|
|
474
474
|
if (profileName || profileLevel) {
|
package/dist/cli/onboard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/cli/onboard.ts"],"sourcesContent":["/**\n * TITAN — Onboarding Wizard\n * Interactive setup for first-time users. Covers all key settings.\n */\nimport { select, input, confirm, password, checkbox } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport { exec } from 'child_process';\nimport { saveConfig, getDefaultConfig } from '../config/config.js';\nimport { TITAN_HOME, TITAN_WORKSPACE, TITAN_SKILLS_DIR, TITAN_CONFIG_PATH } from '../utils/constants.js';\nimport { ensureDir } from '../utils/helpers.js';\nimport { initMemory } from '../memory/memory.js';\nimport { loadProfile, saveProfile } from '../memory/relationship.js';\n\n\n// ─── Ollama helpers ───────────────────────────────────────────────\nasync function fetchOllamaModels(baseUrl: string): Promise<string[]> {\n try {\n const res = await fetch(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(3000) });\n if (!res.ok) return [];\n const json = await res.json() as { models?: { name: string }[] };\n return (json.models || []).map((m) => m.name).filter(Boolean);\n } catch {\n return [];\n }\n}\n\n// ─── API key validation ──────────────────────────────────────────\n// Tests a provider key against its real models endpoint.\n// Returns { ok: true } on success or { ok: false, error: string } on failure.\n// Used by the onboarding wizard to catch typo'd / expired / fake keys\n// BEFORE the user finishes setup and hits a generic 500 in the gateway.\nasync function validateProviderKey(\n provider: string,\n apiKey: string,\n): Promise<{ ok: boolean; error?: string }> {\n if (!apiKey || apiKey.trim().length === 0) {\n return { ok: false, error: 'Key is empty' };\n }\n const trimmed = apiKey.trim();\n try {\n const ctl = AbortSignal.timeout(8000);\n if (provider === 'anthropic') {\n const res = await fetch('https://api.anthropic.com/v1/models', {\n method: 'GET',\n headers: { 'x-api-key': trimmed, 'anthropic-version': '2023-06-01' },\n signal: ctl,\n });\n if (res.status === 401 || res.status === 403) return { ok: false, error: 'Authentication failed (401/403)' };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };\n return { ok: true };\n }\n if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/models', {\n method: 'GET',\n headers: { Authorization: `Bearer ${trimmed}` },\n signal: ctl,\n });\n if (res.status === 401 || res.status === 403) return { ok: false, error: 'Authentication failed (401/403)' };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };\n return { ok: true };\n }\n if (provider === 'google') {\n const res = await fetch(\n `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(trimmed)}`,\n { method: 'GET', signal: ctl },\n );\n if (res.status === 401 || res.status === 403 || res.status === 400) {\n return { ok: false, error: `Authentication failed (${res.status})` };\n }\n if (!res.ok) return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };\n return { ok: true };\n }\n // Unknown provider — skip validation, accept the key\n return { ok: true };\n } catch (err) {\n const msg = (err as Error).message;\n if (msg.includes('aborted')) return { ok: false, error: 'Request timed out (network or wrong endpoint)' };\n return { ok: false, error: msg };\n }\n}\n\n// Captures and validates an API key with retry. User can also skip.\n// Returns the validated key, or empty string if user chose to skip.\nasync function captureAndValidateKey(provider: string): Promise<string> {\n for (;;) { // retry loop until user provides a valid key, skips, or forces\n const apiKey = await password({\n message: `Paste your ${provider} API key here (input is hidden):`,\n mask: '*',\n });\n if (!apiKey || apiKey.trim().length === 0) {\n const skip = await confirm({\n message: 'No key entered. Skip validation and continue anyway?',\n default: false,\n });\n if (skip) return '';\n continue;\n }\n process.stdout.write(chalk.gray(` → Testing key against ${provider}... `));\n const result = await validateProviderKey(provider, apiKey);\n if (result.ok) {\n console.log(chalk.green('✅ valid'));\n return apiKey.trim();\n }\n console.log(chalk.red(`❌ ${result.error}`));\n const choice = await select({\n message: 'What would you like to do?',\n choices: [\n { name: '🔁 Re-enter the key', value: 'retry' },\n { name: '⏭️ Skip key validation and use it anyway (advanced)', value: 'force' },\n { name: '❌ Skip this provider', value: 'skip' },\n ],\n });\n if (choice === 'retry') continue;\n if (choice === 'force') return apiKey.trim();\n if (choice === 'skip') return '';\n }\n}\n\nimport { TITAN_VERSION } from '../utils/constants.js';\n\n// ─── Channel token validation ────────────────────────────────────\n// Quick check if a channel bot token is valid by calling the platform's identity endpoint.\nasync function validateChannelToken(channel: string, token: string): Promise<boolean> {\n try {\n const ctl = AbortSignal.timeout(8000);\n if (channel === 'discord') {\n const res = await fetch('https://discord.com/api/v10/users/@me', {\n headers: { Authorization: `Bot ${token}` }, signal: ctl,\n });\n return res.ok;\n }\n if (channel === 'telegram') {\n const res = await fetch(`https://api.telegram.org/bot${token}/getMe`, { signal: ctl });\n return res.ok;\n }\n if (channel === 'slack') {\n const res = await fetch('https://slack.com/api/auth.test', {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/x-www-form-urlencoded' },\n signal: ctl,\n });\n if (!res.ok) return false;\n const data = await res.json() as { ok?: boolean };\n return data.ok === true;\n }\n return true; // unknown channel — skip validation\n } catch {\n return false;\n }\n}\n\nfunction printLogo(): void {\n const c = chalk;\n const border = c.cyan;\n const row1 = c.yellowBright;\n const row2 = c.yellow;\n const row3 = c.greenBright;\n const row4 = c.cyanBright;\n const row5 = c.blueBright;\n const tagline = c.white;\n const credit = c.magentaBright;\n const ver = c.gray;\n\n console.log('');\n console.log(border(' ╔══════════════════════════════════════════════════════════╗'));\n console.log(border(' ║ ║'));\n console.log(border(' ║ ') + row1('████████╗██╗████████╗ █████╗ ███╗ ██╗') + border(' ║'));\n console.log(border(' ║ ') + row2(' ██║ ██║ ██║ ██╔══██╗ ████╗ ██║') + border(' ║'));\n console.log(border(' ║ ') + row3(' ██║ ██║ ██║ ███████║ ██╔██╗ ██║') + border(' ║'));\n console.log(border(' ║ ') + row4(' ██║ ██║ ██║ ██╔══██║ ██║╚██╗██║') + border(' ║'));\n console.log(border(' ║ ') + row5(' ██║ ██║ ██║ ██║ ██║ ██║ ╚████║') + border(' ║'));\n console.log(border(' ║ ') + c.blue(' ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝') + border(' ║'));\n console.log(border(' ║ ║'));\n console.log(border(' ║ ') + tagline('The Intelligent Task Automation Network') + border(' ║'));\n console.log(border(' ║ ') + ver(`v${TITAN_VERSION}`) + c.gray(' • ') + credit('by Tony Elliott') + border(' ║'));\n console.log(border(' ╚══════════════════════════════════════════════════════════╝'));\n console.log('');\n}\n\n// ─── Main wizard ──────────────────────────────────────────────────\nexport async function runOnboard(_installDaemon?: boolean): Promise<boolean> {\n printLogo();\n console.log(chalk.gray(' Welcome! This wizard will configure your personal AI assistant.'));\n console.log(chalk.gray(' Press Ctrl+C at any time to cancel.\\n'));\n\n\n const config = getDefaultConfig();\n\n // ─── Step 0: System Check + Profile ──────────────────────────\n console.log(chalk.yellow('─── Step 0: System Check ───\\n'));\n\n // Node.js version\n console.log(chalk.green(` ✅ Node.js ${process.version}`));\n\n // Check Ollama\n const ollamaModelsCheck = await fetchOllamaModels('http://localhost:11434');\n if (ollamaModelsCheck.length > 0) {\n console.log(chalk.green(` ✅ Ollama detected (${ollamaModelsCheck.length} model(s): ${ollamaModelsCheck.slice(0, 3).join(', ')}${ollamaModelsCheck.length > 3 ? ', ...' : ''})`));\n } else {\n console.log(chalk.yellow(' ⚠️ Ollama not detected (local free AI unavailable — install from ollama.ai)'));\n }\n\n // Check Docker\n await new Promise<void>((resolve) => {\n exec('docker info --format \"{{.ServerVersion}}\"', { timeout: 3000 }, (err, stdout) => {\n if (!err && stdout.trim()) {\n console.log(chalk.green(` ✅ Docker available (v${stdout.trim()} — docker sandbox mode enabled)`));\n } else {\n console.log(chalk.yellow(' ⚠️ Docker not found (docker sandbox mode won\\'t be available)'));\n }\n resolve();\n });\n });\n\n console.log('');\n console.log(chalk.yellow('─── Step 0: Personalization ───\\n'));\n console.log(chalk.gray(' This helps TITAN respond in your style — like a JARVIS that knows you.\\n'));\n\n const profileName = await input({\n message: 'Your first name (optional, for personalization):',\n default: '',\n });\n\n const profileLevel = await select({\n message: 'Your technical level:',\n choices: [\n { name: 'Beginner — explain everything in plain English', value: 'beginner' },\n { name: 'Intermediate — I know the basics', value: 'intermediate' },\n { name: 'Expert — no hand-holding', value: 'expert' },\n ],\n });\n\n // ─── Step 1: Primary AI Provider ─────────────────────────────\n console.log(chalk.yellow('\\n─── Step 1 of 7: AI Provider ───\\n'));\n\n const provider = await select({\n message: 'Which AI provider would you like to use as your primary?',\n choices: [\n { name: '🟣 Anthropic (Claude) — Best reasoning, recommended', value: 'anthropic' },\n { name: '🟢 OpenAI (GPT-4o) — Great all-rounder', value: 'openai' },\n { name: '🔵 Google (Gemini) — Fast & multimodal', value: 'google' },\n { name: '🟠 Ollama (Local) — Free, private, runs on your machine', value: 'ollama' },\n ],\n });\n\n // ─── Step 2: Model Selection ──────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 2 of 7: Model ───\\n'));\n\n if (provider === 'ollama') {\n const ollamaUrl = await input({\n message: 'Ollama base URL:',\n default: 'http://localhost:11434',\n });\n config.providers.ollama.baseUrl = ollamaUrl;\n\n console.log(chalk.gray(`\\n 🔍 Detecting models at ${ollamaUrl}...`));\n const installedModels = await fetchOllamaModels(ollamaUrl);\n\n if (installedModels.length > 0) {\n console.log(chalk.green(` ✅ Found ${installedModels.length} installed model(s)\\n`));\n const chosen = await select({\n message: 'Select a model to use:',\n choices: installedModels.map((m) => ({ name: m, value: `ollama/${m}` })),\n });\n config.agent.model = chosen;\n } else {\n console.log(chalk.yellow(' ⚠️ No models detected (Ollama may not be running, or no models pulled yet).'));\n console.log(chalk.gray(' Run: ollama pull qwen3.5:4b to install a model'));\n console.log(chalk.gray(' See: docs/MODELS.md for GPU-tiered model recommendations\\n'));\n const modelName = await input({\n message: 'Enter the Ollama model name to use:',\n default: 'qwen3.5:4b',\n });\n config.agent.model = `ollama/${modelName}`;\n }\n } else {\n // Cloud provider — guide user to get their API key\n const keyGuides: Record<string, { url: string; hint: string; name: string }> = {\n anthropic: {\n url: 'https://console.anthropic.com/settings/keys',\n hint: 'Looks like: sk-ant-api03-...',\n name: 'Anthropic Console',\n },\n openai: {\n url: 'https://platform.openai.com/api-keys',\n hint: 'Looks like: sk-proj-... or sk-...',\n name: 'OpenAI Platform',\n },\n google: {\n url: 'https://aistudio.google.com/app/apikey',\n hint: 'Looks like: AIza...',\n name: 'Google AI Studio',\n },\n };\n\n const guide = keyGuides[provider];\n if (guide) {\n console.log(chalk.cyan(`\\n 📋 To get your ${chalk.white(provider)} API key:`));\n console.log(chalk.white(` → Go to: ${chalk.underline(guide.url)}`));\n console.log(chalk.gray(` → ${guide.hint}`));\n console.log(chalk.green(`\\n 🔒 Security guarantee:`));\n console.log(chalk.gray(` Your key is stored ONLY on YOUR computer at:`));\n console.log(chalk.gray(` ~/.titan/config.json`));\n console.log(chalk.gray(` It goes directly to ${provider.charAt(0).toUpperCase() + provider.slice(1)}'s servers.`));\n console.log(chalk.gray(` TITAN never sees it. No one else ever sees it.\\n`));\n }\n\n const apiKey = await captureAndValidateKey(provider);\n\n const modelChoices: Record<string, { name: string; value: string }[]> = {\n anthropic: [\n { name: 'claude-sonnet-4-20250514 (Latest, recommended)', value: 'anthropic/claude-sonnet-4-20250514' },\n { name: 'claude-opus-4-0 (Most capable, slower)', value: 'anthropic/claude-opus-4-0' },\n { name: 'claude-3-5-haiku-20241022 (Fastest, cheapest)', value: 'anthropic/claude-3-5-haiku-20241022' },\n ],\n openai: [\n { name: 'gpt-4o (Recommended)', value: 'openai/gpt-4o' },\n { name: 'gpt-4o-mini (Fast & cheap)', value: 'openai/gpt-4o-mini' },\n { name: 'o3 (Best reasoning)', value: 'openai/o3' },\n { name: 'o4-mini (Fast reasoning)', value: 'openai/o4-mini' },\n ],\n google: [\n { name: 'gemini-2.5-flash (Recommended)', value: 'google/gemini-2.5-flash' },\n { name: 'gemini-2.5-pro (Most capable)', value: 'google/gemini-2.5-pro' },\n { name: 'gemini-2.0-flash (Fast)', value: 'google/gemini-2.0-flash' },\n ],\n };\n\n const models = modelChoices[provider] || [];\n const selectedModel = await select({\n message: 'Which model would you like to use?',\n choices: [...models, { name: '✏️ Enter manually', value: '__manual__' }],\n });\n\n if (selectedModel === '__manual__') {\n config.agent.model = await input({ message: 'Enter model identifier:' });\n } else {\n config.agent.model = selectedModel;\n }\n\n if (provider === 'anthropic') {\n config.providers.anthropic.apiKey = apiKey;\n } else if (provider === 'openai') {\n config.providers.openai.apiKey = apiKey;\n } else if (provider === 'google') {\n config.providers.google.apiKey = apiKey;\n }\n\n // Offer to add additional providers as fallback\n const addFallback = await confirm({\n message: 'Add a second provider as fallback (for failover if the primary is unavailable)?',\n default: false,\n });\n\n if (addFallback) {\n const fallbackProviders = ['anthropic', 'openai', 'google', 'ollama'].filter((p) => p !== provider);\n const fallback = await select({\n message: 'Select fallback provider:',\n choices: fallbackProviders.map((p) => ({ name: p.charAt(0).toUpperCase() + p.slice(1), value: p })),\n });\n if (fallback === 'ollama') {\n const ollamaUrl = await input({ message: 'Ollama base URL:', default: 'http://localhost:11434' });\n config.providers.ollama.baseUrl = ollamaUrl;\n const ollamaModels = await fetchOllamaModels(ollamaUrl);\n if (ollamaModels.length === 0) {\n console.log(chalk.yellow(` ⚠️ Ollama at ${ollamaUrl} is unreachable or has no models — fallback may not work.`));\n } else {\n console.log(chalk.green(` ✅ Ollama fallback ready (${ollamaModels.length} models available)`));\n }\n } else {\n const fallbackKey = await captureAndValidateKey(fallback);\n if (fallback === 'anthropic') config.providers.anthropic.apiKey = fallbackKey;\n else if (fallback === 'openai') config.providers.openai.apiKey = fallbackKey;\n else if (fallback === 'google') config.providers.google.apiKey = fallbackKey;\n }\n }\n }\n\n // ─── Step 3: Autonomy Mode ────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 3 of 7: Autonomy ───\\n'));\n console.log(chalk.gray(' Controls how independently TITAN acts.\\n'));\n\n const autonomyMode = await select({\n message: 'How much autonomy should TITAN have?',\n choices: [\n {\n name: '🟡 Supervised (Recommended) — safe ops run freely, dangerous ops ask first',\n value: 'supervised',\n },\n {\n name: '🟢 Autonomous — full auto, acts without asking. Best for power users.',\n value: 'autonomous',\n },\n {\n name: '🔴 Locked — every single action requires your approval.',\n value: 'locked',\n },\n ],\n });\n config.autonomy.mode = autonomyMode as 'supervised' | 'autonomous' | 'locked';\n\n // ─── Step 4: Security / Sandbox ──────────────────────────────\n console.log(chalk.yellow('\\n─── Step 4 of 7: Security ───\\n'));\n\n const sandboxMode = await select({\n message: 'Sandbox mode for shell commands:',\n choices: [\n { name: '🖥️ Host (Full access — single user machines)', value: 'host' },\n { name: '🐳 Docker (Isolated containers — recommended for shared machines)', value: 'docker' },\n { name: '🚫 None (No restrictions — not recommended)', value: 'none' },\n ],\n });\n config.security.sandboxMode = sandboxMode as 'host' | 'docker' | 'none';\n\n const enableShield = await confirm({\n message: 'Enable Prompt Injection Shield? (blocks attempts to hijack TITAN via chat messages)',\n default: true,\n });\n config.security.shield.enabled = enableShield;\n if (enableShield) {\n const shieldMode = await select({\n message: 'Shield strictness:',\n choices: [\n { name: 'Strict (recommended) — blocks suspicious payloads aggressively', value: 'strict' },\n { name: 'Standard — blocks only obvious injection attempts', value: 'standard' },\n ],\n });\n config.security.shield.mode = shieldMode as 'strict' | 'standard';\n }\n\n // ─── Step 5: Channels ─────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 5 of 7: Messaging Channels ───\\n'));\n console.log(chalk.gray(' Connect TITAN to Discord, Telegram, Slack, etc. (all optional)\\n'));\n\n const channelChoices = await checkbox({\n message: 'Which channels would you like to configure? (space to select, enter to continue)',\n choices: [\n { name: '🎮 Discord', value: 'discord' },\n { name: '✈️ Telegram', value: 'telegram' },\n { name: '💼 Slack', value: 'slack' },\n { name: '💬 Google Chat (webhook)', value: 'googlechat' },\n { name: '📱 WhatsApp (requires phone pairing after setup)', value: 'whatsapp' },\n { name: '⏭️ Skip — configure later in Mission Control Settings', value: 'skip' },\n ],\n });\n\n if (!channelChoices.includes('skip')) {\n for (const channel of channelChoices) {\n if (channel === 'whatsapp') {\n config.channels.whatsapp.enabled = true;\n console.log(chalk.gray(' ℹ️ Run titan pairing after setup to link your phone'));\n } else if (channel === 'googlechat') {\n const webhook = await input({ message: ' Google Chat incoming webhook URL:' });\n config.channels.googlechat.enabled = true;\n config.channels.googlechat.token = webhook;\n } else {\n const token = await password({ message: ` ${channel} bot token:`, mask: '*' });\n if (token && token.trim().length > 0) {\n // Validate token with a quick API call\n process.stdout.write(chalk.gray(` → Testing ${channel} token... `));\n const valid = await validateChannelToken(channel, token.trim());\n if (valid) {\n console.log(chalk.green('✅ valid'));\n } else {\n console.log(chalk.yellow('⚠️ could not verify (token saved anyway — check it in Mission Control if the channel fails)'));\n }\n }\n if (channel === 'discord') {\n config.channels.discord.enabled = true;\n config.channels.discord.token = token;\n } else if (channel === 'telegram') {\n config.channels.telegram.enabled = true;\n config.channels.telegram.token = token;\n } else if (channel === 'slack') {\n config.channels.slack.enabled = true;\n config.channels.slack.token = token;\n }\n }\n }\n }\n\n // ─── Step 6: Gateway ─────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 6 of 7: Gateway ───\\n'));\n console.log(chalk.gray(' Mission Control is served at http://127.0.0.1:<port>\\n'));\n\n const useDefaultPort = await confirm({\n message: 'Use default gateway port (48420)?',\n default: true,\n });\n if (!useDefaultPort) {\n const port = await input({ message: 'Enter gateway port:', default: '48420' });\n config.gateway.port = parseInt(port, 10);\n }\n\n const enableGatewayAuth = await confirm({\n message: 'Enable gateway authentication? (recommended if accessible from other devices)',\n default: false,\n });\n if (enableGatewayAuth) {\n const authMode = await select({\n message: 'Authentication mode:',\n choices: [\n { name: 'Token (API key in request header)', value: 'token' },\n { name: 'Password (browser prompt)', value: 'password' },\n ],\n });\n config.gateway.auth.mode = authMode as 'token' | 'password';\n if (authMode === 'token') {\n config.gateway.auth.token = await password({ message: 'Set a gateway token:', mask: '*' });\n } else {\n config.gateway.auth.password = await password({ message: 'Set a gateway password:', mask: '*' });\n }\n }\n\n // ─── Step 6.5: Daemon Installation ───────────────────────────\n console.log(chalk.yellow('\\n─── Auto-Start: System Service ───\\n'));\n console.log(chalk.gray(' Install TITAN as a system service so it auto-starts on login.\\n'));\n\n let daemonInstalled = false;\n const installDaemon = await confirm({\n message: 'Install TITAN as a system service? (auto-starts on login)',\n default: true,\n });\n if (installDaemon) {\n await installDaemonService();\n daemonInstalled = true;\n }\n\n // ─── Step 7: Logging ─────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 7 of 7: Logging ───\\n'));\n\n const logLevel = await select({\n message: 'Log level:',\n choices: [\n { name: 'info (recommended)', value: 'info' },\n { name: 'debug (verbose — for troubleshooting)', value: 'debug' },\n { name: 'warn (quiet — warnings and errors only)', value: 'warn' },\n { name: 'silent (no logs)', value: 'silent' },\n ],\n });\n config.logging.level = logLevel as 'info' | 'debug' | 'warn' | 'silent';\n\n // ─── Finalise ─────────────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Setting up workspace ───\\n'));\n ensureDir(TITAN_HOME);\n ensureDir(TITAN_WORKSPACE);\n ensureDir(TITAN_SKILLS_DIR);\n initMemory();\n saveConfig(config);\n\n // Save user profile\n if (profileName || profileLevel) {\n const profile = loadProfile();\n if (profileName) profile.name = profileName;\n if (profileLevel) profile.technicalLevel = profileLevel as typeof profile.technicalLevel;\n saveProfile(profile);\n }\n\n const modeEmoji = autonomyMode === 'autonomous' ? '🟢' : autonomyMode === 'locked' ? '🔴' : '🟡';\n const providerName = config.agent.model.split('/')[0];\n const modelName = config.agent.model.split('/').slice(1).join('/');\n const enabledChannels = ['discord','telegram','slack','googlechat','whatsapp']\n .filter(ch => config.channels[ch as keyof typeof config.channels]?.enabled);\n\n console.log(chalk.green('\\n╔══════════════════════════════════════════════════╗'));\n console.log(chalk.green('║ ✅ TITAN is ready! ║'));\n console.log(chalk.green('╚══════════════════════════════════════════════════╝\\n'));\n console.log(chalk.white(' Your configuration:'));\n if (profileName) console.log(chalk.gray(` Name: ${profileName} (${profileLevel})`));\n console.log(chalk.gray(` Provider: ${providerName} / ${modelName}`));\n console.log(chalk.gray(` Autonomy: ${modeEmoji} ${autonomyMode}`));\n console.log(chalk.gray(` Sandbox: ${config.security.sandboxMode}`));\n console.log(chalk.gray(` Logs: ${config.logging.level}`));\n if (enabledChannels.length > 0) {\n console.log(chalk.gray(` Channels: ${enabledChannels.map(ch => '✅ ' + ch).join(', ')}`));\n }\n console.log(chalk.gray(` Service: ${daemonInstalled ? '✅ Installed (auto-starts on login)' : '❌ Manual only (run titan gateway)'}`));\n console.log(chalk.gray(` Config: ${TITAN_CONFIG_PATH}`));\n console.log(chalk.white('\\n Next steps:'));\n console.log(chalk.cyan(' titan gateway ') + chalk.gray(`→ Open Mission Control at http://127.0.0.1:${config.gateway.port}`));\n console.log(chalk.cyan(' titan agent -m \"Hello\" ') + chalk.gray('→ Send a direct message'));\n console.log(chalk.cyan(' titan doctor ') + chalk.gray('→ Diagnose configuration & connectivity'));\n console.log();\n\n const launch = await confirm({\n message: `Start Mission Control (web GUI) now at http://127.0.0.1:${config.gateway.port}?`,\n default: true,\n });\n\n return launch;\n}\n\nasync function installDaemonService(): Promise<void> {\n const platform = process.platform;\n\n if (platform === 'linux') {\n console.log(chalk.gray('Creating systemd user service...'));\n const serviceContent = `[Unit]\nDescription=TITAN Gateway\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${process.execPath} ${process.argv[1]} gateway\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n`;\n const { writeFileSync, mkdirSync, existsSync } = await import('fs');\n const { join } = await import('path');\n const { homedir } = await import('os');\n const serviceDir = join(homedir(), '.config', 'systemd', 'user');\n if (!existsSync(serviceDir)) mkdirSync(serviceDir, { recursive: true });\n writeFileSync(join(serviceDir, 'titan.service'), serviceContent);\n console.log(chalk.green(' Service file installed. Enable with:'));\n console.log(chalk.gray(' $ systemctl --user enable titan'));\n console.log(chalk.gray(' $ systemctl --user start titan'));\n } else if (platform === 'darwin') {\n console.log(chalk.yellow(' macOS: create a LaunchAgent plist manually to run as a daemon.'));\n } else {\n console.log(chalk.yellow(' Daemon installation not supported on this platform yet.'));\n }\n}\n"],"mappings":"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","names":["modelName"]}
|
|
1
|
+
{"version":3,"sources":["../../src/cli/onboard.ts"],"sourcesContent":["/**\n * TITAN — Onboarding Wizard\n * Interactive setup for first-time users. Covers all key settings.\n */\nimport { select, input, confirm, password, checkbox } from '@inquirer/prompts';\nimport chalk from 'chalk';\nimport { exec } from 'child_process';\nimport { saveConfig, getDefaultConfig } from '../config/config.js';\nimport { TITAN_HOME, TITAN_WORKSPACE, TITAN_SKILLS_DIR, TITAN_CONFIG_PATH } from '../utils/constants.js';\nimport { mkdirIfNotExists } from '../utils/helpers.js';\nimport { initMemory } from '../memory/memory.js';\nimport { loadProfile, saveProfile } from '../memory/relationship.js';\n\n\n// ─── Ollama helpers ───────────────────────────────────────────────\nasync function fetchOllamaModels(baseUrl: string): Promise<string[]> {\n try {\n const res = await fetch(`${baseUrl}/api/tags`, { signal: AbortSignal.timeout(3000) });\n if (!res.ok) return [];\n const json = await res.json() as { models?: { name: string }[] };\n return (json.models || []).map((m) => m.name).filter(Boolean);\n } catch {\n return [];\n }\n}\n\n// ─── API key validation ──────────────────────────────────────────\n// Tests a provider key against its real models endpoint.\n// Returns { ok: true } on success or { ok: false, error: string } on failure.\n// Used by the onboarding wizard to catch typo'd / expired / fake keys\n// BEFORE the user finishes setup and hits a generic 500 in the gateway.\nasync function validateProviderKey(\n provider: string,\n apiKey: string,\n): Promise<{ ok: boolean; error?: string }> {\n if (!apiKey || apiKey.trim().length === 0) {\n return { ok: false, error: 'Key is empty' };\n }\n const trimmed = apiKey.trim();\n try {\n const ctl = AbortSignal.timeout(8000);\n if (provider === 'anthropic') {\n const res = await fetch('https://api.anthropic.com/v1/models', {\n method: 'GET',\n headers: { 'x-api-key': trimmed, 'anthropic-version': '2023-06-01' },\n signal: ctl,\n });\n if (res.status === 401 || res.status === 403) return { ok: false, error: 'Authentication failed (401/403)' };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };\n return { ok: true };\n }\n if (provider === 'openai') {\n const res = await fetch('https://api.openai.com/v1/models', {\n method: 'GET',\n headers: { Authorization: `Bearer ${trimmed}` },\n signal: ctl,\n });\n if (res.status === 401 || res.status === 403) return { ok: false, error: 'Authentication failed (401/403)' };\n if (!res.ok) return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };\n return { ok: true };\n }\n if (provider === 'google') {\n const res = await fetch(\n `https://generativelanguage.googleapis.com/v1beta/models?key=${encodeURIComponent(trimmed)}`,\n { method: 'GET', signal: ctl },\n );\n if (res.status === 401 || res.status === 403 || res.status === 400) {\n return { ok: false, error: `Authentication failed (${res.status})` };\n }\n if (!res.ok) return { ok: false, error: `HTTP ${res.status} ${res.statusText}` };\n return { ok: true };\n }\n // Unknown provider — skip validation, accept the key\n return { ok: true };\n } catch (err) {\n const msg = (err as Error).message;\n if (msg.includes('aborted')) return { ok: false, error: 'Request timed out (network or wrong endpoint)' };\n return { ok: false, error: msg };\n }\n}\n\n// Captures and validates an API key with retry. User can also skip.\n// Returns the validated key, or empty string if user chose to skip.\nasync function captureAndValidateKey(provider: string): Promise<string> {\n for (;;) { // retry loop until user provides a valid key, skips, or forces\n const apiKey = await password({\n message: `Paste your ${provider} API key here (input is hidden):`,\n mask: '*',\n });\n if (!apiKey || apiKey.trim().length === 0) {\n const skip = await confirm({\n message: 'No key entered. Skip validation and continue anyway?',\n default: false,\n });\n if (skip) return '';\n continue;\n }\n process.stdout.write(chalk.gray(` → Testing key against ${provider}... `));\n const result = await validateProviderKey(provider, apiKey);\n if (result.ok) {\n console.log(chalk.green('✅ valid'));\n return apiKey.trim();\n }\n console.log(chalk.red(`❌ ${result.error}`));\n const choice = await select({\n message: 'What would you like to do?',\n choices: [\n { name: '🔁 Re-enter the key', value: 'retry' },\n { name: '⏭️ Skip key validation and use it anyway (advanced)', value: 'force' },\n { name: '❌ Skip this provider', value: 'skip' },\n ],\n });\n if (choice === 'retry') continue;\n if (choice === 'force') return apiKey.trim();\n if (choice === 'skip') return '';\n }\n}\n\nimport { TITAN_VERSION } from '../utils/constants.js';\n\n// ─── Channel token validation ────────────────────────────────────\n// Quick check if a channel bot token is valid by calling the platform's identity endpoint.\nasync function validateChannelToken(channel: string, token: string): Promise<boolean> {\n try {\n const ctl = AbortSignal.timeout(8000);\n if (channel === 'discord') {\n const res = await fetch('https://discord.com/api/v10/users/@me', {\n headers: { Authorization: `Bot ${token}` }, signal: ctl,\n });\n return res.ok;\n }\n if (channel === 'telegram') {\n const res = await fetch(`https://api.telegram.org/bot${token}/getMe`, { signal: ctl });\n return res.ok;\n }\n if (channel === 'slack') {\n const res = await fetch('https://slack.com/api/auth.test', {\n method: 'POST',\n headers: { Authorization: `Bearer ${token}`, 'Content-Type': 'application/x-www-form-urlencoded' },\n signal: ctl,\n });\n if (!res.ok) return false;\n const data = await res.json() as { ok?: boolean };\n return data.ok === true;\n }\n return true; // unknown channel — skip validation\n } catch {\n return false;\n }\n}\n\nfunction printLogo(): void {\n const c = chalk;\n const border = c.cyan;\n const row1 = c.yellowBright;\n const row2 = c.yellow;\n const row3 = c.greenBright;\n const row4 = c.cyanBright;\n const row5 = c.blueBright;\n const tagline = c.white;\n const credit = c.magentaBright;\n const ver = c.gray;\n\n console.log('');\n console.log(border(' ╔══════════════════════════════════════════════════════════╗'));\n console.log(border(' ║ ║'));\n console.log(border(' ║ ') + row1('████████╗██╗████████╗ █████╗ ███╗ ██╗') + border(' ║'));\n console.log(border(' ║ ') + row2(' ██║ ██║ ██║ ██╔══██╗ ████╗ ██║') + border(' ║'));\n console.log(border(' ║ ') + row3(' ██║ ██║ ██║ ███████║ ██╔██╗ ██║') + border(' ║'));\n console.log(border(' ║ ') + row4(' ██║ ██║ ██║ ██╔══██║ ██║╚██╗██║') + border(' ║'));\n console.log(border(' ║ ') + row5(' ██║ ██║ ██║ ██║ ██║ ██║ ╚████║') + border(' ║'));\n console.log(border(' ║ ') + c.blue(' ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═╝ ╚═══╝') + border(' ║'));\n console.log(border(' ║ ║'));\n console.log(border(' ║ ') + tagline('The Intelligent Task Automation Network') + border(' ║'));\n console.log(border(' ║ ') + ver(`v${TITAN_VERSION}`) + c.gray(' • ') + credit('by Tony Elliott') + border(' ║'));\n console.log(border(' ╚══════════════════════════════════════════════════════════╝'));\n console.log('');\n}\n\n// ─── Main wizard ──────────────────────────────────────────────────\nexport async function runOnboard(_installDaemon?: boolean): Promise<boolean> {\n printLogo();\n console.log(chalk.gray(' Welcome! This wizard will configure your personal AI assistant.'));\n console.log(chalk.gray(' Press Ctrl+C at any time to cancel.\\n'));\n\n\n const config = getDefaultConfig();\n\n // ─── Step 0: System Check + Profile ──────────────────────────\n console.log(chalk.yellow('─── Step 0: System Check ───\\n'));\n\n // Node.js version\n console.log(chalk.green(` ✅ Node.js ${process.version}`));\n\n // Check Ollama\n const ollamaModelsCheck = await fetchOllamaModels('http://localhost:11434');\n if (ollamaModelsCheck.length > 0) {\n console.log(chalk.green(` ✅ Ollama detected (${ollamaModelsCheck.length} model(s): ${ollamaModelsCheck.slice(0, 3).join(', ')}${ollamaModelsCheck.length > 3 ? ', ...' : ''})`));\n } else {\n console.log(chalk.yellow(' ⚠️ Ollama not detected (local free AI unavailable — install from ollama.ai)'));\n }\n\n // Check Docker\n await new Promise<void>((resolve) => {\n exec('docker info --format \"{{.ServerVersion}}\"', { timeout: 3000 }, (err, stdout) => {\n if (!err && stdout.trim()) {\n console.log(chalk.green(` ✅ Docker available (v${stdout.trim()} — docker sandbox mode enabled)`));\n } else {\n console.log(chalk.yellow(' ⚠️ Docker not found (docker sandbox mode won\\'t be available)'));\n }\n resolve();\n });\n });\n\n console.log('');\n console.log(chalk.yellow('─── Step 0: Personalization ───\\n'));\n console.log(chalk.gray(' This helps TITAN respond in your style — like a JARVIS that knows you.\\n'));\n\n const profileName = await input({\n message: 'Your first name (optional, for personalization):',\n default: '',\n });\n\n const profileLevel = await select({\n message: 'Your technical level:',\n choices: [\n { name: 'Beginner — explain everything in plain English', value: 'beginner' },\n { name: 'Intermediate — I know the basics', value: 'intermediate' },\n { name: 'Expert — no hand-holding', value: 'expert' },\n ],\n });\n\n // ─── Step 1: Primary AI Provider ─────────────────────────────\n console.log(chalk.yellow('\\n─── Step 1 of 7: AI Provider ───\\n'));\n\n const provider = await select({\n message: 'Which AI provider would you like to use as your primary?',\n choices: [\n { name: '🟣 Anthropic (Claude) — Best reasoning, recommended', value: 'anthropic' },\n { name: '🟢 OpenAI (GPT-4o) — Great all-rounder', value: 'openai' },\n { name: '🔵 Google (Gemini) — Fast & multimodal', value: 'google' },\n { name: '🟠 Ollama (Local) — Free, private, runs on your machine', value: 'ollama' },\n ],\n });\n\n // ─── Step 2: Model Selection ──────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 2 of 7: Model ───\\n'));\n\n if (provider === 'ollama') {\n const ollamaUrl = await input({\n message: 'Ollama base URL:',\n default: 'http://localhost:11434',\n });\n config.providers.ollama.baseUrl = ollamaUrl;\n\n console.log(chalk.gray(`\\n 🔍 Detecting models at ${ollamaUrl}...`));\n const installedModels = await fetchOllamaModels(ollamaUrl);\n\n if (installedModels.length > 0) {\n console.log(chalk.green(` ✅ Found ${installedModels.length} installed model(s)\\n`));\n const chosen = await select({\n message: 'Select a model to use:',\n choices: installedModels.map((m) => ({ name: m, value: `ollama/${m}` })),\n });\n config.agent.model = chosen;\n } else {\n console.log(chalk.yellow(' ⚠️ No models detected (Ollama may not be running, or no models pulled yet).'));\n console.log(chalk.gray(' Run: ollama pull qwen3.5:4b to install a model'));\n console.log(chalk.gray(' See: docs/MODELS.md for GPU-tiered model recommendations\\n'));\n const modelName = await input({\n message: 'Enter the Ollama model name to use:',\n default: 'qwen3.5:4b',\n });\n config.agent.model = `ollama/${modelName}`;\n }\n } else {\n // Cloud provider — guide user to get their API key\n const keyGuides: Record<string, { url: string; hint: string; name: string }> = {\n anthropic: {\n url: 'https://console.anthropic.com/settings/keys',\n hint: 'Looks like: sk-ant-api03-...',\n name: 'Anthropic Console',\n },\n openai: {\n url: 'https://platform.openai.com/api-keys',\n hint: 'Looks like: sk-proj-... or sk-...',\n name: 'OpenAI Platform',\n },\n google: {\n url: 'https://aistudio.google.com/app/apikey',\n hint: 'Looks like: AIza...',\n name: 'Google AI Studio',\n },\n };\n\n const guide = keyGuides[provider];\n if (guide) {\n console.log(chalk.cyan(`\\n 📋 To get your ${chalk.white(provider)} API key:`));\n console.log(chalk.white(` → Go to: ${chalk.underline(guide.url)}`));\n console.log(chalk.gray(` → ${guide.hint}`));\n console.log(chalk.green(`\\n 🔒 Security guarantee:`));\n console.log(chalk.gray(` Your key is stored ONLY on YOUR computer at:`));\n console.log(chalk.gray(` ~/.titan/config.json`));\n console.log(chalk.gray(` It goes directly to ${provider.charAt(0).toUpperCase() + provider.slice(1)}'s servers.`));\n console.log(chalk.gray(` TITAN never sees it. No one else ever sees it.\\n`));\n }\n\n const apiKey = await captureAndValidateKey(provider);\n\n const modelChoices: Record<string, { name: string; value: string }[]> = {\n anthropic: [\n { name: 'claude-sonnet-4-20250514 (Latest, recommended)', value: 'anthropic/claude-sonnet-4-20250514' },\n { name: 'claude-opus-4-0 (Most capable, slower)', value: 'anthropic/claude-opus-4-0' },\n { name: 'claude-3-5-haiku-20241022 (Fastest, cheapest)', value: 'anthropic/claude-3-5-haiku-20241022' },\n ],\n openai: [\n { name: 'gpt-4o (Recommended)', value: 'openai/gpt-4o' },\n { name: 'gpt-4o-mini (Fast & cheap)', value: 'openai/gpt-4o-mini' },\n { name: 'o3 (Best reasoning)', value: 'openai/o3' },\n { name: 'o4-mini (Fast reasoning)', value: 'openai/o4-mini' },\n ],\n google: [\n { name: 'gemini-2.5-flash (Recommended)', value: 'google/gemini-2.5-flash' },\n { name: 'gemini-2.5-pro (Most capable)', value: 'google/gemini-2.5-pro' },\n { name: 'gemini-2.0-flash (Fast)', value: 'google/gemini-2.0-flash' },\n ],\n };\n\n const models = modelChoices[provider] || [];\n const selectedModel = await select({\n message: 'Which model would you like to use?',\n choices: [...models, { name: '✏️ Enter manually', value: '__manual__' }],\n });\n\n if (selectedModel === '__manual__') {\n config.agent.model = await input({ message: 'Enter model identifier:' });\n } else {\n config.agent.model = selectedModel;\n }\n\n if (provider === 'anthropic') {\n config.providers.anthropic.apiKey = apiKey;\n } else if (provider === 'openai') {\n config.providers.openai.apiKey = apiKey;\n } else if (provider === 'google') {\n config.providers.google.apiKey = apiKey;\n }\n\n // Offer to add additional providers as fallback\n const addFallback = await confirm({\n message: 'Add a second provider as fallback (for failover if the primary is unavailable)?',\n default: false,\n });\n\n if (addFallback) {\n const fallbackProviders = ['anthropic', 'openai', 'google', 'ollama'].filter((p) => p !== provider);\n const fallback = await select({\n message: 'Select fallback provider:',\n choices: fallbackProviders.map((p) => ({ name: p.charAt(0).toUpperCase() + p.slice(1), value: p })),\n });\n if (fallback === 'ollama') {\n const ollamaUrl = await input({ message: 'Ollama base URL:', default: 'http://localhost:11434' });\n config.providers.ollama.baseUrl = ollamaUrl;\n const ollamaModels = await fetchOllamaModels(ollamaUrl);\n if (ollamaModels.length === 0) {\n console.log(chalk.yellow(` ⚠️ Ollama at ${ollamaUrl} is unreachable or has no models — fallback may not work.`));\n } else {\n console.log(chalk.green(` ✅ Ollama fallback ready (${ollamaModels.length} models available)`));\n }\n } else {\n const fallbackKey = await captureAndValidateKey(fallback);\n if (fallback === 'anthropic') config.providers.anthropic.apiKey = fallbackKey;\n else if (fallback === 'openai') config.providers.openai.apiKey = fallbackKey;\n else if (fallback === 'google') config.providers.google.apiKey = fallbackKey;\n }\n }\n }\n\n // ─── Step 3: Autonomy Mode ────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 3 of 7: Autonomy ───\\n'));\n console.log(chalk.gray(' Controls how independently TITAN acts.\\n'));\n\n const autonomyMode = await select({\n message: 'How much autonomy should TITAN have?',\n choices: [\n {\n name: '🟡 Supervised (Recommended) — safe ops run freely, dangerous ops ask first',\n value: 'supervised',\n },\n {\n name: '🟢 Autonomous — full auto, acts without asking. Best for power users.',\n value: 'autonomous',\n },\n {\n name: '🔴 Locked — every single action requires your approval.',\n value: 'locked',\n },\n ],\n });\n config.autonomy.mode = autonomyMode as 'supervised' | 'autonomous' | 'locked';\n\n // ─── Step 4: Security / Sandbox ──────────────────────────────\n console.log(chalk.yellow('\\n─── Step 4 of 7: Security ───\\n'));\n\n const sandboxMode = await select({\n message: 'Sandbox mode for shell commands:',\n choices: [\n { name: '🖥️ Host (Full access — single user machines)', value: 'host' },\n { name: '🐳 Docker (Isolated containers — recommended for shared machines)', value: 'docker' },\n { name: '🚫 None (No restrictions — not recommended)', value: 'none' },\n ],\n });\n config.security.sandboxMode = sandboxMode as 'host' | 'docker' | 'none';\n\n const enableShield = await confirm({\n message: 'Enable Prompt Injection Shield? (blocks attempts to hijack TITAN via chat messages)',\n default: true,\n });\n config.security.shield.enabled = enableShield;\n if (enableShield) {\n const shieldMode = await select({\n message: 'Shield strictness:',\n choices: [\n { name: 'Strict (recommended) — blocks suspicious payloads aggressively', value: 'strict' },\n { name: 'Standard — blocks only obvious injection attempts', value: 'standard' },\n ],\n });\n config.security.shield.mode = shieldMode as 'strict' | 'standard';\n }\n\n // ─── Step 5: Channels ─────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 5 of 7: Messaging Channels ───\\n'));\n console.log(chalk.gray(' Connect TITAN to Discord, Telegram, Slack, etc. (all optional)\\n'));\n\n const channelChoices = await checkbox({\n message: 'Which channels would you like to configure? (space to select, enter to continue)',\n choices: [\n { name: '🎮 Discord', value: 'discord' },\n { name: '✈️ Telegram', value: 'telegram' },\n { name: '💼 Slack', value: 'slack' },\n { name: '💬 Google Chat (webhook)', value: 'googlechat' },\n { name: '📱 WhatsApp (requires phone pairing after setup)', value: 'whatsapp' },\n { name: '⏭️ Skip — configure later in Mission Control Settings', value: 'skip' },\n ],\n });\n\n if (!channelChoices.includes('skip')) {\n for (const channel of channelChoices) {\n if (channel === 'whatsapp') {\n config.channels.whatsapp.enabled = true;\n console.log(chalk.gray(' ℹ️ Run titan pairing after setup to link your phone'));\n } else if (channel === 'googlechat') {\n const webhook = await input({ message: ' Google Chat incoming webhook URL:' });\n config.channels.googlechat.enabled = true;\n config.channels.googlechat.token = webhook;\n } else {\n const token = await password({ message: ` ${channel} bot token:`, mask: '*' });\n if (token && token.trim().length > 0) {\n // Validate token with a quick API call\n process.stdout.write(chalk.gray(` → Testing ${channel} token... `));\n const valid = await validateChannelToken(channel, token.trim());\n if (valid) {\n console.log(chalk.green('✅ valid'));\n } else {\n console.log(chalk.yellow('⚠️ could not verify (token saved anyway — check it in Mission Control if the channel fails)'));\n }\n }\n if (channel === 'discord') {\n config.channels.discord.enabled = true;\n config.channels.discord.token = token;\n } else if (channel === 'telegram') {\n config.channels.telegram.enabled = true;\n config.channels.telegram.token = token;\n } else if (channel === 'slack') {\n config.channels.slack.enabled = true;\n config.channels.slack.token = token;\n }\n }\n }\n }\n\n // ─── Step 6: Gateway ─────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 6 of 7: Gateway ───\\n'));\n console.log(chalk.gray(' Mission Control is served at http://127.0.0.1:<port>\\n'));\n\n const useDefaultPort = await confirm({\n message: 'Use default gateway port (48420)?',\n default: true,\n });\n if (!useDefaultPort) {\n const port = await input({ message: 'Enter gateway port:', default: '48420' });\n config.gateway.port = parseInt(port, 10);\n }\n\n const enableGatewayAuth = await confirm({\n message: 'Enable gateway authentication? (recommended if accessible from other devices)',\n default: false,\n });\n if (enableGatewayAuth) {\n const authMode = await select({\n message: 'Authentication mode:',\n choices: [\n { name: 'Token (API key in request header)', value: 'token' },\n { name: 'Password (browser prompt)', value: 'password' },\n ],\n });\n config.gateway.auth.mode = authMode as 'token' | 'password';\n if (authMode === 'token') {\n config.gateway.auth.token = await password({ message: 'Set a gateway token:', mask: '*' });\n } else {\n config.gateway.auth.password = await password({ message: 'Set a gateway password:', mask: '*' });\n }\n }\n\n // ─── Step 6.5: Daemon Installation ───────────────────────────\n console.log(chalk.yellow('\\n─── Auto-Start: System Service ───\\n'));\n console.log(chalk.gray(' Install TITAN as a system service so it auto-starts on login.\\n'));\n\n let daemonInstalled = false;\n const installDaemon = await confirm({\n message: 'Install TITAN as a system service? (auto-starts on login)',\n default: true,\n });\n if (installDaemon) {\n await installDaemonService();\n daemonInstalled = true;\n }\n\n // ─── Step 7: Logging ─────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Step 7 of 7: Logging ───\\n'));\n\n const logLevel = await select({\n message: 'Log level:',\n choices: [\n { name: 'info (recommended)', value: 'info' },\n { name: 'debug (verbose — for troubleshooting)', value: 'debug' },\n { name: 'warn (quiet — warnings and errors only)', value: 'warn' },\n { name: 'silent (no logs)', value: 'silent' },\n ],\n });\n config.logging.level = logLevel as 'info' | 'debug' | 'warn' | 'silent';\n\n // ─── Finalise ─────────────────────────────────────────────────\n console.log(chalk.yellow('\\n─── Setting up workspace ───\\n'));\n mkdirIfNotExists(TITAN_HOME);\n mkdirIfNotExists(TITAN_WORKSPACE);\n mkdirIfNotExists(TITAN_SKILLS_DIR);\n initMemory();\n saveConfig(config);\n\n // Save user profile\n if (profileName || profileLevel) {\n const profile = loadProfile();\n if (profileName) profile.name = profileName;\n if (profileLevel) profile.technicalLevel = profileLevel as typeof profile.technicalLevel;\n saveProfile(profile);\n }\n\n const modeEmoji = autonomyMode === 'autonomous' ? '🟢' : autonomyMode === 'locked' ? '🔴' : '🟡';\n const providerName = config.agent.model.split('/')[0];\n const modelName = config.agent.model.split('/').slice(1).join('/');\n const enabledChannels = ['discord','telegram','slack','googlechat','whatsapp']\n .filter(ch => config.channels[ch as keyof typeof config.channels]?.enabled);\n\n console.log(chalk.green('\\n╔══════════════════════════════════════════════════╗'));\n console.log(chalk.green('║ ✅ TITAN is ready! ║'));\n console.log(chalk.green('╚══════════════════════════════════════════════════╝\\n'));\n console.log(chalk.white(' Your configuration:'));\n if (profileName) console.log(chalk.gray(` Name: ${profileName} (${profileLevel})`));\n console.log(chalk.gray(` Provider: ${providerName} / ${modelName}`));\n console.log(chalk.gray(` Autonomy: ${modeEmoji} ${autonomyMode}`));\n console.log(chalk.gray(` Sandbox: ${config.security.sandboxMode}`));\n console.log(chalk.gray(` Logs: ${config.logging.level}`));\n if (enabledChannels.length > 0) {\n console.log(chalk.gray(` Channels: ${enabledChannels.map(ch => '✅ ' + ch).join(', ')}`));\n }\n console.log(chalk.gray(` Service: ${daemonInstalled ? '✅ Installed (auto-starts on login)' : '❌ Manual only (run titan gateway)'}`));\n console.log(chalk.gray(` Config: ${TITAN_CONFIG_PATH}`));\n console.log(chalk.white('\\n Next steps:'));\n console.log(chalk.cyan(' titan gateway ') + chalk.gray(`→ Open Mission Control at http://127.0.0.1:${config.gateway.port}`));\n console.log(chalk.cyan(' titan agent -m \"Hello\" ') + chalk.gray('→ Send a direct message'));\n console.log(chalk.cyan(' titan doctor ') + chalk.gray('→ Diagnose configuration & connectivity'));\n console.log();\n\n const launch = await confirm({\n message: `Start Mission Control (web GUI) now at http://127.0.0.1:${config.gateway.port}?`,\n default: true,\n });\n\n return launch;\n}\n\nasync function installDaemonService(): Promise<void> {\n const platform = process.platform;\n\n if (platform === 'linux') {\n console.log(chalk.gray('Creating systemd user service...'));\n const serviceContent = `[Unit]\nDescription=TITAN Gateway\nAfter=network.target\n\n[Service]\nType=simple\nExecStart=${process.execPath} ${process.argv[1]} gateway\nRestart=on-failure\nRestartSec=10\n\n[Install]\nWantedBy=default.target\n`;\n const { writeFileSync, mkdirSync, existsSync } = await import('fs');\n const { join } = await import('path');\n const { homedir } = await import('os');\n const serviceDir = join(homedir(), '.config', 'systemd', 'user');\n if (!existsSync(serviceDir)) mkdirSync(serviceDir, { recursive: true });\n writeFileSync(join(serviceDir, 'titan.service'), serviceContent);\n console.log(chalk.green(' Service file installed. Enable with:'));\n console.log(chalk.gray(' $ systemctl --user enable titan'));\n console.log(chalk.gray(' $ systemctl --user start titan'));\n } else if (platform === 'darwin') {\n console.log(chalk.yellow(' macOS: create a LaunchAgent plist manually to run as a daemon.'));\n } else {\n console.log(chalk.yellow(' Daemon installation not supported on this platform yet.'));\n }\n}\n"],"mappings":"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","names":["modelName"]}
|
package/dist/config/config.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { existsSync } from "fs";
|
|
3
3
|
import { TITAN_CONFIG_PATH, TITAN_HOME } from "../utils/constants.js";
|
|
4
|
-
import { readJsonFile, writeJsonFile,
|
|
4
|
+
import { readJsonFile, writeJsonFile, mkdirIfNotExists, deepMerge } from "../utils/helpers.js";
|
|
5
5
|
import { TitanConfigSchema } from "./schema.js";
|
|
6
6
|
import logger from "../utils/logger.js";
|
|
7
7
|
const COMPONENT = "Config";
|
|
@@ -11,7 +11,7 @@ function getDefaultConfig() {
|
|
|
11
11
|
}
|
|
12
12
|
function loadConfig() {
|
|
13
13
|
if (cachedConfig) return cachedConfig;
|
|
14
|
-
|
|
14
|
+
mkdirIfNotExists(TITAN_HOME);
|
|
15
15
|
let rawConfig = {};
|
|
16
16
|
if (existsSync(TITAN_CONFIG_PATH)) {
|
|
17
17
|
const loaded = readJsonFile(TITAN_CONFIG_PATH);
|
|
@@ -68,7 +68,7 @@ function loadConfig() {
|
|
|
68
68
|
return cachedConfig;
|
|
69
69
|
}
|
|
70
70
|
function saveConfig(config) {
|
|
71
|
-
|
|
71
|
+
mkdirIfNotExists(TITAN_HOME);
|
|
72
72
|
writeJsonFile(TITAN_CONFIG_PATH, config);
|
|
73
73
|
cachedConfig = config;
|
|
74
74
|
logger.info(COMPONENT, `Config saved to ${TITAN_CONFIG_PATH}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/config/config.ts"],"sourcesContent":["/**\n * TITAN Configuration Manager\n * Loads, validates, and persists configuration from ~/.titan/titan.json\n */\nimport { existsSync } from 'fs';\nimport { TITAN_CONFIG_PATH, TITAN_HOME } from '../utils/constants.js';\nimport { readJsonFile, writeJsonFile, ensureDir, deepMerge } from '../utils/helpers.js';\nimport { TitanConfigSchema, type TitanConfig } from './schema.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Config';\n\nlet cachedConfig: TitanConfig | null = null;\n\n/** Get the default configuration */\nexport function getDefaultConfig(): TitanConfig {\n return TitanConfigSchema.parse({});\n}\n\n/** Load configuration from disk, merging with defaults */\nexport function loadConfig(): TitanConfig {\n if (cachedConfig) return cachedConfig;\n\n ensureDir(TITAN_HOME);\n\n let rawConfig: Record<string, unknown> = {};\n\n if (existsSync(TITAN_CONFIG_PATH)) {\n const loaded = readJsonFile<Record<string, unknown>>(TITAN_CONFIG_PATH);\n if (loaded) {\n rawConfig = loaded;\n logger.debug(COMPONENT, `Loaded config from ${TITAN_CONFIG_PATH}`);\n } else {\n logger.warn(COMPONENT, `Failed to parse config at ${TITAN_CONFIG_PATH}, using defaults`);\n }\n } else {\n logger.info(COMPONENT, 'No config file found, using defaults');\n }\n\n // Apply environment variables\n applyEnvOverrides(rawConfig);\n\n // v4.8.4: migrate a top-level `auth` block to `gateway.auth`. The\n // documented path has always been `gateway.auth`, but users (and\n // Claude) naturally try `auth` at the root. Rather than strip it\n // silently and warn, move it to the canonical location and continue.\n if (rawConfig && typeof rawConfig === 'object' && 'auth' in rawConfig) {\n const raw = rawConfig as Record<string, unknown>;\n const topAuth = raw.auth as Record<string, unknown> | undefined;\n if (topAuth && typeof topAuth === 'object') {\n const gateway = (raw.gateway as Record<string, unknown> | undefined) ?? {};\n const gatewayAuth = (gateway.auth as Record<string, unknown> | undefined) ?? {};\n // gateway.auth wins if both are set — explicit nested wins\n // over migrated top-level.\n raw.gateway = { ...gateway, auth: { ...topAuth, ...gatewayAuth } };\n delete raw.auth;\n logger.info(COMPONENT, 'Migrated top-level `auth` → `gateway.auth`. Update titan.json to nest it under `gateway` to silence this notice.');\n }\n }\n\n // Detect unknown keys at every nesting depth BEFORE Zod silently strips\n // them. Hunt Finding #1 (2026-04-14) covered the top-level case\n // (`facebook: {...}` ignored). This expanded check also catches nested\n // typos like `providers.anthropic.unknownField` so users learn about\n // dropped keys immediately rather than chasing \"why doesn't my setting\n // do anything\" days later.\n //\n // The diff is purely informational — invalid keys never block startup\n // (we still feed `rawConfig` through Zod's permissive parse).\n try {\n const parsed = TitanConfigSchema.safeParse(rawConfig);\n if (parsed.success) {\n const droppedPaths = findDroppedKeys(rawConfig, parsed.data as Record<string, unknown>, '');\n for (const path of droppedPaths) {\n logger.warn(\n COMPONENT,\n `Unknown config key: ${path}. Will be ignored. ` +\n `If intentional, extend TitanConfigSchema in src/config/schema.ts.`,\n );\n }\n }\n } catch {\n // Introspection failed — never let a diagnostic warning block load.\n }\n\n // Validate and merge with defaults via Zod.\n // CRITICAL: On validation failure, deep-merge raw config over defaults\n // so that valid user settings (daemon.enabled, autonomy.mode, etc.) survive.\n // Previously this fell back to pure defaults, wiping ALL user config on any error.\n const result = TitanConfigSchema.safeParse(rawConfig);\n if (result.success) {\n cachedConfig = result.data;\n } else {\n const issues = result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', ');\n logger.warn(COMPONENT, `Config validation issues (${issues}) — merging valid fields over defaults`);\n // Deep-merge raw config over defaults so valid sections survive\n const defaults = getDefaultConfig();\n const merged = deepMerge(defaults as Record<string, unknown>, rawConfig) as TitanConfig;\n // Try parsing the merged result — if it still fails, use defaults but log loudly\n const reparse = TitanConfigSchema.safeParse(merged);\n if (reparse.success) {\n cachedConfig = reparse.data;\n } else {\n logger.error(COMPONENT, `Config still invalid after merge — falling back to defaults. Fix your titan.json.`);\n cachedConfig = defaults;\n }\n }\n\n return cachedConfig;\n}\n\n/** Save current configuration to disk */\nexport function saveConfig(config: TitanConfig): void {\n ensureDir(TITAN_HOME);\n writeJsonFile(TITAN_CONFIG_PATH, config);\n cachedConfig = config;\n logger.info(COMPONENT, `Config saved to ${TITAN_CONFIG_PATH}`);\n}\n\n/** Update specific fields in the config */\nexport function updateConfig(partial: Partial<TitanConfig>): TitanConfig {\n const current = loadConfig();\n const updated = deepMerge(current as Record<string, unknown>, partial as Record<string, unknown>) as TitanConfig;\n const validated = TitanConfigSchema.parse(updated);\n saveConfig(validated);\n return validated;\n}\n\n/** Reset config cache (useful for testing) */\nexport function resetConfigCache(): void {\n cachedConfig = null;\n}\n\n/** Check if the configuration file exists */\nexport function configExists(): boolean {\n return existsSync(TITAN_CONFIG_PATH);\n}\n\n/**\n * Check if at least one usable AI provider is configured.\n *\n * \"Usable\" means one of:\n * - Any cloud provider has a non-empty `apiKey` set in config\n * - Any *_API_KEY env var is set (Anthropic, OpenAI, Google, Groq, etc.)\n * - Ollama is reachable at the configured baseUrl (returns at least one model)\n *\n * Used by the gateway boot guard and CLI to refuse to start with empty config\n * instead of letting the user hit \"Internal Server Error\" later.\n *\n * Note: Ollama check is async and is the slowest part of this function (~3s timeout).\n * Callers should `await` and only call once at boot.\n */\nexport async function hasUsableProvider(): Promise<{ ok: boolean; details: string }> {\n const config = loadConfig();\n\n // 1. Check config-file API keys (cloud providers)\n const providers = (config.providers as Record<string, unknown> | undefined) || {};\n const cloudProviderNames = [\n 'anthropic', 'openai', 'google', 'groq', 'mistral', 'openrouter',\n 'fireworks', 'xai', 'together', 'deepseek', 'cerebras', 'cohere',\n 'perplexity', 'venice', 'bedrock', 'litellm', 'azure', 'deepinfra',\n 'sambanova', 'kimi', 'huggingface', 'ai21', 'cohere-v2', 'reka',\n 'zhipu', 'yi', 'inflection', 'novita', 'replicate', 'lepton',\n 'anyscale', 'octo', 'nous', 'minimax', 'nvidia',\n ];\n for (const name of cloudProviderNames) {\n const p = providers[name] as { apiKey?: string } | undefined;\n if (p?.apiKey && p.apiKey.trim().length > 0) {\n return { ok: true, details: `${name} has an API key configured` };\n }\n }\n\n // 2. Check env-var API keys (in case config wasn't reloaded after env var change)\n const envKeys = [\n 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GOOGLE_API_KEY', 'GROQ_API_KEY',\n 'MISTRAL_API_KEY', 'OPENROUTER_API_KEY', 'FIREWORKS_API_KEY', 'XAI_API_KEY',\n 'TOGETHER_API_KEY', 'DEEPSEEK_API_KEY', 'CEREBRAS_API_KEY', 'COHERE_API_KEY',\n 'PERPLEXITY_API_KEY', 'AZURE_OPENAI_API_KEY',\n ];\n for (const key of envKeys) {\n if (process.env[key] && process.env[key]!.trim().length > 0) {\n return { ok: true, details: `${key} is set in environment` };\n }\n }\n\n // 3. Check Ollama reachability (last resort — slow)\n const ollamaUrl = (providers.ollama as { baseUrl?: string } | undefined)?.baseUrl\n || process.env.OLLAMA_BASE_URL\n || 'http://localhost:11434';\n try {\n const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(3000) });\n if (res.ok) {\n const json = await res.json() as { models?: { name: string }[] };\n const count = (json.models || []).length;\n if (count > 0) {\n return { ok: true, details: `Ollama at ${ollamaUrl} is reachable (${count} models)` };\n }\n return { ok: false, details: `Ollama at ${ollamaUrl} is reachable but has 0 models — run \"ollama pull qwen3.5:4b\"` };\n }\n } catch {\n // Ollama unreachable, fall through to \"no providers\"\n }\n\n return { ok: false, details: 'No API keys configured and Ollama is not running' };\n}\n\n/** Apply environment variable overrides to raw config */\nfunction applyEnvOverrides(config: Record<string, unknown>): void {\n const envMap: Record<string, (val: string) => void> = {\n TITAN_MODEL: (val) => setNested(config, 'agent.model', val),\n TITAN_GATEWAY_PORT: (val) => setNested(config, 'gateway.port', parseInt(val, 10)),\n TITAN_GATEWAY_HOST: (val) => setNested(config, 'gateway.host', val),\n TITAN_LOG_LEVEL: (val) => setNested(config, 'logging.level', val),\n ANTHROPIC_API_KEY: (val) => setNested(config, 'providers.anthropic.apiKey', val),\n OPENAI_API_KEY: (val) => setNested(config, 'providers.openai.apiKey', val),\n GOOGLE_API_KEY: (val) => setNested(config, 'providers.google.apiKey', val),\n OLLAMA_BASE_URL: (val) => setNested(config, 'providers.ollama.baseUrl', val),\n DISCORD_TOKEN: (val) => setNested(config, 'channels.discord.token', val),\n TELEGRAM_TOKEN: (val) => setNested(config, 'channels.telegram.token', val),\n SLACK_TOKEN: (val) => setNested(config, 'channels.slack.token', val),\n GOOGLE_OAUTH_CLIENT_ID: (val) => setNested(config, 'oauth.google.clientId', val),\n GOOGLE_OAUTH_CLIENT_SECRET: (val) => setNested(config, 'oauth.google.clientSecret', val),\n OPENROUTER_API_KEY: (val) => setNested(config, 'providers.openrouter.apiKey', val),\n };\n\n // Cloud mode: auto-configure OpenRouter to point at SaaS gateway\n if (process.env.TITAN_CLOUD_MODE === 'true' && process.env.TITAN_CLOUD_API) {\n const cloudApi = process.env.TITAN_CLOUD_API;\n setNested(config, 'providers.openrouter.baseUrl', cloudApi + '/api/v1');\n logger.debug(COMPONENT, `Cloud mode: OpenRouter base URL set to ${cloudApi}/api/v1`);\n }\n\n for (const [envKey, setter] of Object.entries(envMap)) {\n const val = process.env[envKey];\n if (val) {\n setter(val);\n logger.debug(COMPONENT, `Applied env override: ${envKey}`);\n }\n }\n}\n\n/**\n * Recursively diff `raw` against the Zod-parsed `parsed` and return the set\n * of dot-notation paths that exist in raw but were stripped by the schema.\n *\n * Behaviour:\n * - Only walks plain objects (Records). Arrays and primitives are leaf\n * comparisons — if the raw value is an object at a key the schema parsed\n * as something else, we treat the whole subtree as dropped at that path.\n * - Recurses into objects that survive parsing so we catch keys deep inside\n * untyped/permissive subtrees (e.g. `providers.<name>.unknownField`).\n * - Cap recursion depth to defend against pathological self-referential\n * configs; 8 levels covers every legitimate path in TitanConfigSchema.\n */\nfunction findDroppedKeys(\n raw: unknown,\n parsed: unknown,\n pathPrefix: string,\n depth: number = 0,\n): string[] {\n if (depth > 8) return [];\n if (!isPlainObject(raw)) return [];\n const dropped: string[] = [];\n for (const key of Object.keys(raw)) {\n const fullPath = pathPrefix ? `${pathPrefix}.${key}` : key;\n const rawVal = raw[key];\n const parsedVal = isPlainObject(parsed) ? (parsed as Record<string, unknown>)[key] : undefined;\n\n if (parsedVal === undefined) {\n // Skip explicit `undefined` in raw (treats `key: undefined` as not-set\n // rather than a dropped key — JSON config files can't express this\n // anyway, but in-memory configs sometimes do via env merging).\n if (rawVal !== undefined) dropped.push(fullPath);\n continue;\n }\n\n // Recurse into matching subtrees so nested unknown keys are also surfaced.\n if (isPlainObject(rawVal) && isPlainObject(parsedVal)) {\n dropped.push(...findDroppedKeys(rawVal, parsedVal, fullPath, depth + 1));\n }\n }\n return dropped;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/** Set a nested property by dot-notation path */\nfunction setNested(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {\n current[parts[i]] = {};\n }\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../src/config/config.ts"],"sourcesContent":["/**\n * TITAN Configuration Manager\n * Loads, validates, and persists configuration from ~/.titan/titan.json\n */\nimport { existsSync } from 'fs';\nimport { TITAN_CONFIG_PATH, TITAN_HOME } from '../utils/constants.js';\nimport { readJsonFile, writeJsonFile, mkdirIfNotExists, deepMerge } from '../utils/helpers.js';\nimport { TitanConfigSchema, type TitanConfig } from './schema.js';\nimport logger from '../utils/logger.js';\n\nconst COMPONENT = 'Config';\n\nlet cachedConfig: TitanConfig | null = null;\n\n/** Get the default configuration */\nexport function getDefaultConfig(): TitanConfig {\n return TitanConfigSchema.parse({});\n}\n\n/** Load configuration from disk, merging with defaults */\nexport function loadConfig(): TitanConfig {\n if (cachedConfig) return cachedConfig;\n\n mkdirIfNotExists(TITAN_HOME);\n\n let rawConfig: Record<string, unknown> = {};\n\n if (existsSync(TITAN_CONFIG_PATH)) {\n const loaded = readJsonFile<Record<string, unknown>>(TITAN_CONFIG_PATH);\n if (loaded) {\n rawConfig = loaded;\n logger.debug(COMPONENT, `Loaded config from ${TITAN_CONFIG_PATH}`);\n } else {\n logger.warn(COMPONENT, `Failed to parse config at ${TITAN_CONFIG_PATH}, using defaults`);\n }\n } else {\n logger.info(COMPONENT, 'No config file found, using defaults');\n }\n\n // Apply environment variables\n applyEnvOverrides(rawConfig);\n\n // v4.8.4: migrate a top-level `auth` block to `gateway.auth`. The\n // documented path has always been `gateway.auth`, but users (and\n // Claude) naturally try `auth` at the root. Rather than strip it\n // silently and warn, move it to the canonical location and continue.\n if (rawConfig && typeof rawConfig === 'object' && 'auth' in rawConfig) {\n const raw = rawConfig as Record<string, unknown>;\n const topAuth = raw.auth as Record<string, unknown> | undefined;\n if (topAuth && typeof topAuth === 'object') {\n const gateway = (raw.gateway as Record<string, unknown> | undefined) ?? {};\n const gatewayAuth = (gateway.auth as Record<string, unknown> | undefined) ?? {};\n // gateway.auth wins if both are set — explicit nested wins\n // over migrated top-level.\n raw.gateway = { ...gateway, auth: { ...topAuth, ...gatewayAuth } };\n delete raw.auth;\n logger.info(COMPONENT, 'Migrated top-level `auth` → `gateway.auth`. Update titan.json to nest it under `gateway` to silence this notice.');\n }\n }\n\n // Detect unknown keys at every nesting depth BEFORE Zod silently strips\n // them. Hunt Finding #1 (2026-04-14) covered the top-level case\n // (`facebook: {...}` ignored). This expanded check also catches nested\n // typos like `providers.anthropic.unknownField` so users learn about\n // dropped keys immediately rather than chasing \"why doesn't my setting\n // do anything\" days later.\n //\n // The diff is purely informational — invalid keys never block startup\n // (we still feed `rawConfig` through Zod's permissive parse).\n try {\n const parsed = TitanConfigSchema.safeParse(rawConfig);\n if (parsed.success) {\n const droppedPaths = findDroppedKeys(rawConfig, parsed.data as Record<string, unknown>, '');\n for (const path of droppedPaths) {\n logger.warn(\n COMPONENT,\n `Unknown config key: ${path}. Will be ignored. ` +\n `If intentional, extend TitanConfigSchema in src/config/schema.ts.`,\n );\n }\n }\n } catch {\n // Introspection failed — never let a diagnostic warning block load.\n }\n\n // Validate and merge with defaults via Zod.\n // CRITICAL: On validation failure, deep-merge raw config over defaults\n // so that valid user settings (daemon.enabled, autonomy.mode, etc.) survive.\n // Previously this fell back to pure defaults, wiping ALL user config on any error.\n const result = TitanConfigSchema.safeParse(rawConfig);\n if (result.success) {\n cachedConfig = result.data;\n } else {\n const issues = result.error.issues.map(i => `${i.path.join('.')}: ${i.message}`).join(', ');\n logger.warn(COMPONENT, `Config validation issues (${issues}) — merging valid fields over defaults`);\n // Deep-merge raw config over defaults so valid sections survive\n const defaults = getDefaultConfig();\n const merged = deepMerge(defaults as Record<string, unknown>, rawConfig) as TitanConfig;\n // Try parsing the merged result — if it still fails, use defaults but log loudly\n const reparse = TitanConfigSchema.safeParse(merged);\n if (reparse.success) {\n cachedConfig = reparse.data;\n } else {\n logger.error(COMPONENT, `Config still invalid after merge — falling back to defaults. Fix your titan.json.`);\n cachedConfig = defaults;\n }\n }\n\n return cachedConfig;\n}\n\n/** Save current configuration to disk */\nexport function saveConfig(config: TitanConfig): void {\n mkdirIfNotExists(TITAN_HOME);\n writeJsonFile(TITAN_CONFIG_PATH, config);\n cachedConfig = config;\n logger.info(COMPONENT, `Config saved to ${TITAN_CONFIG_PATH}`);\n}\n\n/** Update specific fields in the config */\nexport function updateConfig(partial: Partial<TitanConfig>): TitanConfig {\n const current = loadConfig();\n const updated = deepMerge(current as Record<string, unknown>, partial as Record<string, unknown>) as TitanConfig;\n const validated = TitanConfigSchema.parse(updated);\n saveConfig(validated);\n return validated;\n}\n\n/** Reset config cache (useful for testing) */\nexport function resetConfigCache(): void {\n cachedConfig = null;\n}\n\n/** Check if the configuration file exists */\nexport function configExists(): boolean {\n return existsSync(TITAN_CONFIG_PATH);\n}\n\n/**\n * Check if at least one usable AI provider is configured.\n *\n * \"Usable\" means one of:\n * - Any cloud provider has a non-empty `apiKey` set in config\n * - Any *_API_KEY env var is set (Anthropic, OpenAI, Google, Groq, etc.)\n * - Ollama is reachable at the configured baseUrl (returns at least one model)\n *\n * Used by the gateway boot guard and CLI to refuse to start with empty config\n * instead of letting the user hit \"Internal Server Error\" later.\n *\n * Note: Ollama check is async and is the slowest part of this function (~3s timeout).\n * Callers should `await` and only call once at boot.\n */\nexport async function hasUsableProvider(): Promise<{ ok: boolean; details: string }> {\n const config = loadConfig();\n\n // 1. Check config-file API keys (cloud providers)\n const providers = (config.providers as Record<string, unknown> | undefined) || {};\n const cloudProviderNames = [\n 'anthropic', 'openai', 'google', 'groq', 'mistral', 'openrouter',\n 'fireworks', 'xai', 'together', 'deepseek', 'cerebras', 'cohere',\n 'perplexity', 'venice', 'bedrock', 'litellm', 'azure', 'deepinfra',\n 'sambanova', 'kimi', 'huggingface', 'ai21', 'cohere-v2', 'reka',\n 'zhipu', 'yi', 'inflection', 'novita', 'replicate', 'lepton',\n 'anyscale', 'octo', 'nous', 'minimax', 'nvidia',\n ];\n for (const name of cloudProviderNames) {\n const p = providers[name] as { apiKey?: string } | undefined;\n if (p?.apiKey && p.apiKey.trim().length > 0) {\n return { ok: true, details: `${name} has an API key configured` };\n }\n }\n\n // 2. Check env-var API keys (in case config wasn't reloaded after env var change)\n const envKeys = [\n 'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GOOGLE_API_KEY', 'GROQ_API_KEY',\n 'MISTRAL_API_KEY', 'OPENROUTER_API_KEY', 'FIREWORKS_API_KEY', 'XAI_API_KEY',\n 'TOGETHER_API_KEY', 'DEEPSEEK_API_KEY', 'CEREBRAS_API_KEY', 'COHERE_API_KEY',\n 'PERPLEXITY_API_KEY', 'AZURE_OPENAI_API_KEY',\n ];\n for (const key of envKeys) {\n if (process.env[key] && process.env[key]!.trim().length > 0) {\n return { ok: true, details: `${key} is set in environment` };\n }\n }\n\n // 3. Check Ollama reachability (last resort — slow)\n const ollamaUrl = (providers.ollama as { baseUrl?: string } | undefined)?.baseUrl\n || process.env.OLLAMA_BASE_URL\n || 'http://localhost:11434';\n try {\n const res = await fetch(`${ollamaUrl}/api/tags`, { signal: AbortSignal.timeout(3000) });\n if (res.ok) {\n const json = await res.json() as { models?: { name: string }[] };\n const count = (json.models || []).length;\n if (count > 0) {\n return { ok: true, details: `Ollama at ${ollamaUrl} is reachable (${count} models)` };\n }\n return { ok: false, details: `Ollama at ${ollamaUrl} is reachable but has 0 models — run \"ollama pull qwen3.5:4b\"` };\n }\n } catch {\n // Ollama unreachable, fall through to \"no providers\"\n }\n\n return { ok: false, details: 'No API keys configured and Ollama is not running' };\n}\n\n/** Apply environment variable overrides to raw config */\nfunction applyEnvOverrides(config: Record<string, unknown>): void {\n const envMap: Record<string, (val: string) => void> = {\n TITAN_MODEL: (val) => setNested(config, 'agent.model', val),\n TITAN_GATEWAY_PORT: (val) => setNested(config, 'gateway.port', parseInt(val, 10)),\n TITAN_GATEWAY_HOST: (val) => setNested(config, 'gateway.host', val),\n TITAN_LOG_LEVEL: (val) => setNested(config, 'logging.level', val),\n ANTHROPIC_API_KEY: (val) => setNested(config, 'providers.anthropic.apiKey', val),\n OPENAI_API_KEY: (val) => setNested(config, 'providers.openai.apiKey', val),\n GOOGLE_API_KEY: (val) => setNested(config, 'providers.google.apiKey', val),\n OLLAMA_BASE_URL: (val) => setNested(config, 'providers.ollama.baseUrl', val),\n DISCORD_TOKEN: (val) => setNested(config, 'channels.discord.token', val),\n TELEGRAM_TOKEN: (val) => setNested(config, 'channels.telegram.token', val),\n SLACK_TOKEN: (val) => setNested(config, 'channels.slack.token', val),\n GOOGLE_OAUTH_CLIENT_ID: (val) => setNested(config, 'oauth.google.clientId', val),\n GOOGLE_OAUTH_CLIENT_SECRET: (val) => setNested(config, 'oauth.google.clientSecret', val),\n OPENROUTER_API_KEY: (val) => setNested(config, 'providers.openrouter.apiKey', val),\n };\n\n // Cloud mode: auto-configure OpenRouter to point at SaaS gateway\n if (process.env.TITAN_CLOUD_MODE === 'true' && process.env.TITAN_CLOUD_API) {\n const cloudApi = process.env.TITAN_CLOUD_API;\n setNested(config, 'providers.openrouter.baseUrl', cloudApi + '/api/v1');\n logger.debug(COMPONENT, `Cloud mode: OpenRouter base URL set to ${cloudApi}/api/v1`);\n }\n\n for (const [envKey, setter] of Object.entries(envMap)) {\n const val = process.env[envKey];\n if (val) {\n setter(val);\n logger.debug(COMPONENT, `Applied env override: ${envKey}`);\n }\n }\n}\n\n/**\n * Recursively diff `raw` against the Zod-parsed `parsed` and return the set\n * of dot-notation paths that exist in raw but were stripped by the schema.\n *\n * Behaviour:\n * - Only walks plain objects (Records). Arrays and primitives are leaf\n * comparisons — if the raw value is an object at a key the schema parsed\n * as something else, we treat the whole subtree as dropped at that path.\n * - Recurses into objects that survive parsing so we catch keys deep inside\n * untyped/permissive subtrees (e.g. `providers.<name>.unknownField`).\n * - Cap recursion depth to defend against pathological self-referential\n * configs; 8 levels covers every legitimate path in TitanConfigSchema.\n */\nfunction findDroppedKeys(\n raw: unknown,\n parsed: unknown,\n pathPrefix: string,\n depth: number = 0,\n): string[] {\n if (depth > 8) return [];\n if (!isPlainObject(raw)) return [];\n const dropped: string[] = [];\n for (const key of Object.keys(raw)) {\n const fullPath = pathPrefix ? `${pathPrefix}.${key}` : key;\n const rawVal = raw[key];\n const parsedVal = isPlainObject(parsed) ? (parsed as Record<string, unknown>)[key] : undefined;\n\n if (parsedVal === undefined) {\n // Skip explicit `undefined` in raw (treats `key: undefined` as not-set\n // rather than a dropped key — JSON config files can't express this\n // anyway, but in-memory configs sometimes do via env merging).\n if (rawVal !== undefined) dropped.push(fullPath);\n continue;\n }\n\n // Recurse into matching subtrees so nested unknown keys are also surfaced.\n if (isPlainObject(rawVal) && isPlainObject(parsedVal)) {\n dropped.push(...findDroppedKeys(rawVal, parsedVal, fullPath, depth + 1));\n }\n }\n return dropped;\n}\n\nfunction isPlainObject(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === 'object' && !Array.isArray(value);\n}\n\n/** Set a nested property by dot-notation path */\nfunction setNested(obj: Record<string, unknown>, path: string, value: unknown): void {\n const parts = path.split('.');\n let current: Record<string, unknown> = obj;\n for (let i = 0; i < parts.length - 1; i++) {\n if (!current[parts[i]] || typeof current[parts[i]] !== 'object') {\n current[parts[i]] = {};\n }\n current = current[parts[i]] as Record<string, unknown>;\n }\n current[parts[parts.length - 1]] = value;\n}\n"],"mappings":"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","names":[]}
|
package/dist/config/schema.js
CHANGED
|
@@ -55,7 +55,7 @@ const SecurityConfigSchema = z.object({
|
|
|
55
55
|
allowedTools: z.array(z.string()).default(ALLOWED_TOOLS_DEFAULT),
|
|
56
56
|
deniedTools: z.array(z.string()).default([]),
|
|
57
57
|
maxConcurrentTasks: z.number().default(5),
|
|
58
|
-
commandTimeout: z.number().default(
|
|
58
|
+
commandTimeout: z.number().default(6e4),
|
|
59
59
|
/** Per-tool timeout overrides (ms) â keys are tool names */
|
|
60
60
|
toolTimeouts: z.record(z.string(), z.number()).default({
|
|
61
61
|
browser_auto_nav: 6e4,
|
|
@@ -169,6 +169,13 @@ const AgentConfigSchema = z.object({
|
|
|
169
169
|
/** Force tool_choice=required in autonomous mode */
|
|
170
170
|
forceToolUse: z.boolean().default(true),
|
|
171
171
|
thinkingMode: z.enum(["off", "low", "medium", "high"]).default("medium"),
|
|
172
|
+
/**
|
|
173
|
+
* Wall-clock timeout (ms) for each individual LLM call within the agent loop.
|
|
174
|
+
* Default 300 s (5 min) is appropriate for complex agentic tasks but too long
|
|
175
|
+
* for simple webchat queries. Set to e.g. 90_000 (90 s) for faster failure on
|
|
176
|
+
* slow/unreachable providers. Per-request overrides via LoopContext.chatTimeoutMs.
|
|
177
|
+
*/
|
|
178
|
+
chatTimeoutMs: z.number().int().positive().default(3e5),
|
|
172
179
|
/** Model aliases â e.g. { fast: "openai/gpt-4o-mini", smart: "anthropic/claude-sonnet-4-20250514", local: "ollama/qwen3.5:4b" } */
|
|
173
180
|
// Hunt Finding #42 (2026-04-15): README promises built-in aliases
|
|
174
181
|
// `fast, smart, cheap, reasoning, local`. Zod's .default() replaces the
|