capman 0.4.0 → 0.4.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +222 -106
- package/bin/capman.js +420 -91
- package/dist/cjs/cache.d.ts +16 -8
- package/dist/cjs/cache.d.ts.map +1 -1
- package/dist/cjs/cache.js +45 -31
- package/dist/cjs/cache.js.map +1 -1
- package/dist/cjs/engine.d.ts +2 -2
- package/dist/cjs/engine.d.ts.map +1 -1
- package/dist/cjs/engine.js +5 -3
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/generator.js +1 -1
- package/dist/cjs/generator.js.map +1 -1
- package/dist/cjs/index.d.ts +13 -10
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +23 -37
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/learning.d.ts.map +1 -1
- package/dist/cjs/learning.js +10 -0
- package/dist/cjs/learning.js.map +1 -1
- package/dist/cjs/matcher.d.ts.map +1 -1
- package/dist/cjs/matcher.js +18 -9
- package/dist/cjs/matcher.js.map +1 -1
- package/dist/cjs/parser.d.ts +11 -0
- package/dist/cjs/parser.d.ts.map +1 -0
- package/dist/cjs/parser.js +304 -0
- package/dist/cjs/parser.js.map +1 -0
- package/dist/cjs/resolver.d.ts.map +1 -1
- package/dist/cjs/resolver.js +31 -25
- package/dist/cjs/resolver.js.map +1 -1
- package/dist/cjs/types.d.ts +2 -2
- package/dist/cjs/types.d.ts.map +1 -1
- package/dist/cjs/version.d.ts +1 -1
- package/dist/cjs/version.js +1 -1
- package/dist/esm/cache.js +44 -32
- package/dist/esm/engine.js +5 -3
- package/dist/esm/generator.js +1 -1
- package/dist/esm/index.js +20 -37
- package/dist/esm/learning.js +10 -0
- package/dist/esm/matcher.js +18 -9
- package/dist/esm/parser.js +267 -0
- package/dist/esm/resolver.js +31 -25
- package/dist/esm/version.js +1 -1
- package/package.json +1 -1
package/bin/capman.js
CHANGED
|
@@ -65,7 +65,9 @@ function cmdHelp() {
|
|
|
65
65
|
console.log()
|
|
66
66
|
console.log(`${c.bold} Commands:${c.reset}`)
|
|
67
67
|
console.log(` ${c.teal}init${c.reset} Create a starter capman.config.js`)
|
|
68
|
-
console.log(` ${c.teal}generate${c.reset}
|
|
68
|
+
console.log(` ${c.teal}generate${c.reset} Generate manifest from capman.config.js`)
|
|
69
|
+
console.log(` ${c.teal}generate --from <path|url>${c.reset} Generate from OpenAPI/Swagger spec`)
|
|
70
|
+
console.log(` ${c.teal}generate --ai${c.reset} Generate manifest using AI (needs API key)`)
|
|
69
71
|
console.log(` ${c.teal}validate${c.reset} Validate an existing manifest.json`)
|
|
70
72
|
console.log(` ${c.teal}inspect${c.reset} Print all capabilities in manifest`)
|
|
71
73
|
console.log(` ${c.teal}demo${c.reset} Run a live demo with sample queries`)
|
|
@@ -93,13 +95,153 @@ function cmdInit() {
|
|
|
93
95
|
console.log(`\n node bin/capman.js generate\n`)
|
|
94
96
|
}
|
|
95
97
|
|
|
96
|
-
function cmdGenerate() {
|
|
98
|
+
async function cmdGenerate() {
|
|
97
99
|
header()
|
|
98
|
-
const {
|
|
100
|
+
const { generate, loadConfig, writeManifest, validate, generateStarterConfig, parseOpenAPI } = requireSrc()
|
|
101
|
+
|
|
102
|
+
const fromFlag = getFlag('--from')
|
|
103
|
+
const aiFlag = flags.includes('--ai')
|
|
104
|
+
const outPath = getFlag('--out') ?? 'manifest.json'
|
|
105
|
+
const configOut = getFlag('--config-out') ?? 'capman.config.js'
|
|
106
|
+
|
|
107
|
+
// ── Path 1: OpenAPI parser ─────────────────────────────────────────────────
|
|
108
|
+
if (fromFlag) {
|
|
109
|
+
log.info(`Parsing OpenAPI spec: ${fromFlag}`)
|
|
110
|
+
let result
|
|
111
|
+
try {
|
|
112
|
+
result = await parseOpenAPI(fromFlag)
|
|
113
|
+
} catch (e) {
|
|
114
|
+
log.error(e.message)
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
99
117
|
|
|
100
|
-
|
|
101
|
-
|
|
118
|
+
const { config, stats } = result
|
|
119
|
+
|
|
120
|
+
log.success(`Parsed ${stats.total} capabilities from spec`)
|
|
121
|
+
if (stats.skipped > 0) {
|
|
122
|
+
log.info(`Skipped ${stats.skipped} operations (insufficient info)`)
|
|
123
|
+
}
|
|
124
|
+
if (stats.warnings.length) {
|
|
125
|
+
stats.warnings.forEach(w => log.warn(w))
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Write capman.config.js
|
|
129
|
+
const configContent = `// Auto-generated by capman from OpenAPI spec
|
|
130
|
+
// Review and adjust before committing
|
|
131
|
+
|
|
132
|
+
module.exports = ${JSON.stringify(config, null, 2)
|
|
133
|
+
.replace(/"([^"]+)":/g, '$1:')
|
|
134
|
+
.replace(/"/g, "'")
|
|
135
|
+
}
|
|
136
|
+
`
|
|
137
|
+
const fs = require('fs')
|
|
138
|
+
fs.writeFileSync(configOut, configContent)
|
|
139
|
+
log.success(`Config written to ${configOut}`)
|
|
140
|
+
|
|
141
|
+
// Also generate manifest.json
|
|
142
|
+
try {
|
|
143
|
+
const manifest = generate(config)
|
|
144
|
+
writeManifest(manifest, outPath)
|
|
145
|
+
log.success(`Manifest written to ${outPath}`)
|
|
146
|
+
log.info(`${manifest.capabilities.length} capabilities registered`)
|
|
147
|
+
} catch (e) {
|
|
148
|
+
log.warn(`Could not generate manifest: ${e.message}`)
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
console.log()
|
|
152
|
+
console.log(` ${c.gray}Next steps:${c.reset}`)
|
|
153
|
+
console.log(` 1. Review ${c.teal}${configOut}${c.reset} — adjust descriptions and examples`)
|
|
154
|
+
console.log(` 2. Run ${c.teal}npx capman validate${c.reset} to check your manifest`)
|
|
155
|
+
console.log(` 3. Run ${c.teal}npx capman demo${c.reset} to see it in action`)
|
|
156
|
+
console.log()
|
|
157
|
+
return
|
|
158
|
+
}
|
|
102
159
|
|
|
160
|
+
// ── Path 2: LLM-assisted ───────────────────────────────────────────────────
|
|
161
|
+
if (aiFlag) {
|
|
162
|
+
log.info('LLM-assisted manifest generation')
|
|
163
|
+
console.log()
|
|
164
|
+
|
|
165
|
+
const readline = require('readline')
|
|
166
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout })
|
|
167
|
+
|
|
168
|
+
const ask = (q) => new Promise(resolve => rl.question(q, resolve))
|
|
169
|
+
|
|
170
|
+
console.log(` ${c.gray}Describe your app and its main capabilities.${c.reset}`)
|
|
171
|
+
console.log(` ${c.gray}Example: "A CRM app. Users can create contacts, log calls,`)
|
|
172
|
+
console.log(` view pipeline stages. Admins can manage teams and billing."${c.reset}\n`)
|
|
173
|
+
|
|
174
|
+
const description = await ask(` ${c.teal}>${c.reset} `)
|
|
175
|
+
rl.close()
|
|
176
|
+
|
|
177
|
+
if (!description.trim()) {
|
|
178
|
+
log.error('No description provided.')
|
|
179
|
+
process.exit(1)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
// Detect LLM provider from env
|
|
183
|
+
const apiKey = process.env.ANTHROPIC_API_KEY ?? process.env.OPENAI_API_KEY ?? process.env.OPENROUTER_API_KEY
|
|
184
|
+
const provider =
|
|
185
|
+
process.env.ANTHROPIC_API_KEY ? 'anthropic' :
|
|
186
|
+
process.env.OPENAI_API_KEY ? 'openai' :
|
|
187
|
+
process.env.OPENROUTER_API_KEY ? 'openrouter' : null
|
|
188
|
+
|
|
189
|
+
if (!apiKey || !provider) {
|
|
190
|
+
log.error('No LLM API key found.')
|
|
191
|
+
console.log(` Set one of: ${c.teal}ANTHROPIC_API_KEY${c.reset}, ${c.teal}OPENAI_API_KEY${c.reset}, or ${c.teal}OPENROUTER_API_KEY${c.reset}`)
|
|
192
|
+
process.exit(1)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
log.info(`Using ${provider} to generate manifest...`)
|
|
196
|
+
|
|
197
|
+
const prompt = buildAIPrompt(description)
|
|
198
|
+
let raw
|
|
199
|
+
try {
|
|
200
|
+
raw = await callLLM(provider, apiKey, prompt)
|
|
201
|
+
} catch (e) {
|
|
202
|
+
log.error(`LLM call failed: ${e.message}`)
|
|
203
|
+
process.exit(1)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
// Parse response
|
|
207
|
+
let config
|
|
208
|
+
try {
|
|
209
|
+
const clean = raw.replace(/```json|```javascript|```js|```/g, '').trim()
|
|
210
|
+
const match = clean.match(/module\.exports\s*=\s*(\{[\s\S]+\})/)
|
|
211
|
+
const jsonStr = match ? match[1] : clean
|
|
212
|
+
config = JSON.parse(jsonStr)
|
|
213
|
+
} catch (e) {
|
|
214
|
+
log.error('Could not parse LLM response. Try again or use --from with an OpenAPI spec.')
|
|
215
|
+
console.log(`\n Raw response:\n${raw.slice(0, 500)}`)
|
|
216
|
+
process.exit(1)
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
// Write config
|
|
220
|
+
const fs = require('fs')
|
|
221
|
+
const configContent = `// Auto-generated by capman AI\n// Review before committing\n\nmodule.exports = ${JSON.stringify(config, null, 2)}\n`
|
|
222
|
+
fs.writeFileSync(configOut, configContent)
|
|
223
|
+
log.success(`Config written to ${configOut}`)
|
|
224
|
+
|
|
225
|
+
// Generate manifest
|
|
226
|
+
try {
|
|
227
|
+
const manifest = generate(config)
|
|
228
|
+
writeManifest(manifest, outPath)
|
|
229
|
+
log.success(`Manifest written to ${outPath}`)
|
|
230
|
+
log.info(`${manifest.capabilities.length} capabilities generated`)
|
|
231
|
+
} catch (e) {
|
|
232
|
+
log.warn(`Manifest generation failed — review your config: ${e.message}`)
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
console.log()
|
|
236
|
+
console.log(` ${c.gray}Review ${configOut} — the AI may have missed things.${c.reset}`)
|
|
237
|
+
console.log(` ${c.teal}npx capman validate${c.reset} ${c.gray}→ check for errors${c.reset}`)
|
|
238
|
+
console.log(` ${c.teal}npx capman inspect${c.reset} ${c.gray}→ see all capabilities${c.reset}`)
|
|
239
|
+
console.log()
|
|
240
|
+
return
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ── Path 3: Manual (existing behaviour) ───────────────────────────────────
|
|
244
|
+
const configPath = getFlag('--config')
|
|
103
245
|
log.info('Loading config...')
|
|
104
246
|
let config
|
|
105
247
|
try {
|
|
@@ -109,25 +251,143 @@ function cmdGenerate() {
|
|
|
109
251
|
process.exit(1)
|
|
110
252
|
}
|
|
111
253
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const result = validate(manifest)
|
|
254
|
+
const outManifestPath = getFlag('--out') ?? 'manifest.json'
|
|
255
|
+
log.info(`Generating manifest for ${config.app}...`)
|
|
115
256
|
|
|
116
|
-
|
|
117
|
-
|
|
257
|
+
let manifest
|
|
258
|
+
try {
|
|
259
|
+
manifest = generate(config)
|
|
260
|
+
} catch (e) {
|
|
261
|
+
log.error(`Generation failed: ${e.message}`)
|
|
262
|
+
process.exit(1)
|
|
263
|
+
}
|
|
118
264
|
|
|
119
|
-
|
|
120
|
-
|
|
265
|
+
const validation = validate(manifest)
|
|
266
|
+
if (!validation.valid) {
|
|
267
|
+
validation.errors.forEach(e => log.error(e))
|
|
121
268
|
process.exit(1)
|
|
122
269
|
}
|
|
270
|
+
if (validation.warnings.length) {
|
|
271
|
+
validation.warnings.forEach(w => log.warn(w))
|
|
272
|
+
}
|
|
123
273
|
|
|
124
|
-
|
|
125
|
-
log.
|
|
126
|
-
log.success(`Manifest written to ${c.bold}${written}${c.reset}`)
|
|
274
|
+
writeManifest(manifest, outManifestPath)
|
|
275
|
+
log.success(`Manifest written to ${outManifestPath}`)
|
|
127
276
|
log.info(`${manifest.capabilities.length} capabilities registered`)
|
|
128
277
|
console.log()
|
|
129
278
|
}
|
|
130
279
|
|
|
280
|
+
// ─── AI prompt builder ─────────────────────────────────────────────────────────
|
|
281
|
+
|
|
282
|
+
function buildAIPrompt(description) {
|
|
283
|
+
return `You are helping generate a capman capability manifest config.
|
|
284
|
+
|
|
285
|
+
The user's app description:
|
|
286
|
+
${JSON.stringify({ app_description: description })}
|
|
287
|
+
|
|
288
|
+
Generate a valid capman config as a JSON object (not module.exports, just the raw JSON object).
|
|
289
|
+
|
|
290
|
+
The config must follow this exact structure:
|
|
291
|
+
{
|
|
292
|
+
"app": "app-name-in-kebab-case",
|
|
293
|
+
"baseUrl": "https://api.your-app.com",
|
|
294
|
+
"capabilities": [
|
|
295
|
+
{
|
|
296
|
+
"id": "snake_case_id",
|
|
297
|
+
"name": "Human readable name",
|
|
298
|
+
"description": "What this capability does (min 10 chars)",
|
|
299
|
+
"examples": ["Example query 1", "Example query 2", "Example query 3"],
|
|
300
|
+
"params": [
|
|
301
|
+
{
|
|
302
|
+
"name": "param_name",
|
|
303
|
+
"description": "What this param is",
|
|
304
|
+
"required": true,
|
|
305
|
+
"source": "user_query"
|
|
306
|
+
}
|
|
307
|
+
],
|
|
308
|
+
"returns": ["resource_name"],
|
|
309
|
+
"resolver": {
|
|
310
|
+
"type": "api",
|
|
311
|
+
"endpoints": [{ "method": "GET", "path": "/resource/{param_name}" }]
|
|
312
|
+
},
|
|
313
|
+
"privacy": { "level": "public" }
|
|
314
|
+
}
|
|
315
|
+
]
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
Rules:
|
|
319
|
+
- privacy.level must be "public", "user_owned", or "admin"
|
|
320
|
+
- resolver.type must be "api", "nav", or "hybrid"
|
|
321
|
+
- method must be "GET", "POST", "PUT", "PATCH", or "DELETE"
|
|
322
|
+
- source must be "user_query", "session", "context", or "static"
|
|
323
|
+
- Generate 3-8 capabilities based on the description
|
|
324
|
+
- Each capability needs at least 2 examples
|
|
325
|
+
- Respond ONLY with the raw JSON object — no markdown, no explanation`
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
// ─── LLM caller ───────────────────────────────────────────────────────────────
|
|
329
|
+
|
|
330
|
+
async function callLLM(provider, apiKey, prompt) {
|
|
331
|
+
if (provider === 'anthropic') {
|
|
332
|
+
const res = await fetch('https://api.anthropic.com/v1/messages', {
|
|
333
|
+
method: 'POST',
|
|
334
|
+
headers: {
|
|
335
|
+
'Content-Type': 'application/json',
|
|
336
|
+
'x-api-key': apiKey,
|
|
337
|
+
'anthropic-version': '2023-06-01',
|
|
338
|
+
},
|
|
339
|
+
body: JSON.stringify({
|
|
340
|
+
model: 'claude-sonnet-4-20250514',
|
|
341
|
+
max_tokens: 4000,
|
|
342
|
+
messages: [{ role: 'user', content: prompt }],
|
|
343
|
+
}),
|
|
344
|
+
})
|
|
345
|
+
const data = await res.json()
|
|
346
|
+
if (!res.ok) throw new Error(data.error?.message ?? res.statusText)
|
|
347
|
+
return data.content[0].text
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (provider === 'openai') {
|
|
351
|
+
const res = await fetch('https://api.openai.com/v1/chat/completions', {
|
|
352
|
+
method: 'POST',
|
|
353
|
+
headers: {
|
|
354
|
+
'Content-Type': 'application/json',
|
|
355
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
356
|
+
},
|
|
357
|
+
body: JSON.stringify({
|
|
358
|
+
model: 'gpt-4o-mini',
|
|
359
|
+
max_tokens: 4000,
|
|
360
|
+
messages: [{ role: 'user', content: prompt }],
|
|
361
|
+
}),
|
|
362
|
+
})
|
|
363
|
+
const data = await res.json()
|
|
364
|
+
if (!res.ok) throw new Error(data.error?.message ?? res.statusText)
|
|
365
|
+
return data.choices[0].message.content
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (provider === 'openrouter') {
|
|
369
|
+
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
|
370
|
+
method: 'POST',
|
|
371
|
+
headers: {
|
|
372
|
+
'Content-Type': 'application/json',
|
|
373
|
+
'Authorization': `Bearer ${apiKey}`,
|
|
374
|
+
'HTTP-Referer': 'https://github.com/Hobbydefiningdoctory/capman',
|
|
375
|
+
},
|
|
376
|
+
body: JSON.stringify({
|
|
377
|
+
model: 'openai/gpt-oss-120b:free',
|
|
378
|
+
max_tokens: 4000,
|
|
379
|
+
messages: [{ role: 'user', content: prompt }],
|
|
380
|
+
provider: { order: ['open-inference'], allow_fallbacks: true },
|
|
381
|
+
}),
|
|
382
|
+
})
|
|
383
|
+
const data = await res.json()
|
|
384
|
+
if (!res.ok) throw new Error(data.error?.message ?? res.statusText)
|
|
385
|
+
return data.choices[0].message.content
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
throw new Error(`Unknown provider: ${provider}`)
|
|
389
|
+
}
|
|
390
|
+
|
|
131
391
|
function cmdValidate() {
|
|
132
392
|
header()
|
|
133
393
|
const { readManifest, validate } = requireSrc()
|
|
@@ -189,12 +449,13 @@ function cmdInspect() {
|
|
|
189
449
|
|
|
190
450
|
function cmdDemo() {
|
|
191
451
|
header()
|
|
192
|
-
const { generate, match
|
|
452
|
+
const { generate, match } = requireSrc()
|
|
193
453
|
|
|
194
|
-
|
|
454
|
+
// ── Title ──────────────────────────────────────────────────────────────────
|
|
455
|
+
console.log(`${c.bold} Turn natural language into reliable, explainable backend actions.${c.reset}`)
|
|
195
456
|
console.log(`${c.gray} ─────────────────────────────────────────${c.reset}\n`)
|
|
196
457
|
|
|
197
|
-
//
|
|
458
|
+
// ── Manifest ───────────────────────────────────────────────────────────────
|
|
198
459
|
const config = {
|
|
199
460
|
app: 'demo-store',
|
|
200
461
|
baseUrl: 'https://api.demo-store.com',
|
|
@@ -206,9 +467,8 @@ function cmdDemo() {
|
|
|
206
467
|
examples: [
|
|
207
468
|
'Is the blue jacket in stock?',
|
|
208
469
|
'Check stock for blue jacket',
|
|
209
|
-
'
|
|
470
|
+
'Check availability for blue jacket',
|
|
210
471
|
'Product availability for jacket',
|
|
211
|
-
'Is jacket available?',
|
|
212
472
|
],
|
|
213
473
|
params: [
|
|
214
474
|
{ name: 'product', description: 'Product name or ID', required: true, source: 'user_query' }
|
|
@@ -220,11 +480,11 @@ function cmdDemo() {
|
|
|
220
480
|
{
|
|
221
481
|
id: 'get_order_status',
|
|
222
482
|
name: 'Get order status',
|
|
223
|
-
description: 'Retrieve the current status and tracking info for an order.',
|
|
483
|
+
description: 'Retrieve the current status and tracking info for an order by order ID.',
|
|
224
484
|
examples: [
|
|
225
485
|
'Where is my order?',
|
|
226
486
|
'Track order 1234',
|
|
227
|
-
'What is the status of my
|
|
487
|
+
'What is the status of my purchase?',
|
|
228
488
|
],
|
|
229
489
|
params: [
|
|
230
490
|
{ name: 'order_id', description: 'Order ID', required: true, source: 'user_query' }
|
|
@@ -242,7 +502,6 @@ function cmdDemo() {
|
|
|
242
502
|
'Open cart',
|
|
243
503
|
'Go to checkout',
|
|
244
504
|
'Navigate to account',
|
|
245
|
-
'Show homepage',
|
|
246
505
|
],
|
|
247
506
|
params: [
|
|
248
507
|
{ name: 'destination', description: 'Target screen', required: true, source: 'user_query' }
|
|
@@ -256,71 +515,138 @@ function cmdDemo() {
|
|
|
256
515
|
|
|
257
516
|
const manifest = generate(config)
|
|
258
517
|
|
|
518
|
+
console.log(`${c.gray} app:${c.reset} ${c.bold}${config.app}${c.reset}`)
|
|
519
|
+
console.log(`${c.gray} capabilities:${c.reset} ${manifest.capabilities.length}`)
|
|
520
|
+
console.log(`${c.gray} matcher:${c.reset} keyword (no LLM, no API key needed)\n`)
|
|
521
|
+
|
|
522
|
+
// ── Queries ────────────────────────────────────────────────────────────────
|
|
259
523
|
const queries = [
|
|
260
|
-
'Check availability for blue jacket',
|
|
261
|
-
'Track order 1234',
|
|
262
|
-
'Go to cart',
|
|
263
|
-
'Is the website down?',
|
|
524
|
+
{ text: 'Check availability for blue jacket', expectMatch: true },
|
|
525
|
+
{ text: 'Track order 1234', expectMatch: true },
|
|
526
|
+
{ text: 'Go to cart', expectMatch: true },
|
|
527
|
+
{ text: 'Is the website down?', expectMatch: false },
|
|
264
528
|
]
|
|
265
529
|
|
|
266
|
-
console.log(`${c.gray} App: ${c.reset}${c.bold}${config.app}${c.reset}`)
|
|
267
|
-
console.log(`${c.gray} Capabilities: ${c.reset}${manifest.capabilities.length}`)
|
|
268
|
-
console.log(`${c.gray} Mode: keyword matcher (no LLM required)\n${c.reset}`)
|
|
269
|
-
|
|
270
530
|
let passed = 0
|
|
271
531
|
let outOfScope = 0
|
|
272
532
|
|
|
273
|
-
for (const
|
|
274
|
-
const
|
|
275
|
-
const result = match(
|
|
276
|
-
const
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
533
|
+
for (const q of queries) {
|
|
534
|
+
const t0 = Date.now()
|
|
535
|
+
const result = match(q.text, manifest)
|
|
536
|
+
const ms = Date.now() - t0
|
|
537
|
+
|
|
538
|
+
console.log(`${c.gray} ────────────────────────────────────────${c.reset}`)
|
|
539
|
+
console.log()
|
|
540
|
+
|
|
541
|
+
// ── 1. QUERY ──────────────────────────────────────────────────────────
|
|
542
|
+
console.log(` ${c.bold}QUERY${c.reset}`)
|
|
543
|
+
console.log(` "${c.bold}${q.text}${c.reset}"\n`)
|
|
544
|
+
|
|
545
|
+
if (!result.capability) {
|
|
546
|
+
outOfScope++
|
|
547
|
+
|
|
548
|
+
// ── MATCH (out of scope) ─────────────────────────────────────────
|
|
549
|
+
console.log(` ${c.bold}MATCH${c.reset}`)
|
|
550
|
+
console.log(` ${c.yellow}○ OUT_OF_SCOPE${c.reset} — no capability handles this query\n`)
|
|
551
|
+
|
|
552
|
+
// ── EXECUTION ────────────────────────────────────────────────────
|
|
553
|
+
console.log(` ${c.bold}EXECUTION${c.reset}`)
|
|
554
|
+
console.log(` ${c.gray}[1] keyword_match no match ${ms}ms${c.reset}\n`)
|
|
555
|
+
|
|
556
|
+
// ── RESULT ───────────────────────────────────────────────────────
|
|
557
|
+
console.log(` ${c.bold}RESULT${c.reset}`)
|
|
558
|
+
console.log(` ${c.yellow}No action taken — query is outside manifest scope${c.reset}\n`)
|
|
559
|
+
|
|
560
|
+
// ── EXPLANATION ──────────────────────────────────────────────────
|
|
561
|
+
console.log(` ${c.bold}EXPLANATION${c.reset}`)
|
|
562
|
+
if (result.candidates.length) {
|
|
563
|
+
const best = result.candidates.sort((a, b) => b.score - a.score)[0]
|
|
564
|
+
console.log(` ${c.gray}Closest capability was "${best.capabilityId}" (${best.score}%) —`)
|
|
565
|
+
console.log(` below the 50% confidence threshold. Correctly rejected.${c.reset}`)
|
|
293
566
|
}
|
|
567
|
+
console.log()
|
|
568
|
+
continue
|
|
569
|
+
}
|
|
294
570
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
let dest = result.capability.resolver.destination
|
|
305
|
-
for (const [k, v] of Object.entries(result.extractedParams)) {
|
|
306
|
-
if (v) dest = dest.replace(`{${k}}`, v)
|
|
307
|
-
}
|
|
308
|
-
console.log(` ${c.gray}→ nav target:${c.reset} ${dest}`)
|
|
571
|
+
passed++
|
|
572
|
+
|
|
573
|
+
// Build API call or nav target
|
|
574
|
+
let actionLine = ''
|
|
575
|
+
if (result.capability.resolver.type === 'api') {
|
|
576
|
+
const endpoint = result.capability.resolver.endpoints[0]
|
|
577
|
+
let path = endpoint.path
|
|
578
|
+
for (const [k, v] of Object.entries(result.extractedParams)) {
|
|
579
|
+
if (v) path = path.replace(`{${k}}`, String(v))
|
|
309
580
|
}
|
|
581
|
+
actionLine = `${endpoint.method} ${config.baseUrl}${path}`
|
|
582
|
+
} else if (result.capability.resolver.type === 'nav') {
|
|
583
|
+
let dest = result.capability.resolver.destination
|
|
584
|
+
for (const [k, v] of Object.entries(result.extractedParams)) {
|
|
585
|
+
if (v) dest = dest.replace(`{${k}}`, String(v))
|
|
586
|
+
}
|
|
587
|
+
actionLine = `navigate → ${dest}`
|
|
588
|
+
}
|
|
310
589
|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
590
|
+
// Candidates
|
|
591
|
+
const sorted = [...result.candidates].sort((a, b) => b.score - a.score)
|
|
592
|
+
const winner = sorted[0]
|
|
593
|
+
const runners = sorted.slice(1).filter(r => r.score > 0)
|
|
594
|
+
|
|
595
|
+
// Extracted params
|
|
596
|
+
const paramEntries = Object.entries(result.extractedParams).filter(([, v]) => v !== null)
|
|
597
|
+
|
|
598
|
+
// ── 2. MATCH ──────────────────────────────────────────────────────────
|
|
599
|
+
console.log(` ${c.bold}MATCH${c.reset}`)
|
|
600
|
+
console.log(` ${c.green}✓ ${result.capability.id}${c.reset}`)
|
|
601
|
+
console.log(` ${c.gray}intent: ${c.reset}${result.intent}`)
|
|
602
|
+
console.log(` ${c.gray}confidence: ${c.reset}${c.bold}${result.confidence}%${c.reset}`)
|
|
603
|
+
console.log(` ${c.gray}privacy: ${c.reset}${result.capability.privacy.level}`)
|
|
604
|
+
if (paramEntries.length) {
|
|
605
|
+
const pStr = paramEntries.map(([k, v]) => `${k}=${v}`).join(', ')
|
|
606
|
+
console.log(` ${c.gray}params: ${c.reset}${pStr}`)
|
|
316
607
|
}
|
|
608
|
+
console.log()
|
|
609
|
+
|
|
610
|
+
// ── 3. EXECUTION ──────────────────────────────────────────────────────
|
|
611
|
+
console.log(` ${c.bold}EXECUTION${c.reset}`)
|
|
612
|
+
console.log(` ${c.gray}[1] cache_check miss 0ms${c.reset}`)
|
|
613
|
+
console.log(` ${c.gray}[2]${c.reset} keyword_match ${c.green}pass${c.reset} ${ms}ms confidence: ${result.confidence}%`)
|
|
614
|
+
console.log(` ${c.gray}[3] privacy_check pass 0ms level: ${result.capability.privacy.level}${c.reset}`)
|
|
615
|
+
console.log(` ${c.gray}[4] resolve pass ${ms}ms via ${result.capability.resolver.type}${c.reset}`)
|
|
616
|
+
console.log()
|
|
617
|
+
|
|
618
|
+
// ── 4. RESULT ─────────────────────────────────────────────────────────
|
|
619
|
+
console.log(` ${c.bold}RESULT${c.reset}`)
|
|
620
|
+
console.log(` ${c.green}${actionLine}${c.reset}`)
|
|
621
|
+
console.log()
|
|
622
|
+
|
|
623
|
+
// ── 5. EXPLANATION ────────────────────────────────────────────────────
|
|
624
|
+
console.log(` ${c.bold}EXPLANATION${c.reset}`)
|
|
625
|
+
console.log(` ${c.gray}Why "${winner.capabilityId}"?${c.reset}`)
|
|
626
|
+
console.log(` ${c.gray} scored ${winner.score}% — highest match against examples and description${c.reset}`)
|
|
627
|
+
if (runners.length) {
|
|
628
|
+
const rStr = runners.map(r => `${r.capabilityId} (${r.score}%)`).join(', ')
|
|
629
|
+
console.log(` ${c.gray} rejected: ${rStr}${c.reset}`)
|
|
630
|
+
}
|
|
631
|
+
if (paramEntries.length) {
|
|
632
|
+
const pStr = paramEntries.map(([k, v]) => `${k}="${v}"`).join(', ')
|
|
633
|
+
console.log(` ${c.gray} extracted from query: ${pStr}${c.reset}`)
|
|
634
|
+
}
|
|
635
|
+
console.log()
|
|
317
636
|
}
|
|
318
637
|
|
|
319
|
-
|
|
320
|
-
console.log(
|
|
321
|
-
console.log(` ${c.gray}
|
|
322
|
-
console.log(` ${c.
|
|
323
|
-
console.log(` ${c.
|
|
638
|
+
// ── Summary ────────────────────────────────────────────────────────────────
|
|
639
|
+
console.log(`${c.gray} ────────────────────────────────────────${c.reset}\n`)
|
|
640
|
+
console.log(` ${c.green}${passed} matched${c.reset} ${c.gray}·${c.reset} ${c.yellow}${outOfScope} out of scope${c.reset} ${c.gray}·${c.reset} ${manifest.capabilities.length} capabilities ${c.gray}·${c.reset} no LLM required\n`)
|
|
641
|
+
console.log(` ${c.bold}Every query above is fully traced.${c.reset}`)
|
|
642
|
+
console.log(` ${c.gray}You saw what matched, why it matched, how it executed, what it called,`)
|
|
643
|
+
console.log(` and which alternatives were considered and rejected.${c.reset}`)
|
|
644
|
+
console.log(` ${c.gray}No black box. No guessing. Full control.\n${c.reset}`)
|
|
645
|
+
console.log(` ${c.gray}Next steps:${c.reset}`)
|
|
646
|
+
console.log(` ${c.teal}npx capman init${c.reset} ${c.gray}→ define your app's capabilities${c.reset}`)
|
|
647
|
+
console.log(` ${c.teal}npx capman generate${c.reset} ${c.gray}→ generate manifest.json${c.reset}`)
|
|
648
|
+
console.log(` ${c.teal}npx capman run "your query" --debug${c.reset} ${c.gray}→ trace any query live${c.reset}`)
|
|
649
|
+
console.log(` ${c.teal}npm install capman${c.reset} ${c.gray}→ use in your AI agent${c.reset}\n`)
|
|
324
650
|
}
|
|
325
651
|
|
|
326
652
|
function cmdRun() {
|
|
@@ -390,19 +716,22 @@ function cmdRun() {
|
|
|
390
716
|
console.log()
|
|
391
717
|
}
|
|
392
718
|
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
719
|
+
(async () => {
|
|
720
|
+
switch (command) {
|
|
721
|
+
case 'init': cmdInit(); break
|
|
722
|
+
case 'generate': await cmdGenerate(); break
|
|
723
|
+
case 'validate': cmdValidate(); break
|
|
724
|
+
case 'inspect': cmdInspect(); break
|
|
725
|
+
case 'demo': cmdDemo(); break
|
|
726
|
+
case 'run': cmdRun(); break
|
|
727
|
+
case undefined:
|
|
728
|
+
case '--help':
|
|
729
|
+
case '-h': cmdHelp(); break
|
|
730
|
+
default:
|
|
731
|
+
header()
|
|
732
|
+
log.error(`Unknown command: ${command}`)
|
|
733
|
+
console.log(` Run: node bin/capman.js --help\n`)
|
|
734
|
+
process.exit(1)
|
|
735
|
+
}
|
|
736
|
+
})()
|
|
737
|
+
|
package/dist/cjs/cache.d.ts
CHANGED
|
@@ -6,15 +6,23 @@ export interface CacheEntry {
|
|
|
6
6
|
hits: number;
|
|
7
7
|
}
|
|
8
8
|
export interface CacheStore {
|
|
9
|
-
get(
|
|
10
|
-
set(
|
|
9
|
+
get(key: string): Promise<CacheEntry | null>;
|
|
10
|
+
set(key: string, result: MatchResult): Promise<void>;
|
|
11
11
|
clear(): Promise<void>;
|
|
12
12
|
size(): Promise<number>;
|
|
13
13
|
}
|
|
14
|
+
export declare function normalizeQuery(query: string): string;
|
|
15
|
+
/**
|
|
16
|
+
* Build a smarter cache key based on matched capability + extracted params.
|
|
17
|
+
* Two different queries that resolve to the same capability with the same params
|
|
18
|
+
* will share a cache entry — dramatically improving hit rate.
|
|
19
|
+
* Falls back to normalized query if no capability matched.
|
|
20
|
+
*/
|
|
21
|
+
export declare function buildCacheKey(query: string, capabilityId: string | null, extractedParams: Record<string, string | null>): string;
|
|
14
22
|
export declare class MemoryCache implements CacheStore {
|
|
15
23
|
private store;
|
|
16
|
-
get(
|
|
17
|
-
set(
|
|
24
|
+
get(key: string): Promise<CacheEntry | null>;
|
|
25
|
+
set(key: string, result: MatchResult): Promise<void>;
|
|
18
26
|
clear(): Promise<void>;
|
|
19
27
|
size(): Promise<number>;
|
|
20
28
|
}
|
|
@@ -25,8 +33,8 @@ export declare class FileCache implements CacheStore {
|
|
|
25
33
|
constructor(filePath?: string);
|
|
26
34
|
private load;
|
|
27
35
|
private save;
|
|
28
|
-
get(
|
|
29
|
-
set(
|
|
36
|
+
get(key: string): Promise<CacheEntry | null>;
|
|
37
|
+
set(key: string, result: MatchResult): Promise<void>;
|
|
30
38
|
clear(): Promise<void>;
|
|
31
39
|
size(): Promise<number>;
|
|
32
40
|
}
|
|
@@ -34,8 +42,8 @@ export declare class ComboCache implements CacheStore {
|
|
|
34
42
|
private memory;
|
|
35
43
|
private file;
|
|
36
44
|
constructor(filePath?: string);
|
|
37
|
-
get(
|
|
38
|
-
set(
|
|
45
|
+
get(key: string): Promise<CacheEntry | null>;
|
|
46
|
+
set(key: string, result: MatchResult): Promise<void>;
|
|
39
47
|
clear(): Promise<void>;
|
|
40
48
|
size(): Promise<number>;
|
|
41
49
|
}
|
package/dist/cjs/cache.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"cache.d.ts","sourceRoot":"","sources":["../../src/cache.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAK1C,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAA;IACb,MAAM,EAAE,WAAW,CAAA;IACnB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAID,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAA;IAC5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC,CAAA;CACxB;AAID,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEpD;AAED;;;;;GAKG;AAEH,wBAAgB,aAAa,CAC3B,KAAK,EAAE,MAAM,EACb,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC,GAC7C,MAAM,CAQR;AAMD,qBAAa,WAAY,YAAW,UAAU;IAC5C,OAAO,CAAC,KAAK,CAAgC;IAEvC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAU5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAepD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IACtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAC9B;AAID,qBAAa,SAAU,YAAW,UAAU;IAC1C,OAAO,CAAC,QAAQ,CAAQ;IACxB,OAAO,CAAC,KAAK,CAAqC;IAClD,OAAO,CAAC,MAAM,CAAQ;gBAEV,QAAQ,SAAuB;YAK7B,IAAI;YAYJ,IAAI;IAaZ,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAW5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAYpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAKtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAI9B;AAID,qBAAa,UAAW,YAAW,UAAU;IAC3C,OAAO,CAAC,MAAM,CAAa;IAC3B,OAAO,CAAC,IAAI,CAAW;gBAEX,QAAQ,SAAuB;IAKrC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAY5C,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAOpD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAOtB,IAAI,IAAI,OAAO,CAAC,MAAM,CAAC;CAG9B"}
|