@gokulvenkatareddy/cortex 0.1.7
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 +1295 -0
- package/apps/octogent/.github/workflows/ci.yml +40 -0
- package/apps/octogent/.shims/claude +4 -0
- package/apps/octogent/AGENTS.md +71 -0
- package/apps/octogent/CONTRIBUTING.md +72 -0
- package/apps/octogent/LICENSE +21 -0
- package/apps/octogent/README.md +184 -0
- package/apps/octogent/apps/api/AGENTS.md +32 -0
- package/apps/octogent/apps/api/package.json +19 -0
- package/apps/octogent/apps/api/src/agentStateDetection.ts +181 -0
- package/apps/octogent/apps/api/src/claudeSessionScanner.ts +235 -0
- package/apps/octogent/apps/api/src/claudeSkills.ts +182 -0
- package/apps/octogent/apps/api/src/claudeUsage.ts +922 -0
- package/apps/octogent/apps/api/src/cli.ts +595 -0
- package/apps/octogent/apps/api/src/codeIntelStore.ts +46 -0
- package/apps/octogent/apps/api/src/codexUsage.ts +278 -0
- package/apps/octogent/apps/api/src/createApiServer/codeIntelRoutes.ts +60 -0
- package/apps/octogent/apps/api/src/createApiServer/conversationRoutes.ts +128 -0
- package/apps/octogent/apps/api/src/createApiServer/deckRoutes.ts +873 -0
- package/apps/octogent/apps/api/src/createApiServer/gitParsers.ts +140 -0
- package/apps/octogent/apps/api/src/createApiServer/gitRoutes.ts +214 -0
- package/apps/octogent/apps/api/src/createApiServer/miscRoutes.ts +316 -0
- package/apps/octogent/apps/api/src/createApiServer/monitorParsers.ts +137 -0
- package/apps/octogent/apps/api/src/createApiServer/monitorRoutes.ts +95 -0
- package/apps/octogent/apps/api/src/createApiServer/requestHandler.ts +311 -0
- package/apps/octogent/apps/api/src/createApiServer/requestParsers.ts +25 -0
- package/apps/octogent/apps/api/src/createApiServer/routeHelpers.ts +97 -0
- package/apps/octogent/apps/api/src/createApiServer/security.ts +70 -0
- package/apps/octogent/apps/api/src/createApiServer/terminalParsers.ts +167 -0
- package/apps/octogent/apps/api/src/createApiServer/terminalRoutes.ts +315 -0
- package/apps/octogent/apps/api/src/createApiServer/types.ts +24 -0
- package/apps/octogent/apps/api/src/createApiServer/uiStateParsers.ts +255 -0
- package/apps/octogent/apps/api/src/createApiServer/upgradeHandler.ts +38 -0
- package/apps/octogent/apps/api/src/createApiServer/usageRoutes.ts +84 -0
- package/apps/octogent/apps/api/src/createApiServer.ts +176 -0
- package/apps/octogent/apps/api/src/deck/readDeckTentacles.ts +595 -0
- package/apps/octogent/apps/api/src/githubRepoSummary.ts +397 -0
- package/apps/octogent/apps/api/src/logging.ts +9 -0
- package/apps/octogent/apps/api/src/monitor/defaults.ts +3 -0
- package/apps/octogent/apps/api/src/monitor/index.ts +8 -0
- package/apps/octogent/apps/api/src/monitor/repository.ts +303 -0
- package/apps/octogent/apps/api/src/monitor/service.ts +349 -0
- package/apps/octogent/apps/api/src/monitor/types.ts +120 -0
- package/apps/octogent/apps/api/src/monitor/xProvider.ts +587 -0
- package/apps/octogent/apps/api/src/projectPersistence.ts +377 -0
- package/apps/octogent/apps/api/src/prompts/index.ts +10 -0
- package/apps/octogent/apps/api/src/prompts/promptResolver.ts +145 -0
- package/apps/octogent/apps/api/src/runtimeMetadata.ts +69 -0
- package/apps/octogent/apps/api/src/server.ts +80 -0
- package/apps/octogent/apps/api/src/setupState.ts +80 -0
- package/apps/octogent/apps/api/src/setupStatus.ts +174 -0
- package/apps/octogent/apps/api/src/startupPrerequisites.ts +146 -0
- package/apps/octogent/apps/api/src/terminalRuntime/channelMessaging.ts +87 -0
- package/apps/octogent/apps/api/src/terminalRuntime/claudeTranscript.ts +279 -0
- package/apps/octogent/apps/api/src/terminalRuntime/constants.ts +15 -0
- package/apps/octogent/apps/api/src/terminalRuntime/conversations.ts +492 -0
- package/apps/octogent/apps/api/src/terminalRuntime/gitOperations.ts +341 -0
- package/apps/octogent/apps/api/src/terminalRuntime/hookProcessor.ts +405 -0
- package/apps/octogent/apps/api/src/terminalRuntime/protocol.ts +46 -0
- package/apps/octogent/apps/api/src/terminalRuntime/ptyEnvironment.ts +50 -0
- package/apps/octogent/apps/api/src/terminalRuntime/registry.ts +423 -0
- package/apps/octogent/apps/api/src/terminalRuntime/sessionRuntime.ts +671 -0
- package/apps/octogent/apps/api/src/terminalRuntime/systemClients.ts +432 -0
- package/apps/octogent/apps/api/src/terminalRuntime/types.ts +157 -0
- package/apps/octogent/apps/api/src/terminalRuntime/worktreeManager.ts +135 -0
- package/apps/octogent/apps/api/src/terminalRuntime.ts +567 -0
- package/apps/octogent/apps/api/src/usageUtils.ts +16 -0
- package/apps/octogent/apps/api/src/ws-shim.d.ts +28 -0
- package/apps/octogent/apps/api/tests/agentStateDetection.test.ts +67 -0
- package/apps/octogent/apps/api/tests/claudeUsage.test.ts +583 -0
- package/apps/octogent/apps/api/tests/codexUsage.test.ts +107 -0
- package/apps/octogent/apps/api/tests/createApiServer.test.ts +3207 -0
- package/apps/octogent/apps/api/tests/githubRepoSummary.test.ts +100 -0
- package/apps/octogent/apps/api/tests/logging.test.ts +33 -0
- package/apps/octogent/apps/api/tests/monitorApi.test.ts +467 -0
- package/apps/octogent/apps/api/tests/monitorCore.test.ts +104 -0
- package/apps/octogent/apps/api/tests/promptResolver.test.ts +109 -0
- package/apps/octogent/apps/api/tests/protocol.test.ts +14 -0
- package/apps/octogent/apps/api/tests/sessionRuntime.test.ts +608 -0
- package/apps/octogent/apps/api/tests/startupPrerequisites.test.ts +70 -0
- package/apps/octogent/apps/api/tests/upgradeHandler.test.ts +40 -0
- package/apps/octogent/apps/api/tests/xMonitorProvider.test.ts +109 -0
- package/apps/octogent/apps/api/tsconfig.json +7 -0
- package/apps/octogent/apps/api/vitest.config.ts +7 -0
- package/apps/octogent/apps/web/AGENTS.md +38 -0
- package/apps/octogent/apps/web/index.html +13 -0
- package/apps/octogent/apps/web/package.json +32 -0
- package/apps/octogent/apps/web/public/octopus-favicon.svg +26 -0
- package/apps/octogent/apps/web/src/App.tsx +646 -0
- package/apps/octogent/apps/web/src/app/canvas/types.ts +34 -0
- package/apps/octogent/apps/web/src/app/codeIntelAggregation.ts +278 -0
- package/apps/octogent/apps/web/src/app/constants.ts +28 -0
- package/apps/octogent/apps/web/src/app/conversationNormalizers.ts +135 -0
- package/apps/octogent/apps/web/src/app/formatTimestamp.ts +18 -0
- package/apps/octogent/apps/web/src/app/githubMetrics.ts +76 -0
- package/apps/octogent/apps/web/src/app/githubNormalizers.ts +91 -0
- package/apps/octogent/apps/web/src/app/hooks/useAgentRuntimeStates.ts +18 -0
- package/apps/octogent/apps/web/src/app/hooks/useBackendLivenessPolling.ts +53 -0
- package/apps/octogent/apps/web/src/app/hooks/useCanvasGraphData.ts +449 -0
- package/apps/octogent/apps/web/src/app/hooks/useCanvasTransform.ts +260 -0
- package/apps/octogent/apps/web/src/app/hooks/useClaudeUsagePolling.ts +40 -0
- package/apps/octogent/apps/web/src/app/hooks/useClickOutside.ts +30 -0
- package/apps/octogent/apps/web/src/app/hooks/useCodeIntelRuntime.ts +83 -0
- package/apps/octogent/apps/web/src/app/hooks/useCodexUsagePolling.ts +35 -0
- package/apps/octogent/apps/web/src/app/hooks/useConsoleKeyboardShortcuts.ts +31 -0
- package/apps/octogent/apps/web/src/app/hooks/useConversationsRuntime.ts +377 -0
- package/apps/octogent/apps/web/src/app/hooks/useForceSimulation.ts +319 -0
- package/apps/octogent/apps/web/src/app/hooks/useGitHubPrimaryViewModel.ts +143 -0
- package/apps/octogent/apps/web/src/app/hooks/useGithubSummaryPolling.ts +28 -0
- package/apps/octogent/apps/web/src/app/hooks/useInitialColumnsHydration.ts +64 -0
- package/apps/octogent/apps/web/src/app/hooks/useMonitorRuntime.ts +220 -0
- package/apps/octogent/apps/web/src/app/hooks/usePersistedUiState.ts +536 -0
- package/apps/octogent/apps/web/src/app/hooks/usePollingData.ts +79 -0
- package/apps/octogent/apps/web/src/app/hooks/usePromptLibrary.ts +185 -0
- package/apps/octogent/apps/web/src/app/hooks/useTentacleGitLifecycle.ts +530 -0
- package/apps/octogent/apps/web/src/app/hooks/useTerminalCompletionNotification.ts +94 -0
- package/apps/octogent/apps/web/src/app/hooks/useTerminalMutations.ts +266 -0
- package/apps/octogent/apps/web/src/app/hooks/useTerminalStateReconciliation.ts +23 -0
- package/apps/octogent/apps/web/src/app/hooks/useUsageHeatmapPolling.ts +43 -0
- package/apps/octogent/apps/web/src/app/hooks/useWorkspaceSetup.ts +80 -0
- package/apps/octogent/apps/web/src/app/hotkeys.ts +31 -0
- package/apps/octogent/apps/web/src/app/monitorNormalizers.ts +145 -0
- package/apps/octogent/apps/web/src/app/notificationSounds.ts +164 -0
- package/apps/octogent/apps/web/src/app/terminalRuntimeStateStore.ts +261 -0
- package/apps/octogent/apps/web/src/app/terminalState.ts +21 -0
- package/apps/octogent/apps/web/src/app/types.ts +42 -0
- package/apps/octogent/apps/web/src/app/uiStateNormalizers.ts +113 -0
- package/apps/octogent/apps/web/src/app/usageNormalizers.ts +58 -0
- package/apps/octogent/apps/web/src/components/ActiveAgentsSidebar.tsx +60 -0
- package/apps/octogent/apps/web/src/components/ActivityPrimaryView.tsx +21 -0
- package/apps/octogent/apps/web/src/components/AgentStateBadge.tsx +47 -0
- package/apps/octogent/apps/web/src/components/CanvasPrimaryView.tsx +1532 -0
- package/apps/octogent/apps/web/src/components/ClearAllConversationsDialog.tsx +33 -0
- package/apps/octogent/apps/web/src/components/CodeIntelArcDiagram.tsx +245 -0
- package/apps/octogent/apps/web/src/components/CodeIntelPrimaryView.tsx +104 -0
- package/apps/octogent/apps/web/src/components/CodeIntelTreemap.tsx +138 -0
- package/apps/octogent/apps/web/src/components/ConsolePrimaryNav.tsx +31 -0
- package/apps/octogent/apps/web/src/components/ConversationsPrimaryView.tsx +243 -0
- package/apps/octogent/apps/web/src/components/DeckPrimaryView.tsx +613 -0
- package/apps/octogent/apps/web/src/components/DeleteTentacleDialog.tsx +91 -0
- package/apps/octogent/apps/web/src/components/EmptyOctopus.tsx +715 -0
- package/apps/octogent/apps/web/src/components/GitHubPrimaryView.tsx +494 -0
- package/apps/octogent/apps/web/src/components/MonitorPrimaryView.tsx +475 -0
- package/apps/octogent/apps/web/src/components/PrimaryViewRouter.tsx +99 -0
- package/apps/octogent/apps/web/src/components/PromptsPrimaryView.tsx +243 -0
- package/apps/octogent/apps/web/src/components/RuntimeStatusStrip.tsx +273 -0
- package/apps/octogent/apps/web/src/components/SettingsPrimaryView.tsx +92 -0
- package/apps/octogent/apps/web/src/components/SidebarActionPanel.tsx +124 -0
- package/apps/octogent/apps/web/src/components/SidebarConversationsList.tsx +279 -0
- package/apps/octogent/apps/web/src/components/SidebarPromptsList.tsx +116 -0
- package/apps/octogent/apps/web/src/components/TelemetryTape.tsx +106 -0
- package/apps/octogent/apps/web/src/components/TentacleGitActionsDialog.tsx +341 -0
- package/apps/octogent/apps/web/src/components/Terminal.tsx +524 -0
- package/apps/octogent/apps/web/src/components/TerminalPromptPicker.tsx +140 -0
- package/apps/octogent/apps/web/src/components/UsageHeatmap.tsx +702 -0
- package/apps/octogent/apps/web/src/components/canvas/CanvasTentaclePanel.tsx +485 -0
- package/apps/octogent/apps/web/src/components/canvas/CanvasTerminalColumn.tsx +89 -0
- package/apps/octogent/apps/web/src/components/canvas/DeleteAllTerminalsDialog.tsx +221 -0
- package/apps/octogent/apps/web/src/components/canvas/OctopusNode.tsx +307 -0
- package/apps/octogent/apps/web/src/components/canvas/SessionNode.tsx +185 -0
- package/apps/octogent/apps/web/src/components/deck/ActionCards.tsx +118 -0
- package/apps/octogent/apps/web/src/components/deck/AddTentacleForm.tsx +269 -0
- package/apps/octogent/apps/web/src/components/deck/DeckBottomActions.tsx +56 -0
- package/apps/octogent/apps/web/src/components/deck/TentaclePod.tsx +334 -0
- package/apps/octogent/apps/web/src/components/deck/WorkspaceSetupCard.tsx +105 -0
- package/apps/octogent/apps/web/src/components/deck/octopusVisuals.ts +72 -0
- package/apps/octogent/apps/web/src/components/terminalReplay.ts +62 -0
- package/apps/octogent/apps/web/src/components/terminalWheel.ts +54 -0
- package/apps/octogent/apps/web/src/components/ui/ActionButton.tsx +34 -0
- package/apps/octogent/apps/web/src/components/ui/ConfirmationDialog.tsx +86 -0
- package/apps/octogent/apps/web/src/components/ui/MarkdownContent.tsx +43 -0
- package/apps/octogent/apps/web/src/components/ui/SettingsToggle.tsx +34 -0
- package/apps/octogent/apps/web/src/components/ui/StatusBadge.tsx +24 -0
- package/apps/octogent/apps/web/src/main.tsx +17 -0
- package/apps/octogent/apps/web/src/runtime/HttpTerminalSnapshotReader.ts +87 -0
- package/apps/octogent/apps/web/src/runtime/runtimeEndpoints.ts +412 -0
- package/apps/octogent/apps/web/src/styles/chrome-and-buttons.css +272 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-activity.css +358 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-canvas.css +1843 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-code-intel.css +227 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-conversations.css +705 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-deck.css +1524 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-github.css +541 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-monitor.css +595 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-pixpack.css +81 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-prompts.css +474 -0
- package/apps/octogent/apps/web/src/styles/console-canvas-settings.css +207 -0
- package/apps/octogent/apps/web/src/styles/console-chrome-status-nav.css +441 -0
- package/apps/octogent/apps/web/src/styles/console-overrides-telemetry.css +320 -0
- package/apps/octogent/apps/web/src/styles/console-theme-tokens.css +25 -0
- package/apps/octogent/apps/web/src/styles/cortex-theme.css +412 -0
- package/apps/octogent/apps/web/src/styles/foundation.css +100 -0
- package/apps/octogent/apps/web/src/styles/sidebar-and-scrollbars.css +447 -0
- package/apps/octogent/apps/web/src/styles/terminal-and-status.css +356 -0
- package/apps/octogent/apps/web/src/styles.css +25 -0
- package/apps/octogent/apps/web/src/types/ws.d.ts +23 -0
- package/apps/octogent/apps/web/tests/CanvasPrimaryView.test.tsx +347 -0
- package/apps/octogent/apps/web/tests/HttpTerminalSnapshotReader.test.tsx +54 -0
- package/apps/octogent/apps/web/tests/RuntimeStatusStrip.test.tsx +70 -0
- package/apps/octogent/apps/web/tests/Terminal.test.tsx +87 -0
- package/apps/octogent/apps/web/tests/add-tentacle-form.test.tsx +48 -0
- package/apps/octogent/apps/web/tests/app-github-runtime.test.tsx +162 -0
- package/apps/octogent/apps/web/tests/app-monitor-runtime.test.tsx +657 -0
- package/apps/octogent/apps/web/tests/app-shell-navigation.test.tsx +109 -0
- package/apps/octogent/apps/web/tests/app-swarm-refresh.test.tsx +268 -0
- package/apps/octogent/apps/web/tests/app-ui-state-persistence.test.tsx +116 -0
- package/apps/octogent/apps/web/tests/app-workspace-setup.test.tsx +217 -0
- package/apps/octogent/apps/web/tests/canvas-tentacle-panel.test.tsx +195 -0
- package/apps/octogent/apps/web/tests/delete-all-terminals-dialog.test.tsx +76 -0
- package/apps/octogent/apps/web/tests/githubMetrics.test.tsx +52 -0
- package/apps/octogent/apps/web/tests/hotkeys.test.tsx +44 -0
- package/apps/octogent/apps/web/tests/runtimeEndpoints.test.tsx +240 -0
- package/apps/octogent/apps/web/tests/setup.ts +39 -0
- package/apps/octogent/apps/web/tests/tentacle-pod.test.tsx +62 -0
- package/apps/octogent/apps/web/tests/terminalReplay.test.ts +71 -0
- package/apps/octogent/apps/web/tests/terminalState.test.tsx +49 -0
- package/apps/octogent/apps/web/tests/terminalWheel.test.tsx +51 -0
- package/apps/octogent/apps/web/tests/test-utils/appTestHarness.ts +48 -0
- package/apps/octogent/apps/web/tests/uiPrimitives.test.tsx +31 -0
- package/apps/octogent/apps/web/tests/useAgentRuntimeStates.test.tsx +47 -0
- package/apps/octogent/apps/web/tsconfig.json +8 -0
- package/apps/octogent/apps/web/vite.api.bundle.config.mts +32 -0
- package/apps/octogent/apps/web/vite.config.ts +22 -0
- package/apps/octogent/bin/octogent +3 -0
- package/apps/octogent/biome.json +21 -0
- package/apps/octogent/docs/concepts/mental-model.md +79 -0
- package/apps/octogent/docs/concepts/runtime-and-api.md +60 -0
- package/apps/octogent/docs/concepts/tentacles.md +85 -0
- package/apps/octogent/docs/getting-started/installation.md +54 -0
- package/apps/octogent/docs/getting-started/quickstart.md +79 -0
- package/apps/octogent/docs/guides/inter-agent-messaging.md +43 -0
- package/apps/octogent/docs/guides/orchestrating-child-agents.md +49 -0
- package/apps/octogent/docs/guides/working-with-todos.md +56 -0
- package/apps/octogent/docs/index.md +40 -0
- package/apps/octogent/docs/reference/api.md +103 -0
- package/apps/octogent/docs/reference/cli.md +71 -0
- package/apps/octogent/docs/reference/experimental-features.md +28 -0
- package/apps/octogent/docs/reference/filesystem-layout.md +62 -0
- package/apps/octogent/docs/reference/troubleshooting.md +49 -0
- package/apps/octogent/package.json +35 -0
- package/apps/octogent/packages/core/AGENTS.md +31 -0
- package/apps/octogent/packages/core/package.json +12 -0
- package/apps/octogent/packages/core/src/adapters/InMemoryTerminalSnapshotReader.ts +10 -0
- package/apps/octogent/packages/core/src/application/buildTerminalList.ts +13 -0
- package/apps/octogent/packages/core/src/domain/agentRuntime.ts +18 -0
- package/apps/octogent/packages/core/src/domain/channel.ts +8 -0
- package/apps/octogent/packages/core/src/domain/completionSound.ts +14 -0
- package/apps/octogent/packages/core/src/domain/conversation.ts +48 -0
- package/apps/octogent/packages/core/src/domain/deck.ts +33 -0
- package/apps/octogent/packages/core/src/domain/git.ts +32 -0
- package/apps/octogent/packages/core/src/domain/monitor.ts +62 -0
- package/apps/octogent/packages/core/src/domain/setup.ts +27 -0
- package/apps/octogent/packages/core/src/domain/terminal.ts +17 -0
- package/apps/octogent/packages/core/src/domain/uiState.ts +22 -0
- package/apps/octogent/packages/core/src/domain/usage.ts +60 -0
- package/apps/octogent/packages/core/src/index.ts +15 -0
- package/apps/octogent/packages/core/src/ports/TerminalSnapshotReader.ts +5 -0
- package/apps/octogent/packages/core/src/util/typeCoercion.ts +20 -0
- package/apps/octogent/packages/core/tests/buildTerminalList.test.ts +75 -0
- package/apps/octogent/packages/core/tsconfig.json +7 -0
- package/apps/octogent/packages/core/tsconfig.tsbuildinfo +1 -0
- package/apps/octogent/packages/core/vitest.config.ts +7 -0
- package/apps/octogent/pnpm-lock.yaml +3212 -0
- package/apps/octogent/pnpm-workspace.yaml +3 -0
- package/apps/octogent/prompts/meta-prompt-generator.md +223 -0
- package/apps/octogent/prompts/octoboss-clean-contexts.md +30 -0
- package/apps/octogent/prompts/octoboss-reorganize-tentacles.md +29 -0
- package/apps/octogent/prompts/octoboss-reorganize-todos.md +27 -0
- package/apps/octogent/prompts/sandbox-init.md +3 -0
- package/apps/octogent/prompts/swarm-parent.md +83 -0
- package/apps/octogent/prompts/swarm-worker.md +50 -0
- package/apps/octogent/prompts/tentacle-context-init.md +1 -0
- package/apps/octogent/prompts/tentacle-planner.md +110 -0
- package/apps/octogent/prompts/tentacle-reorganize-todos.md +20 -0
- package/apps/octogent/prompts/tentacle-update-tentacle.md +18 -0
- package/apps/octogent/scripts/build-package.mjs +23 -0
- package/apps/octogent/scripts/dev.mjs +158 -0
- package/apps/octogent/scripts/smoke-public-install.mjs +271 -0
- package/apps/octogent/static/images/octogent-header.png +0 -0
- package/apps/octogent/static/images/preview_1.jpg +0 -0
- package/apps/octogent/static/images/preview_2.jpg +0 -0
- package/apps/octogent/static/images/preview_3.jpg +0 -0
- package/apps/octogent/static/images/preview_4.jpg +0 -0
- package/apps/octogent/static/images/preview_5.jpg +0 -0
- package/apps/octogent/static/images/preview_6.jpg +0 -0
- package/apps/octogent/tsconfig.base.json +16 -0
- package/bin/AGI +3 -0
- package/bin/AGI-install-app +71 -0
- package/bin/AGI-ui +16 -0
- package/bin/AGI-voice +15 -0
- package/bin/AGI-web +16 -0
- package/bin/cortex +109 -0
- package/bin/cortex-octogent +99 -0
- package/bin/import-specifier.mjs +13 -0
- package/bin/import-specifier.test.mjs +13 -0
- package/bin/octo +150 -0
- package/dist/cli.mjs +555650 -0
- package/package.json +157 -0
- package/scripts/setup-wizard.ts +390 -0
|
@@ -0,0 +1,377 @@
|
|
|
1
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
+
import {
|
|
3
|
+
appendFileSync,
|
|
4
|
+
copyFileSync,
|
|
5
|
+
cpSync,
|
|
6
|
+
existsSync,
|
|
7
|
+
mkdirSync,
|
|
8
|
+
readFileSync,
|
|
9
|
+
writeFileSync,
|
|
10
|
+
} from "node:fs";
|
|
11
|
+
import { homedir } from "node:os";
|
|
12
|
+
import { basename, join } from "node:path";
|
|
13
|
+
|
|
14
|
+
export const GLOBAL_OCTOGENT_DIR = join(homedir(), ".octogent");
|
|
15
|
+
export const PROJECTS_FILE = join(GLOBAL_OCTOGENT_DIR, "projects.json");
|
|
16
|
+
export const PROJECT_CONFIG_RELATIVE_PATH = join(".octogent", "project.json");
|
|
17
|
+
|
|
18
|
+
type ProjectConfigDocument = {
|
|
19
|
+
version: 1;
|
|
20
|
+
projectId: string;
|
|
21
|
+
displayName: string;
|
|
22
|
+
createdAt: string;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export type ProjectRegistryEntry = {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
path: string;
|
|
29
|
+
createdAt: string;
|
|
30
|
+
lastOpenedAt?: string;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type LegacyProjectRegistryEntry = {
|
|
34
|
+
name?: unknown;
|
|
35
|
+
path?: unknown;
|
|
36
|
+
createdAt?: unknown;
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
export type ProjectsRegistry = { projects: ProjectRegistryEntry[] };
|
|
40
|
+
|
|
41
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
42
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
43
|
+
|
|
44
|
+
const toProjectRegistryEntry = (
|
|
45
|
+
value: unknown,
|
|
46
|
+
workspaceCwd?: string,
|
|
47
|
+
): ProjectRegistryEntry | null => {
|
|
48
|
+
if (!isRecord(value)) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (
|
|
53
|
+
typeof value.id === "string" &&
|
|
54
|
+
value.id.trim().length > 0 &&
|
|
55
|
+
typeof value.name === "string" &&
|
|
56
|
+
value.name.trim().length > 0 &&
|
|
57
|
+
typeof value.path === "string" &&
|
|
58
|
+
value.path.trim().length > 0 &&
|
|
59
|
+
typeof value.createdAt === "string" &&
|
|
60
|
+
value.createdAt.trim().length > 0
|
|
61
|
+
) {
|
|
62
|
+
return {
|
|
63
|
+
id: value.id,
|
|
64
|
+
name: value.name,
|
|
65
|
+
path: value.path,
|
|
66
|
+
createdAt: value.createdAt,
|
|
67
|
+
...(typeof value.lastOpenedAt === "string" && value.lastOpenedAt.trim().length > 0
|
|
68
|
+
? { lastOpenedAt: value.lastOpenedAt }
|
|
69
|
+
: {}),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (
|
|
74
|
+
workspaceCwd &&
|
|
75
|
+
typeof value.name === "string" &&
|
|
76
|
+
value.name.trim().length > 0 &&
|
|
77
|
+
typeof value.path === "string" &&
|
|
78
|
+
value.path === workspaceCwd
|
|
79
|
+
) {
|
|
80
|
+
return {
|
|
81
|
+
id: `legacy-${randomUUID()}`,
|
|
82
|
+
name: value.name,
|
|
83
|
+
path: value.path,
|
|
84
|
+
createdAt:
|
|
85
|
+
typeof value.createdAt === "string" && value.createdAt.trim().length > 0
|
|
86
|
+
? value.createdAt
|
|
87
|
+
: new Date().toISOString(),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return null;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
const toProjectConfigDocument = (value: unknown): ProjectConfigDocument | null => {
|
|
95
|
+
if (
|
|
96
|
+
!isRecord(value) ||
|
|
97
|
+
value.version !== 1 ||
|
|
98
|
+
typeof value.projectId !== "string" ||
|
|
99
|
+
value.projectId.trim().length === 0 ||
|
|
100
|
+
typeof value.displayName !== "string" ||
|
|
101
|
+
value.displayName.trim().length === 0 ||
|
|
102
|
+
typeof value.createdAt !== "string" ||
|
|
103
|
+
value.createdAt.trim().length === 0
|
|
104
|
+
) {
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
version: 1,
|
|
110
|
+
projectId: value.projectId,
|
|
111
|
+
displayName: value.displayName,
|
|
112
|
+
createdAt: value.createdAt,
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const readJsonFile = (filePath: string): unknown | null => {
|
|
117
|
+
try {
|
|
118
|
+
return JSON.parse(readFileSync(filePath, "utf8")) as unknown;
|
|
119
|
+
} catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
export const ensureGlobalOctogentDir = () => {
|
|
125
|
+
if (!existsSync(GLOBAL_OCTOGENT_DIR)) {
|
|
126
|
+
mkdirSync(GLOBAL_OCTOGENT_DIR, { recursive: true });
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
export const loadProjectsRegistry = (): ProjectsRegistry => {
|
|
131
|
+
ensureGlobalOctogentDir();
|
|
132
|
+
|
|
133
|
+
if (!existsSync(PROJECTS_FILE)) {
|
|
134
|
+
return { projects: [] };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const parsed = readJsonFile(PROJECTS_FILE);
|
|
138
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.projects)) {
|
|
139
|
+
return { projects: [] };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return {
|
|
143
|
+
projects: parsed.projects
|
|
144
|
+
.map((entry) => toProjectRegistryEntry(entry))
|
|
145
|
+
.filter((entry): entry is ProjectRegistryEntry => entry !== null),
|
|
146
|
+
};
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
export const saveProjectsRegistry = (registry: ProjectsRegistry) => {
|
|
150
|
+
ensureGlobalOctogentDir();
|
|
151
|
+
writeFileSync(PROJECTS_FILE, `${JSON.stringify(registry, null, 2)}\n`, "utf8");
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export const resolveProjectConfigPath = (workspaceCwd: string) =>
|
|
155
|
+
join(workspaceCwd, PROJECT_CONFIG_RELATIVE_PATH);
|
|
156
|
+
|
|
157
|
+
export const deriveProjectIdFromWorkspace = (workspaceCwd: string) =>
|
|
158
|
+
`workspace-${createHash("sha1").update(workspaceCwd).digest("hex").slice(0, 16)}`;
|
|
159
|
+
|
|
160
|
+
const inferLegacyProjectName = (workspaceCwd: string): string | null => {
|
|
161
|
+
const parsed = readJsonFile(PROJECTS_FILE);
|
|
162
|
+
if (!isRecord(parsed) || !Array.isArray(parsed.projects)) {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
for (const legacyEntry of parsed.projects as LegacyProjectRegistryEntry[]) {
|
|
167
|
+
if (legacyEntry.path === workspaceCwd && typeof legacyEntry.name === "string") {
|
|
168
|
+
return legacyEntry.name;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return null;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
export const loadProjectConfig = (workspaceCwd: string): ProjectConfigDocument | null => {
|
|
176
|
+
const configPath = resolveProjectConfigPath(workspaceCwd);
|
|
177
|
+
if (!existsSync(configPath)) {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return toProjectConfigDocument(readJsonFile(configPath));
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
export const ensureProjectConfig = (
|
|
185
|
+
workspaceCwd: string,
|
|
186
|
+
preferredName?: string,
|
|
187
|
+
preferredProjectId?: string,
|
|
188
|
+
): ProjectConfigDocument => {
|
|
189
|
+
const existing = loadProjectConfig(workspaceCwd);
|
|
190
|
+
if (existing) {
|
|
191
|
+
return existing;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const displayName =
|
|
195
|
+
preferredName?.trim() ||
|
|
196
|
+
inferLegacyProjectName(workspaceCwd) ||
|
|
197
|
+
basename(workspaceCwd) ||
|
|
198
|
+
"octogent-project";
|
|
199
|
+
const config: ProjectConfigDocument = {
|
|
200
|
+
version: 1,
|
|
201
|
+
projectId: preferredProjectId?.trim() || randomUUID(),
|
|
202
|
+
displayName,
|
|
203
|
+
createdAt: new Date().toISOString(),
|
|
204
|
+
};
|
|
205
|
+
|
|
206
|
+
const configPath = resolveProjectConfigPath(workspaceCwd);
|
|
207
|
+
mkdirSync(join(workspaceCwd, ".octogent"), { recursive: true });
|
|
208
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
209
|
+
return config;
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export const registerProject = (
|
|
213
|
+
workspaceCwd: string,
|
|
214
|
+
preferredName?: string,
|
|
215
|
+
): ProjectRegistryEntry => {
|
|
216
|
+
const projectConfig = ensureProjectConfig(workspaceCwd, preferredName);
|
|
217
|
+
const registry = loadProjectsRegistry();
|
|
218
|
+
const lastOpenedAt = new Date().toISOString();
|
|
219
|
+
const existing = registry.projects.find((entry) => entry.id === projectConfig.projectId);
|
|
220
|
+
|
|
221
|
+
if (existing) {
|
|
222
|
+
existing.name = projectConfig.displayName;
|
|
223
|
+
existing.path = workspaceCwd;
|
|
224
|
+
existing.lastOpenedAt = lastOpenedAt;
|
|
225
|
+
saveProjectsRegistry(registry);
|
|
226
|
+
return existing;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const nextEntry: ProjectRegistryEntry = {
|
|
230
|
+
id: projectConfig.projectId,
|
|
231
|
+
name: projectConfig.displayName,
|
|
232
|
+
path: workspaceCwd,
|
|
233
|
+
createdAt: projectConfig.createdAt,
|
|
234
|
+
lastOpenedAt,
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const filteredProjects = registry.projects.filter(
|
|
238
|
+
(entry) => entry.path !== workspaceCwd && entry.id !== projectConfig.projectId,
|
|
239
|
+
);
|
|
240
|
+
filteredProjects.push(nextEntry);
|
|
241
|
+
saveProjectsRegistry({ projects: filteredProjects });
|
|
242
|
+
return nextEntry;
|
|
243
|
+
};
|
|
244
|
+
|
|
245
|
+
export const resolveGlobalProjectDir = (projectId: string) =>
|
|
246
|
+
join(GLOBAL_OCTOGENT_DIR, "projects", projectId);
|
|
247
|
+
|
|
248
|
+
export const resolveEphemeralProjectStateDir = (workspaceCwd: string) =>
|
|
249
|
+
resolveGlobalProjectDir(deriveProjectIdFromWorkspace(workspaceCwd));
|
|
250
|
+
|
|
251
|
+
export const resolveProjectStateDir = (workspaceCwd: string, preferredName?: string): string => {
|
|
252
|
+
const entry = registerProject(workspaceCwd, preferredName);
|
|
253
|
+
const projectDir = resolveGlobalProjectDir(entry.id);
|
|
254
|
+
mkdirSync(join(projectDir, "state"), { recursive: true });
|
|
255
|
+
return projectDir;
|
|
256
|
+
};
|
|
257
|
+
|
|
258
|
+
export const ensureProjectScaffold = (
|
|
259
|
+
workspaceCwd: string,
|
|
260
|
+
preferredName?: string,
|
|
261
|
+
preferredProjectId?: string,
|
|
262
|
+
) => {
|
|
263
|
+
const octogentDir = join(workspaceCwd, ".octogent");
|
|
264
|
+
for (const subdirectory of ["tentacles", "worktrees"]) {
|
|
265
|
+
mkdirSync(join(octogentDir, subdirectory), { recursive: true });
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
return ensureProjectConfig(workspaceCwd, preferredName, preferredProjectId);
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
export const hasOctogentGitignoreEntry = (workspaceCwd: string) => {
|
|
272
|
+
const gitignorePath = join(workspaceCwd, ".gitignore");
|
|
273
|
+
if (!existsSync(gitignorePath)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
278
|
+
return content
|
|
279
|
+
.split("\n")
|
|
280
|
+
.map((line) => line.trim())
|
|
281
|
+
.includes(".octogent");
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
export const ensureOctogentGitignoreEntry = (workspaceCwd: string) => {
|
|
285
|
+
const gitignorePath = join(workspaceCwd, ".gitignore");
|
|
286
|
+
const entry = ".octogent";
|
|
287
|
+
|
|
288
|
+
if (existsSync(gitignorePath)) {
|
|
289
|
+
const content = readFileSync(gitignorePath, "utf-8");
|
|
290
|
+
if (
|
|
291
|
+
content
|
|
292
|
+
.split("\n")
|
|
293
|
+
.map((line) => line.trim())
|
|
294
|
+
.includes(entry)
|
|
295
|
+
) {
|
|
296
|
+
return { changed: false };
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
appendFileSync(gitignorePath, `\n${entry}\n`, "utf-8");
|
|
300
|
+
return { changed: true };
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
writeFileSync(gitignorePath, `${entry}\n`, "utf-8");
|
|
304
|
+
return { changed: true };
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
export const migrateStateToGlobal = (workspaceCwd: string, projectStateDir: string) => {
|
|
308
|
+
const fallbackProjectDir = join(workspaceCwd, ".octogent");
|
|
309
|
+
if (projectStateDir === fallbackProjectDir) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const currentConfig = loadProjectConfig(workspaceCwd);
|
|
314
|
+
const legacyProjectName = inferLegacyProjectName(workspaceCwd);
|
|
315
|
+
const legacyGlobalProjectDir =
|
|
316
|
+
legacyProjectName && currentConfig
|
|
317
|
+
? join(GLOBAL_OCTOGENT_DIR, "projects", legacyProjectName)
|
|
318
|
+
: null;
|
|
319
|
+
const oldStateDir = join(fallbackProjectDir, "state");
|
|
320
|
+
const newStateDir = join(projectStateDir, "state");
|
|
321
|
+
|
|
322
|
+
mkdirSync(newStateDir, { recursive: true });
|
|
323
|
+
|
|
324
|
+
const stateFiles = [
|
|
325
|
+
"tentacles.json",
|
|
326
|
+
"deck.json",
|
|
327
|
+
"monitor-config.json",
|
|
328
|
+
"monitor-cache.json",
|
|
329
|
+
"code-intel-events.jsonl",
|
|
330
|
+
"claude-usage-snapshot.json",
|
|
331
|
+
"runtime.json",
|
|
332
|
+
];
|
|
333
|
+
|
|
334
|
+
let migrated = 0;
|
|
335
|
+
for (const file of stateFiles) {
|
|
336
|
+
const destination = join(newStateDir, file);
|
|
337
|
+
if (existsSync(destination)) {
|
|
338
|
+
continue;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const localSource = join(oldStateDir, file);
|
|
342
|
+
if (existsSync(localSource)) {
|
|
343
|
+
copyFileSync(localSource, destination);
|
|
344
|
+
migrated += 1;
|
|
345
|
+
continue;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
if (!legacyGlobalProjectDir) {
|
|
349
|
+
continue;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
const legacySource = join(legacyGlobalProjectDir, "state", file);
|
|
353
|
+
if (existsSync(legacySource)) {
|
|
354
|
+
copyFileSync(legacySource, destination);
|
|
355
|
+
migrated += 1;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
const transcriptDestination = join(newStateDir, "transcripts");
|
|
360
|
+
if (!existsSync(transcriptDestination)) {
|
|
361
|
+
const localTranscriptSource = join(oldStateDir, "transcripts");
|
|
362
|
+
if (existsSync(localTranscriptSource)) {
|
|
363
|
+
cpSync(localTranscriptSource, transcriptDestination, { recursive: true });
|
|
364
|
+
migrated += 1;
|
|
365
|
+
} else if (legacyGlobalProjectDir) {
|
|
366
|
+
const legacyTranscriptSource = join(legacyGlobalProjectDir, "state", "transcripts");
|
|
367
|
+
if (existsSync(legacyTranscriptSource)) {
|
|
368
|
+
cpSync(legacyTranscriptSource, transcriptDestination, { recursive: true });
|
|
369
|
+
migrated += 1;
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (migrated > 0) {
|
|
375
|
+
console.log(` Migrated state to ${projectStateDir}`);
|
|
376
|
+
}
|
|
377
|
+
};
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import { mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Interpolate `{{key}}` placeholders in a template string with values from the
|
|
6
|
+
* provided variables map. Unknown placeholders are left as-is.
|
|
7
|
+
*/
|
|
8
|
+
export const interpolatePrompt = (template: string, variables: Record<string, string>): string =>
|
|
9
|
+
template.replace(/\{\{(\w+)\}\}/g, (match, key: string) => variables[key] ?? match);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Read a prompt template from `<promptsDir>/<name>.md` and return the raw
|
|
13
|
+
* template string. Returns `undefined` if the file does not exist.
|
|
14
|
+
*/
|
|
15
|
+
export const readPromptTemplate = async (
|
|
16
|
+
promptsDir: string,
|
|
17
|
+
name: string,
|
|
18
|
+
): Promise<string | undefined> => {
|
|
19
|
+
// Guard against path traversal.
|
|
20
|
+
if (name.includes("/") || name.includes("\\") || name.includes("..")) {
|
|
21
|
+
return undefined;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const filePath = join(promptsDir, `${name}.md`);
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const content = await readFile(filePath, "utf-8");
|
|
28
|
+
return content.trimEnd();
|
|
29
|
+
} catch {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Read and resolve a prompt template, interpolating the given variables.
|
|
36
|
+
* Returns `undefined` if the template does not exist.
|
|
37
|
+
*/
|
|
38
|
+
export const resolvePrompt = async (
|
|
39
|
+
promptsDir: string,
|
|
40
|
+
name: string,
|
|
41
|
+
variables: Record<string, string>,
|
|
42
|
+
): Promise<string | undefined> => {
|
|
43
|
+
const template = await readPromptTemplate(promptsDir, name);
|
|
44
|
+
if (template === undefined) {
|
|
45
|
+
return undefined;
|
|
46
|
+
}
|
|
47
|
+
return interpolatePrompt(template, variables);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* List all available prompt template names (file basenames without `.md`).
|
|
52
|
+
*/
|
|
53
|
+
export const listPromptTemplates = async (promptsDir: string): Promise<string[]> => {
|
|
54
|
+
try {
|
|
55
|
+
const entries = await readdir(promptsDir);
|
|
56
|
+
return entries.filter((e) => e.endsWith(".md")).map((e) => e.replace(/\.md$/, ""));
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// ─── Multi-directory helpers (builtin + user) ─────────────────────────────
|
|
63
|
+
|
|
64
|
+
type PromptEntry = { name: string; source: "builtin" | "user" };
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List prompts from both built-in and user directories.
|
|
68
|
+
* User prompts shadow built-in prompts with the same name.
|
|
69
|
+
*/
|
|
70
|
+
export const listAllPrompts = async (
|
|
71
|
+
builtinDir: string,
|
|
72
|
+
userDir: string,
|
|
73
|
+
): Promise<PromptEntry[]> => {
|
|
74
|
+
const [builtinNames, userNames] = await Promise.all([
|
|
75
|
+
listPromptTemplates(builtinDir),
|
|
76
|
+
listPromptTemplates(userDir),
|
|
77
|
+
]);
|
|
78
|
+
|
|
79
|
+
const seen = new Set<string>();
|
|
80
|
+
const result: PromptEntry[] = [];
|
|
81
|
+
|
|
82
|
+
for (const name of userNames) {
|
|
83
|
+
seen.add(name);
|
|
84
|
+
result.push({ name, source: "user" });
|
|
85
|
+
}
|
|
86
|
+
for (const name of builtinNames) {
|
|
87
|
+
if (!seen.has(name)) {
|
|
88
|
+
result.push({ name, source: "builtin" });
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return result;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Read a prompt from user dir first, falling back to builtin dir.
|
|
97
|
+
*/
|
|
98
|
+
export const readPromptFromDirs = async (
|
|
99
|
+
builtinDir: string,
|
|
100
|
+
userDir: string,
|
|
101
|
+
name: string,
|
|
102
|
+
): Promise<{ name: string; source: "builtin" | "user"; content: string } | undefined> => {
|
|
103
|
+
const userContent = await readPromptTemplate(userDir, name);
|
|
104
|
+
if (userContent !== undefined) {
|
|
105
|
+
return { name, source: "user", content: userContent };
|
|
106
|
+
}
|
|
107
|
+
const builtinContent = await readPromptTemplate(builtinDir, name);
|
|
108
|
+
if (builtinContent !== undefined) {
|
|
109
|
+
return { name, source: "builtin", content: builtinContent };
|
|
110
|
+
}
|
|
111
|
+
return undefined;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const VALID_PROMPT_NAME = /^[\w][\w.-]*$/;
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Write a user prompt to the user prompts directory.
|
|
118
|
+
*/
|
|
119
|
+
export const writeUserPrompt = async (
|
|
120
|
+
userDir: string,
|
|
121
|
+
name: string,
|
|
122
|
+
content: string,
|
|
123
|
+
): Promise<boolean> => {
|
|
124
|
+
if (!VALID_PROMPT_NAME.test(name) || name.includes("..")) {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
await mkdir(userDir, { recursive: true });
|
|
128
|
+
await writeFile(join(userDir, `${name}.md`), content, "utf-8");
|
|
129
|
+
return true;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Delete a user prompt from the user prompts directory.
|
|
134
|
+
*/
|
|
135
|
+
export const deleteUserPrompt = async (userDir: string, name: string): Promise<boolean> => {
|
|
136
|
+
if (!VALID_PROMPT_NAME.test(name) || name.includes("..")) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
try {
|
|
140
|
+
await rm(join(userDir, `${name}.md`));
|
|
141
|
+
return true;
|
|
142
|
+
} catch {
|
|
143
|
+
return false;
|
|
144
|
+
}
|
|
145
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
|
|
4
|
+
export type RuntimeMetadata = {
|
|
5
|
+
apiBaseUrl: string;
|
|
6
|
+
host: string;
|
|
7
|
+
port: number;
|
|
8
|
+
pid: number;
|
|
9
|
+
startedAt: string;
|
|
10
|
+
workspaceCwd: string;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const RUNTIME_METADATA_FILENAME = "runtime.json";
|
|
14
|
+
|
|
15
|
+
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
16
|
+
value !== null && typeof value === "object" && !Array.isArray(value);
|
|
17
|
+
|
|
18
|
+
export const resolveRuntimeMetadataPath = (projectStateDir: string) =>
|
|
19
|
+
join(projectStateDir, "state", RUNTIME_METADATA_FILENAME);
|
|
20
|
+
|
|
21
|
+
export const readRuntimeMetadata = (projectStateDir: string): RuntimeMetadata | null => {
|
|
22
|
+
const filePath = resolveRuntimeMetadataPath(projectStateDir);
|
|
23
|
+
if (!existsSync(filePath)) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const parsed = JSON.parse(readFileSync(filePath, "utf8")) as unknown;
|
|
29
|
+
if (
|
|
30
|
+
!isRecord(parsed) ||
|
|
31
|
+
typeof parsed.apiBaseUrl !== "string" ||
|
|
32
|
+
typeof parsed.host !== "string" ||
|
|
33
|
+
typeof parsed.port !== "number" ||
|
|
34
|
+
!Number.isFinite(parsed.port) ||
|
|
35
|
+
typeof parsed.pid !== "number" ||
|
|
36
|
+
!Number.isFinite(parsed.pid) ||
|
|
37
|
+
typeof parsed.startedAt !== "string" ||
|
|
38
|
+
typeof parsed.workspaceCwd !== "string"
|
|
39
|
+
) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return {
|
|
44
|
+
apiBaseUrl: parsed.apiBaseUrl,
|
|
45
|
+
host: parsed.host,
|
|
46
|
+
port: parsed.port,
|
|
47
|
+
pid: parsed.pid,
|
|
48
|
+
startedAt: parsed.startedAt,
|
|
49
|
+
workspaceCwd: parsed.workspaceCwd,
|
|
50
|
+
};
|
|
51
|
+
} catch {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export const writeRuntimeMetadata = (projectStateDir: string, metadata: RuntimeMetadata) => {
|
|
57
|
+
const filePath = resolveRuntimeMetadataPath(projectStateDir);
|
|
58
|
+
mkdirSync(join(projectStateDir, "state"), { recursive: true });
|
|
59
|
+
writeFileSync(filePath, `${JSON.stringify(metadata, null, 2)}\n`, "utf8");
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
export const clearRuntimeMetadata = (projectStateDir: string) => {
|
|
63
|
+
const filePath = resolveRuntimeMetadataPath(projectStateDir);
|
|
64
|
+
if (!existsSync(filePath)) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
rmSync(filePath, { force: true });
|
|
69
|
+
};
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { createApiServer } from "./createApiServer";
|
|
3
|
+
|
|
4
|
+
const parsePort = (value: string | undefined, fallback: number) => {
|
|
5
|
+
if (!value) {
|
|
6
|
+
return fallback;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const parsed = Number.parseInt(value, 10);
|
|
10
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
|
|
11
|
+
return fallback;
|
|
12
|
+
}
|
|
13
|
+
return parsed;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const host = process.env.HOST ?? "127.0.0.1";
|
|
17
|
+
const port = parsePort(process.env.OCTOGENT_API_PORT ?? process.env.PORT, 8787);
|
|
18
|
+
const allowRemoteAccess = process.env.OCTOGENT_ALLOW_REMOTE_ACCESS === "1";
|
|
19
|
+
const workspaceCwd = process.env.OCTOGENT_WORKSPACE_CWD ?? process.cwd();
|
|
20
|
+
const projectStateDir = process.env.OCTOGENT_PROJECT_STATE_DIR;
|
|
21
|
+
const promptsDir = process.env.OCTOGENT_PROMPTS_DIR;
|
|
22
|
+
const webDistDir = process.env.OCTOGENT_WEB_DIST_DIR;
|
|
23
|
+
|
|
24
|
+
// Validate startup environment
|
|
25
|
+
const validateStartupEnv = () => {
|
|
26
|
+
const rawPort = process.env.OCTOGENT_API_PORT ?? process.env.PORT;
|
|
27
|
+
if (rawPort !== undefined) {
|
|
28
|
+
const parsed = Number.parseInt(rawPort, 10);
|
|
29
|
+
if (!Number.isFinite(parsed) || parsed < 1 || parsed > 65535) {
|
|
30
|
+
console.error(`Invalid port "${rawPort}": must be an integer between 1 and 65535.`);
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (process.env.OCTOGENT_WORKSPACE_CWD && !existsSync(process.env.OCTOGENT_WORKSPACE_CWD)) {
|
|
36
|
+
console.error(
|
|
37
|
+
`OCTOGENT_WORKSPACE_CWD directory does not exist: ${process.env.OCTOGENT_WORKSPACE_CWD}`,
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (process.env.OCTOGENT_WEB_DIST_DIR && !existsSync(process.env.OCTOGENT_WEB_DIST_DIR)) {
|
|
43
|
+
console.warn(
|
|
44
|
+
`OCTOGENT_WEB_DIST_DIR directory does not exist: ${process.env.OCTOGENT_WEB_DIST_DIR} — web UI will be unavailable.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
validateStartupEnv();
|
|
50
|
+
|
|
51
|
+
const apiServer = createApiServer({
|
|
52
|
+
workspaceCwd,
|
|
53
|
+
projectStateDir,
|
|
54
|
+
promptsDir,
|
|
55
|
+
webDistDir,
|
|
56
|
+
allowRemoteAccess,
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
const shutdown = async () => {
|
|
60
|
+
await apiServer.stop();
|
|
61
|
+
process.exit(0);
|
|
62
|
+
};
|
|
63
|
+
|
|
64
|
+
process.on("SIGINT", () => {
|
|
65
|
+
void shutdown();
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
process.on("SIGTERM", () => {
|
|
69
|
+
void shutdown();
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
apiServer
|
|
73
|
+
.start(port, host)
|
|
74
|
+
.then(({ port: activePort }) => {
|
|
75
|
+
console.log(`Octogent API listening on http://${host}:${activePort}`);
|
|
76
|
+
})
|
|
77
|
+
.catch((error: unknown) => {
|
|
78
|
+
console.error(error);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|