capman 0.4.2 → 0.4.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (47) hide show
  1. package/CHANGELOG.md +127 -0
  2. package/CODEBASE.md +391 -0
  3. package/README.md +1 -1
  4. package/bin/capman.js +11 -724
  5. package/bin/lib/cmd-demo.js +180 -0
  6. package/bin/lib/cmd-explain.js +72 -0
  7. package/bin/lib/cmd-generate.js +280 -0
  8. package/bin/lib/cmd-help.js +26 -0
  9. package/bin/lib/cmd-init.js +19 -0
  10. package/bin/lib/cmd-inspect.js +33 -0
  11. package/bin/lib/cmd-run.js +71 -0
  12. package/bin/lib/cmd-validate.js +32 -0
  13. package/bin/lib/shared.js +70 -0
  14. package/dist/cjs/engine.d.ts +58 -1
  15. package/dist/cjs/engine.d.ts.map +1 -1
  16. package/dist/cjs/engine.js +307 -12
  17. package/dist/cjs/engine.js.map +1 -1
  18. package/dist/cjs/generator.d.ts.map +1 -1
  19. package/dist/cjs/generator.js +4 -0
  20. package/dist/cjs/generator.js.map +1 -1
  21. package/dist/cjs/index.d.ts +1 -1
  22. package/dist/cjs/index.d.ts.map +1 -1
  23. package/dist/cjs/index.js.map +1 -1
  24. package/dist/cjs/matcher.d.ts.map +1 -1
  25. package/dist/cjs/matcher.js +19 -25
  26. package/dist/cjs/matcher.js.map +1 -1
  27. package/dist/cjs/types.d.ts +27 -0
  28. package/dist/cjs/types.d.ts.map +1 -1
  29. package/dist/cjs/version.d.ts +1 -1
  30. package/dist/cjs/version.js +1 -1
  31. package/dist/esm/cache.d.ts +49 -0
  32. package/dist/esm/engine.d.ts +138 -0
  33. package/dist/esm/engine.js +307 -12
  34. package/dist/esm/generator.d.ts +7 -0
  35. package/dist/esm/generator.js +4 -0
  36. package/dist/esm/index.d.ts +47 -0
  37. package/dist/esm/learning.d.ts +55 -0
  38. package/dist/esm/logger.d.ts +21 -0
  39. package/dist/esm/matcher.d.ts +6 -0
  40. package/dist/esm/matcher.js +19 -25
  41. package/dist/esm/parser.d.ts +10 -0
  42. package/dist/esm/resolver.d.ts +21 -0
  43. package/dist/esm/schema.d.ts +740 -0
  44. package/dist/esm/types.d.ts +136 -0
  45. package/dist/esm/version.d.ts +1 -0
  46. package/dist/esm/version.js +1 -1
  47. package/package.json +5 -3
package/bin/capman.js CHANGED
@@ -1,737 +1,24 @@
1
1
  #!/usr/bin/env node
2
2
  'use strict'
3
3
 
4
- const path = require('path')
5
- const fs = require('fs')
4
+ const { command, header, log, c } = require('./lib/shared')
6
5
 
7
- const args = process.argv.slice(2)
8
- const command = args[0]
9
- const flags = args.slice(1)
10
-
11
- const getFlag = (name) => {
12
- const i = flags.indexOf(name)
13
- return i !== -1 ? flags[i + 1] : undefined
14
- }
15
-
16
- const c = {
17
- reset: '\x1b[0m',
18
- bold: '\x1b[1m',
19
- teal: '\x1b[36m',
20
- yellow: '\x1b[33m',
21
- red: '\x1b[31m',
22
- green: '\x1b[32m',
23
- gray: '\x1b[90m',
24
- }
25
-
26
- const log = {
27
- info: (...a) => console.log(`${c.teal}i${c.reset}`, ...a),
28
- success: (...a) => console.log(`${c.green}✓${c.reset}`, ...a),
29
- warn: (...a) => console.log(`${c.yellow}⚠${c.reset}`, ...a),
30
- error: (...a) => console.error(`${c.red}✗${c.reset}`, ...a),
31
- blank: () => console.log(),
32
- }
33
-
34
- function header() {
35
- const pkg = require(path.join(__dirname, '..', 'package.json'))
36
- console.log()
37
- console.log(`${c.bold}${c.teal} capman${c.reset} ${c.gray}v${pkg.version} — Capability Manifest Engine${c.reset}`)
38
- console.log(`${c.gray} ─────────────────────────────────────────${c.reset}`)
39
- console.log()
40
- }
41
-
42
- function requireSrc() {
43
- const distPath = path.join(__dirname, '..', 'dist', 'cjs', 'index.js')
44
- if (fs.existsSync(distPath)) return require(distPath)
45
-
46
- // dist not built — try to build automatically
47
- log.info('dist/cjs not found — running build...')
48
- try {
49
- require('child_process').execSync('npm run build', {
50
- cwd: path.join(__dirname, '..'),
51
- stdio: 'inherit'
52
- })
53
- if (fs.existsSync(distPath)) return require(distPath)
54
- } catch {
55
- // build failed
56
- }
57
-
58
- log.error('Cannot find dist/cjs/. Run: pnpm run build')
59
- process.exit(1)
60
- }
61
-
62
- function cmdHelp() {
63
- header()
64
- console.log(`${c.bold} Usage:${c.reset} node bin/capman.js <command>`)
65
- console.log()
66
- console.log(`${c.bold} Commands:${c.reset}`)
67
- console.log(` ${c.teal}init${c.reset} Create a starter capman.config.js`)
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)`)
71
- console.log(` ${c.teal}validate${c.reset} Validate an existing manifest.json`)
72
- console.log(` ${c.teal}inspect${c.reset} Print all capabilities in manifest`)
73
- console.log(` ${c.teal}demo${c.reset} Run a live demo with sample queries`)
74
- console.log(` ${c.teal}run${c.reset} Run a query against your manifest`)
75
- console.log()
76
- console.log(`${c.bold} Options:${c.reset}`)
77
- console.log(` ${c.gray}--config Path to config file (default: capman.config.js)${c.reset}`)
78
- console.log(` ${c.gray}--out Output path (default: manifest.json)${c.reset}`)
79
- console.log(` ${c.gray}--manifest Manifest to read (default: manifest.json)${c.reset}`)
80
- console.log(` ${c.gray}Options: --debug (show all candidates)${c.reset}`)
81
- console.log()
82
- }
83
-
84
- function cmdInit() {
85
- header()
86
- const outPath = path.resolve(process.cwd(), 'capman.config.js')
87
- if (fs.existsSync(outPath)) {
88
- log.warn('capman.config.js already exists — not overwriting.')
89
- process.exit(0)
90
- }
91
- const { generateStarterConfig } = requireSrc()
92
- fs.writeFileSync(outPath, generateStarterConfig())
93
- log.success(`Created ${c.bold}capman.config.js${c.reset}`)
94
- log.info(`Edit it with your app's capabilities, then run:`)
95
- console.log(`\n node bin/capman.js generate\n`)
96
- }
97
-
98
- async function cmdGenerate() {
99
- header()
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
- }
117
-
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
- }
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')
245
- log.info('Loading config...')
246
- let config
247
- try {
248
- config = loadConfig(configPath)
249
- } catch (e) {
250
- log.error(e.message)
251
- process.exit(1)
252
- }
253
-
254
- const outManifestPath = getFlag('--out') ?? 'manifest.json'
255
- log.info(`Generating manifest for ${config.app}...`)
256
-
257
- let manifest
258
- try {
259
- manifest = generate(config)
260
- } catch (e) {
261
- log.error(`Generation failed: ${e.message}`)
262
- process.exit(1)
263
- }
264
-
265
- const validation = validate(manifest)
266
- if (!validation.valid) {
267
- validation.errors.forEach(e => log.error(e))
268
- process.exit(1)
269
- }
270
- if (validation.warnings.length) {
271
- validation.warnings.forEach(w => log.warn(w))
272
- }
273
-
274
- writeManifest(manifest, outManifestPath)
275
- log.success(`Manifest written to ${outManifestPath}`)
276
- log.info(`${manifest.capabilities.length} capabilities registered`)
277
- console.log()
278
- }
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
-
391
- function cmdValidate() {
392
- header()
393
- const { readManifest, validate } = requireSrc()
394
-
395
- const manifestPath = getFlag('--manifest') ?? 'manifest.json'
396
- let manifest
397
- try {
398
- manifest = readManifest(manifestPath)
399
- } catch (e) {
400
- log.error(e.message)
401
- process.exit(1)
402
- }
403
-
404
- log.info(`Validating ${c.bold}${manifestPath}${c.reset}...`)
405
- const result = validate(manifest)
406
- log.blank()
407
-
408
- for (const w of result.warnings) log.warn(w)
409
- for (const e of result.errors) log.error(e)
410
-
411
- if (result.valid) {
412
- log.success(`${manifest.capabilities.length} capabilities — all valid`)
413
- } else {
414
- log.error(`${result.errors.length} error(s) found.`)
415
- process.exit(1)
416
- }
417
- console.log()
418
- }
419
-
420
- function cmdInspect() {
421
- header()
422
- const { readManifest } = requireSrc()
423
-
424
- const manifestPath = getFlag('--manifest') ?? 'manifest.json'
425
- let manifest
426
- try {
427
- manifest = readManifest(manifestPath)
428
- } catch (e) {
429
- log.error(e.message)
430
- process.exit(1)
431
- }
432
-
433
- console.log(`${c.bold} App:${c.reset} ${manifest.app}`)
434
- console.log(`${c.bold} Generated:${c.reset} ${manifest.generatedAt}`)
435
- console.log(`${c.bold} Capabilities:${c.reset} ${manifest.capabilities.length}`)
436
- console.log()
437
-
438
- for (const cap of manifest.capabilities) {
439
- const col = cap.resolver.type === 'api' ? c.teal : cap.resolver.type === 'nav' ? c.teal : c.yellow
440
- console.log(` ${c.bold}${cap.name}${c.reset} ${col}[${cap.resolver.type}]${c.reset} ${c.gray}${cap.privacy.level}${c.reset}`)
441
- console.log(` ${c.gray}id: ${cap.id}${c.reset}`)
442
- console.log(` ${cap.description}`)
443
- if (cap.examples?.length) {
444
- console.log(` ${c.gray}e.g. "${cap.examples[0]}"${c.reset}`)
445
- }
446
- console.log()
447
- }
448
- }
449
-
450
- function cmdDemo() {
451
- header()
452
- const { generate, match } = requireSrc()
453
-
454
- // ── Title ──────────────────────────────────────────────────────────────────
455
- console.log(`${c.bold} Turn natural language into reliable, explainable backend actions.${c.reset}`)
456
- console.log(`${c.gray} ─────────────────────────────────────────${c.reset}\n`)
457
-
458
- // ── Manifest ───────────────────────────────────────────────────────────────
459
- const config = {
460
- app: 'demo-store',
461
- baseUrl: 'https://api.demo-store.com',
462
- capabilities: [
463
- {
464
- id: 'check_product_availability',
465
- name: 'Check product availability',
466
- description: 'Check stock and pricing for a product by name or ID.',
467
- examples: [
468
- 'Is the blue jacket in stock?',
469
- 'Check stock for blue jacket',
470
- 'Check availability for blue jacket',
471
- 'Product availability for jacket',
472
- ],
473
- params: [
474
- { name: 'product', description: 'Product name or ID', required: true, source: 'user_query' }
475
- ],
476
- returns: ['stock', 'price', 'variants'],
477
- resolver: { type: 'api', endpoints: [{ method: 'GET', path: '/products/{product}/availability' }] },
478
- privacy: { level: 'public' },
479
- },
480
- {
481
- id: 'get_order_status',
482
- name: 'Get order status',
483
- description: 'Retrieve the current status and tracking info for an order by order ID.',
484
- examples: [
485
- 'Where is my order?',
486
- 'Track order 1234',
487
- 'What is the status of my purchase?',
488
- ],
489
- params: [
490
- { name: 'order_id', description: 'Order ID', required: true, source: 'user_query' }
491
- ],
492
- returns: ['status', 'tracking', 'estimated_delivery'],
493
- resolver: { type: 'api', endpoints: [{ method: 'GET', path: '/orders/{order_id}' }] },
494
- privacy: { level: 'user_owned' },
495
- },
496
- {
497
- id: 'navigate_to_screen',
498
- name: 'Navigate to screen',
499
- description: 'Route the user to a specific page in the store.',
500
- examples: [
501
- 'Take me to cart',
502
- 'Open cart',
503
- 'Go to checkout',
504
- 'Navigate to account',
505
- ],
506
- params: [
507
- { name: 'destination', description: 'Target screen', required: true, source: 'user_query' }
508
- ],
509
- returns: ['deep_link'],
510
- resolver: { type: 'nav', destination: '/{destination}' },
511
- privacy: { level: 'public' },
512
- },
513
- ],
514
- }
515
-
516
- const manifest = generate(config)
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 ────────────────────────────────────────────────────────────────
523
- const queries = [
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 },
528
- ]
529
-
530
- let passed = 0
531
- let outOfScope = 0
532
-
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}`)
566
- }
567
- console.log()
568
- continue
569
- }
570
-
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))
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
- }
589
-
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}`)
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()
636
- }
637
-
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`)
650
- }
651
-
652
- function cmdRun() {
653
- header()
654
- const query = args[1]
655
- const debug = flags.includes('--debug')
656
- const manifestPath = getFlag('--manifest') ?? 'manifest.json'
657
-
658
- if (!query) {
659
- log.error('Please provide a query. Example: node bin/capman.js run "show me articles"')
660
- process.exit(1)
661
- }
662
-
663
- const { readManifest, match } = requireSrc()
664
-
665
- let manifest
666
- try {
667
- manifest = readManifest(manifestPath)
668
- } catch (e) {
669
- log.error(e.message)
670
- process.exit(1)
671
- }
672
-
673
- log.info(`Query: "${query}"`)
674
- log.blank()
675
-
676
- const result = match(query, manifest)
677
-
678
- if (result.capability) {
679
- console.log(` ${c.green}✓${c.reset} Matched: ${c.bold}${result.capability.id}${c.reset}`)
680
- console.log(` Intent: ${result.intent}`)
681
- console.log(` Confidence: ${result.confidence}%`)
682
- console.log(` Resolver: ${result.capability.resolver.type}`)
683
-
684
- if (Object.keys(result.extractedParams).length > 0) {
685
- const params = Object.entries(result.extractedParams)
686
- .map(([k, v]) => `${k}=${v}`)
687
- .join(', ')
688
- console.log(` Params: ${params}`)
689
- }
690
-
691
- if (debug && result.candidates?.length) {
692
- log.blank()
693
- console.log(` ${c.gray}── All candidates:${c.reset}`)
694
- result.candidates
695
- .sort((a, b) => b.score - a.score)
696
- .forEach(c2 => {
697
- const marker = c2.matched ? c.green + '✓' : c.gray + '○'
698
- console.log(` ${marker}${c.reset} ${c2.capabilityId}: ${c2.score}%`)
699
- })
700
- }
701
- } else {
702
- console.log(` ${c.yellow}○${c.reset} OUT_OF_SCOPE — no capability matched`)
703
- console.log(` ${c.gray}${result.reasoning}${c.reset}`)
704
-
705
- if (debug && result.candidates?.length) {
706
- log.blank()
707
- console.log(` ${c.gray}── All candidates:${c.reset}`)
708
- result.candidates
709
- .sort((a, b) => b.score - a.score)
710
- .slice(0, 5)
711
- .forEach(c2 => {
712
- console.log(` ${c.gray}○ ${c2.capabilityId}: ${c2.score}%${c.reset}`)
713
- })
714
- }
715
- }
716
- console.log()
717
- }
718
-
719
- (async () => {
6
+ ;(async () => {
720
7
  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
8
+ case 'init': require('./lib/cmd-init')(); break
9
+ case 'generate': await require('./lib/cmd-generate')(); break
10
+ case 'validate': require('./lib/cmd-validate')(); break
11
+ case 'inspect': require('./lib/cmd-inspect')(); break
12
+ case 'demo': require('./lib/cmd-demo')(); break
13
+ case 'run': require('./lib/cmd-run')(); break
14
+ case 'explain': await require('./lib/cmd-explain')(); break
727
15
  case undefined:
728
16
  case '--help':
729
- case '-h': cmdHelp(); break
17
+ case '-h': require('./lib/cmd-help')(); break
730
18
  default:
731
19
  header()
732
20
  log.error(`Unknown command: ${command}`)
733
- console.log(` Run: node bin/capman.js --help\n`)
21
+ console.log(` Run: ${c.teal}capman --help${c.reset}\n`)
734
22
  process.exit(1)
735
23
  }
736
24
  })()
737
-