imprint-mcp 0.2.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/CHANGELOG.md +168 -0
- package/LICENSE +21 -0
- package/README.md +322 -0
- package/examples/discoverandgo/README.md +57 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/cron.json +8 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/index.ts +89 -0
- package/examples/discoverandgo/book_discoverandgo_museum_pass/workflow.json +39 -0
- package/examples/echo/README.md +37 -0
- package/examples/echo/echo_test/index.ts +31 -0
- package/examples/google-flights/search_google_flights/index.ts +101 -0
- package/examples/google-flights/search_google_flights/parser.test.ts +140 -0
- package/examples/google-flights/search_google_flights/parser.ts +189 -0
- package/examples/google-flights/search_google_flights/playbook.yaml +130 -0
- package/examples/google-flights/search_google_flights/workflow.json +48 -0
- package/examples/google-hotels/search_google_hotels/index.ts +194 -0
- package/examples/google-hotels/search_google_hotels/parser.test.ts +168 -0
- package/examples/google-hotels/search_google_hotels/parser.ts +330 -0
- package/examples/google-hotels/search_google_hotels/playbook.yaml +125 -0
- package/examples/google-hotels/search_google_hotels/workflow.json +111 -0
- package/examples/namecheap-domains/search_namecheap_domains/index.ts +144 -0
- package/examples/namecheap-domains/search_namecheap_domains/parser.ts +380 -0
- package/examples/namecheap-domains/search_namecheap_domains/playbook.yaml +50 -0
- package/examples/namecheap-domains/search_namecheap_domains/request-transform.ts +136 -0
- package/examples/namecheap-domains/search_namecheap_domains/workflow.json +97 -0
- package/examples/southwest/README.md +81 -0
- package/examples/southwest/search_southwest_flights/backends.json +23 -0
- package/examples/southwest/search_southwest_flights/cron.json +19 -0
- package/examples/southwest/search_southwest_flights/index.ts +110 -0
- package/examples/southwest/search_southwest_flights/playbook.yaml +46 -0
- package/examples/southwest/search_southwest_flights/workflow.json +54 -0
- package/package.json +78 -0
- package/prompts/compile-agent.md +580 -0
- package/prompts/intent-detection.md +198 -0
- package/prompts/playbook-compilation.md +279 -0
- package/prompts/request-triage.md +74 -0
- package/prompts/tool-candidate-detection.md +104 -0
- package/src/cli.ts +1287 -0
- package/src/imprint/agent.ts +468 -0
- package/src/imprint/app-api-hosts.ts +53 -0
- package/src/imprint/backend-ladder.ts +568 -0
- package/src/imprint/check.ts +136 -0
- package/src/imprint/chromium.ts +211 -0
- package/src/imprint/claude-cli-compile.ts +640 -0
- package/src/imprint/cli-credential.ts +394 -0
- package/src/imprint/codex-cli-compile.ts +712 -0
- package/src/imprint/compile-agent-types.ts +40 -0
- package/src/imprint/compile-agent.ts +404 -0
- package/src/imprint/compile-tools.ts +1389 -0
- package/src/imprint/compile.ts +720 -0
- package/src/imprint/cookie-jar.ts +246 -0
- package/src/imprint/credential-bundle.ts +195 -0
- package/src/imprint/credential-extract.ts +290 -0
- package/src/imprint/credential-store.ts +707 -0
- package/src/imprint/cron.ts +312 -0
- package/src/imprint/doctor.ts +223 -0
- package/src/imprint/emit.ts +154 -0
- package/src/imprint/etld.ts +134 -0
- package/src/imprint/freeform-redact.ts +216 -0
- package/src/imprint/inject-listener.ts +137 -0
- package/src/imprint/install.ts +795 -0
- package/src/imprint/integrations.ts +385 -0
- package/src/imprint/is-compiled.ts +2 -0
- package/src/imprint/json-path.ts +100 -0
- package/src/imprint/llm.ts +998 -0
- package/src/imprint/load-json.ts +54 -0
- package/src/imprint/log.ts +33 -0
- package/src/imprint/login.ts +166 -0
- package/src/imprint/mcp-compile-server.ts +282 -0
- package/src/imprint/mcp-maintenance.ts +1790 -0
- package/src/imprint/mcp-server.ts +350 -0
- package/src/imprint/multi-progress.ts +69 -0
- package/src/imprint/notify.ts +155 -0
- package/src/imprint/paths.ts +64 -0
- package/src/imprint/playbook-parser.ts +21 -0
- package/src/imprint/playbook-runner.ts +465 -0
- package/src/imprint/probe-backends.ts +251 -0
- package/src/imprint/progress.ts +28 -0
- package/src/imprint/record.ts +470 -0
- package/src/imprint/redact.ts +550 -0
- package/src/imprint/replay-capture.ts +387 -0
- package/src/imprint/request-context.ts +66 -0
- package/src/imprint/runtime-link.ts +73 -0
- package/src/imprint/runtime.ts +942 -0
- package/src/imprint/sensitive-keys.ts +156 -0
- package/src/imprint/session-diff.ts +409 -0
- package/src/imprint/session-merge.ts +198 -0
- package/src/imprint/session-writer.ts +149 -0
- package/src/imprint/sites.ts +27 -0
- package/src/imprint/stealth-fetch.ts +434 -0
- package/src/imprint/teach-state.ts +235 -0
- package/src/imprint/teach.ts +2120 -0
- package/src/imprint/tool-candidates.ts +423 -0
- package/src/imprint/tool-loader.ts +186 -0
- package/src/imprint/tool-selection.ts +70 -0
- package/src/imprint/tracing.ts +508 -0
- package/src/imprint/types.ts +472 -0
- package/src/imprint/version.ts +21 -0
|
@@ -0,0 +1,385 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `imprint teach` integration module — generate platform-specific paste
|
|
3
|
+
* snippets and inline SKILL.md content for registering Imprint MCP tools
|
|
4
|
+
* with Claude Code, Codex, Claude Desktop, OpenClaw, and Hermes.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'node:child_process';
|
|
8
|
+
import { resolve as pathResolve } from 'node:path';
|
|
9
|
+
import { stringify as yamlStringify } from 'yaml';
|
|
10
|
+
import type { CronConfig, Playbook, Workflow, WorkflowParameter } from './types.ts';
|
|
11
|
+
|
|
12
|
+
export type Platform = 'claude-code' | 'codex' | 'claude-desktop' | 'openclaw' | 'hermes';
|
|
13
|
+
|
|
14
|
+
export const PLATFORMS: readonly Platform[] = [
|
|
15
|
+
'claude-code',
|
|
16
|
+
'codex',
|
|
17
|
+
'claude-desktop',
|
|
18
|
+
'openclaw',
|
|
19
|
+
'hermes',
|
|
20
|
+
] as const;
|
|
21
|
+
|
|
22
|
+
interface ImprintCommand {
|
|
23
|
+
command: string;
|
|
24
|
+
args: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface McpServerConfig {
|
|
28
|
+
name: string;
|
|
29
|
+
command: string;
|
|
30
|
+
args: string[];
|
|
31
|
+
env?: Record<string, string>;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Detects whether `imprint` is available on PATH; falls back to
|
|
36
|
+
* `bun run <abs-path>` if not. Used by teach.ts to generate paste snippets.
|
|
37
|
+
*/
|
|
38
|
+
export function detectImprintCommand(): ImprintCommand {
|
|
39
|
+
try {
|
|
40
|
+
execSync('which imprint', { stdio: 'ignore' });
|
|
41
|
+
return { command: 'imprint', args: [] };
|
|
42
|
+
} catch {
|
|
43
|
+
return detectDirectBunImprintCommand();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function detectDirectBunImprintCommand(): ImprintCommand {
|
|
48
|
+
const cliPath = pathResolve(import.meta.dir, '..', 'cli.ts');
|
|
49
|
+
return { command: process.execPath || 'bun', args: ['run', cliPath] };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Generate the paste snippet for a given platform — the quick-install
|
|
54
|
+
* instructions users can paste into their shell to register the MCP server.
|
|
55
|
+
*/
|
|
56
|
+
export function generatePasteSnippet(opts: {
|
|
57
|
+
site: string;
|
|
58
|
+
workflow: Workflow;
|
|
59
|
+
workflows?: Workflow[];
|
|
60
|
+
platform: Platform;
|
|
61
|
+
imprintCommand: ImprintCommand;
|
|
62
|
+
env?: Record<string, string>;
|
|
63
|
+
}): string {
|
|
64
|
+
const { site, workflow, workflows, platform, imprintCommand: ic, env } = opts;
|
|
65
|
+
const toolName = `imprint-${site}`;
|
|
66
|
+
const workflowList = workflows && workflows.length > 0 ? workflows : [workflow];
|
|
67
|
+
const descLower =
|
|
68
|
+
workflowList.length === 1
|
|
69
|
+
? workflow.intent.description.toLowerCase()
|
|
70
|
+
: `${workflowList.length} tools: ${workflowList.map((w) => w.toolName).join(', ')}`;
|
|
71
|
+
const paramList =
|
|
72
|
+
workflowList.length === 1
|
|
73
|
+
? formatParams(workflow.parameters)
|
|
74
|
+
: workflowList.map((w) => `${w.toolName}: ${formatParams(w.parameters)}`).join('; ');
|
|
75
|
+
const mcpArgs = [...ic.args, 'mcp-server', site];
|
|
76
|
+
const argsStr = `[${mcpArgs.map((a) => `"${a}"`).join(', ')}]`;
|
|
77
|
+
const envStr = env ? `, "env": ${JSON.stringify(env)}` : '';
|
|
78
|
+
const registrationCommand = buildRegistrationCommand({
|
|
79
|
+
site,
|
|
80
|
+
platform,
|
|
81
|
+
imprintCommand: ic,
|
|
82
|
+
env,
|
|
83
|
+
});
|
|
84
|
+
const shellCmd = registrationCommand
|
|
85
|
+
? registrationCommand.map(shellQuote).join(' ')
|
|
86
|
+
: [ic.command, ...mcpArgs].map(shellQuote).join(' ');
|
|
87
|
+
|
|
88
|
+
switch (platform) {
|
|
89
|
+
case 'claude-code':
|
|
90
|
+
return `Add the ${toolName} tool: run \`${shellCmd}\` to register ${descLower}. Parameters: ${paramList}. The backend ladder handles browser/API state and bot detection automatically (fetch → gated fetch-bootstrap → stealth-fetch → playbook).`;
|
|
91
|
+
|
|
92
|
+
case 'codex':
|
|
93
|
+
return `Add the ${toolName} tool: run \`${shellCmd}\` to register ${descLower}. Parameters: ${paramList}.`;
|
|
94
|
+
|
|
95
|
+
case 'claude-desktop':
|
|
96
|
+
return `Add to ~/Library/Application Support/Claude/claude_desktop_config.json under "mcpServers":
|
|
97
|
+
|
|
98
|
+
"${toolName}": { "command": "${ic.command}", "args": ${argsStr}${envStr} }`;
|
|
99
|
+
|
|
100
|
+
case 'openclaw':
|
|
101
|
+
return `Add the ${toolName} tool: add to ~/.openclaw/openclaw.json under mcp.servers:
|
|
102
|
+
|
|
103
|
+
"${toolName}": { "command": "${ic.command}", "args": ${argsStr}${envStr} }
|
|
104
|
+
|
|
105
|
+
This gives your agent a tool that ${descLower}. Parameters: ${paramList}.`;
|
|
106
|
+
|
|
107
|
+
case 'hermes':
|
|
108
|
+
return `Add the ${toolName} tool: add to ~/.hermes/config.yaml under mcp_servers:
|
|
109
|
+
|
|
110
|
+
${toolName}:
|
|
111
|
+
command: "${ic.command}"
|
|
112
|
+
args: ${argsStr}
|
|
113
|
+
${env ? ` env: ${JSON.stringify(env)}` : ''}
|
|
114
|
+
|
|
115
|
+
This gives your agent a tool that ${descLower}. Parameters: ${paramList}.`;
|
|
116
|
+
|
|
117
|
+
default: {
|
|
118
|
+
const _exhaustive: never = platform;
|
|
119
|
+
throw new Error(`Unknown platform: ${_exhaustive}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Format a list of workflow parameters as a human-readable string for
|
|
126
|
+
* inline documentation — "param1 (type, default: X), param2 (type, required)".
|
|
127
|
+
*/
|
|
128
|
+
function formatParams(params: WorkflowParameter[]): string {
|
|
129
|
+
if (params.length === 0) return 'none';
|
|
130
|
+
return params
|
|
131
|
+
.map((p) => {
|
|
132
|
+
const defaultOrRequired =
|
|
133
|
+
p.default !== undefined ? `default: ${JSON.stringify(p.default)}` : 'required';
|
|
134
|
+
return `${p.name} (${p.type}, ${defaultOrRequired})`;
|
|
135
|
+
})
|
|
136
|
+
.join(', ');
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Build the platform-specific command that registers the MCP server.
|
|
141
|
+
* Returns null for platforms that require manual config editing (claude-desktop).
|
|
142
|
+
*/
|
|
143
|
+
export function buildRegistrationCommand(opts: {
|
|
144
|
+
site: string;
|
|
145
|
+
platform: Platform;
|
|
146
|
+
imprintCommand: ImprintCommand;
|
|
147
|
+
env?: Record<string, string>;
|
|
148
|
+
}): string[] | null {
|
|
149
|
+
const { site, platform, imprintCommand: ic, env } = opts;
|
|
150
|
+
const toolName = `imprint-${site}`;
|
|
151
|
+
const imprintArgs = [ic.command, ...ic.args, 'mcp-server', site];
|
|
152
|
+
const envPairs = Object.entries(env ?? {}).map(([key, value]) => `${key}=${value}`);
|
|
153
|
+
|
|
154
|
+
switch (platform) {
|
|
155
|
+
case 'claude-code':
|
|
156
|
+
return [
|
|
157
|
+
'claude',
|
|
158
|
+
'mcp',
|
|
159
|
+
'add',
|
|
160
|
+
'--scope',
|
|
161
|
+
'user',
|
|
162
|
+
...envPairs.flatMap((pair) => ['-e', pair]),
|
|
163
|
+
toolName,
|
|
164
|
+
'--',
|
|
165
|
+
...imprintArgs,
|
|
166
|
+
];
|
|
167
|
+
case 'codex':
|
|
168
|
+
return [
|
|
169
|
+
'codex',
|
|
170
|
+
'mcp',
|
|
171
|
+
'add',
|
|
172
|
+
...envPairs.flatMap((pair) => ['--env', pair]),
|
|
173
|
+
toolName,
|
|
174
|
+
'--',
|
|
175
|
+
...imprintArgs,
|
|
176
|
+
];
|
|
177
|
+
case 'claude-desktop':
|
|
178
|
+
return null;
|
|
179
|
+
case 'openclaw':
|
|
180
|
+
return null;
|
|
181
|
+
case 'hermes':
|
|
182
|
+
return null;
|
|
183
|
+
default: {
|
|
184
|
+
const _exhaustive: never = platform;
|
|
185
|
+
throw new Error(`Unknown platform: ${_exhaustive}`);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
export function buildUnregistrationCommand(opts: { site: string; platform: Platform }):
|
|
191
|
+
| string[]
|
|
192
|
+
| null {
|
|
193
|
+
const toolName = `imprint-${opts.site}`;
|
|
194
|
+
|
|
195
|
+
switch (opts.platform) {
|
|
196
|
+
case 'claude-code':
|
|
197
|
+
return ['claude', 'mcp', 'remove', '--scope', 'user', toolName];
|
|
198
|
+
case 'codex':
|
|
199
|
+
return ['codex', 'mcp', 'remove', toolName];
|
|
200
|
+
case 'claude-desktop':
|
|
201
|
+
return null;
|
|
202
|
+
case 'openclaw':
|
|
203
|
+
return null;
|
|
204
|
+
case 'hermes':
|
|
205
|
+
return null;
|
|
206
|
+
default: {
|
|
207
|
+
const _exhaustive: never = opts.platform;
|
|
208
|
+
throw new Error(`Unknown platform: ${_exhaustive}`);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function buildMcpServerConfig(opts: {
|
|
214
|
+
site: string;
|
|
215
|
+
imprintCommand: ImprintCommand;
|
|
216
|
+
env?: Record<string, string>;
|
|
217
|
+
}): McpServerConfig {
|
|
218
|
+
const { site, imprintCommand, env } = opts;
|
|
219
|
+
return {
|
|
220
|
+
name: `imprint-${site}`,
|
|
221
|
+
command: imprintCommand.command,
|
|
222
|
+
args: [...imprintCommand.args, 'mcp-server', site],
|
|
223
|
+
...(env && Object.keys(env).length > 0 ? { env } : {}),
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
export function shellQuote(value: string): string {
|
|
228
|
+
if (/^[A-Za-z0-9_./:=@+-]+$/.test(value)) return value;
|
|
229
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Generate inline SKILL.md for OpenClaw or Hermes — a single markdown file
|
|
234
|
+
* with frontmatter, workflow JSON, playbook YAML (if present), parameter
|
|
235
|
+
* table, and platform-specific config snippet.
|
|
236
|
+
*/
|
|
237
|
+
export function generateSkillMd(opts: {
|
|
238
|
+
site: string;
|
|
239
|
+
workflow: Workflow;
|
|
240
|
+
workflows?: Workflow[];
|
|
241
|
+
playbook?: Playbook;
|
|
242
|
+
playbooks?: Playbook[];
|
|
243
|
+
cronConfig?: CronConfig;
|
|
244
|
+
platform: 'openclaw' | 'hermes';
|
|
245
|
+
}): string {
|
|
246
|
+
const { site, workflow, workflows, playbook, playbooks, cronConfig, platform } = opts;
|
|
247
|
+
const workflowList = workflows && workflows.length > 0 ? workflows : [workflow];
|
|
248
|
+
const playbookList = playbooks && playbooks.length > 0 ? playbooks : playbook ? [playbook] : [];
|
|
249
|
+
const primaryWorkflow = workflowList[0] ?? workflow;
|
|
250
|
+
const toolName = `imprint-${site}`;
|
|
251
|
+
const description =
|
|
252
|
+
workflowList.length === 1
|
|
253
|
+
? primaryWorkflow.intent.description
|
|
254
|
+
: `${workflowList.length} Imprint tools for ${site}: ${workflowList.map((w) => w.toolName).join(', ')}`;
|
|
255
|
+
|
|
256
|
+
const frontmatter = `---
|
|
257
|
+
name: ${toolName}
|
|
258
|
+
description: ${description}
|
|
259
|
+
version: 1.0.0
|
|
260
|
+
metadata:
|
|
261
|
+
${platform}:
|
|
262
|
+
tags: [automation, imprint]
|
|
263
|
+
category: workflow
|
|
264
|
+
---`;
|
|
265
|
+
|
|
266
|
+
const contextBlock =
|
|
267
|
+
workflowList.length === 1 && primaryWorkflow.intent.userSaid !== undefined
|
|
268
|
+
? `\nRecording context: ${primaryWorkflow.intent.userSaid}\n`
|
|
269
|
+
: '';
|
|
270
|
+
|
|
271
|
+
// Generate platform-specific config snippet.
|
|
272
|
+
const imprintCommand = detectImprintCommand();
|
|
273
|
+
const configSnippet = generatePasteSnippet({
|
|
274
|
+
site,
|
|
275
|
+
workflow: primaryWorkflow,
|
|
276
|
+
workflows: workflowList,
|
|
277
|
+
platform,
|
|
278
|
+
imprintCommand,
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
// Workflow JSON block.
|
|
282
|
+
const workflowBlock =
|
|
283
|
+
workflowList.length === 1
|
|
284
|
+
? `## Workflow (API replay)
|
|
285
|
+
|
|
286
|
+
\`\`\`json
|
|
287
|
+
${JSON.stringify(primaryWorkflow, null, 2)}
|
|
288
|
+
\`\`\``
|
|
289
|
+
: `## Workflows (API replay)
|
|
290
|
+
|
|
291
|
+
${workflowList
|
|
292
|
+
.map(
|
|
293
|
+
(w) => `### ${w.toolName}
|
|
294
|
+
|
|
295
|
+
\`\`\`json
|
|
296
|
+
${JSON.stringify(w, null, 2)}
|
|
297
|
+
\`\`\``,
|
|
298
|
+
)
|
|
299
|
+
.join('\n\n')}`;
|
|
300
|
+
|
|
301
|
+
// Playbook YAML block (optional).
|
|
302
|
+
let playbookBlock = '';
|
|
303
|
+
if (playbookList.length === 1 && playbookList[0] !== undefined) {
|
|
304
|
+
const playbookYaml = yamlStringify(playbookList[0], { lineWidth: 0 });
|
|
305
|
+
playbookBlock = `\n## Playbook (DOM replay fallback)
|
|
306
|
+
|
|
307
|
+
\`\`\`yaml
|
|
308
|
+
${playbookYaml.trim()}
|
|
309
|
+
\`\`\``;
|
|
310
|
+
} else if (playbookList.length > 1) {
|
|
311
|
+
playbookBlock = `\n## Playbooks (DOM replay fallbacks)
|
|
312
|
+
|
|
313
|
+
${playbookList
|
|
314
|
+
.map(
|
|
315
|
+
(p) => `### ${p.toolName}
|
|
316
|
+
|
|
317
|
+
\`\`\`yaml
|
|
318
|
+
${yamlStringify(p, { lineWidth: 0 }).trim()}
|
|
319
|
+
\`\`\``,
|
|
320
|
+
)
|
|
321
|
+
.join('\n\n')}`;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Parameter table.
|
|
325
|
+
let paramTableBlock = '## Parameters\n\n';
|
|
326
|
+
if (workflowList.length === 1 && primaryWorkflow.parameters.length === 0) {
|
|
327
|
+
paramTableBlock += 'None.\n';
|
|
328
|
+
} else if (workflowList.length === 1) {
|
|
329
|
+
paramTableBlock += '| Name | Type | Default | Description |\n';
|
|
330
|
+
paramTableBlock += '|------|------|---------|-------------|\n';
|
|
331
|
+
for (const p of primaryWorkflow.parameters) {
|
|
332
|
+
const defaultVal = p.default !== undefined ? JSON.stringify(p.default) : 'required';
|
|
333
|
+
paramTableBlock += `| ${p.name} | ${p.type} | ${defaultVal} | ${p.description} |\n`;
|
|
334
|
+
}
|
|
335
|
+
} else {
|
|
336
|
+
for (const w of workflowList) {
|
|
337
|
+
paramTableBlock += `### ${w.toolName}\n\n`;
|
|
338
|
+
if (w.parameters.length === 0) {
|
|
339
|
+
paramTableBlock += 'None.\n\n';
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
paramTableBlock += '| Name | Type | Default | Description |\n';
|
|
343
|
+
paramTableBlock += '|------|------|---------|-------------|\n';
|
|
344
|
+
for (const p of w.parameters) {
|
|
345
|
+
const defaultVal = p.default !== undefined ? JSON.stringify(p.default) : 'required';
|
|
346
|
+
paramTableBlock += `| ${p.name} | ${p.type} | ${defaultVal} | ${p.description} |\n`;
|
|
347
|
+
}
|
|
348
|
+
paramTableBlock += '\n';
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Backend ladder explanation.
|
|
353
|
+
const backendBlock = `## Backend Ladder
|
|
354
|
+
|
|
355
|
+
The MCP server automatically escalates from fetch API replay to gated fetch-bootstrap when browser-minted state is declared, then stealth-fetch for bot-defense state, then playbook for full DOM replay.
|
|
356
|
+
Bot detection is handled transparently.`;
|
|
357
|
+
|
|
358
|
+
// Scheduling block (optional).
|
|
359
|
+
let scheduleBlock = '';
|
|
360
|
+
if (cronConfig !== undefined) {
|
|
361
|
+
scheduleBlock = `\n## Scheduling
|
|
362
|
+
|
|
363
|
+
Imprint cron schedule: \`${cronConfig.schedule}\``;
|
|
364
|
+
if (platform === 'hermes') {
|
|
365
|
+
scheduleBlock += `\nHermes equivalent: \`/cron add "${cronConfig.schedule}" "Run ${toolName} ..."\``;
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
return `${frontmatter}
|
|
370
|
+
|
|
371
|
+
# ${toolName}
|
|
372
|
+
|
|
373
|
+
${description}${contextBlock}
|
|
374
|
+
|
|
375
|
+
## MCP Integration
|
|
376
|
+
|
|
377
|
+
${configSnippet}
|
|
378
|
+
|
|
379
|
+
${workflowBlock}${playbookBlock}
|
|
380
|
+
|
|
381
|
+
${paramTableBlock}
|
|
382
|
+
|
|
383
|
+
${backendBlock}${scheduleBlock}
|
|
384
|
+
`;
|
|
385
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JSON dot-path walker.
|
|
3
|
+
*
|
|
4
|
+
* Paths WITHOUT `[]` navigate to a single value and return it as-is:
|
|
5
|
+
* extractAt({a:{b:{c:42}}}, "a.b") → {c:42}
|
|
6
|
+
* extractAt({data:{results:[1,2]}}, "data.results") → [1,2]
|
|
7
|
+
*
|
|
8
|
+
* Paths WITH `[]` iterate arrays and collect leaf values:
|
|
9
|
+
* extractAt({a:[{b:1},{b:2}]}, "a[].b") → [1, 2]
|
|
10
|
+
* extractAt({x:[{y:[{z:5}]}]}, "x[].y[].z") → [5]
|
|
11
|
+
*
|
|
12
|
+
* Throws on shape mismatches (non-array where `[]` expected, primitive
|
|
13
|
+
* where descent expected) so misconfigured paths fail loudly.
|
|
14
|
+
*/
|
|
15
|
+
export function extractNumbers(data: unknown, path: string): number[] {
|
|
16
|
+
const result = extractAt(data, path);
|
|
17
|
+
if (Array.isArray(result)) {
|
|
18
|
+
const nums: number[] = [];
|
|
19
|
+
for (const v of result) {
|
|
20
|
+
if (typeof v === 'number' && Number.isFinite(v)) nums.push(v);
|
|
21
|
+
else if (typeof v === 'string') {
|
|
22
|
+
const n = Number(v);
|
|
23
|
+
if (Number.isFinite(n)) nums.push(n);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return nums;
|
|
27
|
+
}
|
|
28
|
+
if (typeof result === 'number' && Number.isFinite(result)) return [result];
|
|
29
|
+
if (typeof result === 'string') {
|
|
30
|
+
const n = Number(result);
|
|
31
|
+
if (Number.isFinite(n)) return [n];
|
|
32
|
+
}
|
|
33
|
+
return [];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function extractAt(data: unknown, path: string): unknown {
|
|
37
|
+
if (path.length === 0) throw new Error('extractAt: empty path');
|
|
38
|
+
const segments = parsePath(path);
|
|
39
|
+
const hasIterate = segments.some((s) => s.iterate);
|
|
40
|
+
if (!hasIterate) {
|
|
41
|
+
return navigatePath(data, segments);
|
|
42
|
+
}
|
|
43
|
+
const out: unknown[] = [];
|
|
44
|
+
walkCollect(data, segments, 0, out);
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
interface PathSegment {
|
|
49
|
+
key: string;
|
|
50
|
+
iterate: boolean;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function parsePath(path: string): PathSegment[] {
|
|
54
|
+
return path.split('.').map((raw) => {
|
|
55
|
+
if (raw.endsWith('[]')) {
|
|
56
|
+
return { key: raw.slice(0, -2), iterate: true };
|
|
57
|
+
}
|
|
58
|
+
return { key: raw, iterate: false };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function navigatePath(node: unknown, segs: PathSegment[]): unknown {
|
|
63
|
+
let current = node;
|
|
64
|
+
for (const seg of segs) {
|
|
65
|
+
if (typeof current !== 'object' || current === null) {
|
|
66
|
+
throw new Error(
|
|
67
|
+
`extractAt: expected object/array at segment "${seg.key}", got ${current === null ? 'null' : typeof current}`,
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
current = (current as Record<string, unknown>)[seg.key];
|
|
71
|
+
if (current === undefined) return undefined;
|
|
72
|
+
}
|
|
73
|
+
return current;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function walkCollect(node: unknown, segs: PathSegment[], i: number, out: unknown[]): void {
|
|
77
|
+
if (i === segs.length) {
|
|
78
|
+
if (node !== null && node !== undefined) {
|
|
79
|
+
out.push(node);
|
|
80
|
+
}
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const seg = segs[i];
|
|
84
|
+
if (!seg) return;
|
|
85
|
+
if (typeof node !== 'object' || node === null) {
|
|
86
|
+
throw new Error(
|
|
87
|
+
`extractAt: expected object/array at segment "${seg.key}", got ${node === null ? 'null' : typeof node}`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
const next = (node as Record<string, unknown>)[seg.key];
|
|
91
|
+
if (next === undefined) return;
|
|
92
|
+
if (seg.iterate) {
|
|
93
|
+
if (!Array.isArray(next)) {
|
|
94
|
+
throw new Error(`extractAt: "${seg.key}[]" expected an array, got ${typeof next}`);
|
|
95
|
+
}
|
|
96
|
+
for (const item of next) walkCollect(item, segs, i + 1, out);
|
|
97
|
+
} else {
|
|
98
|
+
walkCollect(next, segs, i + 1, out);
|
|
99
|
+
}
|
|
100
|
+
}
|