capman 0.4.1 → 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/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} Generate manifest.json from config`)
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 { loadConfig, generate, writeManifest, validate } = requireSrc()
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
- const configPath = getFlag('--config')
101
- const outPath = getFlag('--out') ?? 'manifest.json'
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
- log.info(`Generating manifest for ${c.bold}${config.app}${c.reset}...`)
113
- const manifest = generate(config)
114
- const result = validate(manifest)
254
+ const outManifestPath = getFlag('--out') ?? 'manifest.json'
255
+ log.info(`Generating manifest for ${config.app}...`)
115
256
 
116
- for (const w of result.warnings) log.warn(w)
117
- for (const e of result.errors) log.error(e)
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
- if (!result.valid) {
120
- log.error('Manifest has errors — fix them before writing.')
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
- const written = writeManifest(manifest, outPath)
125
- log.blank()
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, resolve } = requireSrc()
452
+ const { generate, match } = requireSrc()
193
453
 
194
- console.log(`${c.bold} Live demo see capman in action${c.reset}`)
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
- // Demo manifest — generic e-commerce app
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
- 'Do you have size M available?',
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 recent purchase?',
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 query of queries) {
274
- const start = Date.now()
275
- const result = match(query, manifest)
276
- const duration = Date.now() - start
277
-
278
- if (result.capability) {
279
- passed++
280
- const resolverColor = result.capability.resolver.type === 'api' ? c.teal :
281
- result.capability.resolver.type === 'nav' ? c.teal : c.yellow
282
-
283
- console.log(` ${c.green}✓${c.reset} ${c.bold}"${query}"${c.reset}`)
284
- console.log(` ${c.gray}→ matched:${c.reset} ${resolverColor}${result.capability.id}${c.reset}`)
285
- console.log(` ${c.gray}→ intent:${c.reset} ${result.intent}`)
286
- console.log(` ${c.gray}→ confidence:${c.reset} ${result.confidence}%`)
287
-
288
- if (Object.keys(result.extractedParams).length > 0) {
289
- const params = Object.entries(result.extractedParams)
290
- .map(([k, v]) => `${k}=${v}`)
291
- .join(', ')
292
- console.log(` ${c.gray}→ params:${c.reset} ${params}`)
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
- // Show what API call would be made
296
- if (result.capability.resolver.type === 'api') {
297
- const endpoint = result.capability.resolver.endpoints[0]
298
- let path = endpoint.path
299
- for (const [k, v] of Object.entries(result.extractedParams)) {
300
- if (v) path = path.replace(`{${k}}`, v)
301
- }
302
- console.log(` ${c.gray}→ api call:${c.reset} ${endpoint.method} ${config.baseUrl}${path}`)
303
- } else if (result.capability.resolver.type === 'nav') {
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
- console.log(` ${c.gray}→ time:${c.reset} ${duration}ms\n`)
312
- } else {
313
- outOfScope++
314
- console.log(` ${c.yellow}○${c.reset} ${c.bold}"${query}"${c.reset}`)
315
- console.log(` ${c.gray}→ out of scope — no capability handles this\n${c.reset}`)
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
- console.log(`${c.gray} ─────────────────────────────────────────${c.reset}`)
320
- 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\n`)
321
- console.log(` ${c.gray}Try it on your own app:${c.reset}`)
322
- console.log(` ${c.teal}npx capman init${c.reset} ${c.gray}→ create your manifest${c.reset}`)
323
- console.log(` ${c.teal}npx capman generate${c.reset} ${c.gray}→ generate manifest.json${c.reset}\n`)
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
- switch (command) {
394
- case 'init': cmdInit(); break
395
- case 'generate': cmdGenerate(); break
396
- case 'validate': cmdValidate(); break
397
- case 'inspect': cmdInspect(); break
398
- case 'demo': cmdDemo(); break
399
- case 'run': cmdRun(); break
400
- case undefined:
401
- case '--help':
402
- case '-h': cmdHelp(); break
403
- default:
404
- header()
405
- log.error(`Unknown command: ${command}`)
406
- console.log(` Run: node bin/capman.js --help\n`)
407
- process.exit(1)
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
+
@@ -1,38 +1,34 @@
1
1
  export { setLogLevel } from './logger';
2
2
  export type { LogLevel } from './logger';
3
- import type { Manifest, MatchResult, ResolveResult } from './types';
4
- import type { LLMMatcherOptions } from './matcher';
5
- import type { ResolveOptions } from './resolver';
6
3
  export type { Capability, CapabilityParam, CapmanConfig, Manifest, MatchResult, ExecutionTrace, TraceStep, MatchCandidate, ResolveResult, ApiCallResult, ValidationResult, Resolver, ApiResolver, NavResolver, HybridResolver, PrivacyScope, ResolverType, HttpMethod, } from './types';
7
4
  export { generate, loadConfig, writeManifest, readManifest, validate, generateStarterConfig, } from './generator';
8
5
  export { match, matchWithLLM, } from './matcher';
9
6
  export type { LLMMatcherOptions } from './matcher';
10
7
  export { resolve } from './resolver';
11
8
  export type { ResolveOptions, AuthContext } from './resolver';
12
- export type MatchMode = 'cheap' | 'balanced' | 'accurate';
13
9
  export { CapmanEngine } from './engine';
14
10
  export type { EngineOptions, EngineResult } from './engine';
15
- export { MemoryCache, FileCache, ComboCache } from './cache';
11
+ export { MemoryCache, FileCache, ComboCache, buildCacheKey, normalizeQuery } from './cache';
16
12
  export type { CacheStore, CacheEntry } from './cache';
17
13
  export { FileLearningStore, MemoryLearningStore } from './learning';
18
14
  export type { LearningStore, LearningEntry, KeywordStats } from './learning';
15
+ export { parseOpenAPI } from './parser';
16
+ export type { ParseResult } from './parser';
17
+ import type { Manifest, MatchResult, ResolveResult } from './types';
18
+ import type { LLMMatcherOptions } from './matcher';
19
+ import type { ResolveOptions } from './resolver';
20
+ export type MatchMode = 'cheap' | 'balanced' | 'accurate';
19
21
  export interface AskOptions extends ResolveOptions {
20
22
  llm?: LLMMatcherOptions['llm'];
21
23
  /**
22
24
  * Controls how intent matching is performed.
23
- *
24
- * - 'cheap' — keyword matching only. No LLM calls. Free but less accurate.
25
- * - 'balanced' — keyword first. Falls back to LLM if confidence < 50%. (default)
26
- * - 'accurate' — LLM first. Falls back to keyword if LLM call fails.
27
- *
25
+ * - 'cheap' — keyword only, no LLM calls
26
+ * - 'balanced' — keyword first, LLM fallback if confidence < 50% (default)
27
+ * - 'accurate' — LLM first, keyword fallback
28
28
  * @default 'balanced'
29
29
  */
30
30
  mode?: MatchMode;
31
31
  }
32
- export interface AskOptions extends ResolveOptions {
33
- llm?: LLMMatcherOptions['llm'];
34
- mode?: MatchMode;
35
- }
36
32
  export interface AskResult {
37
33
  match: MatchResult;
38
34
  resolution: ResolveResult;
@@ -41,12 +37,12 @@ export interface AskResult {
41
37
  * One-shot convenience: match + resolve in a single call.
42
38
  * Delegates to CapmanEngine internally.
43
39
  *
40
+ * @deprecated For full features including trace and caching, use CapmanEngine directly.
41
+ *
44
42
  * @example
45
43
  * const result = await ask("show me the dashboard", manifest, {
46
44
  * baseUrl: 'https://api.your-app.com',
47
45
  * })
48
- *
49
- * @deprecated For full features including trace and caching, use CapmanEngine directly.
50
46
  */
51
47
  export declare function ask(query: string, manifest: Manifest, options?: AskOptions): Promise<AskResult>;
52
48
  //# sourceMappingURL=index.d.ts.map