@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.
- package/dist/init/sdk-guide.d.ts +28 -1
- package/dist/init/sdk-guide.js +102 -98
- package/dist/init/sdk-integration.d.ts +23 -3
- package/dist/init/sdk-integration.js +106 -24
- package/dist/init/skill-resolver.d.ts +19 -0
- package/dist/init/skill-resolver.js +78 -0
- package/dist/skills-bundled.d.ts +35 -0
- package/dist/skills-bundled.js +157 -0
- package/dist/skills-manifest.d.ts +33 -0
- package/dist/skills-manifest.js +244 -0
- package/dist/skills.js +22 -142
- package/package.json +6 -4
- package/skills/integration/index.json +123 -0
- package/skills/integration/integration-node-anthropic/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-anthropic/SKILL.md +150 -0
- package/skills/integration/integration-node-generic/EXAMPLE.md +77 -0
- package/skills/integration/integration-node-generic/SKILL.md +170 -0
- package/skills/integration/integration-node-langchain/EXAMPLE.md +59 -0
- package/skills/integration/integration-node-langchain/SKILL.md +144 -0
- package/skills/integration/integration-node-openai/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-openai/SKILL.md +148 -0
- package/skills/integration/integration-node-vercel-ai-sdk/EXAMPLE.md +55 -0
- package/skills/integration/integration-node-vercel-ai-sdk/SKILL.md +179 -0
- package/skills/integration/integration-node-vercel-ai-sdk/references/prompt-attribution.md +31 -0
- package/skills/integration/integration-python-anthropic/EXAMPLE.md +66 -0
- package/skills/integration/integration-python-anthropic/SKILL.md +153 -0
- package/skills/integration/integration-python-claude-agent-sdk/EXAMPLE.md +61 -0
- package/skills/integration/integration-python-claude-agent-sdk/SKILL.md +129 -0
- package/skills/integration/integration-python-generic/EXAMPLE.md +46 -0
- package/skills/integration/integration-python-generic/SKILL.md +146 -0
- package/skills/integration/integration-python-langchain/EXAMPLE.md +63 -0
- package/skills/integration/integration-python-langchain/SKILL.md +141 -0
- package/skills/integration/integration-python-openai/EXAMPLE.md +65 -0
- package/skills/integration/integration-python-openai/SKILL.md +151 -0
- package/dist/init/detect.d.ts +0 -9
- package/dist/init/detect.js +0 -179
package/dist/init/sdk-guide.d.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
package/dist/init/sdk-guide.js
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
|
74
|
-
|
|
75
|
-
|
|
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
|
-
|
|
158
|
+
...intro,
|
|
80
159
|
'',
|
|
81
|
-
|
|
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.
|
|
162
|
+
'## 1. Install The SDK',
|
|
93
163
|
'',
|
|
94
|
-
'
|
|
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
|
|
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()
|
|
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
|
|
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
|
-
'
|
|
209
|
+
'## 4. Flush Before Exit',
|
|
138
210
|
'',
|
|
139
|
-
'
|
|
211
|
+
'Node.js / TypeScript: `await Moda.flush();` — Python: `moda.flush()`.',
|
|
140
212
|
'',
|
|
141
|
-
'
|
|
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
|
|
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
|
|
22
|
-
* Moda SDK into the repo. Returns `no-agent` (without throwing) when no
|
|
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
|
|
6
|
-
//
|
|
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 {
|
|
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
|
|
11
|
-
* Moda SDK into the repo. Returns `no-agent` (without throwing) when no
|
|
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
|
-
|
|
55
|
-
|
|
56
|
-
|
|
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
|
-
|
|
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
|
-
|
|
72
|
-
'package managers, providers, and frameworks for this repo):',
|
|
112
|
+
renderDetectionBlock(context),
|
|
73
113
|
'',
|
|
74
|
-
|
|
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
|
-
]
|
|
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
|