@moda-ai/cli 1.11.0 → 1.12.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.
Files changed (36) hide show
  1. package/dist/init/sdk-guide.d.ts +28 -1
  2. package/dist/init/sdk-guide.js +102 -98
  3. package/dist/init/sdk-integration.d.ts +23 -3
  4. package/dist/init/sdk-integration.js +106 -24
  5. package/dist/init/skill-resolver.d.ts +19 -0
  6. package/dist/init/skill-resolver.js +78 -0
  7. package/dist/skills-bundled.d.ts +35 -0
  8. package/dist/skills-bundled.js +157 -0
  9. package/dist/skills-manifest.d.ts +33 -0
  10. package/dist/skills-manifest.js +244 -0
  11. package/dist/skills.js +22 -142
  12. package/package.json +6 -4
  13. package/skills/integration/index.json +123 -0
  14. package/skills/integration/integration-node-anthropic/EXAMPLE.md +59 -0
  15. package/skills/integration/integration-node-anthropic/SKILL.md +150 -0
  16. package/skills/integration/integration-node-generic/EXAMPLE.md +77 -0
  17. package/skills/integration/integration-node-generic/SKILL.md +170 -0
  18. package/skills/integration/integration-node-langchain/EXAMPLE.md +59 -0
  19. package/skills/integration/integration-node-langchain/SKILL.md +144 -0
  20. package/skills/integration/integration-node-openai/EXAMPLE.md +55 -0
  21. package/skills/integration/integration-node-openai/SKILL.md +148 -0
  22. package/skills/integration/integration-node-vercel-ai-sdk/EXAMPLE.md +55 -0
  23. package/skills/integration/integration-node-vercel-ai-sdk/SKILL.md +179 -0
  24. package/skills/integration/integration-node-vercel-ai-sdk/references/prompt-attribution.md +31 -0
  25. package/skills/integration/integration-python-anthropic/EXAMPLE.md +66 -0
  26. package/skills/integration/integration-python-anthropic/SKILL.md +153 -0
  27. package/skills/integration/integration-python-claude-agent-sdk/EXAMPLE.md +61 -0
  28. package/skills/integration/integration-python-claude-agent-sdk/SKILL.md +129 -0
  29. package/skills/integration/integration-python-generic/EXAMPLE.md +46 -0
  30. package/skills/integration/integration-python-generic/SKILL.md +146 -0
  31. package/skills/integration/integration-python-langchain/EXAMPLE.md +63 -0
  32. package/skills/integration/integration-python-langchain/SKILL.md +141 -0
  33. package/skills/integration/integration-python-openai/EXAMPLE.md +65 -0
  34. package/skills/integration/integration-python-openai/SKILL.md +151 -0
  35. package/dist/init/detect.d.ts +0 -9
  36. package/dist/init/detect.js +0 -179
@@ -14,8 +14,35 @@ export type SdkGuideContext = {
14
14
  packageManagers: string[];
15
15
  runtimeSummary: RuntimeSummary;
16
16
  };
17
+ /**
18
+ * 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.
26
+ */
17
27
  export declare function writeSdkIntegrationGuide(cwd?: string): string;
28
+ /** Shared renderer: detection block + the resolved skill's full content. */
29
+ export declare function renderSkillBasedGuide(skillId: string, skillMd: string, context: SdkGuideContext): string;
30
+ /** The "What Moda Detected" section, shared by prompts and guides. */
31
+ export declare function renderDetectionBlock(context: SdkGuideContext): string;
32
+ /** Drop a SKILL.md's YAML frontmatter so only the prose body is rendered. */
33
+ export declare function stripSkillFrontmatter(markdown: string): string;
18
34
  export declare function detectSdkGuideContext(cwd: string): SdkGuideContext;
19
- export declare function renderSdkIntegrationGuide(context: SdkGuideContext): string;
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;
20
47
  export {};
21
48
  //# sourceMappingURL=sdk-guide.d.ts.map
@@ -1,13 +1,73 @@
1
1
  import { existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from 'node:fs';
2
2
  import { dirname, join } from 'node:path';
3
+ import { readBundledSkillMd } from '../skills-bundled.js';
4
+ import { resolveIntegrationSkillId } from './skill-resolver.js';
3
5
  export const SDK_GUIDE_PATH = '.moda/SDK_INTEGRATION.md';
6
+ /**
7
+ * Write the human/agent-readable fallback guide (.moda/SDK_INTEGRATION.md).
8
+ *
9
+ * When a bundled integration skill matches the detected stack, the guide is
10
+ * rendered from that skill's content plus the detection block (one shared
11
+ * renderer with the agent-prompt path). When no skill resolves (no language
12
+ * detected) the slim generic guide is used; when a skill resolved but its
13
+ * content could not be loaded (missing/unreadable bundle) the generic guide is
14
+ * flagged as a degraded fallback and pinned to the detected stack.
15
+ */
4
16
  export function writeSdkIntegrationGuide(cwd = process.cwd()) {
5
17
  const relativePath = SDK_GUIDE_PATH;
6
18
  const absolutePath = join(cwd, relativePath);
19
+ const context = detectSdkGuideContext(cwd);
20
+ const skillId = resolveIntegrationSkillId(context);
21
+ const skillMd = skillId ? readBundledSkillMd(skillId) : null;
22
+ const content = skillId && skillMd
23
+ ? renderSkillBasedGuide(skillId, skillMd, context)
24
+ : renderSdkIntegrationGuide(context, skillId ?? undefined);
7
25
  mkdirSync(dirname(absolutePath), { recursive: true });
8
- writeFileSync(absolutePath, renderSdkIntegrationGuide(detectSdkGuideContext(cwd)));
26
+ writeFileSync(absolutePath, content);
9
27
  return relativePath;
10
28
  }
29
+ /** Shared renderer: detection block + the resolved skill's full content. */
30
+ export function renderSkillBasedGuide(skillId, skillMd, context) {
31
+ return [
32
+ '# Moda SDK Integration Guide',
33
+ '',
34
+ `Generated by \`moda init\` from the bundled integration skill \`${skillId}\`.`,
35
+ 'If this project has the skill materialized at',
36
+ `\`.claude/skills/${skillId}/SKILL.md\`, prefer reading it there (EXAMPLE.md`,
37
+ 'beside it shows the finished integration).',
38
+ '',
39
+ renderDetectionBlock(context),
40
+ '',
41
+ '---',
42
+ '',
43
+ stripSkillFrontmatter(skillMd).trim(),
44
+ '',
45
+ ].join('\n');
46
+ }
47
+ /** The "What Moda Detected" section, shared by prompts and guides. */
48
+ export function renderDetectionBlock(context) {
49
+ const languages = context.languages.length > 0 ? context.languages.join(', ') : 'Not detected yet';
50
+ const packageManagers = context.packageManagers.length > 0 ? context.packageManagers.join(', ') : 'Not detected yet';
51
+ const providers = context.providerHints.length > 0 ? context.providerHints.join(', ') : 'No provider imports detected yet';
52
+ const frameworks = context.frameworkHints.length > 0 ? context.frameworkHints.join(', ') : 'No agent framework detected yet';
53
+ const runtimeNames = context.runtimeSummary.runtimeAgentNames.slice(0, 8);
54
+ return [
55
+ '## What Moda Detected',
56
+ '',
57
+ `- Languages: ${languages}`,
58
+ `- Package managers: ${packageManagers}`,
59
+ ...(context.installCommands.length > 0 ? [`- Install command(s): ${context.installCommands.join(' | ')}`] : []),
60
+ `- Model providers: ${providers}`,
61
+ `- Agent frameworks: ${frameworks}`,
62
+ `- Harness graph: ${context.runtimeSummary.runtimeAgents} runtime agent(s), ${context.runtimeSummary.agentFamilies} agent family/families`,
63
+ ...(runtimeNames.length > 0 ? [`- Runtime agents: ${runtimeNames.join(', ')}`] : []),
64
+ ].join('\n');
65
+ }
66
+ const GUIDE_FRONTMATTER_RE = /^---\r?\n[\s\S]*?\r?\n---\r?\n/;
67
+ /** Drop a SKILL.md's YAML frontmatter so only the prose body is rendered. */
68
+ export function stripSkillFrontmatter(markdown) {
69
+ return markdown.replace(GUIDE_FRONTMATTER_RE, '');
70
+ }
11
71
  export function detectSdkGuideContext(cwd) {
12
72
  const languages = [];
13
73
  const installCommands = [];
@@ -64,41 +124,51 @@ export function detectSdkGuideContext(cwd) {
64
124
  runtimeSummary,
65
125
  };
66
126
  }
67
- export function renderSdkIntegrationGuide(context) {
68
- const languages = context.languages.length > 0 ? context.languages.join(', ') : 'Not detected yet';
69
- const packageManagers = context.packageManagers.length > 0 ? context.packageManagers.join(', ') : 'Not detected yet';
127
+ /**
128
+ * Slim last-resort guide, used when no bundled skill resolves (no language
129
+ * detected) or when a skill resolved but its content could not be loaded (the
130
+ * bundle is missing/unreadable). Detected stacks with readable content get the
131
+ * far richer skill-based guide via renderSkillBasedGuide().
132
+ *
133
+ * When `resolvedSkillId` is set the intro flags the degradation and pins the
134
+ * reader to the detection block instead of claiming no stack was detected —
135
+ * mirrors the degraded fallback in the agent-prompt path so a successful
136
+ * detection never reads as stack-less.
137
+ */
138
+ export function renderSdkIntegrationGuide(context, resolvedSkillId) {
70
139
  const installCommands = context.installCommands.length > 0
71
140
  ? context.installCommands
72
141
  : ['npm install moda-ai', 'pip install moda-ai'];
73
- const providers = context.providerHints.length > 0 ? context.providerHints.join(', ') : 'No provider imports detected yet';
74
- const frameworks = context.frameworkHints.length > 0 ? context.frameworkHints.join(', ') : 'No agent framework detected yet';
75
- const runtimeNames = context.runtimeSummary.runtimeAgentNames.slice(0, 8);
142
+ const intro = resolvedSkillId
143
+ ? [
144
+ `Generated by \`moda init\`. This repo's stack resolved to the bundled integration skill`,
145
+ `\`${resolvedSkillId}\`, but its content could not be loaded (the skill bundle is missing`,
146
+ 'or unreadable in this install), so this is the general checklist. Tailor every step to',
147
+ 'the stack in "What Moda Detected" below rather than treating this as a repo with no',
148
+ 'detected stack.',
149
+ ]
150
+ : [
151
+ 'Generated by `moda init`. No specific stack was detected, so this is the',
152
+ 'general checklist; the full per-framework references live at',
153
+ 'https://docs.moda.dev/ingestion.',
154
+ ];
76
155
  return `${[
77
156
  '# Moda SDK Integration Guide',
78
157
  '',
79
- 'Generated by `moda init`.',
158
+ ...intro,
80
159
  '',
81
- 'V1 does not install packages or edit source code automatically. Use this guide as the human-approved checklist for SDK package installation and code instrumentation.',
82
- '',
83
- '## What Moda Detected',
84
- '',
85
- `- Languages: ${languages}`,
86
- `- Package managers: ${packageManagers}`,
87
- `- Model providers: ${providers}`,
88
- `- Agent frameworks: ${frameworks}`,
89
- `- Harness graph: ${context.runtimeSummary.runtimeAgents} runtime agent(s), ${context.runtimeSummary.agentFamilies} agent family/families`,
90
- ...(runtimeNames.length > 0 ? [`- Runtime agents: ${runtimeNames.join(', ')}`] : []),
160
+ renderDetectionBlock(context),
91
161
  '',
92
- '## 1. Approve Package Installation',
162
+ '## 1. Install The SDK',
93
163
  '',
94
- 'Run the command that matches the harness you are instrumenting:',
164
+ 'Use the package manager this repo already uses:',
95
165
  '',
96
166
  ...installCommands.flatMap((command) => ['```bash', command, '```', '']),
97
- 'Do not paste `MODA_API_KEY` into source. Keep it in your deployment environment, secret manager, or local shell.',
167
+ 'Do not paste `MODA_API_KEY` into source or `.env` files. Keep it in your deployment environment, secret manager, or local shell.',
98
168
  '',
99
169
  '## 2. Initialize Moda Once At Startup',
100
170
  '',
101
- 'Initialize before any OpenAI, Anthropic, LangChain, AutoGen, CrewAI, or app-specific LLM client is created.',
171
+ 'Initialize before any LLM client is created.',
102
172
  '',
103
173
  'Node.js / TypeScript:',
104
174
  '',
@@ -111,14 +181,16 @@ export function renderSdkIntegrationGuide(context) {
111
181
  'Python:',
112
182
  '',
113
183
  '```python',
184
+ 'import os',
185
+ '',
114
186
  'import moda',
115
187
  '',
116
- 'moda.init() # reads MODA_API_KEY',
188
+ 'moda.init(os.environ["MODA_API_KEY"])',
117
189
  '```',
118
190
  '',
119
191
  '## 3. Set Conversation And User Context',
120
192
  '',
121
- 'Set a stable conversation id before each LLM call. If one user can reach the same agent through SMS, WhatsApp, voice, or Slack, use the same user id across channels.',
193
+ 'Set a stable conversation id before each LLM call, and a user id when one user can be identified. For concurrent request handlers, use the scoped context helpers from the SDK instead of setting global context across overlapping requests.',
122
194
  '',
123
195
  'Node.js / TypeScript:',
124
196
  '',
@@ -134,87 +206,17 @@ export function renderSdkIntegrationGuide(context) {
134
206
  'moda.user_id = user_id',
135
207
  '```',
136
208
  '',
137
- 'For concurrent request handlers, use the scoped context helpers from the SDK instead of setting global context across overlapping requests.',
209
+ '## 4. Flush Before Exit',
138
210
  '',
139
- '## 4. Attribute Managed Prompts',
211
+ 'Node.js / TypeScript: `await Moda.flush();` — Python: `moda.flush()`.',
140
212
  '',
141
- 'Keep prompts in files, sync them with Moda, and render them through the SDK so production traces point at exact prompt versions.',
142
- '',
143
- '```bash',
144
- 'moda prompts status',
145
- 'moda prompts sync',
146
- '```',
147
- '',
148
- 'Node.js / TypeScript:',
149
- '',
150
- '```ts',
151
- 'const rendered = Moda.prompt("support.triage").render({',
152
- ' ticket: { text: userMessage },',
153
- '});',
154
- '',
155
- 'await client.chat.completions.create({',
156
- ' model: "gpt-4o",',
157
- ' messages: rendered.messages,',
158
- '});',
159
- '```',
160
- '',
161
- 'Python:',
162
- '',
163
- '```python',
164
- 'rendered = moda.prompt("support.triage").render({',
165
- ' "ticket": {"text": user_message},',
166
- '})',
167
- '',
168
- 'client.chat.completions.create(',
169
- ' model="gpt-4o",',
170
- ' messages=rendered["messages"],',
171
- ')',
172
- '```',
173
- '',
174
- '## 5. Vercel AI SDK',
175
- '',
176
- 'If this project uses the `ai` package or `@ai-sdk/*`, pass Moda telemetry explicitly:',
177
- '',
178
- '```ts',
179
- 'const result = await generateText({',
180
- " model: openai('gpt-4o'),",
181
- ' messages: rendered.messages,',
182
- ' experimental_telemetry: Moda.getVercelAITelemetry(),',
183
- '});',
184
- '```',
185
- '',
186
- 'Render the prompt before calling `Moda.getVercelAITelemetry()` so prompt metadata is attached.',
187
- '',
188
- '## 6. Flush Before Exit',
189
- '',
190
- 'Node.js / TypeScript:',
191
- '',
192
- '```ts',
193
- 'await Moda.flush();',
194
- '```',
195
- '',
196
- 'Python:',
197
- '',
198
- '```python',
199
- 'moda.flush()',
200
- '```',
201
- '',
202
- '## 7. Validate',
203
- '',
204
- 'After the code change is reviewed and merged, run:',
213
+ '## 5. Validate',
205
214
  '',
206
215
  '```bash',
207
216
  'moda doctor --online --json',
208
217
  '```',
209
218
  '',
210
- 'The check should show recent traces/logs/runs. If it does not, confirm `MODA_API_KEY` is available at runtime and that `Moda.init()` runs before the first LLM client is created.',
211
- '',
212
- '## Human Approval Boundary',
213
- '',
214
- '- This guide is safe for `moda init` to write automatically.',
215
- '- Package installation is a human-approved step.',
216
- '- Source edits are a human-approved step.',
217
- '- Environment variable files are not created or updated by `moda init`.',
219
+ 'The check should show recent traces/logs/runs. If it does not, confirm `MODA_API_KEY` is available at runtime and that Moda initialization runs before the first LLM client is created.',
218
220
  '',
219
221
  ].join('\n')}`;
220
222
  }
@@ -293,6 +295,8 @@ function addPythonHints(text, providers, frameworks) {
293
295
  providers.add('Anthropic');
294
296
  if (/\blangchain\b|\blanggraph\b/.test(lower))
295
297
  frameworks.add('LangChain / LangGraph');
298
+ if (/claude[-_]agent[-_]sdk/.test(lower))
299
+ frameworks.add('Claude Agent SDK');
296
300
  if (/\bcrewai\b/.test(lower))
297
301
  frameworks.add('CrewAI');
298
302
  if (/\bautogen\b/.test(lower))
@@ -1,7 +1,9 @@
1
1
  import { type CodingAgentAdapter } from '../harness.js';
2
+ import { type SdkGuideContext } from './sdk-guide.js';
2
3
  export type SdkIntegrationResult = {
3
4
  status: 'integrated';
4
5
  adapter: CodingAgentAdapter;
6
+ skillId?: string;
5
7
  } | {
6
8
  status: 'no-agent';
7
9
  } | {
@@ -18,9 +20,27 @@ export interface SdkIntegrationOptions {
18
20
  onProgress?: (message: string) => void;
19
21
  }
20
22
  /**
21
- * Pick an installed coding-agent CLI and run it, in write mode, to integrate the
22
- * Moda SDK into the repo. Returns `no-agent` (without throwing) when no CLI is
23
- * installed so the caller can fall back to writing the static guide.
23
+ * Pick an installed coding-agent CLI and run it, in write mode, to integrate
24
+ * the Moda SDK into the repo. Returns `no-agent` (without throwing) when no
25
+ * CLI is installed so the caller can fall back to writing the static guide.
24
26
  */
25
27
  export declare function runSdkIntegration(options?: SdkIntegrationOptions): Promise<SdkIntegrationResult>;
28
+ /** How the resolved integration skill (if any) reaches the agent prompt. */
29
+ export interface SdkIntegrationPromptSkill {
30
+ /** Skill materialized to `.claude/skills/<id>/` — point the agent at it. */
31
+ installedSkillId?: string;
32
+ /** Resolved skill we could read but not materialize — inline its content. */
33
+ inlineSkill?: {
34
+ skillId: string;
35
+ markdown: string;
36
+ };
37
+ /**
38
+ * The skill the detected stack resolved to, if any. Set even when neither
39
+ * install nor inline succeeded (e.g. the bundle is entirely absent) so the
40
+ * generic fallback can flag the degradation and tell the agent to tailor the
41
+ * checklist to the detected stack instead of treating it as stack-less.
42
+ */
43
+ resolvedSkillId?: string;
44
+ }
45
+ export declare function renderSdkIntegrationPrompt(context: SdkGuideContext, skill?: SdkIntegrationPromptSkill): string;
26
46
  //# sourceMappingURL=sdk-integration.d.ts.map
@@ -2,14 +2,20 @@
2
2
  // Moda SDK directly, instead of leaving a guide for the user to action by hand.
3
3
  //
4
4
  // This reuses the same coding-agent spawn machinery as `moda harness analyze`
5
- // (see src/harness.ts), but in write mode: the agent is expected to install the
6
- // SDK package and edit source to instrument LLM calls.
5
+ // (see src/harness.ts), but in write mode. The integration knowledge itself
6
+ // lives in the bundled integration skills (skills/integration/, authored in
7
+ // packages/integration-skills/): we resolve the skill matching the detected
8
+ // stack, materialize it into .claude/skills/<id>/, and hand the agent a thin
9
+ // prompt that points at the skill file — the prompt no longer inlines API
10
+ // documentation.
7
11
  import { codingAgentDisplayName, detectAvailableCodingAgents, runCodingAgent, } from '../harness.js';
8
- import { detectSdkGuideContext, renderSdkIntegrationGuide } from './sdk-guide.js';
12
+ import { installIntegrationSkill, readBundledSkillMd } from '../skills-bundled.js';
13
+ import { detectSdkGuideContext, renderDetectionBlock, renderSdkIntegrationGuide, stripSkillFrontmatter, } from './sdk-guide.js';
14
+ import { resolveIntegrationSkillId } from './skill-resolver.js';
9
15
  /**
10
- * Pick an installed coding-agent CLI and run it, in write mode, to integrate the
11
- * Moda SDK into the repo. Returns `no-agent` (without throwing) when no CLI is
12
- * installed so the caller can fall back to writing the static guide.
16
+ * Pick an installed coding-agent CLI and run it, in write mode, to integrate
17
+ * the Moda SDK into the repo. Returns `no-agent` (without throwing) when no
18
+ * CLI is installed so the caller can fall back to writing the static guide.
13
19
  */
14
20
  export async function runSdkIntegration(options = {}) {
15
21
  const cwd = options.cwd ?? process.cwd();
@@ -19,13 +25,46 @@ export async function runSdkIntegration(options = {}) {
19
25
  const adapter = options.adapter && available.some((item) => item.adapter === options.adapter)
20
26
  ? options.adapter
21
27
  : available[0].adapter;
22
- const prompt = renderSdkIntegrationPrompt(cwd);
23
28
  const human = options.human ?? false;
24
29
  const emit = (message) => {
25
30
  options.onProgress?.(message);
26
31
  if (human)
27
32
  process.stderr.write(` ${codingAgentDisplayName(adapter)}: ${message}\n`);
28
33
  };
34
+ const context = detectSdkGuideContext(cwd);
35
+ const skillId = resolveIntegrationSkillId(context);
36
+ let installedSkillId;
37
+ let inlineSkill;
38
+ if (skillId) {
39
+ try {
40
+ const install = installIntegrationSkill(skillId, cwd);
41
+ installedSkillId = install.skillId;
42
+ emit(install.status === 'installed'
43
+ ? `Installed integration skill ${skillId} to .claude/skills/${skillId}/`
44
+ : `Integration skill ${skillId} already up to date`);
45
+ }
46
+ catch (err) {
47
+ // Materialization failed (unwritable .claude/skills target, a colliding
48
+ // user-authored file, or a missing/corrupt bundle). Do NOT silently drop
49
+ // the resolved stack to the generic checklist — that would contradict the
50
+ // detection block. When the bundle is still readable, inline the skill's
51
+ // content into the prompt so the agent keeps the stack-specific steps;
52
+ // only a truly absent bundle (e.g. an unbuilt dev tree) falls through to
53
+ // the generic guide.
54
+ const message = err instanceof Error ? err.message : String(err);
55
+ emit(`Could not materialize integration skill ${skillId}: ${message}`);
56
+ const skillMd = readBundledSkillMd(skillId);
57
+ if (skillMd) {
58
+ inlineSkill = { skillId, markdown: skillMd };
59
+ emit(`Inlining ${skillId} instructions into the agent prompt instead`);
60
+ }
61
+ }
62
+ }
63
+ const prompt = renderSdkIntegrationPrompt(context, {
64
+ installedSkillId,
65
+ inlineSkill,
66
+ resolvedSkillId: skillId ?? undefined,
67
+ });
29
68
  const run = await runCodingAgent({
30
69
  adapter,
31
70
  prompt,
@@ -49,32 +88,75 @@ export async function runSdkIntegration(options = {}) {
49
88
  detail: `${codingAgentDisplayName(adapter)} exited with status ${run.status ?? 'unknown'}.${tail ? `\n${tail}` : ''}`,
50
89
  };
51
90
  }
52
- return { status: 'integrated', adapter };
91
+ return { status: 'integrated', adapter, skillId: installedSkillId };
53
92
  }
54
- function renderSdkIntegrationPrompt(cwd) {
55
- const guide = renderSdkIntegrationGuide(detectSdkGuideContext(cwd));
56
- return [
93
+ const SAFETY_RULES = [
94
+ 'Scope and safety rules:',
95
+ '- Only touch this repository (the working directory you were launched in).',
96
+ '- Install the Moda SDK with the package manager this repo already uses.',
97
+ '- Instrument the real LLM call sites; do not invent files or frameworks that are not here.',
98
+ '- Never write MODA_API_KEY (or any secret) into source files, and never create or edit',
99
+ ' .env files. The key belongs in the environment / a secret manager at runtime.',
100
+ '- Make the smallest correct change. Match the existing code style and imports.',
101
+ '- If something is ambiguous or the repo has no LLM calls to instrument, stop and explain',
102
+ ' rather than guessing.',
103
+ ];
104
+ export function renderSdkIntegrationPrompt(context, skill = {}) {
105
+ const header = [
57
106
  'You are running inside `moda init` as a one-shot SDK integration task. The user has',
58
107
  'approved letting you install packages and edit source files in THIS repository to add',
59
108
  'Moda LLM observability. Do the integration now — do not just describe it.',
60
109
  '',
61
- 'Scope and safety rules:',
62
- '- Only touch this repository (the working directory you were launched in).',
63
- '- Install the Moda SDK with the package manager this repo already uses.',
64
- '- Instrument the real LLM call sites; do not invent files or frameworks that are not here.',
65
- '- Never write MODA_API_KEY (or any secret) into source files, and never create or edit',
66
- ' .env files. The key belongs in the environment / a secret manager at runtime.',
67
- '- Make the smallest correct change. Match the existing code style and imports.',
68
- '- If something is ambiguous or the repo has no LLM calls to instrument, stop and explain',
69
- ' rather than guessing.',
110
+ ...SAFETY_RULES,
70
111
  '',
71
- 'Follow this Moda-authored integration spec exactly (it reflects the detected languages,',
72
- 'package managers, providers, and frameworks for this repo):',
112
+ renderDetectionBlock(context),
73
113
  '',
74
- guide,
114
+ ];
115
+ const body = renderPromptBody(context, skill);
116
+ const footer = [
75
117
  '',
76
118
  'When finished, print a short summary of: the package(s) installed, the files you edited,',
77
119
  'and any step a human still needs to do (e.g. setting MODA_API_KEY in the deploy env).',
78
- ].join('\n');
120
+ ];
121
+ return [...header, ...body, ...footer].join('\n');
122
+ }
123
+ /**
124
+ * Four-tier body, most-specific first:
125
+ * 1. skill materialized on disk → point the agent at it;
126
+ * 2. skill resolved but not materialized → inline its content so a detected
127
+ * stack never degrades to the generic checklist;
128
+ * 3. skill resolved but its content is unavailable (bundle absent/unreadable)
129
+ * → generic checklist, but flagged as a degraded fallback and pinned to the
130
+ * detected stack so it is not silently treated as stack-less;
131
+ * 4. nothing resolved (no language detected) → plain generic checklist.
132
+ */
133
+ function renderPromptBody(context, skill) {
134
+ if (skill.installedSkillId) {
135
+ const id = skill.installedSkillId;
136
+ return [
137
+ `Read \`.claude/skills/${id}/SKILL.md\` and follow its steps exactly, including its`,
138
+ `stop conditions. \`EXAMPLE.md\` in the same directory shows the finished integration —`,
139
+ 'match its structure.',
140
+ ];
141
+ }
142
+ if (skill.inlineSkill) {
143
+ return [
144
+ `The bundled integration skill \`${skill.inlineSkill.skillId}\` matches this repo's stack,`,
145
+ 'but could not be written to disk, so its full content is inlined below. Follow its steps',
146
+ 'exactly, including its stop conditions:',
147
+ '',
148
+ '---',
149
+ '',
150
+ stripSkillFrontmatter(skill.inlineSkill.markdown).trim(),
151
+ ];
152
+ }
153
+ // Tiers 3 & 4 share one renderer: renderSdkIntegrationGuide flags the degraded
154
+ // fallback (and pins to the detection block) when a skill resolved but its
155
+ // content was unavailable, and renders the plain generic checklist otherwise.
156
+ return [
157
+ 'Follow this Moda-authored integration checklist:',
158
+ '',
159
+ renderSdkIntegrationGuide(context, skill.resolvedSkillId),
160
+ ];
79
161
  }
80
162
  //# sourceMappingURL=sdk-integration.js.map
@@ -0,0 +1,19 @@
1
+ import type { SdkGuideContext } from './sdk-guide.js';
2
+ type Language = 'node' | 'python';
3
+ type Framework = 'vercel-ai-sdk' | 'langchain' | 'claude-agent-sdk';
4
+ type Provider = 'openai' | 'anthropic';
5
+ interface ResolutionRow {
6
+ language: Language;
7
+ framework?: Framework;
8
+ provider?: Provider;
9
+ skillId: string;
10
+ }
11
+ /** The §2.3 resolution table, encoded as data. Order matters. */
12
+ export declare const RESOLUTION_TABLE: readonly ResolutionRow[];
13
+ /**
14
+ * Resolve the detected context to a bundled skill id, or null when no
15
+ * language was detected (callers fall back to the slim generic guide).
16
+ */
17
+ export declare function resolveIntegrationSkillId(context: SdkGuideContext): string | null;
18
+ export {};
19
+ //# sourceMappingURL=skill-resolver.d.ts.map
@@ -0,0 +1,78 @@
1
+ // src/init/skill-resolver.ts — deterministic resolution from detected project
2
+ // context to a bundled integration-skill id.
3
+ //
4
+ // Pure function, zero LLM involvement. The table is ordered; the first
5
+ // matching row wins. Wrapper frameworks beat raw providers: if the Vercel AI
6
+ // SDK or LangChain is present, the wrapper layer gets instrumented, never the
7
+ // underlying openai/anthropic client.
8
+ /** The §2.3 resolution table, encoded as data. Order matters. */
9
+ export const RESOLUTION_TABLE = [
10
+ { language: 'node', framework: 'vercel-ai-sdk', skillId: 'integration-node-vercel-ai-sdk' },
11
+ { language: 'node', framework: 'langchain', skillId: 'integration-node-langchain' },
12
+ { language: 'python', framework: 'langchain', skillId: 'integration-python-langchain' },
13
+ { language: 'python', framework: 'claude-agent-sdk', skillId: 'integration-python-claude-agent-sdk' },
14
+ { language: 'node', provider: 'openai', skillId: 'integration-node-openai' },
15
+ { language: 'node', provider: 'anthropic', skillId: 'integration-node-anthropic' },
16
+ { language: 'python', provider: 'openai', skillId: 'integration-python-openai' },
17
+ { language: 'python', provider: 'anthropic', skillId: 'integration-python-anthropic' },
18
+ { language: 'node', skillId: 'integration-node-generic' },
19
+ { language: 'python', skillId: 'integration-python-generic' },
20
+ ];
21
+ /**
22
+ * Resolve the detected context to a bundled skill id, or null when no
23
+ * language was detected (callers fall back to the slim generic guide).
24
+ */
25
+ export function resolveIntegrationSkillId(context) {
26
+ const languages = normalizeLanguages(context.languages);
27
+ const frameworks = normalizeFrameworks(context.frameworkHints);
28
+ const providers = normalizeProviders(context.providerHints);
29
+ for (const row of RESOLUTION_TABLE) {
30
+ if (!languages.has(row.language))
31
+ continue;
32
+ if (row.framework && !frameworks.has(row.framework))
33
+ continue;
34
+ if (row.provider && !providers.has(row.provider))
35
+ continue;
36
+ return row.skillId;
37
+ }
38
+ return null;
39
+ }
40
+ function normalizeLanguages(languages) {
41
+ const result = new Set();
42
+ for (const language of languages) {
43
+ const lower = language.toLowerCase();
44
+ if (lower.includes('node') || lower.includes('typescript') || lower.includes('javascript')) {
45
+ result.add('node');
46
+ }
47
+ if (lower.includes('python'))
48
+ result.add('python');
49
+ }
50
+ return result;
51
+ }
52
+ function normalizeFrameworks(frameworkHints) {
53
+ const result = new Set();
54
+ for (const hint of frameworkHints) {
55
+ const lower = hint.toLowerCase();
56
+ if (lower.includes('vercel ai sdk') || lower.includes('vercel-ai-sdk') || lower.includes('ai sdk')) {
57
+ result.add('vercel-ai-sdk');
58
+ }
59
+ if (lower.includes('langchain') || lower.includes('langgraph'))
60
+ result.add('langchain');
61
+ if (lower.includes('claude agent sdk') || lower.includes('claude-agent-sdk')) {
62
+ result.add('claude-agent-sdk');
63
+ }
64
+ }
65
+ return result;
66
+ }
67
+ function normalizeProviders(providerHints) {
68
+ const result = new Set();
69
+ for (const hint of providerHints) {
70
+ const lower = hint.toLowerCase();
71
+ if (lower.includes('openai'))
72
+ result.add('openai');
73
+ if (lower.includes('anthropic'))
74
+ result.add('anthropic');
75
+ }
76
+ return result;
77
+ }
78
+ //# sourceMappingURL=skill-resolver.js.map
@@ -0,0 +1,35 @@
1
+ export interface BundledSkillIndexEntry {
2
+ id: string;
3
+ version: string;
4
+ summary: string;
5
+ targets: {
6
+ language: string;
7
+ frameworks: string[];
8
+ providers: string[];
9
+ };
10
+ }
11
+ export interface BundledSkillIndex {
12
+ schema_version: string;
13
+ bundled_at: string;
14
+ cli_version: string;
15
+ skills: BundledSkillIndexEntry[];
16
+ }
17
+ export interface IntegrationSkillInstallResult {
18
+ skillId: string;
19
+ status: 'installed' | 'unchanged';
20
+ paths: string[];
21
+ }
22
+ /** Directory holding the bundled skills (overridable for tests). */
23
+ export declare function bundledSkillsDir(): string;
24
+ /** Read the bundled skill menu, or null when no bundle is present. */
25
+ export declare function readBundledIndex(): BundledSkillIndex | null;
26
+ /** Read a bundled skill's SKILL.md, or null when it is not bundled. */
27
+ export declare function readBundledSkillMd(skillId: string): string | null;
28
+ /**
29
+ * Materialize a bundled integration skill into the project.
30
+ *
31
+ * Idempotent: unchanged files are left untouched, and a second install with
32
+ * identical content reports status 'unchanged' without writing.
33
+ */
34
+ export declare function installIntegrationSkill(skillId: string, cwd?: string): IntegrationSkillInstallResult;
35
+ //# sourceMappingURL=skills-bundled.d.ts.map