onto-mcp 0.3.2 → 0.4.1
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/.onto/processes/reconstruct/actionable-ontology-seed-recomposition-design.md +447 -0
- package/.onto/processes/reconstruct/foundry-style-ontology-seed-contract.md +934 -0
- package/.onto/processes/reconstruct/reconstruct-boundary-contract.md +303 -725
- package/.onto/processes/reconstruct/reconstruct-contract-registry.yaml +1645 -0
- package/.onto/processes/reconstruct/reconstruct-execution-ux-contract.md +26 -22
- package/.onto/processes/reconstruct/source-profile-contract.md +49 -23
- package/.onto/processes/reconstruct/source-profiles/code.md +6 -3
- package/.onto/processes/reconstruct/source-profiles/database.md +5 -2
- package/.onto/processes/reconstruct/source-profiles/document.md +5 -2
- package/.onto/processes/reconstruct/source-profiles/spreadsheet.md +5 -4
- package/.onto/processes/review/review-execution-ux-contract.md +40 -0
- package/.onto/processes/shared/pipeline-execution-ledger-contract.md +26 -10
- package/.onto/processes/shared/target-material-kind-contract.md +29 -16
- package/AGENTS.md +6 -4
- package/README.md +149 -76
- package/dist/cli.js +8 -8
- package/dist/core-api/reconstruct-api.js +117 -31
- package/dist/core-api/review-api.js +47 -0
- package/dist/core-runtime/cli/codex-review-unit-executor.js +39 -2
- package/dist/core-runtime/cli/complete-review-session.js +2 -2
- package/dist/core-runtime/cli/mock-review-unit-executor.js +1 -1
- package/dist/core-runtime/cli/review-invoke.js +9 -9
- package/dist/core-runtime/cli/run-review-prompt-execution.js +39 -5
- package/dist/core-runtime/cli/spawn-watcher.js +266 -47
- package/dist/core-runtime/cli/start-review-session.js +3 -3
- package/dist/core-runtime/llm/llm-caller.js +11 -0
- package/dist/core-runtime/llm/llm-tool-loop.js +2 -0
- package/dist/core-runtime/observability/runtime-stream-observation.js +118 -0
- package/dist/core-runtime/onboard/cli-host.js +174 -0
- package/dist/core-runtime/onboard/host-target.js +22 -0
- package/dist/core-runtime/onboard/json-config-host.js +122 -0
- package/dist/core-runtime/onboard/path-scan.js +26 -0
- package/dist/core-runtime/onboard/prompt.js +51 -0
- package/dist/core-runtime/onboard/register.js +214 -0
- package/dist/core-runtime/onboard/types.js +27 -0
- package/dist/core-runtime/reconstruct/actionable-seed-validation.js +1777 -0
- package/dist/core-runtime/reconstruct/artifact-types.js +10 -4
- package/dist/core-runtime/reconstruct/contract-registry.js +623 -0
- package/dist/core-runtime/reconstruct/domain-id.js +10 -0
- package/dist/core-runtime/reconstruct/governing-snapshot.js +716 -0
- package/dist/core-runtime/reconstruct/material-profile-validation.js +191 -0
- package/dist/core-runtime/reconstruct/materialize-preparation.js +49 -11
- package/dist/core-runtime/reconstruct/pipeline-execution-ledger.js +269 -79
- package/dist/core-runtime/reconstruct/post-seed-validation.js +1194 -51
- package/dist/core-runtime/reconstruct/record.js +104 -20
- package/dist/core-runtime/reconstruct/run.js +2107 -413
- package/dist/core-runtime/reconstruct/seed-claim-projections.js +268 -0
- package/dist/core-runtime/reconstruct/source-profiles.js +93 -4
- package/dist/core-runtime/reconstruct/terminal-validation.js +807 -0
- package/dist/core-runtime/review/review-invocation-runner.js +4 -4
- package/dist/mcp/server.js +110 -38
- package/dist/mcp/tool-schemas.js +20 -6
- package/package.json +8 -17
- package/scripts/onto-review-watch.sh +486 -0
- package/scripts/onto-runtime-watch.sh +122 -0
- package/scripts/postinstall-hint.js +22 -0
- package/.onto/processes/reconstruct/top-level-concept-discovery-contract.md +0 -387
- package/dist/core-runtime/cli/bootstrap-review-binding.js +0 -186
- package/dist/core-runtime/cli/codex-nested-dispatch.test.js +0 -390
- package/dist/core-runtime/cli/codex-nested-teamlead-executor.test.js +0 -335
- package/dist/core-runtime/cli/coordinator-helpers.js +0 -583
- package/dist/core-runtime/cli/coordinator-state-machine-deliberation.test.js +0 -167
- package/dist/core-runtime/cli/coordinator-state-machine.js +0 -794
- package/dist/core-runtime/cli/e2e-codex-multi-agent-fixes.test.js +0 -615
- package/dist/core-runtime/cli/e2e-start-review-session.test.js +0 -312
- package/dist/core-runtime/cli/health.js +0 -44
- package/dist/core-runtime/cli/inline-http-review-unit-executor.test.js +0 -567
- package/dist/core-runtime/cli/materialize-review-execution-preparation.js +0 -104
- package/dist/core-runtime/cli/migrate-session-roots.js +0 -118
- package/dist/core-runtime/cli/repo-layout-migration-replace.smoke.test.js +0 -106
- package/dist/core-runtime/cli/review-invoke-auto-resolution.test.js +0 -268
- package/dist/core-runtime/cli/review-invoke-coordinator-topology.test.js +0 -136
- package/dist/core-runtime/cli/review-invoke-resolver-caching.test.js +0 -201
- package/dist/core-runtime/cli/review-invoke-topology-dispatch.test.js +0 -192
- package/dist/core-runtime/cli/session-root-guard.js +0 -168
- package/dist/core-runtime/cli/spawn-watcher.test.js +0 -457
- package/dist/core-runtime/cli/strip-wrapping-code-fence.test.js +0 -79
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.js +0 -412
- package/dist/core-runtime/cli/teamcreate-lens-deliberation-executor.test.js +0 -351
- package/dist/core-runtime/cli/topology-executor-mapping.js +0 -139
- package/dist/core-runtime/cli/topology-executor-mapping.test.js +0 -173
- package/dist/core-runtime/cli/write-review-interpretation.js +0 -81
- package/dist/core-runtime/config/onto-config-cli.js +0 -278
- package/dist/core-runtime/config/onto-config-key-path.js +0 -288
- package/dist/core-runtime/config/onto-config-key-path.test.js +0 -195
- package/dist/core-runtime/config/onto-config-preview.js +0 -108
- package/dist/core-runtime/config/onto-config-preview.test.js +0 -132
- package/dist/core-runtime/discovery/config-chain.js +0 -118
- package/dist/core-runtime/discovery/config-chain.test.js +0 -103
- package/dist/core-runtime/discovery/config-profile.js +0 -199
- package/dist/core-runtime/discovery/config-profile.test.js +0 -233
- package/dist/core-runtime/discovery/host-detection.test.js +0 -186
- package/dist/core-runtime/discovery/installation-paths.test.js +0 -65
- package/dist/core-runtime/discovery/lens-registry.test.js +0 -81
- package/dist/core-runtime/discovery/path-normalization.test.js +0 -22
- package/dist/core-runtime/discovery/plugin-path.js +0 -72
- package/dist/core-runtime/discovery/plugin-path.test.js +0 -95
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.js +0 -344
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile-defense.test.js +0 -915
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.js +0 -564
- package/dist/core-runtime/evolve/adapters/code-product/compile/compile.test.js +0 -708
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.js +0 -165
- package/dist/core-runtime/evolve/adapters/code-product/parsers/brief-parser.test.js +0 -227
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.js +0 -59
- package/dist/core-runtime/evolve/adapters/code-product/validators/validate.test.js +0 -205
- package/dist/core-runtime/evolve/adapters/methodology/adapter.js +0 -16
- package/dist/core-runtime/evolve/adapters/methodology/adapter.test.js +0 -9
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.js +0 -298
- package/dist/core-runtime/evolve/adapters/methodology/perspectives/authority-consistency.test.js +0 -70
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.js +0 -46
- package/dist/core-runtime/evolve/adapters/methodology/scope-types/process.test.js +0 -73
- package/dist/core-runtime/evolve/adapters/registry.js +0 -47
- package/dist/core-runtime/evolve/adapters/registry.test.js +0 -67
- package/dist/core-runtime/evolve/cli.js +0 -256
- package/dist/core-runtime/evolve/commands/align.js +0 -194
- package/dist/core-runtime/evolve/commands/align.test.js +0 -82
- package/dist/core-runtime/evolve/commands/apply.js +0 -161
- package/dist/core-runtime/evolve/commands/apply.test.js +0 -138
- package/dist/core-runtime/evolve/commands/close.js +0 -39
- package/dist/core-runtime/evolve/commands/close.test.js +0 -99
- package/dist/core-runtime/evolve/commands/defer.js +0 -40
- package/dist/core-runtime/evolve/commands/defer.test.js +0 -134
- package/dist/core-runtime/evolve/commands/draft.js +0 -323
- package/dist/core-runtime/evolve/commands/draft.test.js +0 -178
- package/dist/core-runtime/evolve/commands/e2e-evolve-full-cycle.test.js +0 -208
- package/dist/core-runtime/evolve/commands/error-messages.js +0 -125
- package/dist/core-runtime/evolve/commands/error-messages.test.js +0 -167
- package/dist/core-runtime/evolve/commands/propose-align.js +0 -222
- package/dist/core-runtime/evolve/commands/propose-align.test.js +0 -136
- package/dist/core-runtime/evolve/commands/reconstruct.js +0 -330
- package/dist/core-runtime/evolve/commands/reconstruct.test.js +0 -278
- package/dist/core-runtime/evolve/commands/shared.js +0 -22
- package/dist/core-runtime/evolve/commands/stale-check.js +0 -103
- package/dist/core-runtime/evolve/commands/stale-check.test.js +0 -84
- package/dist/core-runtime/evolve/commands/start.js +0 -887
- package/dist/core-runtime/evolve/commands/start.test.js +0 -396
- package/dist/core-runtime/evolve/config/project-config.js +0 -99
- package/dist/core-runtime/evolve/config/project-config.test.js +0 -170
- package/dist/core-runtime/evolve/renderers/align-packet.js +0 -280
- package/dist/core-runtime/evolve/renderers/align-packet.test.js +0 -332
- package/dist/core-runtime/evolve/renderers/draft-packet.js +0 -303
- package/dist/core-runtime/evolve/renderers/draft-packet.test.js +0 -377
- package/dist/core-runtime/evolve/renderers/format.js +0 -5
- package/dist/core-runtime/evolve/renderers/scope-md.js +0 -237
- package/dist/core-runtime/evolve/renderers/scope-md.test.js +0 -306
- package/dist/core-runtime/govern/cli.js +0 -369
- package/dist/core-runtime/govern/cli.test.js +0 -314
- package/dist/core-runtime/govern/drift-engine.js +0 -103
- package/dist/core-runtime/govern/drift-engine.test.js +0 -319
- package/dist/core-runtime/govern/promote-principle.js +0 -206
- package/dist/core-runtime/govern/promote-principle.test.js +0 -368
- package/dist/core-runtime/govern/queue.js +0 -81
- package/dist/core-runtime/govern/types.js +0 -16
- package/dist/core-runtime/install/cli.js +0 -530
- package/dist/core-runtime/install/detect.js +0 -128
- package/dist/core-runtime/install/detect.test.js +0 -155
- package/dist/core-runtime/install/gitignore-update.js +0 -74
- package/dist/core-runtime/install/gitignore-update.test.js +0 -64
- package/dist/core-runtime/install/install-integration.test.js +0 -373
- package/dist/core-runtime/install/prompts.js +0 -389
- package/dist/core-runtime/install/prompts.test.js +0 -293
- package/dist/core-runtime/install/types.js +0 -26
- package/dist/core-runtime/install/validation.js +0 -295
- package/dist/core-runtime/install/validation.test.js +0 -313
- package/dist/core-runtime/install/writer.js +0 -254
- package/dist/core-runtime/install/writer.test.js +0 -218
- package/dist/core-runtime/learning/extractor.js +0 -461
- package/dist/core-runtime/learning/feedback.js +0 -179
- package/dist/core-runtime/learning/health-report.js +0 -165
- package/dist/core-runtime/learning/health-report.test.js +0 -169
- package/dist/core-runtime/learning/loader.js +0 -388
- package/dist/core-runtime/learning/loader.test.js +0 -102
- package/dist/core-runtime/learning/promote/apply-state.js +0 -240
- package/dist/core-runtime/learning/promote/audit-obligation.js +0 -195
- package/dist/core-runtime/learning/promote/collector.js +0 -432
- package/dist/core-runtime/learning/promote/degraded-state.js +0 -125
- package/dist/core-runtime/learning/promote/domain-doc-proposer.js +0 -166
- package/dist/core-runtime/learning/promote/e2e-promote.test.js +0 -6385
- package/dist/core-runtime/learning/promote/health-snapshot.js +0 -150
- package/dist/core-runtime/learning/promote/insight-reclassifier.js +0 -544
- package/dist/core-runtime/learning/promote/judgment-auditor.js +0 -517
- package/dist/core-runtime/learning/promote/panel-reviewer.js +0 -1158
- package/dist/core-runtime/learning/promote/promote-executor.js +0 -1675
- package/dist/core-runtime/learning/promote/promoter.js +0 -307
- package/dist/core-runtime/learning/promote/retirement.js +0 -122
- package/dist/core-runtime/learning/promote/types.js +0 -23
- package/dist/core-runtime/learning/prompt-sections.js +0 -51
- package/dist/core-runtime/learning/shared/artifact-registry-init.js +0 -45
- package/dist/core-runtime/learning/shared/artifact-registry.js +0 -254
- package/dist/core-runtime/learning/shared/audit-obligation-kernel.js +0 -73
- package/dist/core-runtime/learning/shared/audit-state.js +0 -99
- package/dist/core-runtime/learning/shared/duplicate-check.js +0 -28
- package/dist/core-runtime/learning/shared/llm-caller.js +0 -831
- package/dist/core-runtime/learning/shared/llm-caller.test.js +0 -601
- package/dist/core-runtime/learning/shared/llm-tool-loop.js +0 -393
- package/dist/core-runtime/learning/shared/mode.js +0 -25
- package/dist/core-runtime/learning/shared/paths.js +0 -84
- package/dist/core-runtime/learning/shared/paths.test.js +0 -79
- package/dist/core-runtime/learning/shared/patterns.js +0 -37
- package/dist/core-runtime/learning/shared/recoverability.js +0 -355
- package/dist/core-runtime/learning/shared/recovery-context.js +0 -374
- package/dist/core-runtime/learning/shared/scope.js +0 -1
- package/dist/core-runtime/learning/shared/semantic-classifier.js +0 -94
- package/dist/core-runtime/learning/shared/specs/apply-execution-state-spec.js +0 -42
- package/dist/core-runtime/learning/shared/specs/audit-state-spec.js +0 -37
- package/dist/core-runtime/learning/shared/specs/backup-metadata-spec.js +0 -39
- package/dist/core-runtime/learning/shared/specs/emergency-log-spec.js +0 -41
- package/dist/core-runtime/learning/shared/specs/layout-version-spec.js +0 -38
- package/dist/core-runtime/learning/shared/specs/promote-decisions-spec.js +0 -43
- package/dist/core-runtime/learning/shared/specs/promote-report-spec.js +0 -113
- package/dist/core-runtime/learning/shared/specs/prune-log-spec.js +0 -36
- package/dist/core-runtime/learning/shared/specs/recovery-resolution-spec.js +0 -48
- package/dist/core-runtime/learning/shared/specs/restore-manifest-spec.js +0 -43
- package/dist/core-runtime/learning/shared/specs/spec-helpers.js +0 -64
- package/dist/core-runtime/learning/usage-tracker.js +0 -190
- package/dist/core-runtime/learning/usage-tracker.test.js +0 -176
- package/dist/core-runtime/onboard/detect-review-axes.js +0 -122
- package/dist/core-runtime/onboard/detect-review-axes.test.js +0 -127
- package/dist/core-runtime/onboard/write-review-block.js +0 -188
- package/dist/core-runtime/onboard/write-review-block.test.js +0 -240
- package/dist/core-runtime/readers/brownfield-builder.js +0 -150
- package/dist/core-runtime/readers/brownfield-builder.test.js +0 -136
- package/dist/core-runtime/readers/code-chunk-collector.js +0 -53
- package/dist/core-runtime/readers/code-chunk-collector.test.js +0 -136
- package/dist/core-runtime/readers/file-utils.js +0 -240
- package/dist/core-runtime/readers/file-utils.test.js +0 -146
- package/dist/core-runtime/readers/lexicon-citation-check.js +0 -93
- package/dist/core-runtime/readers/lexicon-citation-check.test.js +0 -77
- package/dist/core-runtime/readers/mcp-figma.js +0 -30
- package/dist/core-runtime/readers/mcp-figma.test.js +0 -82
- package/dist/core-runtime/readers/mcp-generic.js +0 -31
- package/dist/core-runtime/readers/mcp-generic.test.js +0 -76
- package/dist/core-runtime/readers/ontology-index.js +0 -148
- package/dist/core-runtime/readers/ontology-index.test.js +0 -245
- package/dist/core-runtime/readers/ontology-query.js +0 -168
- package/dist/core-runtime/readers/ontology-query.test.js +0 -311
- package/dist/core-runtime/readers/ontology-resolve.js +0 -48
- package/dist/core-runtime/readers/ontology-resolve.test.js +0 -48
- package/dist/core-runtime/readers/patterns/index.js +0 -7
- package/dist/core-runtime/readers/review-log.js +0 -213
- package/dist/core-runtime/readers/review-log.test.js +0 -313
- package/dist/core-runtime/readers/scan-local.js +0 -102
- package/dist/core-runtime/readers/scan-local.test.js +0 -102
- package/dist/core-runtime/readers/scan-tarball.js +0 -121
- package/dist/core-runtime/readers/scan-tarball.test.js +0 -283
- package/dist/core-runtime/readers/scan-vault.js +0 -34
- package/dist/core-runtime/readers/scan-vault.test.js +0 -81
- package/dist/core-runtime/readers/types.js +0 -42
- package/dist/core-runtime/readers/types.test.js +0 -94
- package/dist/core-runtime/readers/viewpoint-collectors.js +0 -229
- package/dist/core-runtime/reconstruct/seed-candidate-validation.js +0 -385
- package/dist/core-runtime/review/citation-audit.test.js +0 -165
- package/dist/core-runtime/review/execution-plan-resolver.js +0 -247
- package/dist/core-runtime/review/execution-plan-resolver.test.js +0 -243
- package/dist/core-runtime/review/execution-topology-resolver-axis-first.test.js +0 -246
- package/dist/core-runtime/review/execution-topology-resolver.js +0 -401
- package/dist/core-runtime/review/execution-topology-resolver.test.js +0 -315
- package/dist/core-runtime/review/inline-context-embedder.test.js +0 -154
- package/dist/core-runtime/review/legacy-mode-policy.js +0 -88
- package/dist/core-runtime/review/materializers-effort-persist.test.js +0 -79
- package/dist/core-runtime/review/ontology-path-classifier.js +0 -179
- package/dist/core-runtime/review/ontology-path-classifier.test.js +0 -216
- package/dist/core-runtime/review/packet-boundary-policy.test.js +0 -107
- package/dist/core-runtime/review/participating-lens-paths.test.js +0 -73
- package/dist/core-runtime/review/review-config-legacy-translate.js +0 -244
- package/dist/core-runtime/review/review-config-legacy-translate.test.js +0 -161
- package/dist/core-runtime/review/review-config-validator.js +0 -289
- package/dist/core-runtime/review/review-config-validator.test.js +0 -236
- package/dist/core-runtime/review/shape-pipeline-audit.test.js +0 -311
- package/dist/core-runtime/review/shape-to-topology-id.js +0 -117
- package/dist/core-runtime/review/shape-to-topology-id.test.js +0 -132
- package/dist/core-runtime/review/topology-shape-derivation.js +0 -155
- package/dist/core-runtime/review/topology-shape-derivation.test.js +0 -195
- package/dist/core-runtime/scope-runtime/constants.js +0 -12
- package/dist/core-runtime/scope-runtime/constraint-pool.js +0 -166
- package/dist/core-runtime/scope-runtime/constraint-pool.test.js +0 -674
- package/dist/core-runtime/scope-runtime/domain-validation-log.js +0 -135
- package/dist/core-runtime/scope-runtime/domain-validation-log.test.js +0 -156
- package/dist/core-runtime/scope-runtime/eval-persistence.js +0 -65
- package/dist/core-runtime/scope-runtime/eval-persistence.test.js +0 -84
- package/dist/core-runtime/scope-runtime/event-pipeline.js +0 -64
- package/dist/core-runtime/scope-runtime/event-pipeline.test.js +0 -450
- package/dist/core-runtime/scope-runtime/event-store.js +0 -39
- package/dist/core-runtime/scope-runtime/event-store.test.js +0 -95
- package/dist/core-runtime/scope-runtime/gate-guard.js +0 -348
- package/dist/core-runtime/scope-runtime/gate-guard.test.js +0 -1047
- package/dist/core-runtime/scope-runtime/hash.js +0 -4
- package/dist/core-runtime/scope-runtime/hash.test.js +0 -33
- package/dist/core-runtime/scope-runtime/id.js +0 -4
- package/dist/core-runtime/scope-runtime/id.test.js +0 -17
- package/dist/core-runtime/scope-runtime/reducer.js +0 -297
- package/dist/core-runtime/scope-runtime/reducer.test.js +0 -759
- package/dist/core-runtime/scope-runtime/scope-manager.js +0 -161
- package/dist/core-runtime/scope-runtime/state-machine.js +0 -309
- package/dist/core-runtime/scope-runtime/state-machine.test.js +0 -704
- package/dist/core-runtime/scope-runtime/types.js +0 -116
- package/dist/core-runtime/scope-runtime/types.test.js +0 -69
- package/dist/core-runtime/translate/render-for-user.js +0 -169
- package/dist/core-runtime/translate/render-for-user.test.js +0 -122
- package/dist/providers/capability-contract.js +0 -1
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
import { execFileSync } from "node:child_process";
|
|
2
|
+
import { isCommandOnPath } from "./path-scan.js";
|
|
3
|
+
export const defaultCommandRunner = {
|
|
4
|
+
exists: (command) => isCommandOnPath(command),
|
|
5
|
+
run: (command, args, env) => {
|
|
6
|
+
try {
|
|
7
|
+
const stdout = execFileSync(command, args, {
|
|
8
|
+
encoding: "utf8",
|
|
9
|
+
...(env ? { env: { ...process.env, ...env } } : {}),
|
|
10
|
+
});
|
|
11
|
+
return { status: 0, stdout, stderr: "" };
|
|
12
|
+
}
|
|
13
|
+
catch (error) {
|
|
14
|
+
const err = error;
|
|
15
|
+
return {
|
|
16
|
+
status: typeof err.status === "number" ? err.status : 1,
|
|
17
|
+
stdout: typeof err.stdout === "string" ? err.stdout : "",
|
|
18
|
+
stderr: typeof err.stderr === "string"
|
|
19
|
+
? err.stderr
|
|
20
|
+
: error instanceof Error
|
|
21
|
+
? error.message
|
|
22
|
+
: String(error),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
function probeRegistered(spec, runner, entry) {
|
|
28
|
+
const result = runner.run(spec.cli, spec.listArgs(), spec.commandEnv);
|
|
29
|
+
if (result.status !== 0)
|
|
30
|
+
return "unknown";
|
|
31
|
+
const haystack = `${result.stdout}\n${result.stderr}`;
|
|
32
|
+
// CLIs print one server name per line; a word-boundary match avoids
|
|
33
|
+
// false positives from substrings of other server names.
|
|
34
|
+
const pattern = new RegExp(`(^|[^\\w-])${escapeRegExp(entry.name)}([^\\w-]|$)`, "m");
|
|
35
|
+
return pattern.test(haystack) ? "present" : "absent";
|
|
36
|
+
}
|
|
37
|
+
function escapeRegExp(value) {
|
|
38
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
39
|
+
}
|
|
40
|
+
export function createCliHost(spec, runner = defaultCommandRunner) {
|
|
41
|
+
const detect = () => (runner.exists(spec.cli) ? "cli" : "absent");
|
|
42
|
+
const withNote = (summary) => spec.targetNote ? `${summary} (${spec.targetNote})` : summary;
|
|
43
|
+
return {
|
|
44
|
+
id: spec.id,
|
|
45
|
+
displayName: spec.displayName,
|
|
46
|
+
detect,
|
|
47
|
+
plan(entry) {
|
|
48
|
+
const detection = detect();
|
|
49
|
+
if (detection === "absent") {
|
|
50
|
+
return {
|
|
51
|
+
hostId: spec.id,
|
|
52
|
+
displayName: spec.displayName,
|
|
53
|
+
detection,
|
|
54
|
+
method: "manual",
|
|
55
|
+
summary: `${spec.cli} CLI not found — manual step required`,
|
|
56
|
+
manualInstructions: spec.manualInstructions(entry),
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
const commandLine = `${spec.cli} ${spec.addArgs(entry).join(" ")}`;
|
|
60
|
+
return {
|
|
61
|
+
hostId: spec.id,
|
|
62
|
+
displayName: spec.displayName,
|
|
63
|
+
detection,
|
|
64
|
+
method: "cli",
|
|
65
|
+
summary: withNote(`Run: ${commandLine}`),
|
|
66
|
+
commandLine,
|
|
67
|
+
};
|
|
68
|
+
},
|
|
69
|
+
async apply(entry, options) {
|
|
70
|
+
const base = { hostId: spec.id, displayName: spec.displayName };
|
|
71
|
+
if (detect() === "absent") {
|
|
72
|
+
return { ...base, outcome: "manual", detail: spec.manualInstructions(entry) };
|
|
73
|
+
}
|
|
74
|
+
const before = probeRegistered(spec, runner, entry);
|
|
75
|
+
if (before === "present" && !options.force) {
|
|
76
|
+
return {
|
|
77
|
+
...base,
|
|
78
|
+
outcome: "skipped",
|
|
79
|
+
detail: `${entry.name} already registered (use --force to re-add)`,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
if (before === "present" && options.force) {
|
|
83
|
+
runner.run(spec.cli, spec.removeArgs(entry), spec.commandEnv); // best effort
|
|
84
|
+
}
|
|
85
|
+
const result = runner.run(spec.cli, spec.addArgs(entry), spec.commandEnv);
|
|
86
|
+
if (result.status !== 0) {
|
|
87
|
+
return {
|
|
88
|
+
...base,
|
|
89
|
+
outcome: "failed",
|
|
90
|
+
detail: (result.stderr || result.stdout || "command failed").trim(),
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
// Verify the add actually took effect. A CLI that exits 0 without
|
|
94
|
+
// registering (aliased/wrapper `claude`, wrong profile) must not be
|
|
95
|
+
// reported as success.
|
|
96
|
+
const after = probeRegistered(spec, runner, entry);
|
|
97
|
+
const outcome = before === "present" ? "updated" : "registered";
|
|
98
|
+
if (after === "present") {
|
|
99
|
+
return { ...base, outcome, detail: `${spec.cli} ${spec.addArgs(entry).join(" ")}` };
|
|
100
|
+
}
|
|
101
|
+
if (after === "unknown") {
|
|
102
|
+
return {
|
|
103
|
+
...base,
|
|
104
|
+
outcome,
|
|
105
|
+
detail: `${spec.cli} ${spec.addArgs(entry).join(" ")} (could not verify via ${spec.cli} mcp list)`,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
return {
|
|
109
|
+
...base,
|
|
110
|
+
outcome: "failed",
|
|
111
|
+
detail: `${spec.cli} accepted the command but ${entry.name} is not listed afterward. ` +
|
|
112
|
+
`The ${spec.cli} on PATH may be an alias/wrapper or target a different profile` +
|
|
113
|
+
(spec.targetNote ? ` (${spec.targetNote})` : "") +
|
|
114
|
+
`. Try registering against the real CLI/profile directly.`,
|
|
115
|
+
};
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Claude Code — registers at user scope so it applies across all projects.
|
|
121
|
+
*
|
|
122
|
+
* Config-dir resolution: explicit `configDir` wins; otherwise an ambient
|
|
123
|
+
* `CLAUDE_CONFIG_DIR` is honored (and shown in the plan); otherwise the claude
|
|
124
|
+
* default (`~/.claude`) applies.
|
|
125
|
+
*/
|
|
126
|
+
export function createClaudeCodeHost(options = {}) {
|
|
127
|
+
const effectiveDir = options.configDir ?? process.env.CLAUDE_CONFIG_DIR;
|
|
128
|
+
const spec = {
|
|
129
|
+
id: "claude-code",
|
|
130
|
+
displayName: "Claude Code",
|
|
131
|
+
cli: "claude",
|
|
132
|
+
addArgs: (entry) => [
|
|
133
|
+
"mcp",
|
|
134
|
+
"add",
|
|
135
|
+
entry.name,
|
|
136
|
+
"-s",
|
|
137
|
+
"user",
|
|
138
|
+
"--",
|
|
139
|
+
entry.command,
|
|
140
|
+
...entry.args,
|
|
141
|
+
],
|
|
142
|
+
removeArgs: (entry) => ["mcp", "remove", entry.name, "-s", "user"],
|
|
143
|
+
listArgs: () => ["mcp", "list"],
|
|
144
|
+
manualInstructions: (entry) => `claude CLI not found. Install Claude Code, then run:\n` +
|
|
145
|
+
` claude mcp add ${entry.name} -s user -- ${entry.command} ${entry.args.join(" ")}`,
|
|
146
|
+
...(effectiveDir ? { commandEnv: { CLAUDE_CONFIG_DIR: effectiveDir } } : {}),
|
|
147
|
+
targetNote: effectiveDir ? `config dir: ${effectiveDir}` : "config dir: claude default (~/.claude)",
|
|
148
|
+
};
|
|
149
|
+
return createCliHost(spec, options.runner);
|
|
150
|
+
}
|
|
151
|
+
/** Codex CLI. */
|
|
152
|
+
export function createCodexHost(runner) {
|
|
153
|
+
return createCliHost({
|
|
154
|
+
id: "codex",
|
|
155
|
+
displayName: "Codex CLI",
|
|
156
|
+
cli: "codex",
|
|
157
|
+
addArgs: (entry) => [
|
|
158
|
+
"mcp",
|
|
159
|
+
"add",
|
|
160
|
+
entry.name,
|
|
161
|
+
"--",
|
|
162
|
+
entry.command,
|
|
163
|
+
...entry.args,
|
|
164
|
+
],
|
|
165
|
+
removeArgs: (entry) => ["mcp", "remove", entry.name],
|
|
166
|
+
listArgs: () => ["mcp", "list"],
|
|
167
|
+
manualInstructions: (entry) => `codex CLI not found. Either install Codex and run:\n` +
|
|
168
|
+
` codex mcp add ${entry.name} -- ${entry.command} ${entry.args.join(" ")}\n` +
|
|
169
|
+
`or add this block to ~/.codex/config.toml:\n` +
|
|
170
|
+
` [mcp_servers.${entry.name}]\n` +
|
|
171
|
+
` command = "${entry.command}"\n` +
|
|
172
|
+
` args = [${entry.args.map((a) => `"${a}"`).join(", ")}]`,
|
|
173
|
+
}, runner);
|
|
174
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { createClaudeCodeHost, createCodexHost } from "./cli-host.js";
|
|
2
|
+
import { claudeDesktopConfigPath, createJsonConfigHost, cursorConfigPath, } from "./json-config-host.js";
|
|
3
|
+
/**
|
|
4
|
+
* The four supported hosts in display order. CLI-backed hosts (Claude Code,
|
|
5
|
+
* Codex) come first; JSON-config hosts (Claude Desktop, Cursor) follow.
|
|
6
|
+
*/
|
|
7
|
+
export function getDefaultHostTargets(options = {}) {
|
|
8
|
+
return [
|
|
9
|
+
createClaudeCodeHost(options.claudeConfigDir ? { configDir: options.claudeConfigDir } : {}),
|
|
10
|
+
createCodexHost(),
|
|
11
|
+
createJsonConfigHost({
|
|
12
|
+
id: "claude-desktop",
|
|
13
|
+
displayName: "Claude Desktop",
|
|
14
|
+
resolvePath: () => claudeDesktopConfigPath(),
|
|
15
|
+
}),
|
|
16
|
+
createJsonConfigHost({
|
|
17
|
+
id: "cursor",
|
|
18
|
+
displayName: "Cursor",
|
|
19
|
+
resolvePath: () => cursorConfigPath(),
|
|
20
|
+
}),
|
|
21
|
+
];
|
|
22
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import fsSync from "node:fs";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import { entryMatches, } from "./types.js";
|
|
6
|
+
/**
|
|
7
|
+
* Pure reconciliation: given the previously-parsed config (or undefined when the
|
|
8
|
+
* file is absent/empty), return the new config object and what changed. Our own
|
|
9
|
+
* `<name>` key is always brought to the desired value; sibling servers and other
|
|
10
|
+
* top-level keys are preserved.
|
|
11
|
+
*/
|
|
12
|
+
export function reconcileMcpServers(existing, entry) {
|
|
13
|
+
const root = existing && typeof existing === "object" && !Array.isArray(existing)
|
|
14
|
+
? { ...existing }
|
|
15
|
+
: {};
|
|
16
|
+
const priorServers = root.mcpServers && typeof root.mcpServers === "object" && !Array.isArray(root.mcpServers)
|
|
17
|
+
? root.mcpServers
|
|
18
|
+
: {};
|
|
19
|
+
const servers = { ...priorServers };
|
|
20
|
+
const prior = servers[entry.name];
|
|
21
|
+
if (entryMatches(prior, entry)) {
|
|
22
|
+
return { config: root, outcome: "skipped" };
|
|
23
|
+
}
|
|
24
|
+
servers[entry.name] = { command: entry.command, args: [...entry.args] };
|
|
25
|
+
root.mcpServers = servers;
|
|
26
|
+
return {
|
|
27
|
+
config: root,
|
|
28
|
+
outcome: prior === undefined ? "registered" : "updated",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function defaultDetect(configPath) {
|
|
32
|
+
if (fsSync.existsSync(configPath))
|
|
33
|
+
return "config";
|
|
34
|
+
if (fsSync.existsSync(path.dirname(configPath)))
|
|
35
|
+
return "config";
|
|
36
|
+
return "absent";
|
|
37
|
+
}
|
|
38
|
+
async function readJsonIfPresent(filePath) {
|
|
39
|
+
let text;
|
|
40
|
+
try {
|
|
41
|
+
text = await fs.readFile(filePath, "utf8");
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
return undefined;
|
|
45
|
+
}
|
|
46
|
+
if (text.trim().length === 0)
|
|
47
|
+
return undefined;
|
|
48
|
+
try {
|
|
49
|
+
return JSON.parse(text);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
throw new Error(`Existing config at ${filePath} is not valid JSON: ${error instanceof Error ? error.message : String(error)}. Fix or remove it, then re-run.`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
export function createJsonConfigHost(spec) {
|
|
56
|
+
const detect = spec.detect ?? (() => defaultDetect(spec.resolvePath()));
|
|
57
|
+
return {
|
|
58
|
+
id: spec.id,
|
|
59
|
+
displayName: spec.displayName,
|
|
60
|
+
detect,
|
|
61
|
+
plan(entry) {
|
|
62
|
+
const targetPath = spec.resolvePath();
|
|
63
|
+
return {
|
|
64
|
+
hostId: spec.id,
|
|
65
|
+
displayName: spec.displayName,
|
|
66
|
+
detection: detect(),
|
|
67
|
+
method: "config",
|
|
68
|
+
summary: `Set mcpServers.${entry.name} → ${entry.command} ${entry.args.join(" ")}`.trim(),
|
|
69
|
+
targetPath,
|
|
70
|
+
};
|
|
71
|
+
},
|
|
72
|
+
async apply(entry, _options) {
|
|
73
|
+
const targetPath = spec.resolvePath();
|
|
74
|
+
try {
|
|
75
|
+
const existing = await readJsonIfPresent(targetPath);
|
|
76
|
+
const { config, outcome } = reconcileMcpServers(existing, entry);
|
|
77
|
+
if (outcome === "skipped") {
|
|
78
|
+
return {
|
|
79
|
+
hostId: spec.id,
|
|
80
|
+
displayName: spec.displayName,
|
|
81
|
+
outcome: "skipped",
|
|
82
|
+
detail: `${entry.name} already registered in ${targetPath}`,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
await fs.mkdir(path.dirname(targetPath), { recursive: true });
|
|
86
|
+
await fs.writeFile(targetPath, `${JSON.stringify(config, null, 2)}\n`, "utf8");
|
|
87
|
+
return {
|
|
88
|
+
hostId: spec.id,
|
|
89
|
+
displayName: spec.displayName,
|
|
90
|
+
outcome,
|
|
91
|
+
detail: `${outcome === "registered" ? "Added" : "Updated"} ${entry.name} in ${targetPath}`,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
return {
|
|
96
|
+
hostId: spec.id,
|
|
97
|
+
displayName: spec.displayName,
|
|
98
|
+
outcome: "failed",
|
|
99
|
+
detail: error instanceof Error ? error.message : String(error),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Claude Desktop config path. macOS and Windows use app-data locations; other
|
|
107
|
+
* platforms fall back to a Linux-style XDG path (best effort).
|
|
108
|
+
*/
|
|
109
|
+
export function claudeDesktopConfigPath(homedir = os.homedir(), platform = process.platform) {
|
|
110
|
+
if (platform === "darwin") {
|
|
111
|
+
return path.join(homedir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
|
|
112
|
+
}
|
|
113
|
+
if (platform === "win32") {
|
|
114
|
+
const appData = process.env.APPDATA ?? path.join(homedir, "AppData", "Roaming");
|
|
115
|
+
return path.join(appData, "Claude", "claude_desktop_config.json");
|
|
116
|
+
}
|
|
117
|
+
return path.join(homedir, ".config", "Claude", "claude_desktop_config.json");
|
|
118
|
+
}
|
|
119
|
+
/** Cursor global MCP config path. */
|
|
120
|
+
export function cursorConfigPath(homedir = os.homedir()) {
|
|
121
|
+
return path.join(homedir, ".cursor", "mcp.json");
|
|
122
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import fsSync from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* True when `command` resolves to an executable on PATH.
|
|
5
|
+
*
|
|
6
|
+
* Mirrors the PATH-scan strategy in
|
|
7
|
+
* `src/core-runtime/discovery/host-detection.ts` (`detectCodexBinaryAvailable`),
|
|
8
|
+
* but is generic and does not require any auth file. On Windows it also probes
|
|
9
|
+
* the common executable extensions.
|
|
10
|
+
*/
|
|
11
|
+
export function isCommandOnPath(command, env = process.env) {
|
|
12
|
+
const pathEnv = env.PATH ?? env.Path ?? "";
|
|
13
|
+
const isWindows = process.platform === "win32";
|
|
14
|
+
const candidates = isWindows
|
|
15
|
+
? [command, `${command}.cmd`, `${command}.exe`, `${command}.bat`]
|
|
16
|
+
: [command];
|
|
17
|
+
for (const dir of pathEnv.split(path.delimiter)) {
|
|
18
|
+
if (!dir)
|
|
19
|
+
continue;
|
|
20
|
+
for (const candidate of candidates) {
|
|
21
|
+
if (fsSync.existsSync(path.join(dir, candidate)))
|
|
22
|
+
return true;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import readline from "node:readline";
|
|
2
|
+
function createInterface() {
|
|
3
|
+
return readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
4
|
+
}
|
|
5
|
+
function ask(rl, question) {
|
|
6
|
+
return new Promise((resolve) => rl.question(question, resolve));
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Numbered multi-select. The user enters comma/space-separated numbers; empty
|
|
10
|
+
* input accepts `defaultSelectedIds`. Returns the chosen choice ids in list order.
|
|
11
|
+
*/
|
|
12
|
+
export async function promptMultiSelect(title, choices, defaultSelectedIds) {
|
|
13
|
+
const rl = createInterface();
|
|
14
|
+
try {
|
|
15
|
+
console.log(title);
|
|
16
|
+
choices.forEach((choice, index) => {
|
|
17
|
+
const mark = defaultSelectedIds.includes(choice.id) ? "*" : " ";
|
|
18
|
+
const detail = choice.detail ? ` — ${choice.detail}` : "";
|
|
19
|
+
console.log(` [${mark}] ${index + 1}) ${choice.label}${detail}`);
|
|
20
|
+
});
|
|
21
|
+
const answer = (await ask(rl, "Select numbers (comma/space separated, blank = starred defaults): ")).trim();
|
|
22
|
+
if (answer.length === 0) {
|
|
23
|
+
return choices.filter((c) => defaultSelectedIds.includes(c.id)).map((c) => c.id);
|
|
24
|
+
}
|
|
25
|
+
const picked = new Set();
|
|
26
|
+
for (const token of answer.split(/[\s,]+/).filter(Boolean)) {
|
|
27
|
+
const index = Number.parseInt(token, 10) - 1;
|
|
28
|
+
if (Number.isInteger(index) && index >= 0 && index < choices.length) {
|
|
29
|
+
picked.add(choices[index].id);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return choices.filter((c) => picked.has(c.id)).map((c) => c.id);
|
|
33
|
+
}
|
|
34
|
+
finally {
|
|
35
|
+
rl.close();
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
/** Yes/no confirmation. Empty input returns `defaultYes`. */
|
|
39
|
+
export async function promptYesNo(question, defaultYes) {
|
|
40
|
+
const rl = createInterface();
|
|
41
|
+
try {
|
|
42
|
+
const suffix = defaultYes ? "[Y/n]" : "[y/N]";
|
|
43
|
+
const answer = (await ask(rl, `${question} ${suffix} `)).trim().toLowerCase();
|
|
44
|
+
if (answer.length === 0)
|
|
45
|
+
return defaultYes;
|
|
46
|
+
return answer === "y" || answer === "yes";
|
|
47
|
+
}
|
|
48
|
+
finally {
|
|
49
|
+
rl.close();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
import { ALL_HOST_IDS, } from "./types.js";
|
|
2
|
+
import { getDefaultHostTargets } from "./host-target.js";
|
|
3
|
+
import { promptMultiSelect, promptYesNo } from "./prompt.js";
|
|
4
|
+
const USAGE = [
|
|
5
|
+
"Usage: onto register [options]",
|
|
6
|
+
"",
|
|
7
|
+
"Register the onto MCP server into supported hosts so they launch `onto mcp`.",
|
|
8
|
+
"Interactive when run in a terminal with no --hosts/--all; flag-driven otherwise.",
|
|
9
|
+
"",
|
|
10
|
+
"Hosts: claude-code, codex, claude-desktop, cursor",
|
|
11
|
+
"",
|
|
12
|
+
"Options:",
|
|
13
|
+
" --hosts <a,b,...> Comma-separated host ids to register",
|
|
14
|
+
" --all Register all supported hosts",
|
|
15
|
+
" --list Show host detection status and exit",
|
|
16
|
+
" --dry-run Show intended changes without writing",
|
|
17
|
+
" --yes, -y Skip the confirmation prompt (required in non-TTY)",
|
|
18
|
+
" --force Re-register CLI hosts even if already present",
|
|
19
|
+
" --name <id> MCP server name (default: onto)",
|
|
20
|
+
" --command <cmd> Executable the host launches (default: onto)",
|
|
21
|
+
" --claude-config-dir <path> Target a Claude Code profile (sets",
|
|
22
|
+
" CLAUDE_CONFIG_DIR; default: ambient env or ~/.claude)",
|
|
23
|
+
" --help, -h Show this help",
|
|
24
|
+
].join("\n");
|
|
25
|
+
export function parseRegisterArgs(argv) {
|
|
26
|
+
const parsed = {
|
|
27
|
+
hosts: undefined,
|
|
28
|
+
yes: false,
|
|
29
|
+
dryRun: false,
|
|
30
|
+
force: false,
|
|
31
|
+
list: false,
|
|
32
|
+
help: false,
|
|
33
|
+
name: "onto",
|
|
34
|
+
command: "onto",
|
|
35
|
+
claudeConfigDir: undefined,
|
|
36
|
+
unknownFlags: [],
|
|
37
|
+
invalidHosts: [],
|
|
38
|
+
};
|
|
39
|
+
for (let i = 0; i < argv.length; i++) {
|
|
40
|
+
const arg = argv[i];
|
|
41
|
+
switch (arg) {
|
|
42
|
+
case "--all":
|
|
43
|
+
parsed.hosts = "all";
|
|
44
|
+
break;
|
|
45
|
+
case "--yes":
|
|
46
|
+
case "-y":
|
|
47
|
+
parsed.yes = true;
|
|
48
|
+
break;
|
|
49
|
+
case "--dry-run":
|
|
50
|
+
parsed.dryRun = true;
|
|
51
|
+
break;
|
|
52
|
+
case "--force":
|
|
53
|
+
parsed.force = true;
|
|
54
|
+
break;
|
|
55
|
+
case "--list":
|
|
56
|
+
parsed.list = true;
|
|
57
|
+
break;
|
|
58
|
+
case "--help":
|
|
59
|
+
case "-h":
|
|
60
|
+
parsed.help = true;
|
|
61
|
+
break;
|
|
62
|
+
case "--hosts": {
|
|
63
|
+
const value = argv[++i] ?? "";
|
|
64
|
+
const ids = [];
|
|
65
|
+
for (const raw of value.split(",").map((s) => s.trim()).filter(Boolean)) {
|
|
66
|
+
if (ALL_HOST_IDS.includes(raw)) {
|
|
67
|
+
ids.push(raw);
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
parsed.invalidHosts.push(raw);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
parsed.hosts = parsed.hosts === "all" ? "all" : ids;
|
|
74
|
+
break;
|
|
75
|
+
}
|
|
76
|
+
case "--name":
|
|
77
|
+
parsed.name = argv[++i] ?? parsed.name;
|
|
78
|
+
break;
|
|
79
|
+
case "--command":
|
|
80
|
+
parsed.command = argv[++i] ?? parsed.command;
|
|
81
|
+
break;
|
|
82
|
+
case "--claude-config-dir":
|
|
83
|
+
parsed.claudeConfigDir = argv[++i] ?? parsed.claudeConfigDir;
|
|
84
|
+
break;
|
|
85
|
+
default:
|
|
86
|
+
parsed.unknownFlags.push(arg);
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return parsed;
|
|
91
|
+
}
|
|
92
|
+
function resolveSelection(parsed, targets) {
|
|
93
|
+
if (parsed.hosts === "all")
|
|
94
|
+
return targets.map((t) => t.id);
|
|
95
|
+
if (Array.isArray(parsed.hosts)) {
|
|
96
|
+
const requested = new Set(parsed.hosts);
|
|
97
|
+
return targets.map((t) => t.id).filter((id) => requested.has(id));
|
|
98
|
+
}
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
function detectionLabel(status) {
|
|
102
|
+
if (status === "cli")
|
|
103
|
+
return "detected (CLI)";
|
|
104
|
+
if (status === "config")
|
|
105
|
+
return "detected (config)";
|
|
106
|
+
return "not detected";
|
|
107
|
+
}
|
|
108
|
+
function printPlans(plans) {
|
|
109
|
+
console.log("\nPlanned changes:");
|
|
110
|
+
for (const plan of plans) {
|
|
111
|
+
console.log(` • ${plan.displayName} [${detectionLabel(plan.detection)}]`);
|
|
112
|
+
console.log(` ${plan.summary}`);
|
|
113
|
+
if (plan.method === "manual" && plan.manualInstructions) {
|
|
114
|
+
for (const line of plan.manualInstructions.split("\n")) {
|
|
115
|
+
console.log(` ${line}`);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
function printResults(results) {
|
|
121
|
+
console.log("\nResults:");
|
|
122
|
+
for (const result of results) {
|
|
123
|
+
console.log(` • ${result.displayName}: ${result.outcome} — ${result.detail}`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
export async function runRegister(argv, deps = {}) {
|
|
127
|
+
const parsed = parseRegisterArgs(argv);
|
|
128
|
+
if (parsed.help) {
|
|
129
|
+
console.log(USAGE);
|
|
130
|
+
return 0;
|
|
131
|
+
}
|
|
132
|
+
if (parsed.unknownFlags.length > 0) {
|
|
133
|
+
console.error(`[onto register] Unknown option(s): ${parsed.unknownFlags.join(", ")}`);
|
|
134
|
+
console.error(USAGE);
|
|
135
|
+
return 1;
|
|
136
|
+
}
|
|
137
|
+
if (parsed.invalidHosts.length > 0) {
|
|
138
|
+
console.error(`[onto register] Unknown host(s): ${parsed.invalidHosts.join(", ")}. ` +
|
|
139
|
+
`Valid: ${ALL_HOST_IDS.join(", ")}`);
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
const targets = deps.targets ??
|
|
143
|
+
getDefaultHostTargets(parsed.claudeConfigDir ? { claudeConfigDir: parsed.claudeConfigDir } : {});
|
|
144
|
+
const isTty = deps.isTty ?? Boolean(process.stdin.isTTY);
|
|
145
|
+
if (parsed.list) {
|
|
146
|
+
console.log("Host detection:");
|
|
147
|
+
for (const target of targets) {
|
|
148
|
+
console.log(` • ${target.displayName}: ${detectionLabel(target.detect())}`);
|
|
149
|
+
}
|
|
150
|
+
return 0;
|
|
151
|
+
}
|
|
152
|
+
const entry = {
|
|
153
|
+
name: parsed.name,
|
|
154
|
+
command: parsed.command,
|
|
155
|
+
args: ["mcp"],
|
|
156
|
+
};
|
|
157
|
+
// Determine which hosts to register.
|
|
158
|
+
let selected = resolveSelection(parsed, targets);
|
|
159
|
+
const selectionGivenByFlag = parsed.hosts !== undefined;
|
|
160
|
+
if (!selectionGivenByFlag) {
|
|
161
|
+
if (!isTty) {
|
|
162
|
+
console.error("[onto register] No hosts specified. Use --hosts <ids> or --all " +
|
|
163
|
+
"(interactive selection requires a terminal).");
|
|
164
|
+
return 1;
|
|
165
|
+
}
|
|
166
|
+
const defaults = targets.filter((t) => t.detect() !== "absent").map((t) => t.id);
|
|
167
|
+
const chosen = await promptMultiSelect("Select hosts to register onto with:", targets.map((t) => ({
|
|
168
|
+
id: t.id,
|
|
169
|
+
label: t.displayName,
|
|
170
|
+
detail: detectionLabel(t.detect()),
|
|
171
|
+
})), defaults.length > 0 ? defaults : targets.map((t) => t.id));
|
|
172
|
+
selected = targets.map((t) => t.id).filter((id) => chosen.includes(id));
|
|
173
|
+
}
|
|
174
|
+
if (selected.length === 0) {
|
|
175
|
+
console.error("[onto register] No hosts selected. Nothing to do.");
|
|
176
|
+
return 1;
|
|
177
|
+
}
|
|
178
|
+
const selectedTargets = targets.filter((t) => selected.includes(t.id));
|
|
179
|
+
const options = { force: parsed.force, dryRun: parsed.dryRun };
|
|
180
|
+
const plans = selectedTargets.map((t) => t.plan(entry, options));
|
|
181
|
+
printPlans(plans);
|
|
182
|
+
if (parsed.dryRun) {
|
|
183
|
+
console.log("\n(dry-run) No changes were written.");
|
|
184
|
+
return 0;
|
|
185
|
+
}
|
|
186
|
+
// Confirmation gate (write-before-confirm governance, product-locality §5.3 spirit).
|
|
187
|
+
if (!parsed.yes) {
|
|
188
|
+
if (!isTty) {
|
|
189
|
+
console.error("\n[onto register] Refusing to write without confirmation. Re-run with --yes.");
|
|
190
|
+
return 1;
|
|
191
|
+
}
|
|
192
|
+
const proceed = await promptYesNo("\nApply these changes?", true);
|
|
193
|
+
if (!proceed) {
|
|
194
|
+
console.log("Aborted. No changes written.");
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
const results = [];
|
|
199
|
+
for (const target of selectedTargets) {
|
|
200
|
+
results.push(await target.apply(entry, options));
|
|
201
|
+
}
|
|
202
|
+
printResults(results);
|
|
203
|
+
const failed = results.filter((r) => r.outcome === "failed");
|
|
204
|
+
const manual = results.filter((r) => r.outcome === "manual");
|
|
205
|
+
if (manual.length > 0) {
|
|
206
|
+
console.log(`\nNote: ${manual.length} host(s) need a manual step (CLI not found) — see above.`);
|
|
207
|
+
}
|
|
208
|
+
if (failed.length > 0) {
|
|
209
|
+
console.error(`\n${failed.length} host(s) failed to register.`);
|
|
210
|
+
return 1;
|
|
211
|
+
}
|
|
212
|
+
console.log("\nDone. Restart the host app(s) to pick up the onto MCP server.");
|
|
213
|
+
return 0;
|
|
214
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host MCP registration types.
|
|
3
|
+
*
|
|
4
|
+
* `onto register` writes the onto MCP server into each supported host's own
|
|
5
|
+
* configuration so the host launches `onto mcp` as a stdio MCP server. This is
|
|
6
|
+
* onboarding/integration, not onto runtime data: targets write to host-owned
|
|
7
|
+
* config (user home), never to `{product}/.onto/`.
|
|
8
|
+
*/
|
|
9
|
+
export const ALL_HOST_IDS = [
|
|
10
|
+
"claude-code",
|
|
11
|
+
"codex",
|
|
12
|
+
"claude-desktop",
|
|
13
|
+
"cursor",
|
|
14
|
+
];
|
|
15
|
+
/** True when `command` and `args` of an existing host entry already match. */
|
|
16
|
+
export function entryMatches(existing, entry) {
|
|
17
|
+
if (!existing || typeof existing !== "object")
|
|
18
|
+
return false;
|
|
19
|
+
if (existing.command !== entry.command)
|
|
20
|
+
return false;
|
|
21
|
+
const existingArgs = existing.args;
|
|
22
|
+
if (!Array.isArray(existingArgs))
|
|
23
|
+
return entry.args.length === 0;
|
|
24
|
+
if (existingArgs.length !== entry.args.length)
|
|
25
|
+
return false;
|
|
26
|
+
return existingArgs.every((value, index) => value === entry.args[index]);
|
|
27
|
+
}
|