@moda-ai/cli 1.20.0 → 1.21.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/dist/init/index.d.ts +2 -0
- package/dist/init/index.js +101 -26
- package/dist/init/sdk-catalog.d.ts +10 -0
- package/dist/init/sdk-catalog.js +80 -0
- package/dist/init/sdk-guide.d.ts +5 -34
- package/dist/init/sdk-guide.js +74 -290
- package/dist/init/sdk-integration.d.ts +43 -44
- package/dist/init/sdk-integration.js +796 -133
- package/dist/init/sdk-protocol.d.ts +135 -0
- package/dist/init/sdk-protocol.js +152 -0
- package/dist/init/sdk-verification.d.ts +27 -0
- package/dist/init/sdk-verification.js +123 -0
- package/dist/sync.js +11 -2
- package/package.json +1 -1
- package/skills/integration/index.json +2 -2
- package/dist/init/skill-resolver.d.ts +0 -19
- package/dist/init/skill-resolver.js +0 -78
package/dist/init/index.d.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { type SdkIntegrationOutcome } from './sdk-integration.js';
|
|
1
2
|
export interface InitResult {
|
|
2
3
|
apiKey: string;
|
|
3
4
|
tenantName: string;
|
|
4
5
|
phases: InitPhaseResult[];
|
|
5
6
|
setupPlan: InitSetupAction[];
|
|
7
|
+
sdk_integration?: SdkIntegrationOutcome;
|
|
6
8
|
}
|
|
7
9
|
export interface InitPhaseResult {
|
|
8
10
|
id: 'welcome' | 'auth' | 'workspace' | 'harness' | 'plan' | 'apply' | 'validate';
|
package/dist/init/index.js
CHANGED
|
@@ -16,9 +16,10 @@ import { initPrompts, runPromptSync } from '../prompts.js';
|
|
|
16
16
|
import { runSkillSync } from '../skills.js';
|
|
17
17
|
import { SDK_GUIDE_PATH, writeSdkIntegrationGuide } from './sdk-guide.js';
|
|
18
18
|
import { runSdkIntegration } from './sdk-integration.js';
|
|
19
|
+
import { fetchAuditWithApiKey } from './sdk-verification.js';
|
|
19
20
|
import { InitProgressBoard } from './progress-board.js';
|
|
20
21
|
import { createCommandContext } from '../command-context.js';
|
|
21
|
-
import { resolveModaBaseUrl, VERSION } from '../config.js';
|
|
22
|
+
import { resolveModaBaseUrl, resolveIngestUrl, VERSION } from '../config.js';
|
|
22
23
|
import { CliAuthError, CliInputError } from '../errors.js';
|
|
23
24
|
import { describeSyncIdentity, describeSyncTenantLabel, detectAvailableCodingAgents, isDeterministicLocalAnalyst, runHarnessCommand, } from '../harness.js';
|
|
24
25
|
import { pollRemoteAnalyzeUntilDone, startRemoteAnalyze, } from '../harness-remote.js';
|
|
@@ -516,38 +517,35 @@ export async function runInit(flags, options = {}) {
|
|
|
516
517
|
markApplied(setupPlan, 'harness-rescan');
|
|
517
518
|
}
|
|
518
519
|
const sdkAction = setupPlan.find((action) => action.category === 'sdk');
|
|
520
|
+
let sdkIntegration;
|
|
519
521
|
if (sdkAction?.approved === true) {
|
|
520
522
|
laneBoard?.startLane('sdk', 'SDK integration', {
|
|
521
523
|
detail: 'launching your coding agent',
|
|
522
524
|
expectation: 'usually 3-10 min',
|
|
523
525
|
});
|
|
524
526
|
emit('apply', 'Running your coding agent to integrate the Moda SDK.');
|
|
525
|
-
const
|
|
527
|
+
const verificationBaseUrl = resolveModaBaseUrl({ profile });
|
|
528
|
+
sdkIntegration = await runSdkIntegration({
|
|
529
|
+
apiKey,
|
|
530
|
+
ingestUrl: resolveIngestUrl({ profile }),
|
|
531
|
+
fetchAudit: (id) => fetchAuditWithApiKey(id, apiKey, verificationBaseUrl),
|
|
526
532
|
onProgress: (message) => {
|
|
527
533
|
laneBoard?.update('sdk', message);
|
|
528
534
|
emitLaneProgress('apply', 'sdk', message);
|
|
529
535
|
},
|
|
536
|
+
onPhase: (phase, message) => {
|
|
537
|
+
laneBoard?.update('sdk', message);
|
|
538
|
+
emitLaneProgress('apply', 'sdk', message);
|
|
539
|
+
},
|
|
530
540
|
// Presence of onEvent selects the streaming agent invocation so tool
|
|
531
541
|
// cards arrive incrementally; onProgress above receives the rendered
|
|
532
542
|
// cards (describeCodingAgentEvent), which is all the lane needs.
|
|
533
543
|
onEvent: () => { },
|
|
534
544
|
});
|
|
535
|
-
|
|
536
|
-
laneBoard
|
|
537
|
-
|
|
538
|
-
}
|
|
539
|
-
else if (result.status === 'no-agent') {
|
|
540
|
-
const guidePath = writeSdkIntegrationGuide();
|
|
541
|
-
sdkAction.files = [guidePath];
|
|
542
|
-
laneBoard?.warn('sdk', `no coding agent CLI found; wrote ${guidePath} to integrate by hand`);
|
|
543
|
-
markApplied(setupPlan, 'sdk');
|
|
544
|
-
}
|
|
545
|
-
else {
|
|
546
|
-
const guidePath = writeSdkIntegrationGuide();
|
|
547
|
-
sdkAction.files = [guidePath];
|
|
548
|
-
laneBoard?.warn('sdk', `${result.adapter} did not complete; wrote ${guidePath} to finish manually`);
|
|
549
|
-
markApplied(setupPlan, 'sdk');
|
|
550
|
-
}
|
|
545
|
+
applySdkIntegrationOutcome(setupPlan, sdkIntegration, {
|
|
546
|
+
laneBoard,
|
|
547
|
+
writeGuide: () => writeSdkIntegrationGuide(rootDir),
|
|
548
|
+
});
|
|
551
549
|
}
|
|
552
550
|
else {
|
|
553
551
|
markApplied(setupPlan, 'sdk', 'User skipped SDK integration.');
|
|
@@ -627,7 +625,7 @@ export async function runInit(flags, options = {}) {
|
|
|
627
625
|
const offlineValidation = isTruthyFlag(flagValue(flags, 'offline'));
|
|
628
626
|
log(renderInitStep(5, 5, 'Verify', offlineValidation
|
|
629
627
|
? 'Running local doctor checks so the setup leaves a clear state.'
|
|
630
|
-
: 'Running Moda doctor online
|
|
628
|
+
: 'Running Moda doctor online as a tenant health check (not proof of SDK integration).'));
|
|
631
629
|
emit('validate', `Running Moda doctor diagnostics (${offlineValidation ? 'offline' : 'online'}).`);
|
|
632
630
|
const doctorReport = await buildDoctorReport(createCommandContext({
|
|
633
631
|
command: 'doctor',
|
|
@@ -665,7 +663,7 @@ export async function runInit(flags, options = {}) {
|
|
|
665
663
|
skipped: setupPlan.filter((item) => item.status === 'skipped' || item.status === 'manual'),
|
|
666
664
|
telemetrySeen,
|
|
667
665
|
}));
|
|
668
|
-
return { apiKey, tenantName, phases, setupPlan };
|
|
666
|
+
return { apiKey, tenantName, phases, setupPlan, ...(sdkIntegration ? { sdk_integration: sdkIntegration } : {}) };
|
|
669
667
|
}
|
|
670
668
|
function buildSetupPlan(options) {
|
|
671
669
|
return [
|
|
@@ -716,15 +714,13 @@ function buildSetupPlan(options) {
|
|
|
716
714
|
},
|
|
717
715
|
{
|
|
718
716
|
category: 'sdk',
|
|
719
|
-
title: '
|
|
717
|
+
title: 'Launch your coding agent to integrate the Moda SDK',
|
|
720
718
|
status: options.integrateSdk ? 'planned' : 'manual',
|
|
721
719
|
approved: options.integrateSdk,
|
|
722
|
-
// Files are repo-specific and unknown ahead of the agent run; the fallback
|
|
723
|
-
// path rewrites this to the guide path when no agent CLI is available.
|
|
724
720
|
files: [],
|
|
725
|
-
command: 'moda init',
|
|
721
|
+
command: 'moda init --sdk-integrate',
|
|
726
722
|
reason: options.integrateSdk
|
|
727
|
-
? '
|
|
723
|
+
? 'Your agent investigates the repo, applies matching integration skill(s), installs packages, runs repo checks, sends one verification request, and Moda verifies the exact trace. Your Moda API key is provided to the agent\'s environment for the verification run.'
|
|
728
724
|
: `SDK integration stays manual; see ${SDK_GUIDE_PATH} if you generate the guide.`,
|
|
729
725
|
},
|
|
730
726
|
{
|
|
@@ -758,7 +754,7 @@ function buildSetupPlan(options) {
|
|
|
758
754
|
},
|
|
759
755
|
{
|
|
760
756
|
category: 'validation',
|
|
761
|
-
title: 'Run Moda doctor online
|
|
757
|
+
title: 'Run Moda doctor online as a tenant health check',
|
|
762
758
|
status: 'planned',
|
|
763
759
|
approved: true,
|
|
764
760
|
files: [],
|
|
@@ -778,6 +774,85 @@ function markApplied(setupPlan, category, skipReason) {
|
|
|
778
774
|
if (action.approved)
|
|
779
775
|
action.status = 'applied';
|
|
780
776
|
}
|
|
777
|
+
function markManual(setupPlan, category, reason) {
|
|
778
|
+
const action = setupPlan.find((item) => item.category === category);
|
|
779
|
+
if (!action)
|
|
780
|
+
return;
|
|
781
|
+
action.status = 'manual';
|
|
782
|
+
action.reason = reason;
|
|
783
|
+
}
|
|
784
|
+
function applySdkIntegrationOutcome(setupPlan, outcome, deps) {
|
|
785
|
+
const sdkAction = setupPlan.find((action) => action.category === 'sdk');
|
|
786
|
+
if (!sdkAction)
|
|
787
|
+
return;
|
|
788
|
+
const { laneBoard, writeGuide } = deps;
|
|
789
|
+
const verificationId = outcome.verification_id ?? 'unknown';
|
|
790
|
+
const firstRemaining = outcome.agent_result?.remaining_actions?.[0];
|
|
791
|
+
switch (outcome.status) {
|
|
792
|
+
case 'integrated_verified': {
|
|
793
|
+
const llmSpans = outcome.verification?.llm_spans ?? 0;
|
|
794
|
+
laneBoard?.complete('sdk', `verified — trace reached Moda (${llmSpans} llm span(s), conversation ${verificationId})`);
|
|
795
|
+
markApplied(setupPlan, 'sdk');
|
|
796
|
+
if (outcome.agent_result?.files_changed?.length) {
|
|
797
|
+
sdkAction.files = [...new Set(outcome.agent_result.files_changed)].slice(0, 20);
|
|
798
|
+
}
|
|
799
|
+
break;
|
|
800
|
+
}
|
|
801
|
+
case 'telemetry_not_observed':
|
|
802
|
+
laneBoard?.warn('sdk', `integration applied but the verification trace was not observed; check MODA_API_KEY at runtime, then moda audit ${verificationId}`);
|
|
803
|
+
markManual(setupPlan, 'sdk', outcome.detail ?? 'Verification trace was not observed.');
|
|
804
|
+
break;
|
|
805
|
+
case 'needs_human_input': {
|
|
806
|
+
const securityLead = outcome.security_findings?.length
|
|
807
|
+
? `Security: possible API key leak in ${outcome.security_findings.join(', ')} — remove the key, rotate it, and keep secrets in the environment. `
|
|
808
|
+
: '';
|
|
809
|
+
const note = firstRemaining ?? outcome.detail ?? 'Additional human input required.';
|
|
810
|
+
laneBoard?.warn('sdk', `${securityLead}${note}`);
|
|
811
|
+
markManual(setupPlan, 'sdk', `${securityLead}${note}`.trim());
|
|
812
|
+
break;
|
|
813
|
+
}
|
|
814
|
+
case 'nothing_to_integrate': {
|
|
815
|
+
if (outcome.verification?.observed === true) {
|
|
816
|
+
laneBoard?.complete('sdk', outcome.detail ?? 'existing integration verified — trace reached Moda');
|
|
817
|
+
markApplied(setupPlan, 'sdk');
|
|
818
|
+
sdkAction.reason = outcome.detail ?? 'Existing Moda integration verified.';
|
|
819
|
+
break;
|
|
820
|
+
}
|
|
821
|
+
// With a smoke attempt the verification id is real and auditable (the
|
|
822
|
+
// trace may land late); without one the user must pick their own id.
|
|
823
|
+
const reason = outcome.verification
|
|
824
|
+
? 'agent reports Moda is already integrated but the verification trace was not observed; ' +
|
|
825
|
+
`check MODA_API_KEY at runtime, then \`moda audit ${verificationId}\``
|
|
826
|
+
: 'agent reports Moda is already integrated — not independently verified; ' +
|
|
827
|
+
'send a real request through your app with a conversation id you choose, then run `moda audit` with that id';
|
|
828
|
+
laneBoard?.warn('sdk', reason);
|
|
829
|
+
markManual(setupPlan, 'sdk', reason);
|
|
830
|
+
break;
|
|
831
|
+
}
|
|
832
|
+
case 'agent_unavailable': {
|
|
833
|
+
const guidePath = writeGuide();
|
|
834
|
+
sdkAction.files = [guidePath];
|
|
835
|
+
laneBoard?.warn('sdk', `no coding agent CLI found; wrote ${guidePath} to integrate by hand`);
|
|
836
|
+
markManual(setupPlan, 'sdk', `No coding agent CLI found; wrote ${guidePath}.`);
|
|
837
|
+
break;
|
|
838
|
+
}
|
|
839
|
+
case 'cancelled':
|
|
840
|
+
laneBoard?.fail('sdk', outcome.detail ?? 'SDK integration cancelled');
|
|
841
|
+
markManual(setupPlan, 'sdk', outcome.detail ?? 'SDK integration cancelled.');
|
|
842
|
+
break;
|
|
843
|
+
case 'repository_checks_failed':
|
|
844
|
+
case 'smoke_execution_failed':
|
|
845
|
+
case 'agent_failed':
|
|
846
|
+
default: {
|
|
847
|
+
const guidePath = writeGuide();
|
|
848
|
+
sdkAction.files = [guidePath];
|
|
849
|
+
const detail = outcome.detail ?? 'SDK integration did not complete.';
|
|
850
|
+
laneBoard?.warn('sdk', `${detail}; wrote ${guidePath} to finish manually`);
|
|
851
|
+
markManual(setupPlan, 'sdk', `${detail}; wrote ${guidePath}.`);
|
|
852
|
+
break;
|
|
853
|
+
}
|
|
854
|
+
}
|
|
855
|
+
}
|
|
781
856
|
function changedFiles(setupPlan) {
|
|
782
857
|
return [...new Set(setupPlan
|
|
783
858
|
.filter((action) => action.status === 'applied')
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type BundledSkillIndex } from '../skills-bundled.js';
|
|
2
|
+
export interface MaterializedSdkCatalog {
|
|
3
|
+
dir: string;
|
|
4
|
+
relativeDir: string;
|
|
5
|
+
index: BundledSkillIndex;
|
|
6
|
+
}
|
|
7
|
+
export declare function materializeSdkCatalog(cwd: string, runId: string): MaterializedSdkCatalog;
|
|
8
|
+
export declare function cleanupSdkCatalog(dir: string): void;
|
|
9
|
+
export declare function renderCatalogListing(index: BundledSkillIndex): string;
|
|
10
|
+
//# sourceMappingURL=sdk-catalog.d.ts.map
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/init/sdk-catalog.ts — Ephemeral, run-scoped materialization of the
|
|
2
|
+
// complete bundled SDK integration-skill catalog for a coding agent to browse.
|
|
3
|
+
import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync, statSync, } from 'node:fs';
|
|
4
|
+
import { join, relative, resolve } from 'node:path';
|
|
5
|
+
import { bundledSkillsDir, readBundledIndex, } from '../skills-bundled.js';
|
|
6
|
+
export function materializeSdkCatalog(cwd, runId) {
|
|
7
|
+
if (!/^[a-z0-9-]+$/.test(runId)) {
|
|
8
|
+
throw new Error(`Invalid SDK catalog run id: ${JSON.stringify(runId).slice(0, 80)}`);
|
|
9
|
+
}
|
|
10
|
+
const sourceDir = bundledSkillsDir();
|
|
11
|
+
const indexPath = join(sourceDir, 'index.json');
|
|
12
|
+
if (!existsSync(sourceDir) || !existsSync(indexPath)) {
|
|
13
|
+
throw missingBundleError(sourceDir);
|
|
14
|
+
}
|
|
15
|
+
const index = readBundledIndex();
|
|
16
|
+
if (!index) {
|
|
17
|
+
throw missingBundleError(sourceDir);
|
|
18
|
+
}
|
|
19
|
+
const dir = join(cwd, '.moda', 'tmp', `sdk-catalog-${runId}`);
|
|
20
|
+
cleanupSdkCatalog(dir);
|
|
21
|
+
copyDirectoryRecursive(sourceDir, dir);
|
|
22
|
+
return {
|
|
23
|
+
dir,
|
|
24
|
+
relativeDir: relative(cwd, dir).replace(/\\/g, '/'),
|
|
25
|
+
index,
|
|
26
|
+
};
|
|
27
|
+
}
|
|
28
|
+
export function cleanupSdkCatalog(dir) {
|
|
29
|
+
const normalized = resolve(dir).replace(/\\/g, '/');
|
|
30
|
+
if (!normalized.includes('.moda/tmp/sdk-catalog-'))
|
|
31
|
+
return;
|
|
32
|
+
try {
|
|
33
|
+
rmSync(dir, { recursive: true, force: true });
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Catalog cleanup is best-effort and must never mask the integration result.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function renderCatalogListing(index) {
|
|
40
|
+
return index.skills.map((skill) => {
|
|
41
|
+
const targets = [`language: ${skill.targets.language}`];
|
|
42
|
+
if (skill.targets.frameworks.length > 0) {
|
|
43
|
+
targets.push(`frameworks: ${skill.targets.frameworks.join(',')}`);
|
|
44
|
+
}
|
|
45
|
+
if (skill.targets.providers.length > 0) {
|
|
46
|
+
targets.push(`providers: ${skill.targets.providers.join(',')}`);
|
|
47
|
+
}
|
|
48
|
+
return `- ${skill.id} — ${skill.summary} [${targets.join('; ')}]`;
|
|
49
|
+
}).join('\n');
|
|
50
|
+
}
|
|
51
|
+
function copyDirectoryRecursive(sourceDir, targetDir) {
|
|
52
|
+
mkdirSync(targetDir, { recursive: true });
|
|
53
|
+
for (const entry of readdirSync(sourceDir, { withFileTypes: true })) {
|
|
54
|
+
const source = join(sourceDir, entry.name);
|
|
55
|
+
const target = join(targetDir, entry.name);
|
|
56
|
+
if (entry.isDirectory()) {
|
|
57
|
+
copyDirectoryRecursive(source, target);
|
|
58
|
+
}
|
|
59
|
+
else if (entry.isFile()) {
|
|
60
|
+
copyFileSync(source, target);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
function missingBundleError(sourceDir) {
|
|
65
|
+
let detail = 'is absent';
|
|
66
|
+
try {
|
|
67
|
+
if (existsSync(sourceDir) && !statSync(sourceDir).isDirectory()) {
|
|
68
|
+
detail = 'is not a directory';
|
|
69
|
+
}
|
|
70
|
+
else if (existsSync(sourceDir)) {
|
|
71
|
+
detail = 'has no readable index.json';
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
detail = 'is not readable';
|
|
76
|
+
}
|
|
77
|
+
return new Error(`Bundled SDK skill catalog ${detail} at ${sourceDir}. ` +
|
|
78
|
+
'Set MODA_BUNDLED_SKILLS_DIR to a built bundle, or run the integration-skill bundle build in a dev tree.');
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=sdk-catalog.js.map
|
package/dist/init/sdk-guide.d.ts
CHANGED
|
@@ -6,43 +6,14 @@ type RuntimeSummary = {
|
|
|
6
6
|
providers: string[];
|
|
7
7
|
runtimeAgentNames: string[];
|
|
8
8
|
};
|
|
9
|
-
export type SdkGuideContext = {
|
|
10
|
-
languages: string[];
|
|
11
|
-
installCommands: string[];
|
|
12
|
-
providerHints: string[];
|
|
13
|
-
frameworkHints: string[];
|
|
14
|
-
packageManagers: string[];
|
|
15
|
-
runtimeSummary: RuntimeSummary;
|
|
16
|
-
};
|
|
17
9
|
/**
|
|
18
10
|
* Write the human/agent-readable fallback guide (.moda/SDK_INTEGRATION.md).
|
|
19
|
-
*
|
|
20
|
-
* When a bundled integration skill matches the detected stack, the guide is
|
|
21
|
-
* rendered from that skill's content plus the detection block (one shared
|
|
22
|
-
* renderer with the agent-prompt path). When no skill resolves (no language
|
|
23
|
-
* detected) the slim generic guide is used; when a skill resolved but its
|
|
24
|
-
* content could not be loaded (missing/unreadable bundle) the generic guide is
|
|
25
|
-
* flagged as a degraded fallback and pinned to the detected stack.
|
|
11
|
+
* Used when automatic SDK integration did not complete successfully.
|
|
26
12
|
*/
|
|
27
13
|
export declare function writeSdkIntegrationGuide(cwd?: string): string;
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
export declare function
|
|
32
|
-
/** Drop a SKILL.md's YAML frontmatter so only the prose body is rendered. */
|
|
33
|
-
export declare function stripSkillFrontmatter(markdown: string): string;
|
|
34
|
-
export declare function detectSdkGuideContext(cwd: string): SdkGuideContext;
|
|
35
|
-
/**
|
|
36
|
-
* Slim last-resort guide, used when no bundled skill resolves (no language
|
|
37
|
-
* detected) or when a skill resolved but its content could not be loaded (the
|
|
38
|
-
* bundle is missing/unreadable). Detected stacks with readable content get the
|
|
39
|
-
* far richer skill-based guide via renderSkillBasedGuide().
|
|
40
|
-
*
|
|
41
|
-
* When `resolvedSkillId` is set the intro flags the degradation and pins the
|
|
42
|
-
* reader to the detection block instead of claiming no stack was detected —
|
|
43
|
-
* mirrors the degraded fallback in the agent-prompt path so a successful
|
|
44
|
-
* detection never reads as stack-less.
|
|
45
|
-
*/
|
|
46
|
-
export declare function renderSdkIntegrationGuide(context: SdkGuideContext, resolvedSkillId?: string): string;
|
|
14
|
+
export declare function renderSdkIntegrationGuideContent(): string;
|
|
15
|
+
/** Read optional harness discovery context from `.moda/harness.json`. */
|
|
16
|
+
export declare function readRuntimeSummary(cwd: string): RuntimeSummary;
|
|
17
|
+
export declare function renderRuntimeSummaryBlock(summary: RuntimeSummary): string;
|
|
47
18
|
export {};
|
|
48
19
|
//# sourceMappingURL=sdk-guide.d.ts.map
|