capman 0.4.1 → 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.
- package/CHANGELOG.md +127 -0
- package/CODEBASE.md +391 -0
- package/README.md +76 -97
- package/bin/capman.js +21 -405
- package/bin/lib/cmd-demo.js +180 -0
- package/bin/lib/cmd-explain.js +72 -0
- package/bin/lib/cmd-generate.js +280 -0
- package/bin/lib/cmd-help.js +26 -0
- package/bin/lib/cmd-init.js +19 -0
- package/bin/lib/cmd-inspect.js +33 -0
- package/bin/lib/cmd-run.js +71 -0
- package/bin/lib/cmd-validate.js +32 -0
- package/bin/lib/shared.js +70 -0
- package/dist/cjs/engine.d.ts +58 -1
- package/dist/cjs/engine.d.ts.map +1 -1
- package/dist/cjs/engine.js +307 -12
- package/dist/cjs/engine.js.map +1 -1
- package/dist/cjs/generator.d.ts.map +1 -1
- package/dist/cjs/generator.js +4 -0
- package/dist/cjs/generator.js.map +1 -1
- package/dist/cjs/index.d.ts +13 -17
- package/dist/cjs/index.d.ts.map +1 -1
- package/dist/cjs/index.js +12 -7
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/matcher.d.ts.map +1 -1
- package/dist/cjs/matcher.js +19 -25
- 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/types.d.ts +27 -0
- 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.d.ts +49 -0
- package/dist/esm/engine.d.ts +138 -0
- package/dist/esm/engine.js +307 -12
- package/dist/esm/generator.d.ts +7 -0
- package/dist/esm/generator.js +4 -0
- package/dist/esm/index.d.ts +47 -0
- package/dist/esm/index.js +6 -4
- package/dist/esm/learning.d.ts +55 -0
- package/dist/esm/logger.d.ts +21 -0
- package/dist/esm/matcher.d.ts +6 -0
- package/dist/esm/matcher.js +19 -25
- package/dist/esm/parser.d.ts +10 -0
- package/dist/esm/parser.js +267 -0
- package/dist/esm/resolver.d.ts +21 -0
- package/dist/esm/schema.d.ts +740 -0
- package/dist/esm/types.d.ts +136 -0
- package/dist/esm/version.d.ts +1 -0
- package/dist/esm/version.js +1 -1
- package/package.json +5 -3
package/README.md
CHANGED
|
@@ -32,21 +32,21 @@ User query → match capability → resolve via API or nav → structured result
|
|
|
32
32
|
|
|
33
33
|
## Quick Start
|
|
34
34
|
|
|
35
|
-
**1.
|
|
35
|
+
**1. Generate your manifest — three ways:**
|
|
36
36
|
|
|
37
37
|
```bash
|
|
38
|
-
|
|
39
|
-
|
|
38
|
+
# From an OpenAPI/Swagger spec (fastest, no API key needed)
|
|
39
|
+
npx capman generate --from openapi.json
|
|
40
|
+
npx capman generate --from https://api.your-app.com/openapi.json
|
|
40
41
|
|
|
41
|
-
|
|
42
|
+
# AI-assisted — describe your app in plain English
|
|
43
|
+
npx capman generate --ai
|
|
42
44
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
```bash
|
|
46
|
-
npx capman generate
|
|
45
|
+
# Manual — edit capman.config.js yourself
|
|
46
|
+
npx capman init
|
|
47
47
|
```
|
|
48
48
|
|
|
49
|
-
**
|
|
49
|
+
**2. Use the engine in your AI agent**
|
|
50
50
|
|
|
51
51
|
```typescript
|
|
52
52
|
import { CapmanEngine, readManifest } from 'capman'
|
|
@@ -66,7 +66,7 @@ console.log(result.resolvedVia) // 'keyword' | 'llm' | 'cache'
|
|
|
66
66
|
console.log(result.trace.reasoning) // ['Matched "check_product_availability" with 100% confidence', ...]
|
|
67
67
|
```
|
|
68
68
|
|
|
69
|
-
**
|
|
69
|
+
**3. See it live**
|
|
70
70
|
|
|
71
71
|
```bash
|
|
72
72
|
npx capman demo
|
|
@@ -74,6 +74,61 @@ npx capman demo
|
|
|
74
74
|
|
|
75
75
|
---
|
|
76
76
|
|
|
77
|
+
## Manifest Generation
|
|
78
|
+
|
|
79
|
+
capman gives you three ways to create your manifest — pick based on what you have:
|
|
80
|
+
|
|
81
|
+
### From OpenAPI / Swagger spec
|
|
82
|
+
|
|
83
|
+
If your backend has an OpenAPI spec (most do), capman reads it and generates a complete manifest automatically. No LLM needed, no API key, works offline.
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
npx capman generate --from openapi.json
|
|
87
|
+
npx capman generate --from https://api.your-app.com/openapi.json
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
What it does automatically:
|
|
91
|
+
- Converts every endpoint into a capability with correct ID, name, and description
|
|
92
|
+
- Extracts path params, query params, and request body fields
|
|
93
|
+
- Infers privacy scope from security schemes — bearer token → `user_owned`, admin tags → `admin`, no auth → `public`
|
|
94
|
+
- Generates natural language examples from the operation summary
|
|
95
|
+
- Writes a ready `capman.config.js` for you to review and adjust
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
✓ Parsed 19 capabilities from spec
|
|
99
|
+
✓ Config written to capman.config.js
|
|
100
|
+
✓ Manifest written to manifest.json
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### AI-assisted generation
|
|
104
|
+
|
|
105
|
+
No OpenAPI spec? Describe your app in plain English and capman uses an LLM to generate a full manifest.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npx capman generate --ai
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
Describe your app and its main capabilities:
|
|
113
|
+
> A SaaS CRM. Users can create contacts, log calls, view pipeline
|
|
114
|
+
stages. Admins can manage teams and billing.
|
|
115
|
+
|
|
116
|
+
Using anthropic to generate manifest...
|
|
117
|
+
✓ 6 capabilities generated
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Requires one of: `ANTHROPIC_API_KEY`, `OPENAI_API_KEY`, or `OPENROUTER_API_KEY` in your environment.
|
|
121
|
+
|
|
122
|
+
### Manual
|
|
123
|
+
|
|
124
|
+
For full control, start from a starter config and define capabilities yourself:
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
npx capman init
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
77
132
|
## Execution Trace
|
|
78
133
|
|
|
79
134
|
Every `engine.ask()` call returns a full execution trace — so you always know why the AI did what it did.
|
|
@@ -112,19 +167,6 @@ Debug any query from the CLI:
|
|
|
112
167
|
npx capman run "check availability for blue jacket" --debug
|
|
113
168
|
```
|
|
114
169
|
|
|
115
|
-
```
|
|
116
|
-
✓ Matched: check_product_availability
|
|
117
|
-
Intent: retrieval
|
|
118
|
-
Confidence: 100%
|
|
119
|
-
Resolver: api
|
|
120
|
-
Params: product=blue-jacket
|
|
121
|
-
|
|
122
|
-
── All candidates:
|
|
123
|
-
✓ check_product_availability: 100%
|
|
124
|
-
○ get_order_status: 12%
|
|
125
|
-
○ navigate_to_screen: 0%
|
|
126
|
-
```
|
|
127
|
-
|
|
128
170
|
---
|
|
129
171
|
|
|
130
172
|
## Matching Modes
|
|
@@ -171,29 +213,15 @@ import { CapmanEngine, FileCache, FileLearningStore } from 'capman'
|
|
|
171
213
|
|
|
172
214
|
const engine = new CapmanEngine({
|
|
173
215
|
manifest,
|
|
174
|
-
// Default: MemoryCache (fast, resets on restart)
|
|
175
|
-
// For persistence across restarts:
|
|
176
216
|
cache: new FileCache('.capman/cache.json'),
|
|
177
217
|
learning: new FileLearningStore('.capman/learning.json'),
|
|
178
218
|
})
|
|
179
219
|
|
|
180
|
-
// After real usage, see what's happening
|
|
181
220
|
const stats = await engine.getStats()
|
|
182
|
-
|
|
183
|
-
// {
|
|
184
|
-
// totalQueries: 142,
|
|
185
|
-
// llmQueries: 18,
|
|
186
|
-
// cacheHits: 67,
|
|
187
|
-
// outOfScope: 3,
|
|
188
|
-
// index: { 'availability': { 'check_product_availability': 34 }, ... }
|
|
189
|
-
// }
|
|
221
|
+
// { totalQueries: 142, llmQueries: 18, cacheHits: 67, outOfScope: 3 }
|
|
190
222
|
|
|
191
223
|
const top = await engine.getTopCapabilities(3)
|
|
192
|
-
// [
|
|
193
|
-
// { id: 'check_product_availability', hits: 58 },
|
|
194
|
-
// { id: 'navigate_to_screen', hits: 41 },
|
|
195
|
-
// { id: 'get_order_status', hits: 28 },
|
|
196
|
-
// ]
|
|
224
|
+
// [{ id: 'check_product_availability', hits: 58 }, ...]
|
|
197
225
|
```
|
|
198
226
|
|
|
199
227
|
---
|
|
@@ -212,77 +240,29 @@ const engine = new CapmanEngine({
|
|
|
212
240
|
userId: 'user-123', // auto-injected into session params
|
|
213
241
|
},
|
|
214
242
|
})
|
|
215
|
-
|
|
216
|
-
// user_owned capabilities require auth — blocked without it
|
|
217
|
-
// admin capabilities require role: 'admin' — blocked for regular users
|
|
218
|
-
// session params like {user_id} are auto-replaced from auth.userId
|
|
219
243
|
```
|
|
220
244
|
|
|
221
245
|
---
|
|
222
246
|
|
|
223
247
|
## Resolver Hardening
|
|
224
248
|
|
|
225
|
-
Configure retries and timeouts per call:
|
|
226
|
-
|
|
227
249
|
```typescript
|
|
228
250
|
const result = await engine.ask('show my orders', {
|
|
229
|
-
retries: 2,
|
|
230
|
-
timeoutMs: 3000,
|
|
251
|
+
retries: 2,
|
|
252
|
+
timeoutMs: 3000,
|
|
231
253
|
})
|
|
232
254
|
```
|
|
233
255
|
|
|
234
256
|
---
|
|
235
257
|
|
|
236
|
-
## Capability Config
|
|
237
|
-
|
|
238
|
-
Each capability in `capman.config.js`:
|
|
239
|
-
|
|
240
|
-
```javascript
|
|
241
|
-
module.exports = {
|
|
242
|
-
app: 'your-app',
|
|
243
|
-
baseUrl: 'https://api.your-app.com',
|
|
244
|
-
capabilities: [
|
|
245
|
-
{
|
|
246
|
-
id: 'check_product_availability',
|
|
247
|
-
name: 'Check product availability',
|
|
248
|
-
description: 'Check stock and pricing for a product by name or ID.',
|
|
249
|
-
examples: [
|
|
250
|
-
'Is the blue jacket available?',
|
|
251
|
-
'Check availability for product 42',
|
|
252
|
-
'Do you have size M in stock?',
|
|
253
|
-
],
|
|
254
|
-
params: [
|
|
255
|
-
{
|
|
256
|
-
name: 'product',
|
|
257
|
-
description: 'Product name or ID',
|
|
258
|
-
required: true,
|
|
259
|
-
source: 'user_query', // extracted from the query
|
|
260
|
-
},
|
|
261
|
-
],
|
|
262
|
-
returns: ['stock', 'price', 'variants'],
|
|
263
|
-
resolver: {
|
|
264
|
-
type: 'api', // 'api' | 'nav' | 'hybrid'
|
|
265
|
-
endpoints: [
|
|
266
|
-
{ method: 'GET', path: '/products/{product}/availability' },
|
|
267
|
-
],
|
|
268
|
-
},
|
|
269
|
-
privacy: {
|
|
270
|
-
level: 'public', // 'public' | 'user_owned' | 'admin'
|
|
271
|
-
note: 'No auth required',
|
|
272
|
-
},
|
|
273
|
-
},
|
|
274
|
-
],
|
|
275
|
-
}
|
|
276
|
-
```
|
|
277
|
-
|
|
278
|
-
---
|
|
279
|
-
|
|
280
258
|
## CLI Commands
|
|
281
259
|
|
|
282
260
|
| Command | What it does |
|
|
283
261
|
|---|---|
|
|
284
262
|
| `capman init` | Create a starter `capman.config.js` |
|
|
285
|
-
| `capman generate` | Generate
|
|
263
|
+
| `capman generate` | Generate manifest from `capman.config.js` |
|
|
264
|
+
| `capman generate --from <path\|url>` | Generate from OpenAPI/Swagger spec |
|
|
265
|
+
| `capman generate --ai` | Generate manifest using AI |
|
|
286
266
|
| `capman validate` | Validate your manifest for errors |
|
|
287
267
|
| `capman inspect` | Print all capabilities in the manifest |
|
|
288
268
|
| `capman run "query"` | Run a query against your manifest |
|
|
@@ -326,20 +306,19 @@ module.exports = {
|
|
|
326
306
|
|
|
327
307
|
**Works well:**
|
|
328
308
|
- Structured data retrieval via APIs
|
|
329
|
-
-
|
|
330
|
-
- Multi-endpoint aggregation
|
|
309
|
+
- Auto-generating manifests from OpenAPI specs
|
|
331
310
|
- Privacy enforcement per capability
|
|
332
|
-
- Caching repeated queries
|
|
333
311
|
- Full execution tracing and debugging
|
|
312
|
+
- Caching repeated queries
|
|
334
313
|
|
|
335
314
|
**Current limits:**
|
|
336
315
|
- Real-time infra status (is the server down?)
|
|
337
316
|
- UI-only state with no API backing
|
|
338
317
|
- Very ambiguous queries — use `mode: 'accurate'` with an LLM
|
|
339
|
-
-
|
|
318
|
+
- Multi-instance deployments need Redis adapter (planned for v0.5)
|
|
340
319
|
|
|
341
320
|
---
|
|
342
321
|
|
|
343
322
|
## License
|
|
344
323
|
|
|
345
|
-
MIT — [github.com/Hobbydefiningdoctory/capman](https://github.com/Hobbydefiningdoctory/capman)
|
|
324
|
+
MIT — [(github.com/Hobbydefiningdoctory/capman)](https://github.com/Hobbydefiningdoctory/capman.git)
|
package/bin/capman.js
CHANGED
|
@@ -1,408 +1,24 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict'
|
|
3
3
|
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
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.json from config`)
|
|
69
|
-
console.log(` ${c.teal}validate${c.reset} Validate an existing manifest.json`)
|
|
70
|
-
console.log(` ${c.teal}inspect${c.reset} Print all capabilities in manifest`)
|
|
71
|
-
console.log(` ${c.teal}demo${c.reset} Run a live demo with sample queries`)
|
|
72
|
-
console.log(` ${c.teal}run${c.reset} Run a query against your manifest`)
|
|
73
|
-
console.log()
|
|
74
|
-
console.log(`${c.bold} Options:${c.reset}`)
|
|
75
|
-
console.log(` ${c.gray}--config Path to config file (default: capman.config.js)${c.reset}`)
|
|
76
|
-
console.log(` ${c.gray}--out Output path (default: manifest.json)${c.reset}`)
|
|
77
|
-
console.log(` ${c.gray}--manifest Manifest to read (default: manifest.json)${c.reset}`)
|
|
78
|
-
console.log(` ${c.gray}Options: --debug (show all candidates)${c.reset}`)
|
|
79
|
-
console.log()
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
function cmdInit() {
|
|
83
|
-
header()
|
|
84
|
-
const outPath = path.resolve(process.cwd(), 'capman.config.js')
|
|
85
|
-
if (fs.existsSync(outPath)) {
|
|
86
|
-
log.warn('capman.config.js already exists — not overwriting.')
|
|
87
|
-
process.exit(0)
|
|
88
|
-
}
|
|
89
|
-
const { generateStarterConfig } = requireSrc()
|
|
90
|
-
fs.writeFileSync(outPath, generateStarterConfig())
|
|
91
|
-
log.success(`Created ${c.bold}capman.config.js${c.reset}`)
|
|
92
|
-
log.info(`Edit it with your app's capabilities, then run:`)
|
|
93
|
-
console.log(`\n node bin/capman.js generate\n`)
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
function cmdGenerate() {
|
|
97
|
-
header()
|
|
98
|
-
const { loadConfig, generate, writeManifest, validate } = requireSrc()
|
|
99
|
-
|
|
100
|
-
const configPath = getFlag('--config')
|
|
101
|
-
const outPath = getFlag('--out') ?? 'manifest.json'
|
|
102
|
-
|
|
103
|
-
log.info('Loading config...')
|
|
104
|
-
let config
|
|
105
|
-
try {
|
|
106
|
-
config = loadConfig(configPath)
|
|
107
|
-
} catch (e) {
|
|
108
|
-
log.error(e.message)
|
|
109
|
-
process.exit(1)
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
log.info(`Generating manifest for ${c.bold}${config.app}${c.reset}...`)
|
|
113
|
-
const manifest = generate(config)
|
|
114
|
-
const result = validate(manifest)
|
|
115
|
-
|
|
116
|
-
for (const w of result.warnings) log.warn(w)
|
|
117
|
-
for (const e of result.errors) log.error(e)
|
|
118
|
-
|
|
119
|
-
if (!result.valid) {
|
|
120
|
-
log.error('Manifest has errors — fix them before writing.')
|
|
121
|
-
process.exit(1)
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const written = writeManifest(manifest, outPath)
|
|
125
|
-
log.blank()
|
|
126
|
-
log.success(`Manifest written to ${c.bold}${written}${c.reset}`)
|
|
127
|
-
log.info(`${manifest.capabilities.length} capabilities registered`)
|
|
128
|
-
console.log()
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function cmdValidate() {
|
|
132
|
-
header()
|
|
133
|
-
const { readManifest, validate } = requireSrc()
|
|
134
|
-
|
|
135
|
-
const manifestPath = getFlag('--manifest') ?? 'manifest.json'
|
|
136
|
-
let manifest
|
|
137
|
-
try {
|
|
138
|
-
manifest = readManifest(manifestPath)
|
|
139
|
-
} catch (e) {
|
|
140
|
-
log.error(e.message)
|
|
141
|
-
process.exit(1)
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
log.info(`Validating ${c.bold}${manifestPath}${c.reset}...`)
|
|
145
|
-
const result = validate(manifest)
|
|
146
|
-
log.blank()
|
|
147
|
-
|
|
148
|
-
for (const w of result.warnings) log.warn(w)
|
|
149
|
-
for (const e of result.errors) log.error(e)
|
|
150
|
-
|
|
151
|
-
if (result.valid) {
|
|
152
|
-
log.success(`${manifest.capabilities.length} capabilities — all valid`)
|
|
153
|
-
} else {
|
|
154
|
-
log.error(`${result.errors.length} error(s) found.`)
|
|
155
|
-
process.exit(1)
|
|
156
|
-
}
|
|
157
|
-
console.log()
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function cmdInspect() {
|
|
161
|
-
header()
|
|
162
|
-
const { readManifest } = requireSrc()
|
|
163
|
-
|
|
164
|
-
const manifestPath = getFlag('--manifest') ?? 'manifest.json'
|
|
165
|
-
let manifest
|
|
166
|
-
try {
|
|
167
|
-
manifest = readManifest(manifestPath)
|
|
168
|
-
} catch (e) {
|
|
169
|
-
log.error(e.message)
|
|
170
|
-
process.exit(1)
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
console.log(`${c.bold} App:${c.reset} ${manifest.app}`)
|
|
174
|
-
console.log(`${c.bold} Generated:${c.reset} ${manifest.generatedAt}`)
|
|
175
|
-
console.log(`${c.bold} Capabilities:${c.reset} ${manifest.capabilities.length}`)
|
|
176
|
-
console.log()
|
|
177
|
-
|
|
178
|
-
for (const cap of manifest.capabilities) {
|
|
179
|
-
const col = cap.resolver.type === 'api' ? c.teal : cap.resolver.type === 'nav' ? c.teal : c.yellow
|
|
180
|
-
console.log(` ${c.bold}${cap.name}${c.reset} ${col}[${cap.resolver.type}]${c.reset} ${c.gray}${cap.privacy.level}${c.reset}`)
|
|
181
|
-
console.log(` ${c.gray}id: ${cap.id}${c.reset}`)
|
|
182
|
-
console.log(` ${cap.description}`)
|
|
183
|
-
if (cap.examples?.length) {
|
|
184
|
-
console.log(` ${c.gray}e.g. "${cap.examples[0]}"${c.reset}`)
|
|
185
|
-
}
|
|
186
|
-
console.log()
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
function cmdDemo() {
|
|
191
|
-
header()
|
|
192
|
-
const { generate, match, resolve } = requireSrc()
|
|
193
|
-
|
|
194
|
-
console.log(`${c.bold} Live demo — see capman in action${c.reset}`)
|
|
195
|
-
console.log(`${c.gray} ─────────────────────────────────────────${c.reset}\n`)
|
|
196
|
-
|
|
197
|
-
// Demo manifest — generic e-commerce app
|
|
198
|
-
const config = {
|
|
199
|
-
app: 'demo-store',
|
|
200
|
-
baseUrl: 'https://api.demo-store.com',
|
|
201
|
-
capabilities: [
|
|
202
|
-
{
|
|
203
|
-
id: 'check_product_availability',
|
|
204
|
-
name: 'Check product availability',
|
|
205
|
-
description: 'Check stock and pricing for a product by name or ID.',
|
|
206
|
-
examples: [
|
|
207
|
-
'Is the blue jacket in stock?',
|
|
208
|
-
'Check stock for blue jacket',
|
|
209
|
-
'Do you have size M available?',
|
|
210
|
-
'Product availability for jacket',
|
|
211
|
-
'Is jacket available?',
|
|
212
|
-
],
|
|
213
|
-
params: [
|
|
214
|
-
{ name: 'product', description: 'Product name or ID', required: true, source: 'user_query' }
|
|
215
|
-
],
|
|
216
|
-
returns: ['stock', 'price', 'variants'],
|
|
217
|
-
resolver: { type: 'api', endpoints: [{ method: 'GET', path: '/products/{product}/availability' }] },
|
|
218
|
-
privacy: { level: 'public' },
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
id: 'get_order_status',
|
|
222
|
-
name: 'Get order status',
|
|
223
|
-
description: 'Retrieve the current status and tracking info for an order.',
|
|
224
|
-
examples: [
|
|
225
|
-
'Where is my order?',
|
|
226
|
-
'Track order 1234',
|
|
227
|
-
'What is the status of my recent purchase?',
|
|
228
|
-
],
|
|
229
|
-
params: [
|
|
230
|
-
{ name: 'order_id', description: 'Order ID', required: true, source: 'user_query' }
|
|
231
|
-
],
|
|
232
|
-
returns: ['status', 'tracking', 'estimated_delivery'],
|
|
233
|
-
resolver: { type: 'api', endpoints: [{ method: 'GET', path: '/orders/{order_id}' }] },
|
|
234
|
-
privacy: { level: 'user_owned' },
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
id: 'navigate_to_screen',
|
|
238
|
-
name: 'Navigate to screen',
|
|
239
|
-
description: 'Route the user to a specific page in the store.',
|
|
240
|
-
examples: [
|
|
241
|
-
'Take me to cart',
|
|
242
|
-
'Open cart',
|
|
243
|
-
'Go to checkout',
|
|
244
|
-
'Navigate to account',
|
|
245
|
-
'Show homepage',
|
|
246
|
-
],
|
|
247
|
-
params: [
|
|
248
|
-
{ name: 'destination', description: 'Target screen', required: true, source: 'user_query' }
|
|
249
|
-
],
|
|
250
|
-
returns: ['deep_link'],
|
|
251
|
-
resolver: { type: 'nav', destination: '/{destination}' },
|
|
252
|
-
privacy: { level: 'public' },
|
|
253
|
-
},
|
|
254
|
-
],
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
const manifest = generate(config)
|
|
258
|
-
|
|
259
|
-
const queries = [
|
|
260
|
-
'Check availability for blue jacket',
|
|
261
|
-
'Track order 1234',
|
|
262
|
-
'Go to cart',
|
|
263
|
-
'Is the website down?',
|
|
264
|
-
]
|
|
265
|
-
|
|
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
|
-
let passed = 0
|
|
271
|
-
let outOfScope = 0
|
|
272
|
-
|
|
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}`)
|
|
293
|
-
}
|
|
294
|
-
|
|
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}`)
|
|
309
|
-
}
|
|
310
|
-
|
|
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}`)
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
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`)
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
function cmdRun() {
|
|
327
|
-
header()
|
|
328
|
-
const query = args[1]
|
|
329
|
-
const debug = flags.includes('--debug')
|
|
330
|
-
const manifestPath = getFlag('--manifest') ?? 'manifest.json'
|
|
331
|
-
|
|
332
|
-
if (!query) {
|
|
333
|
-
log.error('Please provide a query. Example: node bin/capman.js run "show me articles"')
|
|
334
|
-
process.exit(1)
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
const { readManifest, match } = requireSrc()
|
|
338
|
-
|
|
339
|
-
let manifest
|
|
340
|
-
try {
|
|
341
|
-
manifest = readManifest(manifestPath)
|
|
342
|
-
} catch (e) {
|
|
343
|
-
log.error(e.message)
|
|
344
|
-
process.exit(1)
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
log.info(`Query: "${query}"`)
|
|
348
|
-
log.blank()
|
|
349
|
-
|
|
350
|
-
const result = match(query, manifest)
|
|
351
|
-
|
|
352
|
-
if (result.capability) {
|
|
353
|
-
console.log(` ${c.green}✓${c.reset} Matched: ${c.bold}${result.capability.id}${c.reset}`)
|
|
354
|
-
console.log(` Intent: ${result.intent}`)
|
|
355
|
-
console.log(` Confidence: ${result.confidence}%`)
|
|
356
|
-
console.log(` Resolver: ${result.capability.resolver.type}`)
|
|
357
|
-
|
|
358
|
-
if (Object.keys(result.extractedParams).length > 0) {
|
|
359
|
-
const params = Object.entries(result.extractedParams)
|
|
360
|
-
.map(([k, v]) => `${k}=${v}`)
|
|
361
|
-
.join(', ')
|
|
362
|
-
console.log(` Params: ${params}`)
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
if (debug && result.candidates?.length) {
|
|
366
|
-
log.blank()
|
|
367
|
-
console.log(` ${c.gray}── All candidates:${c.reset}`)
|
|
368
|
-
result.candidates
|
|
369
|
-
.sort((a, b) => b.score - a.score)
|
|
370
|
-
.forEach(c2 => {
|
|
371
|
-
const marker = c2.matched ? c.green + '✓' : c.gray + '○'
|
|
372
|
-
console.log(` ${marker}${c.reset} ${c2.capabilityId}: ${c2.score}%`)
|
|
373
|
-
})
|
|
374
|
-
}
|
|
375
|
-
} else {
|
|
376
|
-
console.log(` ${c.yellow}○${c.reset} OUT_OF_SCOPE — no capability matched`)
|
|
377
|
-
console.log(` ${c.gray}${result.reasoning}${c.reset}`)
|
|
378
|
-
|
|
379
|
-
if (debug && result.candidates?.length) {
|
|
380
|
-
log.blank()
|
|
381
|
-
console.log(` ${c.gray}── All candidates:${c.reset}`)
|
|
382
|
-
result.candidates
|
|
383
|
-
.sort((a, b) => b.score - a.score)
|
|
384
|
-
.slice(0, 5)
|
|
385
|
-
.forEach(c2 => {
|
|
386
|
-
console.log(` ${c.gray}○ ${c2.capabilityId}: ${c2.score}%${c.reset}`)
|
|
387
|
-
})
|
|
388
|
-
}
|
|
389
|
-
}
|
|
390
|
-
console.log()
|
|
391
|
-
}
|
|
392
|
-
|
|
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
|
-
}
|
|
4
|
+
const { command, header, log, c } = require('./lib/shared')
|
|
5
|
+
|
|
6
|
+
;(async () => {
|
|
7
|
+
switch (command) {
|
|
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
|
|
15
|
+
case undefined:
|
|
16
|
+
case '--help':
|
|
17
|
+
case '-h': require('./lib/cmd-help')(); break
|
|
18
|
+
default:
|
|
19
|
+
header()
|
|
20
|
+
log.error(`Unknown command: ${command}`)
|
|
21
|
+
console.log(` Run: ${c.teal}capman --help${c.reset}\n`)
|
|
22
|
+
process.exit(1)
|
|
23
|
+
}
|
|
24
|
+
})()
|