@uxmaltech/collab-cli 0.1.1 → 0.1.2
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/dist/commands/init.js +168 -31
- package/dist/lib/config.js +12 -0
- package/dist/lib/infra-type.js +56 -0
- package/dist/lib/mcp-client.js +5 -0
- package/package.json +1 -1
package/dist/commands/init.js
CHANGED
|
@@ -12,6 +12,7 @@ const config_1 = require("../lib/config");
|
|
|
12
12
|
const ecosystem_1 = require("../lib/ecosystem");
|
|
13
13
|
const compose_renderer_1 = require("../lib/compose-renderer");
|
|
14
14
|
const errors_1 = require("../lib/errors");
|
|
15
|
+
const infra_type_1 = require("../lib/infra-type");
|
|
15
16
|
const mode_1 = require("../lib/mode");
|
|
16
17
|
const parsers_1 = require("../lib/parsers");
|
|
17
18
|
const orchestrator_1 = require("../lib/orchestrator");
|
|
@@ -52,19 +53,32 @@ function inferComposeMode(config) {
|
|
|
52
53
|
}
|
|
53
54
|
return 'consolidated';
|
|
54
55
|
}
|
|
55
|
-
async function resolveWizardSelection(options, config) {
|
|
56
|
+
async function resolveWizardSelection(options, config, logger) {
|
|
56
57
|
const defaults = {
|
|
57
58
|
mode: (0, mode_1.parseMode)(options.mode, config.mode),
|
|
58
59
|
composeMode: parseComposeMode(options.composeMode, inferComposeMode(config)),
|
|
60
|
+
infraType: (0, infra_type_1.parseInfraType)(options.infraType, config.infraType),
|
|
59
61
|
};
|
|
60
62
|
if (options.yes) {
|
|
61
63
|
if (!options.mode) {
|
|
62
64
|
process.stderr.write('Info: Non-interactive mode defaults to file-only. Use --mode indexed for graph/vector features.\n');
|
|
63
65
|
}
|
|
66
|
+
const mode = options.mode ? (0, mode_1.parseMode)(options.mode) : 'file-only';
|
|
67
|
+
const infraType = mode === 'indexed'
|
|
68
|
+
? (0, infra_type_1.parseInfraType)(options.infraType, 'local')
|
|
69
|
+
: 'local';
|
|
70
|
+
let mcpUrl;
|
|
71
|
+
if (infraType === 'remote') {
|
|
72
|
+
if (!options.mcpUrl) {
|
|
73
|
+
throw new errors_1.CliError('--mcp-url is required with --infra-type remote in non-interactive mode.');
|
|
74
|
+
}
|
|
75
|
+
mcpUrl = (0, infra_type_1.validateMcpUrl)(options.mcpUrl);
|
|
76
|
+
}
|
|
64
77
|
return {
|
|
65
|
-
|
|
66
|
-
mode: options.mode ? (0, mode_1.parseMode)(options.mode) : 'file-only',
|
|
78
|
+
mode,
|
|
67
79
|
composeMode: options.composeMode ? parseComposeMode(options.composeMode) : 'consolidated',
|
|
80
|
+
infraType,
|
|
81
|
+
mcpUrl,
|
|
68
82
|
};
|
|
69
83
|
}
|
|
70
84
|
const mode = options.mode
|
|
@@ -73,9 +87,26 @@ async function resolveWizardSelection(options, config) {
|
|
|
73
87
|
{ value: 'file-only', label: 'file-only (skip infra + MCP startup)' },
|
|
74
88
|
{ value: 'indexed', label: 'indexed (start infra + MCP and enable retrieval)' },
|
|
75
89
|
], defaults.mode);
|
|
76
|
-
//
|
|
77
|
-
|
|
78
|
-
|
|
90
|
+
// ── Indexed-only: infrastructure type selection ─────────────
|
|
91
|
+
let infraType = 'local';
|
|
92
|
+
let mcpUrl;
|
|
93
|
+
if (mode === 'indexed') {
|
|
94
|
+
logger.phaseHeader('collab init', 'Infrastructure');
|
|
95
|
+
infraType = options.infraType
|
|
96
|
+
? (0, infra_type_1.parseInfraType)(options.infraType)
|
|
97
|
+
: await (0, prompt_1.promptChoice)('Infrastructure type:', [
|
|
98
|
+
{ value: 'local', label: 'local (Docker Compose)' },
|
|
99
|
+
{ value: 'remote', label: 'remote (connect to existing MCP server)' },
|
|
100
|
+
], defaults.infraType);
|
|
101
|
+
if (infraType === 'remote') {
|
|
102
|
+
const rawUrl = options.mcpUrl
|
|
103
|
+
?? await (0, prompt_1.promptText)('MCP server base URL:', 'http://127.0.0.1:7337');
|
|
104
|
+
mcpUrl = (0, infra_type_1.validateMcpUrl)(rawUrl);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
// Skip compose-mode prompt when mode is file-only or infra is remote —
|
|
108
|
+
// Docker Compose configuration is only relevant for local infrastructure.
|
|
109
|
+
const composeMode = mode === 'file-only' || infraType === 'remote'
|
|
79
110
|
? parseComposeMode(options.composeMode, 'consolidated')
|
|
80
111
|
: options.composeMode
|
|
81
112
|
? parseComposeMode(options.composeMode)
|
|
@@ -86,11 +117,15 @@ async function resolveWizardSelection(options, config) {
|
|
|
86
117
|
return {
|
|
87
118
|
mode,
|
|
88
119
|
composeMode,
|
|
120
|
+
infraType,
|
|
121
|
+
mcpUrl,
|
|
89
122
|
};
|
|
90
123
|
}
|
|
91
124
|
function renderMcpSnippet(provider, config) {
|
|
92
125
|
const workspace = config.workspaceDir;
|
|
93
|
-
const mcpUrl =
|
|
126
|
+
const mcpUrl = config.mcpUrl
|
|
127
|
+
? `${config.mcpUrl}/mcp`
|
|
128
|
+
: 'http://127.0.0.1:7337/mcp';
|
|
94
129
|
switch (provider) {
|
|
95
130
|
case 'codex':
|
|
96
131
|
return {
|
|
@@ -422,6 +457,70 @@ function buildInfraStages(effectiveConfig, executor, logger, options, composeMod
|
|
|
422
457
|
];
|
|
423
458
|
}
|
|
424
459
|
// ────────────────────────────────────────────────────────────────
|
|
460
|
+
// Remote infra stages (no Docker — connect to existing MCP)
|
|
461
|
+
// ────────────────────────────────────────────────────────────────
|
|
462
|
+
function buildRemoteInfraStages(effectiveConfig, executor, logger, options, mcpUrl) {
|
|
463
|
+
const health = (0, service_health_1.dryRunHealthOptions)(executor, {
|
|
464
|
+
timeoutMs: (0, parsers_1.parseNumber)(options.timeoutMs, 5_000),
|
|
465
|
+
retries: (0, parsers_1.parseNumber)(options.retries, 15),
|
|
466
|
+
retryDelayMs: (0, parsers_1.parseNumber)(options.retryDelayMs, 2_000),
|
|
467
|
+
});
|
|
468
|
+
return [
|
|
469
|
+
{
|
|
470
|
+
id: 'mcp-health-check',
|
|
471
|
+
title: 'Verify remote MCP service health',
|
|
472
|
+
recovery: [
|
|
473
|
+
'Check that the remote MCP server is running and accessible.',
|
|
474
|
+
'Verify the --mcp-url value points to a healthy MCP endpoint.',
|
|
475
|
+
'Run collab init --resume after fixing remote connectivity.',
|
|
476
|
+
],
|
|
477
|
+
run: async () => {
|
|
478
|
+
const parsed = new URL(mcpUrl);
|
|
479
|
+
const env = {
|
|
480
|
+
MCP_HOST: parsed.hostname,
|
|
481
|
+
MCP_PORT: parsed.port || (parsed.protocol === 'https:' ? '443' : '80'),
|
|
482
|
+
};
|
|
483
|
+
const probe = await (0, service_health_1.waitForMcpHealth)(env, health);
|
|
484
|
+
if (!probe.ok) {
|
|
485
|
+
throw new errors_1.CliError(`Remote MCP is not healthy at ${mcpUrl}: ${probe.errors.join(', ')}`);
|
|
486
|
+
}
|
|
487
|
+
(0, service_health_1.logServiceHealth)(logger, 'remote MCP health', probe);
|
|
488
|
+
},
|
|
489
|
+
},
|
|
490
|
+
{
|
|
491
|
+
id: 'mcp-client-config',
|
|
492
|
+
title: 'Generate MCP client config snippets',
|
|
493
|
+
recovery: [
|
|
494
|
+
'Verify permissions in .collab directory.',
|
|
495
|
+
'Run collab init --resume to regenerate MCP config snippets.',
|
|
496
|
+
],
|
|
497
|
+
run: () => {
|
|
498
|
+
if (options.skipMcpSnippets) {
|
|
499
|
+
logger.info('Skipping MCP snippet generation by user choice.');
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
const enabled = (0, providers_1.getEnabledProviders)(effectiveConfig);
|
|
503
|
+
if (enabled.length === 0) {
|
|
504
|
+
logger.info('No providers configured; skipping MCP snippet generation.');
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
for (const provider of enabled) {
|
|
508
|
+
const snippet = renderMcpSnippet(provider, effectiveConfig);
|
|
509
|
+
if (!snippet)
|
|
510
|
+
continue;
|
|
511
|
+
const target = node_path_1.default.join(effectiveConfig.collabDir, snippet.filename);
|
|
512
|
+
executor.writeFile(target, snippet.content, {
|
|
513
|
+
description: `write ${providers_1.PROVIDER_DEFAULTS[provider].label} MCP config snippet`,
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
logger.info(`Generated MCP snippets for: ${enabled.map((k) => providers_1.PROVIDER_DEFAULTS[k].label).join(', ')}`);
|
|
517
|
+
},
|
|
518
|
+
},
|
|
519
|
+
graph_seed_1.graphSeedStage,
|
|
520
|
+
canon_ingest_1.canonIngestStage,
|
|
521
|
+
];
|
|
522
|
+
}
|
|
523
|
+
// ────────────────────────────────────────────────────────────────
|
|
425
524
|
// File-only pipeline (8 stages)
|
|
426
525
|
// ────────────────────────────────────────────────────────────────
|
|
427
526
|
function buildFileOnlyPipeline(effectiveConfig, executor, logger, configExistedBefore, options) {
|
|
@@ -440,20 +539,23 @@ function buildFileOnlyPipeline(effectiveConfig, executor, logger, configExistedB
|
|
|
440
539
|
// ────────────────────────────────────────────────────────────────
|
|
441
540
|
// Indexed pipeline (15 stages)
|
|
442
541
|
// ────────────────────────────────────────────────────────────────
|
|
443
|
-
function buildIndexedPipeline(effectiveConfig, executor, logger, configExistedBefore, options, composeMode) {
|
|
542
|
+
function buildIndexedPipeline(effectiveConfig, executor, logger, configExistedBefore, options, composeMode, infraType = 'local', mcpUrl) {
|
|
543
|
+
const infraStages = infraType === 'remote' && mcpUrl
|
|
544
|
+
? buildRemoteInfraStages(effectiveConfig, executor, logger, options, mcpUrl)
|
|
545
|
+
: buildInfraStages(effectiveConfig, executor, logger, options, composeMode);
|
|
444
546
|
return [
|
|
445
547
|
// Phase A — Local setup (shared with file-only)
|
|
446
|
-
buildPreflightStage(executor, logger, 'indexed'),
|
|
447
|
-
buildConfigStage(effectiveConfig, executor, logger, configExistedBefore, options.force),
|
|
448
|
-
buildGitHubAuthStage(effectiveConfig, logger, options),
|
|
449
|
-
assistant_setup_1.assistantSetupStage,
|
|
450
|
-
canon_sync_1.canonSyncStage,
|
|
451
|
-
repo_scaffold_1.repoScaffoldStage,
|
|
452
|
-
repo_analysis_1.repoAnalysisStage,
|
|
453
|
-
ci_setup_1.ciSetupStage,
|
|
454
|
-
agent_skills_setup_1.agentSkillsSetupStage,
|
|
455
|
-
// Phase B — Infrastructure + Phase C — Ingestion
|
|
456
|
-
...
|
|
548
|
+
buildPreflightStage(executor, logger, infraType === 'local' ? 'indexed' : undefined),
|
|
549
|
+
buildConfigStage(effectiveConfig, executor, logger, configExistedBefore, options.force),
|
|
550
|
+
buildGitHubAuthStage(effectiveConfig, logger, options),
|
|
551
|
+
assistant_setup_1.assistantSetupStage,
|
|
552
|
+
canon_sync_1.canonSyncStage,
|
|
553
|
+
repo_scaffold_1.repoScaffoldStage,
|
|
554
|
+
repo_analysis_1.repoAnalysisStage,
|
|
555
|
+
ci_setup_1.ciSetupStage,
|
|
556
|
+
agent_skills_setup_1.agentSkillsSetupStage,
|
|
557
|
+
// Phase B — Infrastructure + Phase C — Ingestion
|
|
558
|
+
...infraStages,
|
|
457
559
|
];
|
|
458
560
|
}
|
|
459
561
|
// ────────────────────────────────────────────────────────────────
|
|
@@ -473,26 +575,45 @@ async function runInfraOnly(context, options) {
|
|
|
473
575
|
context.executor.ensureDirectory(effectiveConfig.collabDir);
|
|
474
576
|
context.executor.writeFile(effectiveConfig.configFile, `${(0, config_1.serializeUserConfig)(effectiveConfig)}\n`, { description: 'write collab config (infra bootstrap)' });
|
|
475
577
|
}
|
|
578
|
+
const infraType = (0, infra_type_1.parseInfraType)(options.infraType, effectiveConfig.infraType);
|
|
579
|
+
let mcpUrl;
|
|
580
|
+
if (infraType === 'remote') {
|
|
581
|
+
if (!options.mcpUrl && !effectiveConfig.mcpUrl) {
|
|
582
|
+
throw new errors_1.CliError('--mcp-url is required for remote infrastructure.');
|
|
583
|
+
}
|
|
584
|
+
mcpUrl = options.mcpUrl ? (0, infra_type_1.validateMcpUrl)(options.mcpUrl) : effectiveConfig.mcpUrl;
|
|
585
|
+
effectiveConfig.infraType = infraType;
|
|
586
|
+
effectiveConfig.mcpUrl = mcpUrl;
|
|
587
|
+
}
|
|
476
588
|
const composeMode = parseComposeMode(options.composeMode, inferComposeMode(effectiveConfig));
|
|
477
|
-
|
|
478
|
-
|
|
589
|
+
const infraLabel = infraType === 'remote' ? 'Remote MCP services' : 'Docker + MCP services';
|
|
590
|
+
context.logger.phaseHeader('Infrastructure', infraLabel);
|
|
591
|
+
const infraStages = infraType === 'remote' && mcpUrl
|
|
592
|
+
? buildRemoteInfraStages(effectiveConfig, context.executor, context.logger, options, mcpUrl)
|
|
593
|
+
: buildInfraStages(effectiveConfig, context.executor, context.logger, options, composeMode);
|
|
479
594
|
await (0, orchestrator_1.runOrchestration)({
|
|
480
595
|
workflowId: 'init:infra',
|
|
481
596
|
config: effectiveConfig,
|
|
482
597
|
executor: context.executor,
|
|
483
598
|
logger: context.logger,
|
|
484
599
|
resume: options.resume,
|
|
485
|
-
mode:
|
|
600
|
+
mode: `indexed (infra ${infraType})`,
|
|
486
601
|
stageOptions: { outputDir: options.outputDir },
|
|
487
602
|
}, infraStages);
|
|
488
603
|
// Summary
|
|
489
604
|
context.logger.phaseHeader('Infrastructure Ready');
|
|
490
|
-
|
|
491
|
-
{ label: 'Phase', value:
|
|
492
|
-
{ label: 'Compose mode', value: composeMode },
|
|
605
|
+
const summaryEntries = [
|
|
606
|
+
{ label: 'Phase', value: `infra ${infraType}` },
|
|
493
607
|
{ label: 'Dry-run', value: context.executor.dryRun ? 'yes' : 'no' },
|
|
494
608
|
{ label: 'Config', value: effectiveConfig.configFile },
|
|
495
|
-
]
|
|
609
|
+
];
|
|
610
|
+
if (infraType === 'remote' && mcpUrl) {
|
|
611
|
+
summaryEntries.splice(1, 0, { label: 'MCP URL', value: mcpUrl });
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
summaryEntries.splice(1, 0, { label: 'Compose mode', value: composeMode });
|
|
615
|
+
}
|
|
616
|
+
context.logger.summaryFooter(summaryEntries);
|
|
496
617
|
}
|
|
497
618
|
// ────────────────────────────────────────────────────────────────
|
|
498
619
|
// Repo domain generation (collab init --repo=<package>)
|
|
@@ -626,6 +747,8 @@ function registerInitCommand(program) {
|
|
|
626
747
|
.option('--resume', 'Resume from the last incomplete wizard stage')
|
|
627
748
|
.option('--mode <mode>', 'Wizard mode: file-only|indexed')
|
|
628
749
|
.option('--compose-mode <mode>', 'Compose mode: consolidated|split')
|
|
750
|
+
.option('--infra-type <type>', 'Infrastructure type: local|remote (indexed mode only)')
|
|
751
|
+
.option('--mcp-url <url>', 'MCP server base URL for remote infrastructure')
|
|
629
752
|
.option('--output-dir <directory>', 'Directory used to write compose outputs')
|
|
630
753
|
.option('--repos <list>', 'Comma-separated repo directories for workspace mode')
|
|
631
754
|
.option('--repo <package>', 'Generate domain definition from package analysis')
|
|
@@ -647,6 +770,7 @@ Examples:
|
|
|
647
770
|
collab init --repos api,web,shared --yes
|
|
648
771
|
collab init --repo collab-chat-ai-pkg --mode file-only
|
|
649
772
|
collab init --repo collab-chat-ai-pkg --mode indexed
|
|
773
|
+
collab init --yes --mode indexed --infra-type remote --mcp-url http://my-server:7337 --business-canon none
|
|
650
774
|
collab init --resume
|
|
651
775
|
collab init infra
|
|
652
776
|
collab init infra --resume
|
|
@@ -684,12 +808,14 @@ Examples:
|
|
|
684
808
|
}
|
|
685
809
|
// ── Step 1: Configuration wizard ────────────────────────
|
|
686
810
|
context.logger.phaseHeader('collab init', 'Configuration');
|
|
687
|
-
const selections = await resolveWizardSelection(options, context.config);
|
|
811
|
+
const selections = await resolveWizardSelection(options, context.config, context.logger);
|
|
688
812
|
const preserveExisting = configExistedBefore && !options.force;
|
|
689
813
|
const effectiveConfig = {
|
|
690
814
|
...(0, config_1.defaultCollabConfig)(context.config.workspaceDir),
|
|
691
815
|
...context.config,
|
|
692
816
|
mode: preserveExisting ? context.config.mode : selections.mode,
|
|
817
|
+
infraType: preserveExisting ? context.config.infraType : selections.infraType,
|
|
818
|
+
mcpUrl: preserveExisting ? context.config.mcpUrl : selections.mcpUrl,
|
|
693
819
|
};
|
|
694
820
|
// ── Step 2: Business canon configuration ──────────────────
|
|
695
821
|
const canons = await resolveBusinessCanon(options, context.logger);
|
|
@@ -745,8 +871,13 @@ Examples:
|
|
|
745
871
|
}
|
|
746
872
|
// Phase I — infra stages (indexed only)
|
|
747
873
|
if (selections.mode === 'indexed') {
|
|
748
|
-
|
|
749
|
-
|
|
874
|
+
const infraLabel = selections.infraType === 'remote'
|
|
875
|
+
? 'Remote MCP services'
|
|
876
|
+
: 'Docker + MCP services';
|
|
877
|
+
context.logger.phaseHeader('Infrastructure', infraLabel);
|
|
878
|
+
const infraStages = selections.infraType === 'remote' && selections.mcpUrl
|
|
879
|
+
? buildRemoteInfraStages(effectiveConfig, context.executor, context.logger, options, selections.mcpUrl)
|
|
880
|
+
: buildInfraStages(effectiveConfig, context.executor, context.logger, options, selections.composeMode);
|
|
750
881
|
await (0, orchestrator_1.runOrchestration)({
|
|
751
882
|
workflowId: 'init:infra',
|
|
752
883
|
config: effectiveConfig,
|
|
@@ -763,7 +894,7 @@ Examples:
|
|
|
763
894
|
context.logger.phaseHeader('Project Setup', selections.mode);
|
|
764
895
|
const stages = selections.mode === 'file-only'
|
|
765
896
|
? buildFileOnlyPipeline(effectiveConfig, context.executor, context.logger, configExistedBefore, options)
|
|
766
|
-
: buildIndexedPipeline(effectiveConfig, context.executor, context.logger, configExistedBefore, options, selections.composeMode);
|
|
897
|
+
: buildIndexedPipeline(effectiveConfig, context.executor, context.logger, configExistedBefore, options, selections.composeMode, selections.infraType, selections.mcpUrl);
|
|
767
898
|
await (0, orchestrator_1.runOrchestration)({
|
|
768
899
|
workflowId: 'init',
|
|
769
900
|
config: effectiveConfig,
|
|
@@ -790,7 +921,13 @@ Examples:
|
|
|
790
921
|
summaryEntries.splice(1, 0, { label: 'Workspace', value: `${ws.name} (${ws.type})` }, { label: 'Repos', value: ws.repos.join(', ') });
|
|
791
922
|
}
|
|
792
923
|
if (selections.mode === 'indexed') {
|
|
793
|
-
summaryEntries.
|
|
924
|
+
summaryEntries.push({ label: 'Infrastructure', value: selections.infraType });
|
|
925
|
+
if (selections.infraType === 'remote' && selections.mcpUrl) {
|
|
926
|
+
summaryEntries.push({ label: 'MCP URL', value: selections.mcpUrl });
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
summaryEntries.push({ label: 'Compose mode', value: selections.composeMode });
|
|
930
|
+
}
|
|
794
931
|
}
|
|
795
932
|
context.logger.summaryFooter(summaryEntries);
|
|
796
933
|
// Ecosystem compatibility checks
|
package/dist/lib/config.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.deriveWorkspaceName = deriveWorkspaceName;
|
|
|
15
15
|
exports.detectWorkspaceLayout = detectWorkspaceLayout;
|
|
16
16
|
const node_fs_1 = __importDefault(require("node:fs"));
|
|
17
17
|
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const infra_type_1 = require("./infra-type");
|
|
18
19
|
const mode_1 = require("./mode");
|
|
19
20
|
const DEFAULT_COMPOSE_PATHS = {
|
|
20
21
|
consolidatedFile: 'docker-compose.yml',
|
|
@@ -32,6 +33,7 @@ function defaultCollabConfig(cwd = process.cwd()) {
|
|
|
32
33
|
stateFile: node_path_1.default.join(collabDir, 'state.json'),
|
|
33
34
|
envFile: node_path_1.default.join(workspaceDir, '.env'),
|
|
34
35
|
mode: mode_1.DEFAULT_MODE,
|
|
36
|
+
infraType: infra_type_1.DEFAULT_INFRA_TYPE,
|
|
35
37
|
compose: { ...DEFAULT_COMPOSE_PATHS },
|
|
36
38
|
architectureDir,
|
|
37
39
|
uxmaltechDir: node_path_1.default.join(architectureDir, 'uxmaltech'),
|
|
@@ -54,9 +56,12 @@ function loadCollabConfig(cwd = process.cwd()) {
|
|
|
54
56
|
? node_path_1.default.resolve(defaults.workspaceDir, raw.architectureDir)
|
|
55
57
|
: defaults.architectureDir;
|
|
56
58
|
const workspace = migrateWorkspaceConfig(raw.workspace, defaults.workspaceDir);
|
|
59
|
+
const infraType = (0, infra_type_1.parseInfraType)(raw.infraType, defaults.infraType);
|
|
57
60
|
return {
|
|
58
61
|
...defaults,
|
|
59
62
|
mode: (0, mode_1.parseMode)(raw.mode, defaults.mode),
|
|
63
|
+
infraType,
|
|
64
|
+
mcpUrl: infraType === 'remote' && raw.mcpUrl ? raw.mcpUrl : undefined,
|
|
60
65
|
envFile: raw.envFile ? node_path_1.default.resolve(defaults.workspaceDir, raw.envFile) : defaults.envFile,
|
|
61
66
|
compose: {
|
|
62
67
|
consolidatedFile: raw.compose?.consolidatedFile ?? defaults.compose.consolidatedFile,
|
|
@@ -82,6 +87,13 @@ function serializeUserConfig(config) {
|
|
|
82
87
|
compose: config.compose,
|
|
83
88
|
envFile: node_path_1.default.relative(config.workspaceDir, config.envFile),
|
|
84
89
|
};
|
|
90
|
+
// Only persist infraType when it differs from the default (local).
|
|
91
|
+
if (config.infraType && config.infraType !== 'local') {
|
|
92
|
+
data.infraType = config.infraType;
|
|
93
|
+
}
|
|
94
|
+
if (config.mcpUrl) {
|
|
95
|
+
data.mcpUrl = config.mcpUrl;
|
|
96
|
+
}
|
|
85
97
|
if (config.assistants) {
|
|
86
98
|
data.assistants = config.assistants;
|
|
87
99
|
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.DEFAULT_INFRA_TYPE = exports.INFRA_TYPES = void 0;
|
|
4
|
+
exports.isInfraType = isInfraType;
|
|
5
|
+
exports.parseInfraType = parseInfraType;
|
|
6
|
+
exports.validateMcpUrl = validateMcpUrl;
|
|
7
|
+
const errors_1 = require("./errors");
|
|
8
|
+
/** Supported infrastructure types for indexed mode. */
|
|
9
|
+
exports.INFRA_TYPES = ['local', 'remote'];
|
|
10
|
+
const INFRA_SET = new Set(exports.INFRA_TYPES);
|
|
11
|
+
/** Default infra type used when none is explicitly configured. */
|
|
12
|
+
exports.DEFAULT_INFRA_TYPE = 'local';
|
|
13
|
+
/** Type guard that checks whether a string is a valid {@link InfraType}. */
|
|
14
|
+
function isInfraType(value) {
|
|
15
|
+
return INFRA_SET.has(value);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Parses an infra-type string from CLI flags or config, returning the fallback
|
|
19
|
+
* when undefined. Throws on invalid values.
|
|
20
|
+
*/
|
|
21
|
+
function parseInfraType(value, fallback = exports.DEFAULT_INFRA_TYPE) {
|
|
22
|
+
if (value === undefined) {
|
|
23
|
+
return fallback;
|
|
24
|
+
}
|
|
25
|
+
if (isInfraType(value)) {
|
|
26
|
+
return value;
|
|
27
|
+
}
|
|
28
|
+
throw new errors_1.CliError(`Invalid infra type '${value}'. Valid values: ${exports.INFRA_TYPES.join(', ')}`);
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Validates and normalises an MCP base URL.
|
|
32
|
+
*
|
|
33
|
+
* - Must start with `http://` or `https://`.
|
|
34
|
+
* - Must be parseable by the URL constructor.
|
|
35
|
+
* - Trailing slashes are stripped from the origin.
|
|
36
|
+
*
|
|
37
|
+
* @returns The normalised base URL (e.g. `http://my-server:7337`).
|
|
38
|
+
*/
|
|
39
|
+
function validateMcpUrl(url) {
|
|
40
|
+
const trimmed = url.trim();
|
|
41
|
+
if (!trimmed) {
|
|
42
|
+
throw new errors_1.CliError('MCP URL cannot be empty.');
|
|
43
|
+
}
|
|
44
|
+
if (!/^https?:\/\//i.test(trimmed)) {
|
|
45
|
+
throw new errors_1.CliError(`MCP URL must start with http:// or https://. Got: ${trimmed}`);
|
|
46
|
+
}
|
|
47
|
+
let parsed;
|
|
48
|
+
try {
|
|
49
|
+
parsed = new URL(trimmed);
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
throw new errors_1.CliError(`Invalid MCP URL: ${trimmed}`);
|
|
53
|
+
}
|
|
54
|
+
// Return origin (scheme + host + port) without trailing path/slash
|
|
55
|
+
return parsed.origin;
|
|
56
|
+
}
|
package/dist/lib/mcp-client.js
CHANGED
|
@@ -48,6 +48,11 @@ function resolveMcpApiKey(env) {
|
|
|
48
48
|
return firstFromList || undefined;
|
|
49
49
|
}
|
|
50
50
|
function getMcpBaseUrl(config) {
|
|
51
|
+
// Remote infra: use the explicitly configured URL.
|
|
52
|
+
if (config.mcpUrl) {
|
|
53
|
+
return config.mcpUrl;
|
|
54
|
+
}
|
|
55
|
+
// Local infra: derive from .env or defaults.
|
|
51
56
|
const env = (0, service_health_1.loadRuntimeEnv)(config);
|
|
52
57
|
const host = env.MCP_HOST || '127.0.0.1';
|
|
53
58
|
const port = env.MCP_PORT || '7337';
|