patchwork-os 0.2.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.bridge.md +352 -0
- package/README.md +72 -0
- package/deploy/README.md +172 -0
- package/deploy/bootstrap-new-vps.sh +364 -0
- package/deploy/claude-ide-bridge.service.template +67 -0
- package/deploy/claude-ide-bridge@.service +31 -0
- package/deploy/ecosystem.config.js.example +36 -0
- package/deploy/install-vps-service.sh +240 -0
- package/deploy/nginx-claude-bridge.conf.template +129 -0
- package/dist/activityLog.d.ts +112 -0
- package/dist/activityLog.js +399 -0
- package/dist/activityLog.js.map +1 -0
- package/dist/activityTypes.d.ts +28 -0
- package/dist/activityTypes.js +9 -0
- package/dist/activityTypes.js.map +1 -0
- package/dist/adapters/base.d.ts +78 -0
- package/dist/adapters/base.js +14 -0
- package/dist/adapters/base.js.map +1 -0
- package/dist/adapters/claude.d.ts +18 -0
- package/dist/adapters/claude.js +276 -0
- package/dist/adapters/claude.js.map +1 -0
- package/dist/adapters/gemini.d.ts +17 -0
- package/dist/adapters/gemini.js +218 -0
- package/dist/adapters/gemini.js.map +1 -0
- package/dist/adapters/grok.d.ts +7 -0
- package/dist/adapters/grok.js +21 -0
- package/dist/adapters/grok.js.map +1 -0
- package/dist/adapters/index.d.ts +5 -0
- package/dist/adapters/index.js +37 -0
- package/dist/adapters/index.js.map +1 -0
- package/dist/adapters/local.d.ts +7 -0
- package/dist/adapters/local.js +22 -0
- package/dist/adapters/local.js.map +1 -0
- package/dist/adapters/openai.d.ts +22 -0
- package/dist/adapters/openai.js +284 -0
- package/dist/adapters/openai.js.map +1 -0
- package/dist/adapters/sse.d.ts +13 -0
- package/dist/adapters/sse.js +58 -0
- package/dist/adapters/sse.js.map +1 -0
- package/dist/analyticsAggregator.d.ts +28 -0
- package/dist/analyticsAggregator.js +133 -0
- package/dist/analyticsAggregator.js.map +1 -0
- package/dist/analyticsPrefs.d.ts +9 -0
- package/dist/analyticsPrefs.js +50 -0
- package/dist/analyticsPrefs.js.map +1 -0
- package/dist/analyticsSend.d.ts +12 -0
- package/dist/analyticsSend.js +34 -0
- package/dist/analyticsSend.js.map +1 -0
- package/dist/approvalHttp.d.ts +46 -0
- package/dist/approvalHttp.js +370 -0
- package/dist/approvalHttp.js.map +1 -0
- package/dist/approvalQueue.d.ts +49 -0
- package/dist/approvalQueue.js +84 -0
- package/dist/approvalQueue.js.map +1 -0
- package/dist/automation.d.ts +675 -0
- package/dist/automation.js +1038 -0
- package/dist/automation.js.map +1 -0
- package/dist/bridge.d.ts +85 -0
- package/dist/bridge.js +1535 -0
- package/dist/bridge.js.map +1 -0
- package/dist/bridgeLockDiscovery.d.ts +11 -0
- package/dist/bridgeLockDiscovery.js +49 -0
- package/dist/bridgeLockDiscovery.js.map +1 -0
- package/dist/bridgeToken.d.ts +22 -0
- package/dist/bridgeToken.js +114 -0
- package/dist/bridgeToken.js.map +1 -0
- package/dist/bridgeToolsRules.d.ts +20 -0
- package/dist/bridgeToolsRules.js +79 -0
- package/dist/bridgeToolsRules.js.map +1 -0
- package/dist/ccPermissions.d.ts +59 -0
- package/dist/ccPermissions.js +163 -0
- package/dist/ccPermissions.js.map +1 -0
- package/dist/claudeDriver.d.ts +129 -0
- package/dist/claudeDriver.js +459 -0
- package/dist/claudeDriver.js.map +1 -0
- package/dist/claudeMdPatch.d.ts +29 -0
- package/dist/claudeMdPatch.js +164 -0
- package/dist/claudeMdPatch.js.map +1 -0
- package/dist/claudeOrchestrator.d.ts +171 -0
- package/dist/claudeOrchestrator.js +591 -0
- package/dist/claudeOrchestrator.js.map +1 -0
- package/dist/commands/install.d.ts +1 -0
- package/dist/commands/install.js +158 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/marketplace.d.ts +11 -0
- package/dist/commands/marketplace.js +120 -0
- package/dist/commands/marketplace.js.map +1 -0
- package/dist/commands/patchworkInit.d.ts +14 -0
- package/dist/commands/patchworkInit.js +155 -0
- package/dist/commands/patchworkInit.js.map +1 -0
- package/dist/commands/task.d.ts +14 -0
- package/dist/commands/task.js +289 -0
- package/dist/commands/task.js.map +1 -0
- package/dist/commands/tokenEfficiency.d.ts +9 -0
- package/dist/commands/tokenEfficiency.js +211 -0
- package/dist/commands/tokenEfficiency.js.map +1 -0
- package/dist/commands/tools.d.ts +28 -0
- package/dist/commands/tools.js +326 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commitIssueLinkLog.d.ts +77 -0
- package/dist/commitIssueLinkLog.js +142 -0
- package/dist/commitIssueLinkLog.js.map +1 -0
- package/dist/companions/registry.d.ts +12 -0
- package/dist/companions/registry.js +71 -0
- package/dist/companions/registry.js.map +1 -0
- package/dist/config.d.ts +105 -0
- package/dist/config.js +720 -0
- package/dist/config.js.map +1 -0
- package/dist/crypto.d.ts +16 -0
- package/dist/crypto.js +34 -0
- package/dist/crypto.js.map +1 -0
- package/dist/dashboard.d.ts +12 -0
- package/dist/dashboard.js +149 -0
- package/dist/dashboard.js.map +1 -0
- package/dist/decisionTraceLog.d.ts +77 -0
- package/dist/decisionTraceLog.js +147 -0
- package/dist/decisionTraceLog.js.map +1 -0
- package/dist/errors.d.ts +32 -0
- package/dist/errors.js +34 -0
- package/dist/errors.js.map +1 -0
- package/dist/extensionClient.d.ts +279 -0
- package/dist/extensionClient.js +1253 -0
- package/dist/extensionClient.js.map +1 -0
- package/dist/fileLock.d.ts +36 -0
- package/dist/fileLock.js +121 -0
- package/dist/fileLock.js.map +1 -0
- package/dist/fp/activityAnalytics.d.ts +39 -0
- package/dist/fp/activityAnalytics.js +111 -0
- package/dist/fp/activityAnalytics.js.map +1 -0
- package/dist/fp/async.d.ts +48 -0
- package/dist/fp/async.js +60 -0
- package/dist/fp/async.js.map +1 -0
- package/dist/fp/automationInterpreter.d.ts +37 -0
- package/dist/fp/automationInterpreter.js +523 -0
- package/dist/fp/automationInterpreter.js.map +1 -0
- package/dist/fp/automationProgram.d.ts +89 -0
- package/dist/fp/automationProgram.js +29 -0
- package/dist/fp/automationProgram.js.map +1 -0
- package/dist/fp/automationState.d.ts +135 -0
- package/dist/fp/automationState.js +206 -0
- package/dist/fp/automationState.js.map +1 -0
- package/dist/fp/automationUtils.d.ts +31 -0
- package/dist/fp/automationUtils.js +61 -0
- package/dist/fp/automationUtils.js.map +1 -0
- package/dist/fp/brandedTypes.d.ts +32 -0
- package/dist/fp/brandedTypes.js +41 -0
- package/dist/fp/brandedTypes.js.map +1 -0
- package/dist/fp/commandDescription.d.ts +18 -0
- package/dist/fp/commandDescription.js +125 -0
- package/dist/fp/commandDescription.js.map +1 -0
- package/dist/fp/extensionSnapshot.d.ts +10 -0
- package/dist/fp/extensionSnapshot.js +14 -0
- package/dist/fp/extensionSnapshot.js.map +1 -0
- package/dist/fp/index.d.ts +8 -0
- package/dist/fp/index.js +9 -0
- package/dist/fp/index.js.map +1 -0
- package/dist/fp/interpreterContext.d.ts +69 -0
- package/dist/fp/interpreterContext.js +56 -0
- package/dist/fp/interpreterContext.js.map +1 -0
- package/dist/fp/policyParser.d.ts +16 -0
- package/dist/fp/policyParser.js +334 -0
- package/dist/fp/policyParser.js.map +1 -0
- package/dist/fp/result.d.ts +38 -0
- package/dist/fp/result.js +57 -0
- package/dist/fp/result.js.map +1 -0
- package/dist/fp/tokenBucket.d.ts +27 -0
- package/dist/fp/tokenBucket.js +36 -0
- package/dist/fp/tokenBucket.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1465 -0
- package/dist/index.js.map +1 -0
- package/dist/instructionsUtils.d.ts +17 -0
- package/dist/instructionsUtils.js +38 -0
- package/dist/instructionsUtils.js.map +1 -0
- package/dist/lockfile.d.ts +16 -0
- package/dist/lockfile.js +172 -0
- package/dist/lockfile.js.map +1 -0
- package/dist/logger.d.ts +16 -0
- package/dist/logger.js +68 -0
- package/dist/logger.js.map +1 -0
- package/dist/oauth.d.ts +105 -0
- package/dist/oauth.js +880 -0
- package/dist/oauth.js.map +1 -0
- package/dist/orchestrator/childBridgeClient.d.ts +33 -0
- package/dist/orchestrator/childBridgeClient.js +321 -0
- package/dist/orchestrator/childBridgeClient.js.map +1 -0
- package/dist/orchestrator/childBridgeRegistry.d.ts +67 -0
- package/dist/orchestrator/childBridgeRegistry.js +297 -0
- package/dist/orchestrator/childBridgeRegistry.js.map +1 -0
- package/dist/orchestrator/index.d.ts +3 -0
- package/dist/orchestrator/index.js +3 -0
- package/dist/orchestrator/index.js.map +1 -0
- package/dist/orchestrator/orchestratorBridge.d.ts +32 -0
- package/dist/orchestrator/orchestratorBridge.js +412 -0
- package/dist/orchestrator/orchestratorBridge.js.map +1 -0
- package/dist/orchestrator/orchestratorConfig.d.ts +11 -0
- package/dist/orchestrator/orchestratorConfig.js +85 -0
- package/dist/orchestrator/orchestratorConfig.js.map +1 -0
- package/dist/orchestrator/orchestratorTools.d.ts +16 -0
- package/dist/orchestrator/orchestratorTools.js +272 -0
- package/dist/orchestrator/orchestratorTools.js.map +1 -0
- package/dist/patchworkCli.d.ts +15 -0
- package/dist/patchworkCli.js +41 -0
- package/dist/patchworkCli.js.map +1 -0
- package/dist/patchworkConfig.d.ts +28 -0
- package/dist/patchworkConfig.js +30 -0
- package/dist/patchworkConfig.js.map +1 -0
- package/dist/plugin.d.ts +106 -0
- package/dist/plugin.js +31 -0
- package/dist/plugin.js.map +1 -0
- package/dist/pluginLoader.d.ts +44 -0
- package/dist/pluginLoader.js +357 -0
- package/dist/pluginLoader.js.map +1 -0
- package/dist/pluginWatcher.d.ts +24 -0
- package/dist/pluginWatcher.js +139 -0
- package/dist/pluginWatcher.js.map +1 -0
- package/dist/preToolUseHook.d.ts +10 -0
- package/dist/preToolUseHook.js +57 -0
- package/dist/preToolUseHook.js.map +1 -0
- package/dist/probe.d.ts +35 -0
- package/dist/probe.js +143 -0
- package/dist/probe.js.map +1 -0
- package/dist/prompts.d.ts +27 -0
- package/dist/prompts.js +1680 -0
- package/dist/prompts.js.map +1 -0
- package/dist/quickTaskPresets.d.ts +64 -0
- package/dist/quickTaskPresets.js +156 -0
- package/dist/quickTaskPresets.js.map +1 -0
- package/dist/recipes/compiler.d.ts +44 -0
- package/dist/recipes/compiler.js +140 -0
- package/dist/recipes/compiler.js.map +1 -0
- package/dist/recipes/installer.d.ts +25 -0
- package/dist/recipes/installer.js +62 -0
- package/dist/recipes/installer.js.map +1 -0
- package/dist/recipes/parser.d.ts +18 -0
- package/dist/recipes/parser.js +160 -0
- package/dist/recipes/parser.js.map +1 -0
- package/dist/recipes/scheduler.d.ts +45 -0
- package/dist/recipes/scheduler.js +110 -0
- package/dist/recipes/scheduler.js.map +1 -0
- package/dist/recipes/schema.d.ts +71 -0
- package/dist/recipes/schema.js +11 -0
- package/dist/recipes/schema.js.map +1 -0
- package/dist/recipesHttp.d.ts +63 -0
- package/dist/recipesHttp.js +183 -0
- package/dist/recipesHttp.js.map +1 -0
- package/dist/resources.d.ts +33 -0
- package/dist/resources.js +266 -0
- package/dist/resources.js.map +1 -0
- package/dist/riskTier.d.ts +40 -0
- package/dist/riskTier.js +142 -0
- package/dist/riskTier.js.map +1 -0
- package/dist/runLog.d.ts +90 -0
- package/dist/runLog.js +143 -0
- package/dist/runLog.js.map +1 -0
- package/dist/server.d.ts +160 -0
- package/dist/server.js +1244 -0
- package/dist/server.js.map +1 -0
- package/dist/sessionCheckpoint.d.ts +37 -0
- package/dist/sessionCheckpoint.js +123 -0
- package/dist/sessionCheckpoint.js.map +1 -0
- package/dist/streamableHttp.d.ts +86 -0
- package/dist/streamableHttp.js +702 -0
- package/dist/streamableHttp.js.map +1 -0
- package/dist/telemetry.d.ts +18 -0
- package/dist/telemetry.js +95 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/tools/activityLog.d.ts +140 -0
- package/dist/tools/activityLog.js +204 -0
- package/dist/tools/activityLog.js.map +1 -0
- package/dist/tools/auditDependencies.d.ts +67 -0
- package/dist/tools/auditDependencies.js +298 -0
- package/dist/tools/auditDependencies.js.map +1 -0
- package/dist/tools/batchLsp.d.ts +262 -0
- package/dist/tools/batchLsp.js +328 -0
- package/dist/tools/batchLsp.js.map +1 -0
- package/dist/tools/blame-utils.d.ts +30 -0
- package/dist/tools/blame-utils.js +60 -0
- package/dist/tools/blame-utils.js.map +1 -0
- package/dist/tools/bridgeDoctor.d.ts +78 -0
- package/dist/tools/bridgeDoctor.js +542 -0
- package/dist/tools/bridgeDoctor.js.map +1 -0
- package/dist/tools/bridgeStatus.d.ts +122 -0
- package/dist/tools/bridgeStatus.js +250 -0
- package/dist/tools/bridgeStatus.js.map +1 -0
- package/dist/tools/cancelClaudeTask.d.ts +48 -0
- package/dist/tools/cancelClaudeTask.js +56 -0
- package/dist/tools/cancelClaudeTask.js.map +1 -0
- package/dist/tools/checkDocumentDirty.d.ts +56 -0
- package/dist/tools/checkDocumentDirty.js +74 -0
- package/dist/tools/checkDocumentDirty.js.map +1 -0
- package/dist/tools/clipboard.d.ts +80 -0
- package/dist/tools/clipboard.js +211 -0
- package/dist/tools/clipboard.js.map +1 -0
- package/dist/tools/closeTabs.d.ts +84 -0
- package/dist/tools/closeTabs.js +97 -0
- package/dist/tools/closeTabs.js.map +1 -0
- package/dist/tools/codeLens.d.ts +50 -0
- package/dist/tools/codeLens.js +47 -0
- package/dist/tools/codeLens.js.map +1 -0
- package/dist/tools/contextBundle.d.ts +75 -0
- package/dist/tools/contextBundle.js +218 -0
- package/dist/tools/contextBundle.js.map +1 -0
- package/dist/tools/createIssueFromAIComment.d.ts +75 -0
- package/dist/tools/createIssueFromAIComment.js +119 -0
- package/dist/tools/createIssueFromAIComment.js.map +1 -0
- package/dist/tools/ctxGetTaskContext.d.ts +103 -0
- package/dist/tools/ctxGetTaskContext.js +274 -0
- package/dist/tools/ctxGetTaskContext.js.map +1 -0
- package/dist/tools/ctxQueryTraces.d.ts +142 -0
- package/dist/tools/ctxQueryTraces.js +194 -0
- package/dist/tools/ctxQueryTraces.js.map +1 -0
- package/dist/tools/ctxSaveTrace.d.ts +87 -0
- package/dist/tools/ctxSaveTrace.js +94 -0
- package/dist/tools/ctxSaveTrace.js.map +1 -0
- package/dist/tools/debug.d.ts +206 -0
- package/dist/tools/debug.js +234 -0
- package/dist/tools/debug.js.map +1 -0
- package/dist/tools/decorations.d.ts +130 -0
- package/dist/tools/decorations.js +160 -0
- package/dist/tools/decorations.js.map +1 -0
- package/dist/tools/detectUnusedCode.d.ts +78 -0
- package/dist/tools/detectUnusedCode.js +173 -0
- package/dist/tools/detectUnusedCode.js.map +1 -0
- package/dist/tools/documentLinks.d.ts +62 -0
- package/dist/tools/documentLinks.js +55 -0
- package/dist/tools/documentLinks.js.map +1 -0
- package/dist/tools/editText.d.ts +108 -0
- package/dist/tools/editText.js +318 -0
- package/dist/tools/editText.js.map +1 -0
- package/dist/tools/enrichCommit.d.ts +89 -0
- package/dist/tools/enrichCommit.js +201 -0
- package/dist/tools/enrichCommit.js.map +1 -0
- package/dist/tools/enrichStackTrace.d.ts +121 -0
- package/dist/tools/enrichStackTrace.js +194 -0
- package/dist/tools/enrichStackTrace.js.map +1 -0
- package/dist/tools/explainDiagnostic.d.ts +137 -0
- package/dist/tools/explainDiagnostic.js +230 -0
- package/dist/tools/explainDiagnostic.js.map +1 -0
- package/dist/tools/explainSymbol.d.ts +119 -0
- package/dist/tools/explainSymbol.js +177 -0
- package/dist/tools/explainSymbol.js.map +1 -0
- package/dist/tools/fileOperations.d.ts +186 -0
- package/dist/tools/fileOperations.js +330 -0
- package/dist/tools/fileOperations.js.map +1 -0
- package/dist/tools/fileWatcher.d.ts +107 -0
- package/dist/tools/fileWatcher.js +121 -0
- package/dist/tools/fileWatcher.js.map +1 -0
- package/dist/tools/findFiles.d.ts +65 -0
- package/dist/tools/findFiles.js +142 -0
- package/dist/tools/findFiles.js.map +1 -0
- package/dist/tools/findRelatedTests.d.ts +83 -0
- package/dist/tools/findRelatedTests.js +196 -0
- package/dist/tools/findRelatedTests.js.map +1 -0
- package/dist/tools/fixAllLintErrors.d.ts +66 -0
- package/dist/tools/fixAllLintErrors.js +128 -0
- package/dist/tools/fixAllLintErrors.js.map +1 -0
- package/dist/tools/foldingRanges.d.ts +50 -0
- package/dist/tools/foldingRanges.js +51 -0
- package/dist/tools/foldingRanges.js.map +1 -0
- package/dist/tools/formatAndSave.d.ts +57 -0
- package/dist/tools/formatAndSave.js +87 -0
- package/dist/tools/formatAndSave.js.map +1 -0
- package/dist/tools/formatDocument.d.ts +61 -0
- package/dist/tools/formatDocument.js +144 -0
- package/dist/tools/formatDocument.js.map +1 -0
- package/dist/tools/generateAPIDocumentation.d.ts +62 -0
- package/dist/tools/generateAPIDocumentation.js +249 -0
- package/dist/tools/generateAPIDocumentation.js.map +1 -0
- package/dist/tools/generateTests.d.ts +75 -0
- package/dist/tools/generateTests.js +226 -0
- package/dist/tools/generateTests.js.map +1 -0
- package/dist/tools/getAIComments.d.ts +79 -0
- package/dist/tools/getAIComments.js +93 -0
- package/dist/tools/getAIComments.js.map +1 -0
- package/dist/tools/getAnalyticsReport.d.ts +102 -0
- package/dist/tools/getAnalyticsReport.js +137 -0
- package/dist/tools/getAnalyticsReport.js.map +1 -0
- package/dist/tools/getArchitectureContext.d.ts +85 -0
- package/dist/tools/getArchitectureContext.js +135 -0
- package/dist/tools/getArchitectureContext.js.map +1 -0
- package/dist/tools/getBufferContent.d.ts +80 -0
- package/dist/tools/getBufferContent.js +207 -0
- package/dist/tools/getBufferContent.js.map +1 -0
- package/dist/tools/getChangeImpact.d.ts +76 -0
- package/dist/tools/getChangeImpact.js +184 -0
- package/dist/tools/getChangeImpact.js.map +1 -0
- package/dist/tools/getClaudeTaskStatus.d.ts +87 -0
- package/dist/tools/getClaudeTaskStatus.js +89 -0
- package/dist/tools/getClaudeTaskStatus.js.map +1 -0
- package/dist/tools/getCodeCoverage.d.ts +86 -0
- package/dist/tools/getCodeCoverage.js +237 -0
- package/dist/tools/getCodeCoverage.js.map +1 -0
- package/dist/tools/getCommitsForIssue.d.ts +98 -0
- package/dist/tools/getCommitsForIssue.js +106 -0
- package/dist/tools/getCommitsForIssue.js.map +1 -0
- package/dist/tools/getCurrentSelection.d.ts +123 -0
- package/dist/tools/getCurrentSelection.js +113 -0
- package/dist/tools/getCurrentSelection.js.map +1 -0
- package/dist/tools/getDebugState.d.ts +140 -0
- package/dist/tools/getDebugState.js +109 -0
- package/dist/tools/getDebugState.js.map +1 -0
- package/dist/tools/getDependencyTree.d.ts +59 -0
- package/dist/tools/getDependencyTree.js +207 -0
- package/dist/tools/getDependencyTree.js.map +1 -0
- package/dist/tools/getDiagnostics.d.ts +108 -0
- package/dist/tools/getDiagnostics.js +371 -0
- package/dist/tools/getDiagnostics.js.map +1 -0
- package/dist/tools/getDiffFromHandoff.d.ts +89 -0
- package/dist/tools/getDiffFromHandoff.js +163 -0
- package/dist/tools/getDiffFromHandoff.js.map +1 -0
- package/dist/tools/getDocumentSymbols.d.ts +74 -0
- package/dist/tools/getDocumentSymbols.js +177 -0
- package/dist/tools/getDocumentSymbols.js.map +1 -0
- package/dist/tools/getFileTree.d.ts +66 -0
- package/dist/tools/getFileTree.js +131 -0
- package/dist/tools/getFileTree.js.map +1 -0
- package/dist/tools/getGitDiff.d.ts +50 -0
- package/dist/tools/getGitDiff.js +73 -0
- package/dist/tools/getGitDiff.js.map +1 -0
- package/dist/tools/getGitHotspots.d.ts +88 -0
- package/dist/tools/getGitHotspots.js +145 -0
- package/dist/tools/getGitHotspots.js.map +1 -0
- package/dist/tools/getGitLog.d.ts +62 -0
- package/dist/tools/getGitLog.js +87 -0
- package/dist/tools/getGitLog.js.map +1 -0
- package/dist/tools/getGitStatus.d.ts +72 -0
- package/dist/tools/getGitStatus.js +126 -0
- package/dist/tools/getGitStatus.js.map +1 -0
- package/dist/tools/getImportTree.d.ts +73 -0
- package/dist/tools/getImportTree.js +223 -0
- package/dist/tools/getImportTree.js.map +1 -0
- package/dist/tools/getImportedSignatures.d.ts +62 -0
- package/dist/tools/getImportedSignatures.js +255 -0
- package/dist/tools/getImportedSignatures.js.map +1 -0
- package/dist/tools/getOpenEditors.d.ts +62 -0
- package/dist/tools/getOpenEditors.js +126 -0
- package/dist/tools/getOpenEditors.js.map +1 -0
- package/dist/tools/getPRTemplate.d.ts +68 -0
- package/dist/tools/getPRTemplate.js +187 -0
- package/dist/tools/getPRTemplate.js.map +1 -0
- package/dist/tools/getProjectContext.d.ts +114 -0
- package/dist/tools/getProjectContext.js +344 -0
- package/dist/tools/getProjectContext.js.map +1 -0
- package/dist/tools/getProjectInfo.d.ts +51 -0
- package/dist/tools/getProjectInfo.js +325 -0
- package/dist/tools/getProjectInfo.js.map +1 -0
- package/dist/tools/getSecurityAdvisories.d.ts +105 -0
- package/dist/tools/getSecurityAdvisories.js +472 -0
- package/dist/tools/getSecurityAdvisories.js.map +1 -0
- package/dist/tools/getSessionUsage.d.ts +58 -0
- package/dist/tools/getSessionUsage.js +57 -0
- package/dist/tools/getSessionUsage.js.map +1 -0
- package/dist/tools/getSymbolHistory.d.ts +157 -0
- package/dist/tools/getSymbolHistory.js +256 -0
- package/dist/tools/getSymbolHistory.js.map +1 -0
- package/dist/tools/getToolCapabilities.d.ts +69 -0
- package/dist/tools/getToolCapabilities.js +298 -0
- package/dist/tools/getToolCapabilities.js.map +1 -0
- package/dist/tools/getTypeSignature.d.ts +70 -0
- package/dist/tools/getTypeSignature.js +132 -0
- package/dist/tools/getTypeSignature.js.map +1 -0
- package/dist/tools/getWorkspaceFolders.d.ts +58 -0
- package/dist/tools/getWorkspaceFolders.js +69 -0
- package/dist/tools/getWorkspaceFolders.js.map +1 -0
- package/dist/tools/getWorkspaceSettings.d.ts +44 -0
- package/dist/tools/getWorkspaceSettings.js +70 -0
- package/dist/tools/getWorkspaceSettings.js.map +1 -0
- package/dist/tools/git-utils.d.ts +16 -0
- package/dist/tools/git-utils.js +46 -0
- package/dist/tools/git-utils.js.map +1 -0
- package/dist/tools/gitHistory.d.ts +110 -0
- package/dist/tools/gitHistory.js +167 -0
- package/dist/tools/gitHistory.js.map +1 -0
- package/dist/tools/gitWrite.d.ts +612 -0
- package/dist/tools/gitWrite.js +983 -0
- package/dist/tools/gitWrite.js.map +1 -0
- package/dist/tools/github/actions.d.ts +152 -0
- package/dist/tools/github/actions.js +195 -0
- package/dist/tools/github/actions.js.map +1 -0
- package/dist/tools/github/index.d.ts +3 -0
- package/dist/tools/github/index.js +4 -0
- package/dist/tools/github/index.js.map +1 -0
- package/dist/tools/github/issues.d.ts +281 -0
- package/dist/tools/github/issues.js +340 -0
- package/dist/tools/github/issues.js.map +1 -0
- package/dist/tools/github/pr.d.ts +433 -0
- package/dist/tools/github/pr.js +588 -0
- package/dist/tools/github/pr.js.map +1 -0
- package/dist/tools/github/shared.d.ts +4 -0
- package/dist/tools/github/shared.js +12 -0
- package/dist/tools/github/shared.js.map +1 -0
- package/dist/tools/handoffNote.d.ts +106 -0
- package/dist/tools/handoffNote.js +232 -0
- package/dist/tools/handoffNote.js.map +1 -0
- package/dist/tools/headless/lspClient.d.ts +26 -0
- package/dist/tools/headless/lspClient.js +221 -0
- package/dist/tools/headless/lspClient.js.map +1 -0
- package/dist/tools/headless/lspFallback.d.ts +28 -0
- package/dist/tools/headless/lspFallback.js +122 -0
- package/dist/tools/headless/lspFallback.js.map +1 -0
- package/dist/tools/hoverAtCursor.d.ts +54 -0
- package/dist/tools/hoverAtCursor.js +68 -0
- package/dist/tools/hoverAtCursor.js.map +1 -0
- package/dist/tools/httpClient.d.ts +141 -0
- package/dist/tools/httpClient.js +486 -0
- package/dist/tools/httpClient.js.map +1 -0
- package/dist/tools/index.d.ts +49 -0
- package/dist/tools/index.js +672 -0
- package/dist/tools/index.js.map +1 -0
- package/dist/tools/inlayHints.d.ts +81 -0
- package/dist/tools/inlayHints.js +76 -0
- package/dist/tools/inlayHints.js.map +1 -0
- package/dist/tools/issueRefs.d.ts +14 -0
- package/dist/tools/issueRefs.js +27 -0
- package/dist/tools/issueRefs.js.map +1 -0
- package/dist/tools/jumpToFirstError.d.ts +63 -0
- package/dist/tools/jumpToFirstError.js +124 -0
- package/dist/tools/jumpToFirstError.js.map +1 -0
- package/dist/tools/launchQuickTask.d.ts +76 -0
- package/dist/tools/launchQuickTask.js +170 -0
- package/dist/tools/launchQuickTask.js.map +1 -0
- package/dist/tools/linters/biome.d.ts +2 -0
- package/dist/tools/linters/biome.js +44 -0
- package/dist/tools/linters/biome.js.map +1 -0
- package/dist/tools/linters/cargo.d.ts +2 -0
- package/dist/tools/linters/cargo.js +45 -0
- package/dist/tools/linters/cargo.js.map +1 -0
- package/dist/tools/linters/eslint.d.ts +2 -0
- package/dist/tools/linters/eslint.js +59 -0
- package/dist/tools/linters/eslint.js.map +1 -0
- package/dist/tools/linters/govet.d.ts +2 -0
- package/dist/tools/linters/govet.js +37 -0
- package/dist/tools/linters/govet.js.map +1 -0
- package/dist/tools/linters/pyright.d.ts +2 -0
- package/dist/tools/linters/pyright.js +34 -0
- package/dist/tools/linters/pyright.js.map +1 -0
- package/dist/tools/linters/ruff.d.ts +2 -0
- package/dist/tools/linters/ruff.js +30 -0
- package/dist/tools/linters/ruff.js.map +1 -0
- package/dist/tools/linters/types.d.ts +16 -0
- package/dist/tools/linters/types.js +2 -0
- package/dist/tools/linters/types.js.map +1 -0
- package/dist/tools/linters/typescript.d.ts +2 -0
- package/dist/tools/linters/typescript.js +38 -0
- package/dist/tools/linters/typescript.js.map +1 -0
- package/dist/tools/listClaudeTasks.d.ts +84 -0
- package/dist/tools/listClaudeTasks.js +88 -0
- package/dist/tools/listClaudeTasks.js.map +1 -0
- package/dist/tools/listTerminals.d.ts +55 -0
- package/dist/tools/listTerminals.js +78 -0
- package/dist/tools/listTerminals.js.map +1 -0
- package/dist/tools/lsp.d.ts +1086 -0
- package/dist/tools/lsp.js +1339 -0
- package/dist/tools/lsp.js.map +1 -0
- package/dist/tools/navigateToSymbolByName.d.ts +56 -0
- package/dist/tools/navigateToSymbolByName.js +170 -0
- package/dist/tools/navigateToSymbolByName.js.map +1 -0
- package/dist/tools/openDiff.d.ts +66 -0
- package/dist/tools/openDiff.js +126 -0
- package/dist/tools/openDiff.js.map +1 -0
- package/dist/tools/openFile.d.ts +69 -0
- package/dist/tools/openFile.js +129 -0
- package/dist/tools/openFile.js.map +1 -0
- package/dist/tools/openInBrowser.d.ts +55 -0
- package/dist/tools/openInBrowser.js +129 -0
- package/dist/tools/openInBrowser.js.map +1 -0
- package/dist/tools/organizeImports.d.ts +56 -0
- package/dist/tools/organizeImports.js +115 -0
- package/dist/tools/organizeImports.js.map +1 -0
- package/dist/tools/performanceReport.d.ts +133 -0
- package/dist/tools/performanceReport.js +218 -0
- package/dist/tools/performanceReport.js.map +1 -0
- package/dist/tools/planPersistence.d.ts +306 -0
- package/dist/tools/planPersistence.js +485 -0
- package/dist/tools/planPersistence.js.map +1 -0
- package/dist/tools/previewEdit.d.ts +107 -0
- package/dist/tools/previewEdit.js +270 -0
- package/dist/tools/previewEdit.js.map +1 -0
- package/dist/tools/recentTracesDigest.d.ts +35 -0
- package/dist/tools/recentTracesDigest.js +98 -0
- package/dist/tools/recentTracesDigest.js.map +1 -0
- package/dist/tools/refactorAnalyze.d.ts +78 -0
- package/dist/tools/refactorAnalyze.js +141 -0
- package/dist/tools/refactorAnalyze.js.map +1 -0
- package/dist/tools/refactorExtractFunction.d.ts +52 -0
- package/dist/tools/refactorExtractFunction.js +121 -0
- package/dist/tools/refactorExtractFunction.js.map +1 -0
- package/dist/tools/refactorPreview.d.ts +75 -0
- package/dist/tools/refactorPreview.js +93 -0
- package/dist/tools/refactorPreview.js.map +1 -0
- package/dist/tools/replaceBlock.d.ts +62 -0
- package/dist/tools/replaceBlock.js +125 -0
- package/dist/tools/replaceBlock.js.map +1 -0
- package/dist/tools/resumeClaudeTask.d.ts +75 -0
- package/dist/tools/resumeClaudeTask.js +149 -0
- package/dist/tools/resumeClaudeTask.js.map +1 -0
- package/dist/tools/runClaudeTask.d.ts +97 -0
- package/dist/tools/runClaudeTask.js +224 -0
- package/dist/tools/runClaudeTask.js.map +1 -0
- package/dist/tools/runCommand.d.ts +82 -0
- package/dist/tools/runCommand.js +101 -0
- package/dist/tools/runCommand.js.map +1 -0
- package/dist/tools/runTests.d.ts +146 -0
- package/dist/tools/runTests.js +315 -0
- package/dist/tools/runTests.js.map +1 -0
- package/dist/tools/saveDocument.d.ts +50 -0
- package/dist/tools/saveDocument.js +73 -0
- package/dist/tools/saveDocument.js.map +1 -0
- package/dist/tools/screenshot.d.ts +23 -0
- package/dist/tools/screenshot.js +43 -0
- package/dist/tools/screenshot.js.map +1 -0
- package/dist/tools/screenshotAndAnnotate.d.ts +103 -0
- package/dist/tools/screenshotAndAnnotate.js +192 -0
- package/dist/tools/screenshotAndAnnotate.js.map +1 -0
- package/dist/tools/searchAndReplace.d.ts +108 -0
- package/dist/tools/searchAndReplace.js +281 -0
- package/dist/tools/searchAndReplace.js.map +1 -0
- package/dist/tools/searchTools.d.ts +61 -0
- package/dist/tools/searchTools.js +85 -0
- package/dist/tools/searchTools.js.map +1 -0
- package/dist/tools/searchWorkspace.d.ts +99 -0
- package/dist/tools/searchWorkspace.js +189 -0
- package/dist/tools/searchWorkspace.js.map +1 -0
- package/dist/tools/selectionRanges.d.ts +58 -0
- package/dist/tools/selectionRanges.js +61 -0
- package/dist/tools/selectionRanges.js.map +1 -0
- package/dist/tools/semanticTokens.d.ts +87 -0
- package/dist/tools/semanticTokens.js +86 -0
- package/dist/tools/semanticTokens.js.map +1 -0
- package/dist/tools/setActiveWorkspaceFolder.d.ts +41 -0
- package/dist/tools/setActiveWorkspaceFolder.js +38 -0
- package/dist/tools/setActiveWorkspaceFolder.js.map +1 -0
- package/dist/tools/signatureHelp.d.ts +86 -0
- package/dist/tools/signatureHelp.js +79 -0
- package/dist/tools/signatureHelp.js.map +1 -0
- package/dist/tools/spawnWorkspace.d.ts +103 -0
- package/dist/tools/spawnWorkspace.js +268 -0
- package/dist/tools/spawnWorkspace.js.map +1 -0
- package/dist/tools/stackTraceParser.d.ts +43 -0
- package/dist/tools/stackTraceParser.js +139 -0
- package/dist/tools/stackTraceParser.js.map +1 -0
- package/dist/tools/terminal.d.ts +352 -0
- package/dist/tools/terminal.js +670 -0
- package/dist/tools/terminal.js.map +1 -0
- package/dist/tools/testRunners/cargoTest.d.ts +2 -0
- package/dist/tools/testRunners/cargoTest.js +129 -0
- package/dist/tools/testRunners/cargoTest.js.map +1 -0
- package/dist/tools/testRunners/goTest.d.ts +2 -0
- package/dist/tools/testRunners/goTest.js +108 -0
- package/dist/tools/testRunners/goTest.js.map +1 -0
- package/dist/tools/testRunners/pytest.d.ts +2 -0
- package/dist/tools/testRunners/pytest.js +135 -0
- package/dist/tools/testRunners/pytest.js.map +1 -0
- package/dist/tools/testRunners/types.d.ts +18 -0
- package/dist/tools/testRunners/types.js +2 -0
- package/dist/tools/testRunners/types.js.map +1 -0
- package/dist/tools/testRunners/vitestJest.d.ts +3 -0
- package/dist/tools/testRunners/vitestJest.js +215 -0
- package/dist/tools/testRunners/vitestJest.js.map +1 -0
- package/dist/tools/testTraceToSource.d.ts +80 -0
- package/dist/tools/testTraceToSource.js +206 -0
- package/dist/tools/testTraceToSource.js.map +1 -0
- package/dist/tools/transaction.d.ts +243 -0
- package/dist/tools/transaction.js +309 -0
- package/dist/tools/transaction.js.map +1 -0
- package/dist/tools/typeHierarchy.d.ts +77 -0
- package/dist/tools/typeHierarchy.js +86 -0
- package/dist/tools/typeHierarchy.js.map +1 -0
- package/dist/tools/utils.d.ts +124 -0
- package/dist/tools/utils.js +566 -0
- package/dist/tools/utils.js.map +1 -0
- package/dist/tools/vscodeCommands.d.ts +90 -0
- package/dist/tools/vscodeCommands.js +112 -0
- package/dist/tools/vscodeCommands.js.map +1 -0
- package/dist/tools/vscodeTasks.d.ts +102 -0
- package/dist/tools/vscodeTasks.js +110 -0
- package/dist/tools/vscodeTasks.js.map +1 -0
- package/dist/tools/watchDiagnostics.d.ts +64 -0
- package/dist/tools/watchDiagnostics.js +270 -0
- package/dist/tools/watchDiagnostics.js.map +1 -0
- package/dist/tools/workspaceSettings.d.ts +57 -0
- package/dist/tools/workspaceSettings.js +80 -0
- package/dist/tools/workspaceSettings.js.map +1 -0
- package/dist/transport.d.ts +207 -0
- package/dist/transport.js +1272 -0
- package/dist/transport.js.map +1 -0
- package/dist/version.d.ts +13 -0
- package/dist/version.js +31 -0
- package/dist/version.js.map +1 -0
- package/dist/wsUtils.d.ts +8 -0
- package/dist/wsUtils.js +54 -0
- package/dist/wsUtils.js.map +1 -0
- package/package.json +118 -0
- package/scripts/gen-claude-desktop-config.sh +124 -0
- package/scripts/gen-mcp-config.sh +390 -0
- package/scripts/install-extension.sh +106 -0
- package/scripts/mcp-stdio-shim.cjs +482 -0
- package/scripts/postinstall.mjs +68 -0
- package/scripts/start-all.sh +502 -0
- package/scripts/start-orchestrator.sh +186 -0
- package/scripts/start-remote.sh +126 -0
- package/scripts/start-vps.sh +116 -0
- package/templates/CLAUDE.bridge.md +125 -0
- package/templates/automation-policies/security-first.json +46 -0
- package/templates/automation-policies/strict-lint.json +41 -0
- package/templates/automation-policies/test-driven.json +54 -0
- package/templates/automation-policy.example.json +105 -0
- package/templates/bridge-tools.md +111 -0
- package/templates/dispatch-context.md +33 -0
- package/templates/managed-agent/code-review-agent.md +50 -0
- package/templates/managed-agent/managed-agent-mcp.json +102 -0
- package/templates/recipes/ambient-journal.yaml +11 -0
- package/templates/recipes/daily-status.yaml +21 -0
- package/templates/recipes/lint-on-save.yaml +13 -0
- package/templates/recipes/stale-branches.yaml +18 -0
- package/templates/recipes/watch-failing-tests.yaml +15 -0
- package/templates/scheduled-tasks/dependency-audit/SKILL.md +77 -0
- package/templates/scheduled-tasks/health-check/SKILL.md +73 -0
- package/templates/scheduled-tasks/nightly-review/SKILL.md +69 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1465 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Enable V8 compile cache for faster cold-start on repeated restarts (Node 22.8+).
|
|
3
|
+
import nodeModule from "node:module";
|
|
4
|
+
if (typeof nodeModule.enableCompileCache === "function") {
|
|
5
|
+
nodeModule.enableCompileCache();
|
|
6
|
+
}
|
|
7
|
+
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
8
|
+
import crypto from "node:crypto";
|
|
9
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, statSync, unlinkSync, writeFileSync, } from "node:fs";
|
|
10
|
+
import os from "node:os";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import readline from "node:readline";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import { getAnalyticsPref, setAnalyticsPref } from "./analyticsPrefs.js";
|
|
15
|
+
import { Bridge } from "./bridge.js";
|
|
16
|
+
import { isBridgeToolsFileValid, repairBridgeToolsRulesIfStale, } from "./bridgeToolsRules.js";
|
|
17
|
+
import { findEditor, parseConfig } from "./config.js";
|
|
18
|
+
import { PACKAGE_VERSION, semverGt } from "./version.js";
|
|
19
|
+
const __dirnameTop = path.dirname(fileURLToPath(import.meta.url));
|
|
20
|
+
const OPEN_VSX_PUBLISHER = "oolab-labs";
|
|
21
|
+
const OPEN_VSX_NAME = "claude-ide-bridge-extension";
|
|
22
|
+
// CLAUDE.md versioned-block patching moved to ./claudeMdPatch.ts so tests
|
|
23
|
+
// can import the helpers without triggering the top-level CLI side effects
|
|
24
|
+
// at the bottom of this file. Re-exported here for back-compat.
|
|
25
|
+
export { BRIDGE_BLOCK_END, BRIDGE_BLOCK_RE, bridgeBlockStartMarker, extractClaudeMdBlockVersion, patchClaudeMdImport, } from "./claudeMdPatch.js";
|
|
26
|
+
import { extractClaudeMdBlockVersion, patchClaudeMdImport, } from "./claudeMdPatch.js";
|
|
27
|
+
/**
|
|
28
|
+
* Downloads the latest VSIX from Open VSX Registry to a temp file.
|
|
29
|
+
* Returns the temp file path (caller is responsible for deleting it).
|
|
30
|
+
* Throws on network or API errors.
|
|
31
|
+
*/
|
|
32
|
+
async function downloadVsixFromOpenVsx() {
|
|
33
|
+
const metaUrl = `https://open-vsx.org/api/${OPEN_VSX_PUBLISHER}/${OPEN_VSX_NAME}`;
|
|
34
|
+
const metaRes = await fetch(metaUrl);
|
|
35
|
+
if (!metaRes.ok) {
|
|
36
|
+
throw new Error(`Open VSX metadata request failed: ${metaRes.status} ${metaRes.statusText}`);
|
|
37
|
+
}
|
|
38
|
+
const meta = (await metaRes.json());
|
|
39
|
+
const downloadUrl = meta?.files?.download;
|
|
40
|
+
if (typeof downloadUrl !== "string" || !downloadUrl.startsWith("https://")) {
|
|
41
|
+
throw new Error("Open VSX response missing files.download URL");
|
|
42
|
+
}
|
|
43
|
+
const version = meta?.version ?? "unknown";
|
|
44
|
+
process.stderr.write(` Downloading extension v${version} from Open VSX...\n`);
|
|
45
|
+
const vsixRes = await fetch(downloadUrl);
|
|
46
|
+
if (!vsixRes.ok) {
|
|
47
|
+
throw new Error(`Open VSX download failed: ${vsixRes.status} ${vsixRes.statusText}`);
|
|
48
|
+
}
|
|
49
|
+
const tmpPath = path.join(os.tmpdir(), `${OPEN_VSX_NAME}-${version}-${Date.now()}.vsix`);
|
|
50
|
+
const buf = await vsixRes.arrayBuffer();
|
|
51
|
+
writeFileSync(tmpPath, Buffer.from(buf));
|
|
52
|
+
return tmpPath;
|
|
53
|
+
}
|
|
54
|
+
// Handle --version flag — print package version and exit.
|
|
55
|
+
if (process.argv[2] === "--version" || process.argv[2] === "-v") {
|
|
56
|
+
console.log(`claude-ide-bridge ${PACKAGE_VERSION}`);
|
|
57
|
+
process.exit(0);
|
|
58
|
+
}
|
|
59
|
+
// Handle patchwork-init subcommand — T2 from docs/install-ux-plan.md.
|
|
60
|
+
// Separate from the bridge-only `init` to preserve back-compat. See ADR-0008.
|
|
61
|
+
if (process.argv[2] === "patchwork-init") {
|
|
62
|
+
const { runPatchworkInit } = await import("./commands/patchworkInit.js");
|
|
63
|
+
await runPatchworkInit(process.argv.slice(3));
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
// Handle start-all subcommand — launches the full 3-pane tmux orchestrator.
|
|
67
|
+
// Also triggered when invoked as `claude-ide-bridge-start` directly.
|
|
68
|
+
const isStartAll = process.argv[2] === "start-all" ||
|
|
69
|
+
path.basename(process.argv[1] ?? "").startsWith("claude-ide-bridge-start");
|
|
70
|
+
if (isStartAll) {
|
|
71
|
+
const startAllArgs = process.argv[2] === "start-all"
|
|
72
|
+
? process.argv.slice(3)
|
|
73
|
+
: process.argv.slice(2);
|
|
74
|
+
const scriptPath = path.resolve(__dirnameTop, "..", "scripts", "start-all.sh");
|
|
75
|
+
const result = spawnSync("bash", [scriptPath, ...startAllArgs], {
|
|
76
|
+
stdio: "inherit",
|
|
77
|
+
});
|
|
78
|
+
process.exit(result.status ?? 1);
|
|
79
|
+
}
|
|
80
|
+
function writeRulesFileAtomic(rulesFilePath, content) {
|
|
81
|
+
const tmpPath = `${rulesFilePath}.tmp`;
|
|
82
|
+
writeFileSync(tmpPath, content, { encoding: "utf-8", flag: "wx" });
|
|
83
|
+
try {
|
|
84
|
+
renameSync(tmpPath, rulesFilePath);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
try {
|
|
88
|
+
unlinkSync(tmpPath);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
/* best-effort cleanup */
|
|
92
|
+
}
|
|
93
|
+
throw err;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Handles errors from rules file write operations. EACCES → warning + instructions.
|
|
98
|
+
* ELOOP → hard error (symlink cycle — indicates possible symlink attack).
|
|
99
|
+
* EEXIST → hard error (wx exclusive-create failed — indicates a symlink was placed
|
|
100
|
+
* at the .tmp path, since we pre-clean stale .tmp files before every wx write).
|
|
101
|
+
* Others → warning. Returns the exit code to use (0 for warnings, 1 for hard errors).
|
|
102
|
+
*/
|
|
103
|
+
function handleRulesWriteError(err, rulesFilePath, indent) {
|
|
104
|
+
const code = err.code;
|
|
105
|
+
if (code === "EACCES") {
|
|
106
|
+
process.stderr.write(`${indent}[warn] Bridge rules — permission denied writing to ${rulesFilePath}.\n` +
|
|
107
|
+
`${indent} Run with elevated permissions or create the file manually.\n\n`);
|
|
108
|
+
return 0;
|
|
109
|
+
}
|
|
110
|
+
if (code === "ELOOP" || code === "EEXIST") {
|
|
111
|
+
process.stderr.write(`${indent}[error] Bridge rules — suspicious path condition (${code}): ${rulesFilePath}\n\n`);
|
|
112
|
+
return 1;
|
|
113
|
+
}
|
|
114
|
+
process.stderr.write(`${indent}[warn] Bridge rules — write failed (${code ?? String(err)})\n\n`);
|
|
115
|
+
return 0;
|
|
116
|
+
}
|
|
117
|
+
// Handle gen-claude-md subcommand — generates a CLAUDE.md bridge workflow section
|
|
118
|
+
if (process.argv[2] === "gen-claude-md") {
|
|
119
|
+
const argv = process.argv.slice(3);
|
|
120
|
+
if (argv.includes("--help")) {
|
|
121
|
+
console.log(`claude-ide-bridge gen-claude-md — Generate CLAUDE.md bridge section
|
|
122
|
+
|
|
123
|
+
Usage: claude-ide-bridge gen-claude-md [options]
|
|
124
|
+
|
|
125
|
+
Options:
|
|
126
|
+
--write Write to CLAUDE.md in the workspace (default: print to stdout)
|
|
127
|
+
--workspace <path> Target workspace folder (default: cwd)
|
|
128
|
+
--help Show this help`);
|
|
129
|
+
process.exit(0);
|
|
130
|
+
}
|
|
131
|
+
const writeToDisk = argv.includes("--write");
|
|
132
|
+
const workspaceIdx = argv.indexOf("--workspace");
|
|
133
|
+
const workspace = workspaceIdx !== -1 && argv[workspaceIdx + 1]
|
|
134
|
+
? argv[workspaceIdx + 1]
|
|
135
|
+
: process.cwd();
|
|
136
|
+
const templatePath = path.resolve(__dirnameTop, "..", "templates", "CLAUDE.bridge.md");
|
|
137
|
+
let content;
|
|
138
|
+
try {
|
|
139
|
+
content = readFileSync(templatePath, "utf-8");
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
process.stderr.write(`Error: template not found at ${templatePath}\n`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
if (!writeToDisk) {
|
|
146
|
+
process.stdout.write(`${content}\n`);
|
|
147
|
+
process.stderr.write("Note: run with --write to append this section to CLAUDE.md and also write .claude/rules/bridge-tools.md\n");
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
const targetPath = path.join(workspace, "CLAUDE.md");
|
|
151
|
+
const marker = "## Claude IDE Bridge";
|
|
152
|
+
const IMPORT_LINE = "@import .claude/rules/bridge-tools.md";
|
|
153
|
+
// Idempotent: skip if the section already exists (with @import line)
|
|
154
|
+
const patchResult = patchClaudeMdImport(targetPath, marker, IMPORT_LINE);
|
|
155
|
+
if (patchResult === "already-current") {
|
|
156
|
+
process.stderr.write(`CLAUDE.md bridge block already up to date (v${PACKAGE_VERSION}) — no changes made.\n`);
|
|
157
|
+
repairBridgeToolsRulesIfStale(workspace, undefined, {
|
|
158
|
+
writeIfMissing: true,
|
|
159
|
+
});
|
|
160
|
+
process.exit(0);
|
|
161
|
+
}
|
|
162
|
+
if (patchResult === "updated") {
|
|
163
|
+
process.stderr.write(`Updated CLAUDE.md bridge block to v${PACKAGE_VERSION}.\n`);
|
|
164
|
+
repairBridgeToolsRulesIfStale(workspace, undefined, {
|
|
165
|
+
writeIfMissing: true,
|
|
166
|
+
});
|
|
167
|
+
process.exit(0);
|
|
168
|
+
}
|
|
169
|
+
if (patchResult === "patched") {
|
|
170
|
+
process.stderr.write(`Patched existing CLAUDE.md — added missing @import line (v${PACKAGE_VERSION}).\n`);
|
|
171
|
+
repairBridgeToolsRulesIfStale(workspace, undefined, {
|
|
172
|
+
writeIfMissing: true,
|
|
173
|
+
});
|
|
174
|
+
process.exit(0);
|
|
175
|
+
}
|
|
176
|
+
if (patchResult === "already-present") {
|
|
177
|
+
process.stderr.write(`CLAUDE.md already contains a '${marker}' section — no changes made.\n`);
|
|
178
|
+
repairBridgeToolsRulesIfStale(workspace, undefined, {
|
|
179
|
+
writeIfMissing: true,
|
|
180
|
+
});
|
|
181
|
+
process.exit(0);
|
|
182
|
+
}
|
|
183
|
+
// Wrap the template content in a versioned block so future re-runs can detect the version stamp.
|
|
184
|
+
const versionedGenContent = content
|
|
185
|
+
.trimEnd()
|
|
186
|
+
.replace(marker, `<!-- claude-ide-bridge:start:${PACKAGE_VERSION} -->\n${marker}`)
|
|
187
|
+
.concat(`\n<!-- claude-ide-bridge:end -->`);
|
|
188
|
+
if (existsSync(targetPath)) {
|
|
189
|
+
const existing = readFileSync(targetPath, "utf-8");
|
|
190
|
+
// Write tmp first with exclusive-create — if the write fails, the original is intact
|
|
191
|
+
const updated = `${existing.trimEnd()}\n\n${versionedGenContent}\n`;
|
|
192
|
+
const tmpPath = `${targetPath}.tmp`;
|
|
193
|
+
writeFileSync(tmpPath, updated, { encoding: "utf-8", flag: "wx" });
|
|
194
|
+
// Backup existing file before replacing
|
|
195
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
196
|
+
const backupPath = `${targetPath}.${ts}.bak`;
|
|
197
|
+
try {
|
|
198
|
+
renameSync(targetPath, backupPath);
|
|
199
|
+
renameSync(tmpPath, targetPath);
|
|
200
|
+
}
|
|
201
|
+
catch (err) {
|
|
202
|
+
try {
|
|
203
|
+
unlinkSync(tmpPath);
|
|
204
|
+
}
|
|
205
|
+
catch {
|
|
206
|
+
/* best-effort cleanup */
|
|
207
|
+
}
|
|
208
|
+
throw err;
|
|
209
|
+
}
|
|
210
|
+
process.stderr.write(`Backed up existing CLAUDE.md to ${backupPath}\n`);
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
mkdirSync(workspace, { recursive: true });
|
|
214
|
+
writeFileSync(`${targetPath}.tmp`, `${versionedGenContent}\n`, {
|
|
215
|
+
encoding: "utf-8",
|
|
216
|
+
flag: "wx",
|
|
217
|
+
});
|
|
218
|
+
renameSync(`${targetPath}.tmp`, targetPath);
|
|
219
|
+
}
|
|
220
|
+
process.stderr.write(`✓ Bridge workflow section written to ${targetPath} (v${PACKAGE_VERSION})\n`);
|
|
221
|
+
// Also write bridge-tools rules file alongside CLAUDE.md
|
|
222
|
+
repairBridgeToolsRulesIfStale(workspace, undefined, { writeIfMissing: true });
|
|
223
|
+
process.exit(0);
|
|
224
|
+
}
|
|
225
|
+
// Handle install subcommand — install a companion MCP server
|
|
226
|
+
if (process.argv[2] === "install") {
|
|
227
|
+
const { runInstall } = await import("./commands/install.js");
|
|
228
|
+
await runInstall(process.argv.slice(3));
|
|
229
|
+
process.exit(0);
|
|
230
|
+
}
|
|
231
|
+
// Handle marketplace subcommand — browse and install community skills
|
|
232
|
+
if (process.argv[2] === "marketplace") {
|
|
233
|
+
const { runMarketplace } = await import("./commands/marketplace.js");
|
|
234
|
+
await runMarketplace(process.argv.slice(3));
|
|
235
|
+
process.exit(0);
|
|
236
|
+
}
|
|
237
|
+
// Handle tools subcommand — search/list tools without a bridge connection
|
|
238
|
+
if (process.argv[2] === "tools") {
|
|
239
|
+
const { runToolsCommand } = await import("./commands/tools.js");
|
|
240
|
+
await runToolsCommand(process.argv.slice(3));
|
|
241
|
+
process.exit(0);
|
|
242
|
+
}
|
|
243
|
+
// Headless parity subcommands — launch Claude tasks from CLI (no sidebar/VS Code required).
|
|
244
|
+
// Reuse the bridge's running process; no new dependencies.
|
|
245
|
+
if (process.argv[2] === "quick-task") {
|
|
246
|
+
const { runQuickTask } = await import("./commands/task.js");
|
|
247
|
+
await runQuickTask(process.argv.slice(3));
|
|
248
|
+
// runQuickTask calls process.exit() itself
|
|
249
|
+
}
|
|
250
|
+
if (process.argv[2] === "start-task") {
|
|
251
|
+
const { runStartTask } = await import("./commands/task.js");
|
|
252
|
+
await runStartTask(process.argv.slice(3));
|
|
253
|
+
}
|
|
254
|
+
if (process.argv[2] === "continue-handoff") {
|
|
255
|
+
const { runContinueHandoff } = await import("./commands/task.js");
|
|
256
|
+
await runContinueHandoff(process.argv.slice(3));
|
|
257
|
+
}
|
|
258
|
+
// Handle print-token subcommand — print the bridge auth token from a lock file
|
|
259
|
+
if (process.argv[2] === "print-token") {
|
|
260
|
+
const argv = process.argv.slice(3);
|
|
261
|
+
if (argv.includes("--help")) {
|
|
262
|
+
console.log(`claude-ide-bridge print-token — Print bridge auth token
|
|
263
|
+
|
|
264
|
+
Usage: claude-ide-bridge print-token [options]
|
|
265
|
+
|
|
266
|
+
Options:
|
|
267
|
+
--port <number> Read token from a specific port's lock file (default: most recent)
|
|
268
|
+
--help Show this help`);
|
|
269
|
+
process.exit(0);
|
|
270
|
+
}
|
|
271
|
+
const portIdx = argv.indexOf("--port");
|
|
272
|
+
const portArg = portIdx !== -1 ? argv[portIdx + 1] : undefined;
|
|
273
|
+
const lockDir = path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"), "ide");
|
|
274
|
+
let lockFile;
|
|
275
|
+
if (portArg) {
|
|
276
|
+
lockFile = path.join(lockDir, `${portArg}.lock`);
|
|
277
|
+
if (!existsSync(lockFile)) {
|
|
278
|
+
process.stderr.write(`Error: No lock file found for port ${portArg} at ${lockFile}\n`);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
else {
|
|
283
|
+
// Find the most recently modified lock file
|
|
284
|
+
let bestMtime = 0;
|
|
285
|
+
try {
|
|
286
|
+
for (const f of readdirSync(lockDir)) {
|
|
287
|
+
if (!f.endsWith(".lock"))
|
|
288
|
+
continue;
|
|
289
|
+
const full = path.join(lockDir, f);
|
|
290
|
+
const mtime = statSync(full).mtimeMs;
|
|
291
|
+
if (mtime > bestMtime) {
|
|
292
|
+
bestMtime = mtime;
|
|
293
|
+
lockFile = full;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
catch {
|
|
298
|
+
// lock dir doesn't exist — handled below
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!lockFile) {
|
|
302
|
+
process.stderr.write(`Error: No bridge lock file found in ${lockDir}\n`);
|
|
303
|
+
process.stderr.write("Make sure the bridge is running first, or pass --port <port>.\n");
|
|
304
|
+
process.exit(1);
|
|
305
|
+
}
|
|
306
|
+
try {
|
|
307
|
+
const data = JSON.parse(readFileSync(lockFile, "utf-8"));
|
|
308
|
+
if (!data.authToken) {
|
|
309
|
+
process.stderr.write(`Error: Lock file ${lockFile} has no authToken field\n`);
|
|
310
|
+
process.exit(1);
|
|
311
|
+
}
|
|
312
|
+
process.stdout.write(`${data.authToken}\n`);
|
|
313
|
+
}
|
|
314
|
+
catch {
|
|
315
|
+
process.stderr.write(`Error: Could not read lock file ${lockFile}\n`);
|
|
316
|
+
process.exit(1);
|
|
317
|
+
}
|
|
318
|
+
process.exit(0);
|
|
319
|
+
}
|
|
320
|
+
// Handle notify subcommand — called from CC hooks to fire bridge automation
|
|
321
|
+
// Usage: claude-ide-bridge notify <CcEventName> [--cwd <p>] [--taskId <id>] [--prompt <t>] [--tool <n>] [--reason <r>] [--port <n>]
|
|
322
|
+
if (process.argv[2] === "notify") {
|
|
323
|
+
const VALID_NOTIFY_EVENTS = new Set([
|
|
324
|
+
"PreCompact",
|
|
325
|
+
"PostCompact",
|
|
326
|
+
"InstructionsLoaded",
|
|
327
|
+
"TaskCreated",
|
|
328
|
+
"PermissionDenied",
|
|
329
|
+
"CwdChanged",
|
|
330
|
+
]);
|
|
331
|
+
const notifyArgv = process.argv.slice(3);
|
|
332
|
+
const ccEvent = notifyArgv[0];
|
|
333
|
+
if (!ccEvent || !VALID_NOTIFY_EVENTS.has(ccEvent)) {
|
|
334
|
+
process.stderr.write(`Usage: claude-ide-bridge notify <CcEventName> [options]\n\nValid events: ${[...VALID_NOTIFY_EVENTS].join(", ")}\n`);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
const notifyRest = notifyArgv.slice(1);
|
|
338
|
+
const namedArgs = {};
|
|
339
|
+
for (let i = 0; i < notifyRest.length - 1; i++) {
|
|
340
|
+
const arg = notifyRest[i];
|
|
341
|
+
if (arg?.startsWith("--")) {
|
|
342
|
+
const key = arg.slice(2);
|
|
343
|
+
const val = notifyRest[i + 1] ?? "";
|
|
344
|
+
namedArgs[key] = val;
|
|
345
|
+
i++;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
const notifyLockDir = path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"), "ide");
|
|
349
|
+
let notifyLockFile;
|
|
350
|
+
let notifyPort;
|
|
351
|
+
if (namedArgs.port) {
|
|
352
|
+
notifyPort = Number(namedArgs.port);
|
|
353
|
+
notifyLockFile = path.join(notifyLockDir, `${notifyPort}.lock`);
|
|
354
|
+
if (!existsSync(notifyLockFile)) {
|
|
355
|
+
process.stderr.write(`Error: No lock file found for port ${notifyPort}\n`);
|
|
356
|
+
process.exit(1);
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
let bestMtime = 0;
|
|
361
|
+
try {
|
|
362
|
+
for (const f of readdirSync(notifyLockDir)) {
|
|
363
|
+
if (!f.endsWith(".lock"))
|
|
364
|
+
continue;
|
|
365
|
+
const full = path.join(notifyLockDir, f);
|
|
366
|
+
const mtime = statSync(full).mtimeMs;
|
|
367
|
+
if (mtime > bestMtime) {
|
|
368
|
+
bestMtime = mtime;
|
|
369
|
+
notifyLockFile = full;
|
|
370
|
+
notifyPort = Number(path.basename(f, ".lock"));
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
catch {
|
|
375
|
+
// lock dir doesn't exist
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
if (!notifyLockFile || !notifyPort) {
|
|
379
|
+
process.stderr.write(`Error: No bridge lock file found in ${notifyLockDir}\n`);
|
|
380
|
+
process.stderr.write("Make sure the bridge is running first (claude-ide-bridge --watch ...).\n");
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
let notifyToken;
|
|
384
|
+
try {
|
|
385
|
+
const data = JSON.parse(readFileSync(notifyLockFile, "utf-8"));
|
|
386
|
+
if (!data.authToken) {
|
|
387
|
+
process.stderr.write(`Error: Lock file has no authToken field\n`);
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
notifyToken = data.authToken;
|
|
391
|
+
}
|
|
392
|
+
catch {
|
|
393
|
+
process.stderr.write(`Error: Could not read lock file ${notifyLockFile}\n`);
|
|
394
|
+
process.exit(1);
|
|
395
|
+
}
|
|
396
|
+
try {
|
|
397
|
+
const resp = await fetch(`http://127.0.0.1:${notifyPort}/notify`, {
|
|
398
|
+
method: "POST",
|
|
399
|
+
headers: {
|
|
400
|
+
Authorization: `Bearer ${notifyToken}`,
|
|
401
|
+
"Content-Type": "application/json",
|
|
402
|
+
},
|
|
403
|
+
body: JSON.stringify({ event: ccEvent, args: namedArgs }),
|
|
404
|
+
signal: AbortSignal.timeout(5_000),
|
|
405
|
+
});
|
|
406
|
+
if (!resp.ok) {
|
|
407
|
+
const text = await resp.text().catch(() => "");
|
|
408
|
+
process.stderr.write(`Error: Bridge returned ${resp.status}: ${text}\n`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
catch (err) {
|
|
413
|
+
process.stderr.write(`Error: Could not connect to bridge at port ${notifyPort}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
process.exit(0);
|
|
417
|
+
}
|
|
418
|
+
// Handle token-efficiency subcommand — show config/session usage or run benchmark
|
|
419
|
+
if (process.argv[2] === "token-efficiency") {
|
|
420
|
+
const teArgv = process.argv.slice(3);
|
|
421
|
+
const teSubCommand = teArgv[0] ?? "status";
|
|
422
|
+
if (teSubCommand === "--help" || teArgv.includes("--help")) {
|
|
423
|
+
console.log(`claude-ide-bridge token-efficiency — Token usage tools
|
|
424
|
+
|
|
425
|
+
Usage: claude-ide-bridge token-efficiency [status|benchmark] [options]
|
|
426
|
+
|
|
427
|
+
Subcommands:
|
|
428
|
+
status Show current config + live session usage (default)
|
|
429
|
+
benchmark [args...] Run benchmark against a running bridge
|
|
430
|
+
--iterations N Number of iterations (default: 50)
|
|
431
|
+
--json Emit JSON output
|
|
432
|
+
--threshold <ms> Fail if p99 RTT exceeds threshold
|
|
433
|
+
|
|
434
|
+
Options:
|
|
435
|
+
--help Show this help`);
|
|
436
|
+
process.exit(0);
|
|
437
|
+
}
|
|
438
|
+
const { tokenEfficiencyStatus, tokenEfficiencyBenchmark } = await import("./commands/tokenEfficiency.js");
|
|
439
|
+
if (teSubCommand === "benchmark") {
|
|
440
|
+
await tokenEfficiencyBenchmark(teArgv.slice(1));
|
|
441
|
+
}
|
|
442
|
+
else {
|
|
443
|
+
// status (default)
|
|
444
|
+
await tokenEfficiencyStatus();
|
|
445
|
+
}
|
|
446
|
+
process.exit(0);
|
|
447
|
+
}
|
|
448
|
+
// Handle gen-plugin-stub subcommand — scaffolds a new plugin directory
|
|
449
|
+
if (process.argv[2] === "gen-plugin-stub") {
|
|
450
|
+
const argv = process.argv.slice(3);
|
|
451
|
+
// Parse args: gen-plugin-stub <dir> [--name <name>] [--prefix <prefix>]
|
|
452
|
+
const dirArg = argv.find((a) => !a.startsWith("--"));
|
|
453
|
+
if (!dirArg) {
|
|
454
|
+
process.stderr.write("Usage: claude-ide-bridge gen-plugin-stub <output-dir> [--name <org/plugin-name>] [--prefix <toolPrefix>]\n");
|
|
455
|
+
process.exit(1);
|
|
456
|
+
}
|
|
457
|
+
const nameIdx = argv.indexOf("--name");
|
|
458
|
+
const prefixIdx = argv.indexOf("--prefix");
|
|
459
|
+
const pluginName = nameIdx !== -1 && argv[nameIdx + 1]
|
|
460
|
+
? argv[nameIdx + 1]
|
|
461
|
+
: "my-org/my-plugin";
|
|
462
|
+
const toolPrefix = prefixIdx !== -1 && argv[prefixIdx + 1]
|
|
463
|
+
? argv[prefixIdx + 1]
|
|
464
|
+
: "myPlugin";
|
|
465
|
+
// Validate name format
|
|
466
|
+
if (!/^[a-zA-Z0-9@._/-]{1,100}$/.test(pluginName)) {
|
|
467
|
+
process.stderr.write(`Error: --name "${pluginName}" contains invalid characters. Use only letters, numbers, @, ., _, /, -.\n`);
|
|
468
|
+
process.exit(1);
|
|
469
|
+
}
|
|
470
|
+
// Validate prefix format
|
|
471
|
+
if (!/^[a-zA-Z][a-zA-Z0-9_]{1,19}$/.test(toolPrefix)) {
|
|
472
|
+
process.stderr.write(`Error: --prefix "${toolPrefix}" is invalid. Must match /^[a-zA-Z][a-zA-Z0-9_]{1,19}$/ (2–20 chars, start with a letter).\n`);
|
|
473
|
+
process.exit(1);
|
|
474
|
+
}
|
|
475
|
+
const outDir = path.resolve(dirArg);
|
|
476
|
+
if (existsSync(outDir)) {
|
|
477
|
+
process.stderr.write(`Error: "${outDir}" already exists. Choose a new directory.\n`);
|
|
478
|
+
process.exit(1);
|
|
479
|
+
}
|
|
480
|
+
mkdirSync(outDir, { recursive: true });
|
|
481
|
+
// claude-ide-bridge-plugin.json
|
|
482
|
+
const manifest = {
|
|
483
|
+
schemaVersion: 1,
|
|
484
|
+
name: pluginName,
|
|
485
|
+
version: "0.1.0",
|
|
486
|
+
description: "A Claude IDE Bridge plugin",
|
|
487
|
+
entrypoint: "./index.mjs",
|
|
488
|
+
toolNamePrefix: toolPrefix,
|
|
489
|
+
minBridgeVersion: "2.1.24",
|
|
490
|
+
};
|
|
491
|
+
writeFileSync(path.join(outDir, "claude-ide-bridge-plugin.json"), `${JSON.stringify(manifest, null, 2)}\n`, "utf-8");
|
|
492
|
+
// index.mjs entrypoint
|
|
493
|
+
const entrypoint = `/**
|
|
494
|
+
* ${pluginName} — Claude IDE Bridge plugin
|
|
495
|
+
*
|
|
496
|
+
* Each tool must have a name starting with "${toolPrefix}".
|
|
497
|
+
* The \`ctx\` object provides: ctx.workspace, ctx.workspaceFolders,
|
|
498
|
+
* ctx.config (commandTimeout, maxResultSize), and ctx.logger.
|
|
499
|
+
*/
|
|
500
|
+
|
|
501
|
+
/** @param {import('claude-ide-bridge/plugin').PluginContext} ctx */
|
|
502
|
+
export function register(ctx) {
|
|
503
|
+
ctx.logger.info(${JSON.stringify(`${pluginName} loaded`)}, { workspace: ctx.workspace });
|
|
504
|
+
|
|
505
|
+
return {
|
|
506
|
+
tools: [
|
|
507
|
+
{
|
|
508
|
+
schema: {
|
|
509
|
+
name: ${JSON.stringify(`${toolPrefix}Hello`)},
|
|
510
|
+
description: "Example tool — returns a greeting",
|
|
511
|
+
inputSchema: {
|
|
512
|
+
type: "object",
|
|
513
|
+
required: ["name"],
|
|
514
|
+
additionalProperties: false,
|
|
515
|
+
properties: {
|
|
516
|
+
name: { type: "string", description: "Name to greet" },
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
annotations: { readOnlyHint: true },
|
|
520
|
+
},
|
|
521
|
+
handler: async (args, _signal) => ({
|
|
522
|
+
content: [{ type: "text", text: "Hello from " + ${JSON.stringify(pluginName)} + ", " + args.name + "!" }],
|
|
523
|
+
}),
|
|
524
|
+
},
|
|
525
|
+
],
|
|
526
|
+
};
|
|
527
|
+
}
|
|
528
|
+
`;
|
|
529
|
+
writeFileSync(path.join(outDir, "index.mjs"), entrypoint, "utf-8");
|
|
530
|
+
// package.json (optional, for npm publishing)
|
|
531
|
+
const pkg = {
|
|
532
|
+
name: pluginName.replace(/^@[^/]+\//, "").replace(/\//g, "-"),
|
|
533
|
+
version: "0.1.0",
|
|
534
|
+
description: "A Claude IDE Bridge plugin",
|
|
535
|
+
type: "module",
|
|
536
|
+
main: "index.mjs",
|
|
537
|
+
keywords: ["claude-ide-bridge", "claude-ide-bridge-plugin"],
|
|
538
|
+
peerDependencies: { "claude-ide-bridge": ">=2.1.24" },
|
|
539
|
+
};
|
|
540
|
+
writeFileSync(path.join(outDir, "package.json"), `${JSON.stringify(pkg, null, 2)}\n`, "utf-8");
|
|
541
|
+
// .gitignore
|
|
542
|
+
writeFileSync(path.join(outDir, ".gitignore"), "node_modules\n", "utf-8");
|
|
543
|
+
process.stderr.write(`✓ Plugin stub created at ${outDir}\n`);
|
|
544
|
+
process.stderr.write("\nNext steps:\n");
|
|
545
|
+
process.stderr.write(` 1. Edit ${path.join(outDir, "index.mjs")} to implement your tools\n`);
|
|
546
|
+
process.stderr.write(` 2. Run the bridge with: claude-ide-bridge --plugin ${outDir}\n`);
|
|
547
|
+
process.stderr.write(` 3. Or add to your config: { "plugins": ["${outDir}"] }\n`);
|
|
548
|
+
process.exit(0);
|
|
549
|
+
}
|
|
550
|
+
// Patchwork: `patchwork recipe run <name>` — POSTs to a running bridge's
|
|
551
|
+
// /recipes/run endpoint to enqueue the recipe via the Claude orchestrator.
|
|
552
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "run") {
|
|
553
|
+
const name = process.argv[4];
|
|
554
|
+
if (!name) {
|
|
555
|
+
process.stderr.write("Usage: patchwork recipe run <name>\n");
|
|
556
|
+
process.exit(1);
|
|
557
|
+
}
|
|
558
|
+
(async () => {
|
|
559
|
+
try {
|
|
560
|
+
const { findBridgeLock } = await import("./bridgeLockDiscovery.js");
|
|
561
|
+
const lock = findBridgeLock();
|
|
562
|
+
if (!lock) {
|
|
563
|
+
process.stderr.write("Error: no running bridge found under ~/.claude/ide/. Start the bridge with --claude-driver subprocess first.\n");
|
|
564
|
+
process.exit(1);
|
|
565
|
+
return;
|
|
566
|
+
}
|
|
567
|
+
const res = await fetch(`http://127.0.0.1:${lock.port}/recipes/run`, {
|
|
568
|
+
method: "POST",
|
|
569
|
+
headers: {
|
|
570
|
+
Authorization: `Bearer ${lock.authToken}`,
|
|
571
|
+
"Content-Type": "application/json",
|
|
572
|
+
},
|
|
573
|
+
body: JSON.stringify({ name }),
|
|
574
|
+
});
|
|
575
|
+
const body = (await res.json());
|
|
576
|
+
if (!body.ok) {
|
|
577
|
+
process.stderr.write(`Error: ${body.error ?? "unknown"}\n`);
|
|
578
|
+
process.exit(1);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
process.stdout.write(` ✓ enqueued recipe "${name}" as task ${(body.taskId ?? "").slice(0, 8)}\n` +
|
|
582
|
+
" Watch progress on the dashboard Tasks page or via listClaudeTasks.\n");
|
|
583
|
+
process.exit(0);
|
|
584
|
+
}
|
|
585
|
+
catch (err) {
|
|
586
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
587
|
+
process.exit(1);
|
|
588
|
+
}
|
|
589
|
+
})();
|
|
590
|
+
}
|
|
591
|
+
// Handle init subcommand — one-command setup: install extension + write CLAUDE.md + print next steps
|
|
592
|
+
// Patchwork: `patchwork recipe install <file.json>` subcommand.
|
|
593
|
+
if (process.argv[2] === "recipe" && process.argv[3] === "install") {
|
|
594
|
+
const file = process.argv[4];
|
|
595
|
+
if (!file) {
|
|
596
|
+
process.stderr.write("Usage: patchwork recipe install <file.json>\n");
|
|
597
|
+
process.exit(1);
|
|
598
|
+
}
|
|
599
|
+
(async () => {
|
|
600
|
+
try {
|
|
601
|
+
const { installRecipeFromFile } = await import("./recipes/installer.js");
|
|
602
|
+
const recipesDir = path.join(os.homedir(), ".patchwork", "recipes");
|
|
603
|
+
const result = installRecipeFromFile(path.resolve(file), {
|
|
604
|
+
recipesDir,
|
|
605
|
+
});
|
|
606
|
+
process.stdout.write(` ✓ ${result.action} ${result.installedPath}\n` +
|
|
607
|
+
` ℹ permissions snippet written to ${result.installedPath}.permissions.json\n` +
|
|
608
|
+
` Review + merge into ~/.claude/settings.json to pre-approve recipe steps.\n`);
|
|
609
|
+
process.exit(0);
|
|
610
|
+
}
|
|
611
|
+
catch (err) {
|
|
612
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
613
|
+
process.exit(1);
|
|
614
|
+
}
|
|
615
|
+
})();
|
|
616
|
+
}
|
|
617
|
+
if (process.argv[2] === "init") {
|
|
618
|
+
const argv = process.argv.slice(3);
|
|
619
|
+
// Handle init --help
|
|
620
|
+
if (argv.includes("--help")) {
|
|
621
|
+
console.log(`claude-ide-bridge init — One-command setup
|
|
622
|
+
|
|
623
|
+
Usage: claude-ide-bridge init [options]
|
|
624
|
+
|
|
625
|
+
Options:
|
|
626
|
+
--workspace <path> Target workspace folder (default: cwd)
|
|
627
|
+
--help Show this help
|
|
628
|
+
|
|
629
|
+
Steps performed:
|
|
630
|
+
1. Install the companion VS Code extension
|
|
631
|
+
2. Write bridge section to CLAUDE.md
|
|
632
|
+
3. Write .claude/rules/bridge-tools.md
|
|
633
|
+
4. Register MCP shim in ~/.claude.json
|
|
634
|
+
5. Wire CC automation hooks in ~/.claude/settings.json
|
|
635
|
+
6. Verify claude-ide-bridge is on PATH
|
|
636
|
+
7. Print next steps`);
|
|
637
|
+
process.exit(0);
|
|
638
|
+
}
|
|
639
|
+
const workspaceIdx = argv.indexOf("--workspace");
|
|
640
|
+
const workspace = workspaceIdx !== -1 && argv[workspaceIdx + 1]
|
|
641
|
+
? path.resolve(argv[workspaceIdx + 1])
|
|
642
|
+
: process.cwd();
|
|
643
|
+
process.stderr.write("Claude IDE Bridge — setup\n\n");
|
|
644
|
+
// WSL detection: warn early if running in WSL without a detected editor
|
|
645
|
+
const isWsl = process.platform === "linux" &&
|
|
646
|
+
(process.env.WSL_DISTRO_NAME !== undefined ||
|
|
647
|
+
process.env.WSLENV !== undefined ||
|
|
648
|
+
(() => {
|
|
649
|
+
try {
|
|
650
|
+
return readFileSync("/proc/version", "utf-8")
|
|
651
|
+
.toLowerCase()
|
|
652
|
+
.includes("microsoft");
|
|
653
|
+
}
|
|
654
|
+
catch {
|
|
655
|
+
return false;
|
|
656
|
+
}
|
|
657
|
+
})());
|
|
658
|
+
// Step 1: Install extension
|
|
659
|
+
const editor = findEditor();
|
|
660
|
+
if (!editor) {
|
|
661
|
+
const wslHint = isWsl
|
|
662
|
+
? "\n WSL detected: ensure VS Code is installed on the Windows host and\n" +
|
|
663
|
+
" the Remote - WSL extension is active. Then re-run init.\n" +
|
|
664
|
+
" Alternatively, pass the editor explicitly: claude-ide-bridge install-extension code\n"
|
|
665
|
+
: "";
|
|
666
|
+
process.stderr.write(` [skip] Extension install — no supported editor found on PATH.\n Install manually: https://open-vsx.org/extension/${OPEN_VSX_PUBLISHER}/${OPEN_VSX_NAME}\n${wslHint}\n`);
|
|
667
|
+
}
|
|
668
|
+
else {
|
|
669
|
+
process.stderr.write(` Installing extension into ${editor}...\n`);
|
|
670
|
+
const __dirname2 = path.dirname(fileURLToPath(import.meta.url));
|
|
671
|
+
const vsixDir = path.resolve(__dirname2, "..", "vscode-extension");
|
|
672
|
+
let localVsix2;
|
|
673
|
+
if (existsSync(vsixDir)) {
|
|
674
|
+
const vsixFiles = readdirSync(vsixDir)
|
|
675
|
+
.filter((f) => f.endsWith(".vsix"))
|
|
676
|
+
.sort()
|
|
677
|
+
.reverse();
|
|
678
|
+
if (vsixFiles.length > 0)
|
|
679
|
+
localVsix2 = path.join(vsixDir, vsixFiles[0]);
|
|
680
|
+
}
|
|
681
|
+
let tmpVsix2;
|
|
682
|
+
let extensionArg2;
|
|
683
|
+
if (localVsix2) {
|
|
684
|
+
extensionArg2 = localVsix2;
|
|
685
|
+
}
|
|
686
|
+
else {
|
|
687
|
+
try {
|
|
688
|
+
tmpVsix2 = await downloadVsixFromOpenVsx();
|
|
689
|
+
extensionArg2 = tmpVsix2;
|
|
690
|
+
}
|
|
691
|
+
catch {
|
|
692
|
+
// Download failed — warn but don't abort init
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
if (extensionArg2) {
|
|
696
|
+
try {
|
|
697
|
+
execFileSync(editor, ["--install-extension", extensionArg2], {
|
|
698
|
+
stdio: "pipe",
|
|
699
|
+
timeout: 30000,
|
|
700
|
+
});
|
|
701
|
+
process.stderr.write(` ✓ Extension installed via ${editor}\n\n`);
|
|
702
|
+
}
|
|
703
|
+
catch {
|
|
704
|
+
process.stderr.write(` [warn] Extension install failed — download manually from:\n https://open-vsx.org/extension/${OPEN_VSX_PUBLISHER}/${OPEN_VSX_NAME}\n\n`);
|
|
705
|
+
}
|
|
706
|
+
finally {
|
|
707
|
+
if (tmpVsix2) {
|
|
708
|
+
try {
|
|
709
|
+
unlinkSync(tmpVsix2);
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
/* best effort */
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
else {
|
|
718
|
+
process.stderr.write(` [warn] Could not download extension — install manually from:\n https://open-vsx.org/extension/${OPEN_VSX_PUBLISHER}/${OPEN_VSX_NAME}\n\n`);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
// Step 2: Write CLAUDE.md
|
|
722
|
+
const templatePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "templates", "CLAUDE.bridge.md");
|
|
723
|
+
if (!existsSync(templatePath)) {
|
|
724
|
+
process.stderr.write(" [skip] CLAUDE.md — template not found. Run gen-claude-md manually.\n\n");
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
const content = readFileSync(templatePath, "utf-8");
|
|
728
|
+
const targetPath = path.join(workspace, "CLAUDE.md");
|
|
729
|
+
const marker = "## Claude IDE Bridge";
|
|
730
|
+
const importLine = "@import .claude/rules/bridge-tools.md";
|
|
731
|
+
// Capture old version before patching so we can show "v1.2 → v1.3" in the message.
|
|
732
|
+
const prevBlockVersion = existsSync(targetPath)
|
|
733
|
+
? extractClaudeMdBlockVersion(readFileSync(targetPath, "utf-8"))
|
|
734
|
+
: null;
|
|
735
|
+
const initPatchResult = patchClaudeMdImport(targetPath, marker, importLine);
|
|
736
|
+
if (initPatchResult === "already-current") {
|
|
737
|
+
process.stderr.write(` ✓ CLAUDE.md — bridge block already up to date (v${PACKAGE_VERSION})\n\n`);
|
|
738
|
+
}
|
|
739
|
+
else if (initPatchResult === "updated") {
|
|
740
|
+
process.stderr.write(` ✓ CLAUDE.md — bridge block updated${prevBlockVersion ? ` v${prevBlockVersion} →` : ""} v${PACKAGE_VERSION}\n\n`);
|
|
741
|
+
}
|
|
742
|
+
else if (initPatchResult === "patched") {
|
|
743
|
+
process.stderr.write(` ✓ CLAUDE.md — bridge section patched and stamped v${PACKAGE_VERSION}\n\n`);
|
|
744
|
+
}
|
|
745
|
+
else if (initPatchResult === "already-present") {
|
|
746
|
+
process.stderr.write(" ✓ CLAUDE.md — bridge section already present\n\n");
|
|
747
|
+
}
|
|
748
|
+
else {
|
|
749
|
+
// no-section: append or create with versioned block
|
|
750
|
+
mkdirSync(workspace, { recursive: true });
|
|
751
|
+
const existing = existsSync(targetPath)
|
|
752
|
+
? readFileSync(targetPath, "utf-8")
|
|
753
|
+
: null;
|
|
754
|
+
// Wrap the template content in a versioned block before inserting
|
|
755
|
+
const versionedContent = content
|
|
756
|
+
.trimEnd()
|
|
757
|
+
.replace(marker, `<!-- claude-ide-bridge:start:${PACKAGE_VERSION} -->\n${marker}`)
|
|
758
|
+
.concat(`\n<!-- claude-ide-bridge:end -->`);
|
|
759
|
+
const updated = existing !== null
|
|
760
|
+
? `${existing.trimEnd()}\n\n${versionedContent}\n`
|
|
761
|
+
: `${versionedContent}\n`;
|
|
762
|
+
const tmpPath = `${targetPath}.tmp`;
|
|
763
|
+
writeFileSync(tmpPath, updated, { encoding: "utf-8", flag: "wx" });
|
|
764
|
+
if (existing !== null) {
|
|
765
|
+
const ts = new Date().toISOString().replace(/[:.]/g, "-");
|
|
766
|
+
try {
|
|
767
|
+
renameSync(targetPath, `${targetPath}.${ts}.bak`);
|
|
768
|
+
renameSync(tmpPath, targetPath);
|
|
769
|
+
}
|
|
770
|
+
catch (err) {
|
|
771
|
+
try {
|
|
772
|
+
unlinkSync(tmpPath);
|
|
773
|
+
}
|
|
774
|
+
catch {
|
|
775
|
+
/* best-effort cleanup */
|
|
776
|
+
}
|
|
777
|
+
throw err;
|
|
778
|
+
}
|
|
779
|
+
}
|
|
780
|
+
else {
|
|
781
|
+
renameSync(tmpPath, targetPath);
|
|
782
|
+
}
|
|
783
|
+
process.stderr.write(` ✓ CLAUDE.md — bridge section written to ${targetPath} (v${PACKAGE_VERSION})\n\n`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
// Step 2b: Write bridge-tools rules file to .claude/rules/bridge-tools.md
|
|
787
|
+
const rulesDir = path.join(workspace, ".claude", "rules");
|
|
788
|
+
const rulesFilePath = path.join(rulesDir, "bridge-tools.md");
|
|
789
|
+
const bridgeToolsTemplatePath = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "templates", "bridge-tools.md");
|
|
790
|
+
if (isBridgeToolsFileValid(rulesFilePath)) {
|
|
791
|
+
process.stderr.write(` ✓ Bridge rules — already up to date (v${PACKAGE_VERSION}) at ${rulesFilePath}\n\n`);
|
|
792
|
+
}
|
|
793
|
+
else if (existsSync(bridgeToolsTemplatePath)) {
|
|
794
|
+
const repairing = existsSync(rulesFilePath);
|
|
795
|
+
try {
|
|
796
|
+
mkdirSync(rulesDir, { recursive: true });
|
|
797
|
+
writeRulesFileAtomic(rulesFilePath, readFileSync(bridgeToolsTemplatePath, "utf-8").replace("{{VERSION}}", PACKAGE_VERSION));
|
|
798
|
+
process.stderr.write(repairing
|
|
799
|
+
? ` ✓ Bridge rules — updated to v${PACKAGE_VERSION} at ${rulesFilePath}\n\n`
|
|
800
|
+
: ` ✓ Bridge rules — written (v${PACKAGE_VERSION}) to ${rulesFilePath}\n\n`);
|
|
801
|
+
}
|
|
802
|
+
catch (err) {
|
|
803
|
+
const exitCode = handleRulesWriteError(err, rulesFilePath, " ");
|
|
804
|
+
if (exitCode !== 0)
|
|
805
|
+
process.exit(exitCode);
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
process.stderr.write(` [skip] Bridge rules — template not found\n\n`);
|
|
810
|
+
}
|
|
811
|
+
// Step 3: Register shim in ~/.claude.json so bridge tools appear in every claude session.
|
|
812
|
+
// NOTE: ~/.claude.json is a FILE sitting next to the ~/.claude/ directory, with the same
|
|
813
|
+
// dotted prefix. Earlier versions computed `path.join(CONFIG_DIR, "..", "claude.json")`
|
|
814
|
+
// which dropped the leading dot and wrote to ~/claude.json — a ghost file that Claude
|
|
815
|
+
// Code never reads. Append ".json" to the config dir path instead.
|
|
816
|
+
const claudeDirForJson = process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude");
|
|
817
|
+
const claudeJsonAbs = path.resolve(`${claudeDirForJson}.json`);
|
|
818
|
+
try {
|
|
819
|
+
let claudeJson = {};
|
|
820
|
+
if (existsSync(claudeJsonAbs)) {
|
|
821
|
+
claudeJson = JSON.parse(readFileSync(claudeJsonAbs, "utf-8"));
|
|
822
|
+
}
|
|
823
|
+
const mcpServers = (claudeJson.mcpServers ?? {});
|
|
824
|
+
if (mcpServers["claude-ide-bridge"]) {
|
|
825
|
+
process.stderr.write(` ✓ MCP shim — already registered in ${claudeJsonAbs}\n\n`);
|
|
826
|
+
}
|
|
827
|
+
else {
|
|
828
|
+
mcpServers["claude-ide-bridge"] = {
|
|
829
|
+
command: "claude-ide-bridge",
|
|
830
|
+
args: ["shim"],
|
|
831
|
+
type: "stdio",
|
|
832
|
+
};
|
|
833
|
+
claudeJson.mcpServers = mcpServers;
|
|
834
|
+
writeFileSync(claudeJsonAbs, `${JSON.stringify(claudeJson, null, 2)}\n`);
|
|
835
|
+
process.stderr.write(` ✓ MCP shim — registered in ${claudeJsonAbs}\n Note: bridge tools are wired via ~/.claude.json (global), not .mcp.json.\n This is intentional — when VS Code/Windsurf/Cursor launches Claude Code it\n injects --mcp-config which overrides any project .mcp.json. Only ~/.claude.json\n is always loaded. You do not need to add anything to .mcp.json.\n\n`);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
catch {
|
|
839
|
+
process.stderr.write(` [warn] Could not update ${claudeJsonAbs} — add manually:\n { "mcpServers": { "claude-ide-bridge": { "command": "claude-ide-bridge", "args": ["shim"] } } }\n\n`);
|
|
840
|
+
}
|
|
841
|
+
// Step 3b: Wire CC hooks in ~/.claude/settings.json
|
|
842
|
+
const ccSettingsPath = path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"), "settings.json");
|
|
843
|
+
const CC_HOOK_NOTIFY_CMDS = {
|
|
844
|
+
PreCompact: "claude-ide-bridge notify PreCompact",
|
|
845
|
+
PostCompact: "claude-ide-bridge notify PostCompact",
|
|
846
|
+
InstructionsLoaded: "claude-ide-bridge notify InstructionsLoaded",
|
|
847
|
+
TaskCreated: "claude-ide-bridge notify TaskCreated --taskId $TASK_ID --prompt $PROMPT",
|
|
848
|
+
PermissionDenied: "claude-ide-bridge notify PermissionDenied --tool $TOOL --reason $REASON",
|
|
849
|
+
CwdChanged: "claude-ide-bridge notify CwdChanged --cwd $CWD",
|
|
850
|
+
};
|
|
851
|
+
try {
|
|
852
|
+
let ccSettings = {};
|
|
853
|
+
if (existsSync(ccSettingsPath)) {
|
|
854
|
+
ccSettings = JSON.parse(readFileSync(ccSettingsPath, "utf-8"));
|
|
855
|
+
}
|
|
856
|
+
const ccHooks = (ccSettings.hooks ?? {});
|
|
857
|
+
const isNested = (e) => !!e && Array.isArray(e.hooks);
|
|
858
|
+
const normalize = (e) => isNested(e)
|
|
859
|
+
? { matcher: e.matcher ?? "", hooks: e.hooks ?? [] }
|
|
860
|
+
: { matcher: "", hooks: [e] };
|
|
861
|
+
const added = [];
|
|
862
|
+
const migrated = [];
|
|
863
|
+
for (const [ccEvent, cmd] of Object.entries(CC_HOOK_NOTIFY_CMDS)) {
|
|
864
|
+
const rawEntries = ccHooks[ccEvent] ?? [];
|
|
865
|
+
const hadLegacy = rawEntries.some((e) => !isNested(e));
|
|
866
|
+
const normalized = rawEntries.map(normalize);
|
|
867
|
+
const alreadyWired = normalized.some((entry) => (entry.hooks ?? []).some((h) => typeof h.command === "string" &&
|
|
868
|
+
(h.command.includes(cmd) ||
|
|
869
|
+
h.command.includes(`notify ${ccEvent}`))));
|
|
870
|
+
if (!alreadyWired) {
|
|
871
|
+
normalized.push({
|
|
872
|
+
matcher: "",
|
|
873
|
+
hooks: [{ type: "command", command: cmd }],
|
|
874
|
+
});
|
|
875
|
+
added.push(ccEvent);
|
|
876
|
+
}
|
|
877
|
+
if (!alreadyWired || hadLegacy) {
|
|
878
|
+
ccHooks[ccEvent] = normalized;
|
|
879
|
+
if (hadLegacy)
|
|
880
|
+
migrated.push(ccEvent);
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
if (added.length > 0 || migrated.length > 0) {
|
|
884
|
+
ccSettings.hooks = ccHooks;
|
|
885
|
+
writeFileSync(ccSettingsPath, `${JSON.stringify(ccSettings, null, 2)}\n`);
|
|
886
|
+
const addMsg = added.length > 0
|
|
887
|
+
? ` ✓ CC hooks — wired ${added.length} automation hook(s) in ${ccSettingsPath}\n Added: ${added.join(", ")}\n`
|
|
888
|
+
: "";
|
|
889
|
+
const migMsg = migrated.length > 0
|
|
890
|
+
? ` ✓ CC hooks — migrated ${migrated.length} legacy entrie(s) to matcher+hooks format\n Migrated: ${migrated.join(", ")}\n`
|
|
891
|
+
: "";
|
|
892
|
+
process.stderr.write(`${addMsg}${migMsg}\n`);
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
process.stderr.write(` ✓ CC hooks — already wired in ${ccSettingsPath}\n\n`);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
catch {
|
|
899
|
+
process.stderr.write(` [warn] Could not update ${ccSettingsPath} — add CC hook entries manually.\n` +
|
|
900
|
+
` See CLAUDE.md Automation Policy section for the settings.json snippet.\n\n`);
|
|
901
|
+
}
|
|
902
|
+
// Patchwork: register PreToolUse approval hook so the dashboard can
|
|
903
|
+
// approve/reject CC tool calls in real time.
|
|
904
|
+
try {
|
|
905
|
+
const { registerPreToolUseHook } = await import("./preToolUseHook.js");
|
|
906
|
+
const result = registerPreToolUseHook(ccSettingsPath);
|
|
907
|
+
if (result.action === "added") {
|
|
908
|
+
process.stderr.write(` ✓ Patchwork PreToolUse hook — registered\n ${result.hookCommand}\n\n`);
|
|
909
|
+
}
|
|
910
|
+
else if (result.action === "already-wired") {
|
|
911
|
+
process.stderr.write(` ✓ Patchwork PreToolUse hook — already registered\n\n`);
|
|
912
|
+
}
|
|
913
|
+
else {
|
|
914
|
+
process.stderr.write(` [warn] Patchwork PreToolUse hook — could not register: ${result.error}\n\n`);
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
catch (err) {
|
|
918
|
+
process.stderr.write(` [warn] Patchwork PreToolUse hook — ${err instanceof Error ? err.message : String(err)}\n\n`);
|
|
919
|
+
}
|
|
920
|
+
// Step 4: Verify shim can be found on PATH
|
|
921
|
+
let shimOnPath = false;
|
|
922
|
+
try {
|
|
923
|
+
execFileSync("claude-ide-bridge", ["--version"], {
|
|
924
|
+
stdio: "pipe",
|
|
925
|
+
timeout: 5000,
|
|
926
|
+
});
|
|
927
|
+
shimOnPath = true;
|
|
928
|
+
}
|
|
929
|
+
catch {
|
|
930
|
+
// not on PATH or version flag not supported — non-fatal
|
|
931
|
+
shimOnPath = existsSync(path.resolve(__dirnameTop, "..", "scripts", "mcp-stdio-shim.cjs"));
|
|
932
|
+
}
|
|
933
|
+
if (!shimOnPath) {
|
|
934
|
+
process.stderr.write(" [warn] claude-ide-bridge not found on PATH.\n" +
|
|
935
|
+
" If you installed locally, ensure your npm global bin is in PATH:\n" +
|
|
936
|
+
" npm config get prefix # add <prefix>/bin to PATH\n\n");
|
|
937
|
+
}
|
|
938
|
+
else {
|
|
939
|
+
process.stderr.write(" ✓ Shim — claude-ide-bridge found on PATH\n\n");
|
|
940
|
+
}
|
|
941
|
+
// Step 5: Success message + next steps
|
|
942
|
+
// Check if the workspace is a linked git worktree — surface Cowork gotcha if so.
|
|
943
|
+
// (In main worktree .git is a dir; in a linked worktree it is a file.)
|
|
944
|
+
let inWorktree = false;
|
|
945
|
+
try {
|
|
946
|
+
inWorktree = statSync(path.join(workspace, ".git")).isFile();
|
|
947
|
+
}
|
|
948
|
+
catch {
|
|
949
|
+
/* no .git at workspace — not a git repo, fine */
|
|
950
|
+
}
|
|
951
|
+
process.stdout.write("\n✅ Setup complete.\n\n" +
|
|
952
|
+
"Next steps:\n" +
|
|
953
|
+
` 1. Start the bridge: claude-ide-bridge --watch (runs in this workspace)\n` +
|
|
954
|
+
" 2. Restart your IDE once so it picks up the new extension + MCP config.\n" +
|
|
955
|
+
" 3. Open Claude Code and type /mcp — the claude-ide-bridge server should show as connected.\n" +
|
|
956
|
+
" 4. Type /ide to see live workspace state (open editors, diagnostics, git status).\n\n");
|
|
957
|
+
if (inWorktree) {
|
|
958
|
+
process.stdout.write("⚠ This workspace is a linked git worktree. If this is a Cowork session,\n" +
|
|
959
|
+
" bridge MCP tools are unreachable from inside Cowork itself — run\n" +
|
|
960
|
+
" /mcp__bridge__cowork in regular Claude Code/Desktop chat FIRST to\n" +
|
|
961
|
+
" gather context, then switch to Cowork. See docs/cowork.md.\n\n");
|
|
962
|
+
}
|
|
963
|
+
// Step 6: Verify setup
|
|
964
|
+
process.stdout.write("📋 Setup verification:\n");
|
|
965
|
+
process.stdout.write(shimOnPath
|
|
966
|
+
? " ✓ bridge on PATH\n"
|
|
967
|
+
: ' ✗ bridge not on PATH — add npm global bin to your PATH (e.g. export PATH="$(npm bin -g):$PATH")\n');
|
|
968
|
+
let mcpWired = false;
|
|
969
|
+
try {
|
|
970
|
+
const claudeJsonPath = path.join(os.homedir(), ".claude.json");
|
|
971
|
+
const cj = JSON.parse(readFileSync(claudeJsonPath, "utf-8"));
|
|
972
|
+
mcpWired = !!(cj?.mcpServers &&
|
|
973
|
+
typeof cj.mcpServers === "object" &&
|
|
974
|
+
cj.mcpServers["claude-ide-bridge"]);
|
|
975
|
+
}
|
|
976
|
+
catch {
|
|
977
|
+
/* file may not exist yet — non-fatal */
|
|
978
|
+
}
|
|
979
|
+
process.stdout.write(mcpWired
|
|
980
|
+
? " ✓ MCP shim registered in ~/.claude.json\n"
|
|
981
|
+
: " ✗ MCP shim not found in ~/.claude.json — re-run init or check Step 3 output above\n");
|
|
982
|
+
let hooksWired = false;
|
|
983
|
+
try {
|
|
984
|
+
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
985
|
+
const sj = JSON.parse(readFileSync(settingsPath, "utf-8"));
|
|
986
|
+
const hooksObj = sj?.hooks;
|
|
987
|
+
if (hooksObj && typeof hooksObj === "object") {
|
|
988
|
+
hooksWired = Object.values(hooksObj).flat().some((e) => typeof e?.command ===
|
|
989
|
+
"string" &&
|
|
990
|
+
(e.command ?? "").includes("claude-ide-bridge"));
|
|
991
|
+
}
|
|
992
|
+
}
|
|
993
|
+
catch {
|
|
994
|
+
/* file may not exist yet — non-fatal */
|
|
995
|
+
}
|
|
996
|
+
process.stdout.write(hooksWired
|
|
997
|
+
? " ✓ CC hooks wired in ~/.claude/settings.json\n"
|
|
998
|
+
: " ✗ CC hooks not wired — re-run init to add them\n");
|
|
999
|
+
// Auto-open automation docs when --workspace was provided
|
|
1000
|
+
if (workspaceIdx !== -1 && workspace) {
|
|
1001
|
+
const docsPath = path.join(workspace, "docs", "automation.md");
|
|
1002
|
+
const fallbackDocs = path.resolve(__dirnameTop, "..", "docs", "automation.md");
|
|
1003
|
+
const target = existsSync(docsPath)
|
|
1004
|
+
? docsPath
|
|
1005
|
+
: existsSync(fallbackDocs)
|
|
1006
|
+
? fallbackDocs
|
|
1007
|
+
: null;
|
|
1008
|
+
if (target) {
|
|
1009
|
+
const { exec } = await import("node:child_process");
|
|
1010
|
+
exec(`code "${target}"`, { timeout: 3000 }, () => { });
|
|
1011
|
+
}
|
|
1012
|
+
}
|
|
1013
|
+
// Analytics opt-in prompt — only ask once; skip if preference already set
|
|
1014
|
+
const existingPref = getAnalyticsPref();
|
|
1015
|
+
if (existingPref === null) {
|
|
1016
|
+
process.stdout.write("\n");
|
|
1017
|
+
process.stdout.write("Optional: send anonymous usage statistics to help prioritize features?\n" +
|
|
1018
|
+
" Tool names, success/error counts, and durations only.\n" +
|
|
1019
|
+
" No file paths, code, error messages, or personal data. Ever.\n" +
|
|
1020
|
+
" Change anytime: claude-ide-bridge --analytics on|off\n\n");
|
|
1021
|
+
const answer = await new Promise((resolve) => {
|
|
1022
|
+
const rl = readline.createInterface({
|
|
1023
|
+
input: process.stdin,
|
|
1024
|
+
output: process.stdout,
|
|
1025
|
+
});
|
|
1026
|
+
rl.question("Send anonymous usage stats? [y/N]: ", (ans) => {
|
|
1027
|
+
rl.close();
|
|
1028
|
+
resolve(ans.trim().toLowerCase());
|
|
1029
|
+
});
|
|
1030
|
+
// If stdin is not a TTY (e.g. piped), default to no
|
|
1031
|
+
if (!process.stdin.isTTY) {
|
|
1032
|
+
rl.close();
|
|
1033
|
+
resolve("n");
|
|
1034
|
+
}
|
|
1035
|
+
});
|
|
1036
|
+
const opted = answer === "y" || answer === "yes";
|
|
1037
|
+
setAnalyticsPref(opted);
|
|
1038
|
+
process.stdout.write(opted
|
|
1039
|
+
? " ✓ Analytics enabled — thank you.\n"
|
|
1040
|
+
: " ✓ Analytics disabled — no data will be sent.\n");
|
|
1041
|
+
}
|
|
1042
|
+
process.exit(0);
|
|
1043
|
+
}
|
|
1044
|
+
// Handle shim subcommand — stdio relay that auto-discovers the running bridge/orchestrator.
|
|
1045
|
+
// Intended use: add to ~/.claude.json mcpServers so bridge tools are available everywhere.
|
|
1046
|
+
// { "command": "claude-ide-bridge", "args": ["shim"] }
|
|
1047
|
+
if (process.argv[2] === "shim") {
|
|
1048
|
+
const shimPath = path.resolve(__dirnameTop, "..", "scripts", "mcp-stdio-shim.cjs");
|
|
1049
|
+
// Pass remaining args (e.g. explicit port + token for testing)
|
|
1050
|
+
const shimArgs = [shimPath, ...process.argv.slice(3)];
|
|
1051
|
+
const child = spawn(process.execPath, shimArgs, { stdio: "inherit" });
|
|
1052
|
+
child.on("exit", (code, signal) => {
|
|
1053
|
+
process.exit(code ?? (signal ? 1 : 0));
|
|
1054
|
+
});
|
|
1055
|
+
// Forward signals so the shim can clean up
|
|
1056
|
+
for (const sig of ["SIGTERM", "SIGINT"]) {
|
|
1057
|
+
process.once(sig, () => child.kill(sig));
|
|
1058
|
+
}
|
|
1059
|
+
// Prevent fall-through — keep alive until child exits
|
|
1060
|
+
await new Promise(() => { });
|
|
1061
|
+
}
|
|
1062
|
+
// Handle orchestrator subcommand — starts the meta-bridge that coordinates multiple IDEs
|
|
1063
|
+
if (process.argv[2] === "orchestrator") {
|
|
1064
|
+
const { parseOrchestratorArgs, OrchestratorBridge } = await import("./orchestrator/index.js");
|
|
1065
|
+
const orchConfig = parseOrchestratorArgs(process.argv);
|
|
1066
|
+
const orch = new OrchestratorBridge(orchConfig);
|
|
1067
|
+
await orch.start().catch((err) => {
|
|
1068
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1069
|
+
process.stderr.write(`Orchestrator error: ${message}\n`);
|
|
1070
|
+
process.exit(1);
|
|
1071
|
+
});
|
|
1072
|
+
// Stay alive serving connections — do not fall through to parseConfig
|
|
1073
|
+
await new Promise(() => { });
|
|
1074
|
+
}
|
|
1075
|
+
else if (process.argv[2] === "install-extension") {
|
|
1076
|
+
const KNOWN_EDITORS = new Set([
|
|
1077
|
+
"code",
|
|
1078
|
+
"windsurf",
|
|
1079
|
+
"cursor",
|
|
1080
|
+
"antigravity",
|
|
1081
|
+
"ag",
|
|
1082
|
+
]);
|
|
1083
|
+
const editorArg = process.argv[3];
|
|
1084
|
+
if (editorArg !== undefined &&
|
|
1085
|
+
!KNOWN_EDITORS.has(editorArg) &&
|
|
1086
|
+
!path.isAbsolute(editorArg)) {
|
|
1087
|
+
process.stderr.write(`Error: Unknown editor "${editorArg}". Use one of: code, windsurf, cursor, antigravity, ag\n`);
|
|
1088
|
+
process.exit(1);
|
|
1089
|
+
}
|
|
1090
|
+
const editor = process.argv[3] || findEditor();
|
|
1091
|
+
if (!editor) {
|
|
1092
|
+
process.stderr.write("Error: No editor found. Specify the editor command: claude-ide-bridge install-extension <code|cursor|windsurf>\n");
|
|
1093
|
+
process.exit(1);
|
|
1094
|
+
}
|
|
1095
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
1096
|
+
const vsixDir = path.resolve(__dirname, "..", "vscode-extension");
|
|
1097
|
+
// Prefer a local .vsix (source checkout / dev build). When installed via
|
|
1098
|
+
// `npm install -g` there is no vscode-extension/ dir, so download from Open VSX.
|
|
1099
|
+
let localVsix;
|
|
1100
|
+
if (existsSync(vsixDir)) {
|
|
1101
|
+
const vsixFiles = readdirSync(vsixDir)
|
|
1102
|
+
.filter((f) => f.endsWith(".vsix"))
|
|
1103
|
+
.sort()
|
|
1104
|
+
.reverse();
|
|
1105
|
+
if (vsixFiles.length > 0)
|
|
1106
|
+
localVsix = path.join(vsixDir, vsixFiles[0]);
|
|
1107
|
+
}
|
|
1108
|
+
let tmpVsix;
|
|
1109
|
+
let extensionArg;
|
|
1110
|
+
if (localVsix) {
|
|
1111
|
+
extensionArg = localVsix;
|
|
1112
|
+
}
|
|
1113
|
+
else {
|
|
1114
|
+
try {
|
|
1115
|
+
tmpVsix = await downloadVsixFromOpenVsx();
|
|
1116
|
+
extensionArg = tmpVsix;
|
|
1117
|
+
}
|
|
1118
|
+
catch (err) {
|
|
1119
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
1120
|
+
process.stderr.write(`Error downloading extension from Open VSX: ${msg}\n`);
|
|
1121
|
+
process.stderr.write(`Install manually: download from https://open-vsx.org/extension/${OPEN_VSX_PUBLISHER}/${OPEN_VSX_NAME}\n`);
|
|
1122
|
+
process.exit(1);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
try {
|
|
1126
|
+
process.stderr.write(`Installing extension via ${editor}...\n`);
|
|
1127
|
+
execFileSync(editor, ["--install-extension", extensionArg], {
|
|
1128
|
+
stdio: "inherit",
|
|
1129
|
+
timeout: 30000,
|
|
1130
|
+
});
|
|
1131
|
+
process.stderr.write("Extension installed successfully.\n");
|
|
1132
|
+
}
|
|
1133
|
+
catch (err) {
|
|
1134
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1135
|
+
process.stderr.write(`Error installing extension: ${message}\n`);
|
|
1136
|
+
process.exit(1);
|
|
1137
|
+
}
|
|
1138
|
+
finally {
|
|
1139
|
+
if (tmpVsix) {
|
|
1140
|
+
try {
|
|
1141
|
+
unlinkSync(tmpVsix);
|
|
1142
|
+
}
|
|
1143
|
+
catch {
|
|
1144
|
+
/* best effort */
|
|
1145
|
+
}
|
|
1146
|
+
}
|
|
1147
|
+
}
|
|
1148
|
+
process.exit(0);
|
|
1149
|
+
}
|
|
1150
|
+
// Handle --analytics on|off subcommand — update stored preference
|
|
1151
|
+
if (process.argv[2] === "--analytics") {
|
|
1152
|
+
const val = process.argv[3];
|
|
1153
|
+
if (val !== "on" && val !== "off") {
|
|
1154
|
+
process.stderr.write("Usage: claude-ide-bridge --analytics on|off\n");
|
|
1155
|
+
process.exit(1);
|
|
1156
|
+
}
|
|
1157
|
+
setAnalyticsPref(val === "on");
|
|
1158
|
+
process.stdout.write(`Analytics ${val === "on" ? "enabled" : "disabled"}. Preference saved to ~/.claude/ide/analytics.json\n`);
|
|
1159
|
+
process.exit(0);
|
|
1160
|
+
}
|
|
1161
|
+
// Handle status subcommand — check bridge health and extension connectivity
|
|
1162
|
+
if (process.argv[2] === "status") {
|
|
1163
|
+
const argv = process.argv.slice(3);
|
|
1164
|
+
if (argv.includes("--help")) {
|
|
1165
|
+
console.log(`claude-ide-bridge status — Check bridge health
|
|
1166
|
+
|
|
1167
|
+
Usage: claude-ide-bridge status [options]
|
|
1168
|
+
|
|
1169
|
+
Options:
|
|
1170
|
+
--port <number> Check a specific port (default: most recent lock file)
|
|
1171
|
+
--json Output as JSON
|
|
1172
|
+
--help Show this help`);
|
|
1173
|
+
process.exit(0);
|
|
1174
|
+
}
|
|
1175
|
+
const portIdx = argv.indexOf("--port");
|
|
1176
|
+
const portArg = portIdx !== -1 ? argv[portIdx + 1] : undefined;
|
|
1177
|
+
const jsonFlag = argv.includes("--json");
|
|
1178
|
+
const lockDir = path.join(process.env.CLAUDE_CONFIG_DIR ?? path.join(os.homedir(), ".claude"), "ide");
|
|
1179
|
+
let lockFile;
|
|
1180
|
+
let lockPort;
|
|
1181
|
+
if (portArg) {
|
|
1182
|
+
lockFile = path.join(lockDir, `${portArg}.lock`);
|
|
1183
|
+
lockPort = portArg;
|
|
1184
|
+
if (!existsSync(lockFile)) {
|
|
1185
|
+
process.stderr.write(`Error: No lock file found for port ${portArg} at ${lockFile}\n`);
|
|
1186
|
+
process.exit(1);
|
|
1187
|
+
}
|
|
1188
|
+
}
|
|
1189
|
+
else {
|
|
1190
|
+
let bestMtime = 0;
|
|
1191
|
+
try {
|
|
1192
|
+
for (const f of readdirSync(lockDir)) {
|
|
1193
|
+
if (!f.endsWith(".lock"))
|
|
1194
|
+
continue;
|
|
1195
|
+
const full = path.join(lockDir, f);
|
|
1196
|
+
const mtime = statSync(full).mtimeMs;
|
|
1197
|
+
if (mtime > bestMtime) {
|
|
1198
|
+
bestMtime = mtime;
|
|
1199
|
+
lockFile = full;
|
|
1200
|
+
lockPort = f.replace(".lock", "");
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
}
|
|
1204
|
+
catch {
|
|
1205
|
+
// lock dir doesn't exist
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
if (!lockFile) {
|
|
1209
|
+
if (jsonFlag) {
|
|
1210
|
+
process.stdout.write(`${JSON.stringify({ status: "not_running" })}\n`);
|
|
1211
|
+
}
|
|
1212
|
+
else {
|
|
1213
|
+
process.stderr.write(`No bridge lock file found in ${lockDir}\n`);
|
|
1214
|
+
process.stderr.write("Bridge is not running. Start it with: claude-ide-bridge --watch\n");
|
|
1215
|
+
}
|
|
1216
|
+
process.exit(1);
|
|
1217
|
+
}
|
|
1218
|
+
let lockData;
|
|
1219
|
+
try {
|
|
1220
|
+
lockData = JSON.parse(readFileSync(lockFile, "utf-8"));
|
|
1221
|
+
}
|
|
1222
|
+
catch {
|
|
1223
|
+
process.stderr.write(`Error: Could not read lock file ${lockFile}\n`);
|
|
1224
|
+
process.exit(1);
|
|
1225
|
+
}
|
|
1226
|
+
// Check if PID is alive
|
|
1227
|
+
let pidAlive = false;
|
|
1228
|
+
if (lockData.pid) {
|
|
1229
|
+
try {
|
|
1230
|
+
process.kill(lockData.pid, 0);
|
|
1231
|
+
pidAlive = true;
|
|
1232
|
+
}
|
|
1233
|
+
catch {
|
|
1234
|
+
// process not running
|
|
1235
|
+
}
|
|
1236
|
+
}
|
|
1237
|
+
if (!pidAlive) {
|
|
1238
|
+
if (jsonFlag) {
|
|
1239
|
+
process.stdout.write(`${JSON.stringify({
|
|
1240
|
+
status: "stale_lock",
|
|
1241
|
+
port: lockPort,
|
|
1242
|
+
pid: lockData.pid,
|
|
1243
|
+
})}\n`);
|
|
1244
|
+
}
|
|
1245
|
+
else {
|
|
1246
|
+
process.stderr.write(`Bridge lock file exists (port ${lockPort}) but process ${lockData.pid} is not running.\n`);
|
|
1247
|
+
process.stderr.write("The lock file is stale. Restart with: claude-ide-bridge --watch\n");
|
|
1248
|
+
}
|
|
1249
|
+
process.exit(1);
|
|
1250
|
+
}
|
|
1251
|
+
// Fetch /health from the running bridge
|
|
1252
|
+
const healthUrl = `http://127.0.0.1:${lockPort}/health`;
|
|
1253
|
+
try {
|
|
1254
|
+
const resp = await fetch(healthUrl, {
|
|
1255
|
+
headers: {
|
|
1256
|
+
Authorization: `Bearer ${lockData.authToken}`,
|
|
1257
|
+
},
|
|
1258
|
+
signal: AbortSignal.timeout(5_000),
|
|
1259
|
+
});
|
|
1260
|
+
const health = (await resp.json());
|
|
1261
|
+
if (jsonFlag) {
|
|
1262
|
+
process.stdout.write(`${JSON.stringify({
|
|
1263
|
+
status: "running",
|
|
1264
|
+
port: lockPort,
|
|
1265
|
+
pid: lockData.pid,
|
|
1266
|
+
workspace: lockData.workspace,
|
|
1267
|
+
ide: lockData.ideName,
|
|
1268
|
+
...health,
|
|
1269
|
+
})}\n`);
|
|
1270
|
+
}
|
|
1271
|
+
else {
|
|
1272
|
+
const uptimeMs = health.uptimeMs ?? 0;
|
|
1273
|
+
const mins = Math.floor(uptimeMs / 60_000);
|
|
1274
|
+
const secs = Math.floor((uptimeMs % 60_000) / 1_000);
|
|
1275
|
+
const uptime = mins > 0 ? `${mins}m ${secs}s` : `${secs}s`;
|
|
1276
|
+
process.stdout.write("Bridge status: running\n");
|
|
1277
|
+
process.stdout.write(` Port: ${lockPort}\n`);
|
|
1278
|
+
process.stdout.write(` PID: ${lockData.pid}\n`);
|
|
1279
|
+
process.stdout.write(` Workspace: ${lockData.workspace ?? "unknown"}\n`);
|
|
1280
|
+
process.stdout.write(` IDE: ${lockData.ideName ?? "unknown"}\n`);
|
|
1281
|
+
process.stdout.write(` Uptime: ${uptime}\n`);
|
|
1282
|
+
process.stdout.write(` Extension: ${health.extensionConnected === true ? "connected" : health.extensionConnected === false ? "disconnected" : "unknown"}\n`);
|
|
1283
|
+
process.stdout.write(` Sessions: ${health.connections ?? 0}\n`);
|
|
1284
|
+
if (health.toolCount !== undefined) {
|
|
1285
|
+
process.stdout.write(` Tools: ${health.toolCount}\n`);
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
catch (err) {
|
|
1290
|
+
if (jsonFlag) {
|
|
1291
|
+
process.stdout.write(`${JSON.stringify({
|
|
1292
|
+
status: "unreachable",
|
|
1293
|
+
port: lockPort,
|
|
1294
|
+
pid: lockData.pid,
|
|
1295
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1296
|
+
})}\n`);
|
|
1297
|
+
}
|
|
1298
|
+
else {
|
|
1299
|
+
process.stderr.write(`Bridge process is running (PID ${lockData.pid}) but /health endpoint is unreachable.\n`);
|
|
1300
|
+
process.stderr.write(`Error: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1301
|
+
}
|
|
1302
|
+
process.exit(1);
|
|
1303
|
+
}
|
|
1304
|
+
process.exit(0);
|
|
1305
|
+
}
|
|
1306
|
+
// F6: "Did you mean?" for unknown CLI subcommands
|
|
1307
|
+
{
|
|
1308
|
+
const KNOWN_COMMANDS = [
|
|
1309
|
+
"init",
|
|
1310
|
+
"patchwork-init",
|
|
1311
|
+
"start-all",
|
|
1312
|
+
"install-extension",
|
|
1313
|
+
"gen-claude-md",
|
|
1314
|
+
"print-token",
|
|
1315
|
+
"gen-plugin-stub",
|
|
1316
|
+
"notify",
|
|
1317
|
+
"install",
|
|
1318
|
+
"marketplace",
|
|
1319
|
+
"status",
|
|
1320
|
+
"shim",
|
|
1321
|
+
"recipe",
|
|
1322
|
+
];
|
|
1323
|
+
const unknownSub = process.argv[2];
|
|
1324
|
+
if (unknownSub &&
|
|
1325
|
+
!unknownSub.startsWith("-") &&
|
|
1326
|
+
!KNOWN_COMMANDS.includes(unknownSub)) {
|
|
1327
|
+
const lev = (a, b) => {
|
|
1328
|
+
const dp = Array.from({ length: a.length + 1 }, (_, i) => Array.from({ length: b.length + 1 }, (_, j) => i === 0 ? j : j === 0 ? i : 0));
|
|
1329
|
+
for (let i = 1; i <= a.length; i++)
|
|
1330
|
+
for (let j = 1; j <= b.length; j++)
|
|
1331
|
+
// biome-ignore lint/style/noNonNullAssertion: dp is fully pre-allocated
|
|
1332
|
+
dp[i][j] =
|
|
1333
|
+
a[i - 1] === b[j - 1]
|
|
1334
|
+
? // biome-ignore lint/style/noNonNullAssertion: dp is fully pre-allocated
|
|
1335
|
+
dp[i - 1][j - 1]
|
|
1336
|
+
: 1 +
|
|
1337
|
+
// biome-ignore lint/style/noNonNullAssertion: dp is fully pre-allocated
|
|
1338
|
+
Math.min(dp[i - 1][j], dp[i][j - 1], dp[i - 1][j - 1]);
|
|
1339
|
+
// biome-ignore lint/style/noNonNullAssertion: dp is fully pre-allocated
|
|
1340
|
+
return dp[a.length][b.length];
|
|
1341
|
+
};
|
|
1342
|
+
const closest = [...KNOWN_COMMANDS].sort((a, b) => lev(unknownSub, a) - lev(unknownSub, b))[0];
|
|
1343
|
+
console.error(`Unknown command: '${unknownSub}'. Did you mean: ${closest}?`);
|
|
1344
|
+
process.exit(1);
|
|
1345
|
+
}
|
|
1346
|
+
}
|
|
1347
|
+
const config = parseConfig(process.argv);
|
|
1348
|
+
// Patchwork: resolve --model flag (optional, non-invasive) — stashes the
|
|
1349
|
+
// configured adapter on globalThis for consumers that opt into the adapter
|
|
1350
|
+
// layer. Bridge subprocess driver still works when --model is absent.
|
|
1351
|
+
try {
|
|
1352
|
+
const { resolveModel } = await import("./patchworkCli.js");
|
|
1353
|
+
const resolved = resolveModel(process.argv);
|
|
1354
|
+
if (resolved) {
|
|
1355
|
+
globalThis.__patchworkAdapter =
|
|
1356
|
+
resolved.adapter;
|
|
1357
|
+
process.stderr.write(`[patchwork] model adapter initialized: ${resolved.adapter.name}\n`);
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
catch (err) {
|
|
1361
|
+
process.stderr.write(`[patchwork] adapter init failed: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1362
|
+
}
|
|
1363
|
+
// If --analytics flag was passed, persist the preference immediately
|
|
1364
|
+
if (config.analyticsEnabled !== null) {
|
|
1365
|
+
setAnalyticsPref(config.analyticsEnabled);
|
|
1366
|
+
}
|
|
1367
|
+
// Auto-tmux: if requested and not already inside tmux or screen, re-exec inside a tmux session
|
|
1368
|
+
if (config.autoTmux &&
|
|
1369
|
+
!process.env.TMUX &&
|
|
1370
|
+
!process.env.STY &&
|
|
1371
|
+
!process.env.ZELLIJ &&
|
|
1372
|
+
!process.env.ZELLIJ_SESSION_NAME) {
|
|
1373
|
+
const ws = config.workspace.replace(/[^a-zA-Z0-9]/g, "").slice(-8);
|
|
1374
|
+
const hash = crypto
|
|
1375
|
+
.createHash("sha256")
|
|
1376
|
+
.update(config.workspace)
|
|
1377
|
+
.digest("hex")
|
|
1378
|
+
.slice(0, 6);
|
|
1379
|
+
const sessionName = `claude-bridge-${ws}${hash}`;
|
|
1380
|
+
// Check if tmux is available
|
|
1381
|
+
const tmuxCheck = spawnSync("which", ["tmux"], { stdio: "ignore" });
|
|
1382
|
+
if (tmuxCheck.status !== 0) {
|
|
1383
|
+
process.stderr.write("WARNING: --auto-tmux requested but tmux is not installed. Running without tmux.\n");
|
|
1384
|
+
}
|
|
1385
|
+
else {
|
|
1386
|
+
// Strip --auto-tmux from argv to avoid infinite re-exec loop
|
|
1387
|
+
const newArgv = process.argv.filter((a) => a !== "--auto-tmux");
|
|
1388
|
+
// Pass each argv token as a separate tmux argument so paths with spaces work correctly
|
|
1389
|
+
const result = spawnSync("tmux", ["new-session", "-d", "-s", sessionName, ...newArgv], { stdio: "inherit", timeout: 5000 });
|
|
1390
|
+
if (result.status === 0) {
|
|
1391
|
+
process.stderr.write(`Bridge launched in tmux session '${sessionName}'.\n`);
|
|
1392
|
+
process.stderr.write(` Attach with: tmux attach -t ${sessionName}\n`);
|
|
1393
|
+
process.exit(0);
|
|
1394
|
+
}
|
|
1395
|
+
else {
|
|
1396
|
+
// tmux session likely already exists — attach to it or fall through
|
|
1397
|
+
process.stderr.write(`WARNING: Could not create tmux session '${sessionName}' (already exists?). Running without auto-tmux.\n`);
|
|
1398
|
+
}
|
|
1399
|
+
}
|
|
1400
|
+
}
|
|
1401
|
+
// --watch: supervisor mode — spawn this binary as a child (without --watch) and restart on crash
|
|
1402
|
+
if (config.watch) {
|
|
1403
|
+
const childArgv = process.argv.filter((a) => a !== "--watch");
|
|
1404
|
+
const STABLE_THRESHOLD_MS = 60_000;
|
|
1405
|
+
const BASE_DELAY_MS = 2_000;
|
|
1406
|
+
const MAX_DELAY_MS = 30_000;
|
|
1407
|
+
let delay = BASE_DELAY_MS;
|
|
1408
|
+
let stopping = false;
|
|
1409
|
+
function runChild() {
|
|
1410
|
+
if (stopping)
|
|
1411
|
+
return;
|
|
1412
|
+
const startAt = Date.now();
|
|
1413
|
+
process.stderr.write("[supervisor] starting bridge\n");
|
|
1414
|
+
const [cmd, ...args] = childArgv;
|
|
1415
|
+
if (!cmd)
|
|
1416
|
+
return;
|
|
1417
|
+
const child = spawn(cmd, args, {
|
|
1418
|
+
stdio: "inherit",
|
|
1419
|
+
});
|
|
1420
|
+
for (const sig of ["SIGTERM", "SIGINT"]) {
|
|
1421
|
+
process.once(sig, () => {
|
|
1422
|
+
stopping = true;
|
|
1423
|
+
child.kill(sig);
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
child.on("exit", (code, signal) => {
|
|
1427
|
+
if (stopping) {
|
|
1428
|
+
process.stderr.write("[supervisor] bridge stopped\n");
|
|
1429
|
+
process.exit(0);
|
|
1430
|
+
}
|
|
1431
|
+
const uptime = Date.now() - startAt;
|
|
1432
|
+
if (uptime >= STABLE_THRESHOLD_MS) {
|
|
1433
|
+
delay = BASE_DELAY_MS; // reset backoff after a stable run
|
|
1434
|
+
}
|
|
1435
|
+
process.stderr.write(`[supervisor] bridge exited (code=${code ?? signal}), restarting in ${delay / 1000}s\n`);
|
|
1436
|
+
setTimeout(() => {
|
|
1437
|
+
delay = Math.min(delay * 2, MAX_DELAY_MS);
|
|
1438
|
+
runChild();
|
|
1439
|
+
}, delay);
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
runChild();
|
|
1443
|
+
}
|
|
1444
|
+
else {
|
|
1445
|
+
const bridge = new Bridge(config);
|
|
1446
|
+
bridge.start().catch((err) => {
|
|
1447
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
1448
|
+
process.stderr.write(`Error: ${message}\n`);
|
|
1449
|
+
process.exit(1);
|
|
1450
|
+
});
|
|
1451
|
+
// F5: Silent self-update nudge (fire-and-forget)
|
|
1452
|
+
import("node:child_process")
|
|
1453
|
+
.then(({ exec }) => {
|
|
1454
|
+
exec("npm view claude-ide-bridge version", { timeout: 5000 }, (err, stdout) => {
|
|
1455
|
+
if (err || !stdout)
|
|
1456
|
+
return;
|
|
1457
|
+
const latest = stdout.trim();
|
|
1458
|
+
if (latest && semverGt(latest, PACKAGE_VERSION)) {
|
|
1459
|
+
console.log(`\n Bridge v${latest} available — run: npm update -g claude-ide-bridge\n`);
|
|
1460
|
+
}
|
|
1461
|
+
});
|
|
1462
|
+
})
|
|
1463
|
+
.catch(() => { });
|
|
1464
|
+
}
|
|
1465
|
+
//# sourceMappingURL=index.js.map
|