capman 0.5.1 → 0.5.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.
Files changed (52) hide show
  1. package/CHANGELOG.md +29 -0
  2. package/bin/lib/cmd-demo.js +2 -2
  3. package/dist/cjs/cache.d.ts +2 -0
  4. package/dist/cjs/cache.d.ts.map +1 -1
  5. package/dist/cjs/cache.js +31 -14
  6. package/dist/cjs/cache.js.map +1 -1
  7. package/dist/cjs/engine.d.ts +3 -1
  8. package/dist/cjs/engine.d.ts.map +1 -1
  9. package/dist/cjs/engine.js +74 -23
  10. package/dist/cjs/engine.js.map +1 -1
  11. package/dist/cjs/index.d.ts +3 -2
  12. package/dist/cjs/index.d.ts.map +1 -1
  13. package/dist/cjs/index.js +3 -1
  14. package/dist/cjs/index.js.map +1 -1
  15. package/dist/cjs/learning.d.ts +0 -1
  16. package/dist/cjs/learning.d.ts.map +1 -1
  17. package/dist/cjs/learning.js +34 -17
  18. package/dist/cjs/learning.js.map +1 -1
  19. package/dist/cjs/matcher.d.ts +4 -1
  20. package/dist/cjs/matcher.d.ts.map +1 -1
  21. package/dist/cjs/matcher.js +31 -12
  22. package/dist/cjs/matcher.js.map +1 -1
  23. package/dist/cjs/parser.js +2 -2
  24. package/dist/cjs/parser.js.map +1 -1
  25. package/dist/cjs/resolver.js +24 -4
  26. package/dist/cjs/resolver.js.map +1 -1
  27. package/dist/cjs/schema.d.ts +14 -14
  28. package/dist/cjs/schema.d.ts.map +1 -1
  29. package/dist/cjs/schema.js +4 -2
  30. package/dist/cjs/schema.js.map +1 -1
  31. package/dist/cjs/types.d.ts +1 -0
  32. package/dist/cjs/types.d.ts.map +1 -1
  33. package/dist/cjs/version.d.ts +1 -1
  34. package/dist/cjs/version.js +1 -1
  35. package/dist/esm/cache.d.ts +2 -0
  36. package/dist/esm/cache.js +31 -14
  37. package/dist/esm/engine.d.ts +3 -1
  38. package/dist/esm/engine.js +75 -24
  39. package/dist/esm/index.d.ts +3 -2
  40. package/dist/esm/index.js +1 -0
  41. package/dist/esm/learning.d.ts +0 -1
  42. package/dist/esm/learning.js +34 -17
  43. package/dist/esm/matcher.d.ts +4 -1
  44. package/dist/esm/matcher.js +29 -11
  45. package/dist/esm/parser.js +2 -2
  46. package/dist/esm/resolver.js +24 -4
  47. package/dist/esm/schema.d.ts +14 -14
  48. package/dist/esm/schema.js +4 -2
  49. package/dist/esm/types.d.ts +1 -0
  50. package/dist/esm/version.d.ts +1 -1
  51. package/dist/esm/version.js +1 -1
  52. package/package.json +1 -1
@@ -61,7 +61,6 @@ export declare class MemoryLearningStore implements LearningStore {
61
61
  getIndex(): Promise<Record<string, Record<string, number>>>;
62
62
  private updateIndex;
63
63
  private subtractFromIndex;
64
- private rebuildIndex;
65
64
  getTopCapabilities(limit?: number): Promise<Array<{
66
65
  id: string;
67
66
  hits: number;
@@ -1,7 +1,7 @@
1
1
  import * as fs from 'fs';
2
2
  import * as path from 'path';
3
3
  import { logger } from './logger';
4
- const MAX_LEARNING_ENTRIES = 10000;
4
+ const MAX_LEARNING_ENTRIES = 10_000;
5
5
  import { STOPWORDS } from './matcher';
6
6
  // ─── Shared computation helpers ───────────────────────────────────────────────
7
7
  function computeStats(entries) {
@@ -55,11 +55,17 @@ export class FileLearningStore {
55
55
  this.statsCounter = {
56
56
  totalQueries: 0, llmQueries: 0, cacheHits: 0, outOfScope: 0,
57
57
  };
58
- this.filePath = path.resolve(process.cwd(), filePath);
58
+ const cwd = process.cwd();
59
+ const resolved = path.resolve(cwd, filePath);
60
+ const allowedPrefix = cwd === '/' ? '/' : cwd + path.sep;
61
+ if (!resolved.startsWith(allowedPrefix)) {
62
+ throw new Error(`FileCache path "${filePath}" resolves outside the working directory.\n` +
63
+ `Resolved: ${resolved}\nAllowed: ${cwd}`);
64
+ }
65
+ this.filePath = resolved;
59
66
  logger.info(`FileLearningStore initialized — writing to: ${this.filePath}`);
60
67
  }
61
68
  updateIndex(entry) {
62
- var _a;
63
69
  this.statsCounter.totalQueries++;
64
70
  if (entry.resolvedVia === 'llm')
65
71
  this.statsCounter.llmQueries++;
@@ -72,7 +78,7 @@ export class FileLearningStore {
72
78
  .split(/\W+/)
73
79
  .filter(w => w.length > 2 && !STOPWORDS.has(w));
74
80
  for (const word of words) {
75
- (_a = this.index)[word] ?? (_a[word] = {});
81
+ this.index[word] ??= {};
76
82
  this.index[word][entry.capabilityId] =
77
83
  (this.index[word][entry.capabilityId] ?? 0) + 1;
78
84
  }
@@ -155,8 +161,19 @@ export class FileLearningStore {
155
161
  }
156
162
  async record(entry) {
157
163
  await this.load();
158
- this.entries.push(entry);
159
- this.updateIndex(entry);
164
+ // Store only tokenized keywords — never raw query text.
165
+ // Raw queries may contain PII (emails, names, order IDs) that should
166
+ // not be persisted to disk under GDPR/CCPA data retention requirements.
167
+ const sanitized = {
168
+ ...entry,
169
+ query: entry.query
170
+ .toLowerCase()
171
+ .split(/\W+/)
172
+ .filter(w => w.length > 2 && !STOPWORDS.has(w))
173
+ .join(' '),
174
+ };
175
+ this.entries.push(sanitized);
176
+ this.updateIndex(sanitized);
160
177
  if (this.entries.length > MAX_LEARNING_ENTRIES) {
161
178
  const excess = this.entries.length - MAX_LEARNING_ENTRIES;
162
179
  const pruned = this.entries.splice(0, excess);
@@ -197,8 +214,16 @@ export class MemoryLearningStore {
197
214
  };
198
215
  }
199
216
  async record(entry) {
200
- this.entries.push(entry);
201
- this.updateIndex(entry);
217
+ const sanitized = {
218
+ ...entry,
219
+ query: entry.query
220
+ .toLowerCase()
221
+ .split(/\W+/)
222
+ .filter(w => w.length > 2 && !STOPWORDS.has(w))
223
+ .join(' '),
224
+ };
225
+ this.entries.push(sanitized);
226
+ this.updateIndex(sanitized);
202
227
  if (this.entries.length > MAX_LEARNING_ENTRIES) {
203
228
  const excess = this.entries.length - MAX_LEARNING_ENTRIES;
204
229
  const pruned = this.entries.splice(0, excess);
@@ -214,7 +239,6 @@ export class MemoryLearningStore {
214
239
  return structuredClone(this.index);
215
240
  }
216
241
  updateIndex(entry) {
217
- var _a;
218
242
  this.statsCounter.totalQueries++;
219
243
  if (entry.resolvedVia === 'llm')
220
244
  this.statsCounter.llmQueries++;
@@ -227,7 +251,7 @@ export class MemoryLearningStore {
227
251
  .split(/\W+/)
228
252
  .filter(w => w.length > 2 && !STOPWORDS.has(w));
229
253
  for (const word of words) {
230
- (_a = this.index)[word] ?? (_a[word] = {});
254
+ this.index[word] ??= {};
231
255
  this.index[word][entry.capabilityId] =
232
256
  (this.index[word][entry.capabilityId] ?? 0) + 1;
233
257
  }
@@ -264,13 +288,6 @@ export class MemoryLearningStore {
264
288
  }
265
289
  }
266
290
  }
267
- rebuildIndex() {
268
- this.index = {};
269
- this.statsCounter = { totalQueries: 0, llmQueries: 0, cacheHits: 0, outOfScope: 0 };
270
- for (const entry of this.entries) {
271
- this.updateIndex(entry);
272
- }
273
- }
274
291
  async getTopCapabilities(limit = 5) {
275
292
  return computeTopCapabilities(this.entries, limit);
276
293
  }
@@ -1,4 +1,7 @@
1
- import type { Capability, Manifest, MatchResult } from './types';
1
+ import type { Manifest, Capability, MatchResult } from './types';
2
+ export declare class LLMParseError extends Error {
3
+ constructor(message: string);
4
+ }
2
5
  export declare const STOPWORDS: Set<string>;
3
6
  export declare function resolverToIntent(cap: Capability): MatchResult['intent'];
4
7
  /**
@@ -1,4 +1,11 @@
1
1
  import { logger } from './logger';
2
+ // ─── Typed error for LLM parse failures ──────────────────────────────────────
3
+ export class LLMParseError extends Error {
4
+ constructor(message) {
5
+ super(message);
6
+ this.name = 'LLMParseError';
7
+ }
8
+ }
2
9
  export const STOPWORDS = new Set([
3
10
  'show', 'me', 'the', 'get', 'find', 'fetch', 'give', 'please',
4
11
  'can', 'you', 'i', 'want', 'to', 'a', 'an', 'my', 'our', 'your',
@@ -65,7 +72,7 @@ export function extractParams(query, cap) {
65
72
  for (const param of cap.params) {
66
73
  // Session params come from auth context, not query
67
74
  if (param.source === 'session') {
68
- result[param.name] = '[from_session]';
75
+ result[param.name] = null; // injected by resolver from auth context — not extracted from query
69
76
  continue;
70
77
  }
71
78
  if (param.source !== 'user_query') {
@@ -197,7 +204,13 @@ export function match(query, manifest) {
197
204
  * fields can influence LLM routing decisions.
198
205
  */
199
206
  export async function matchWithLLM(query, manifest, options) {
200
- const manifestSummary = manifest.capabilities.map(c => `- ${c.id} (${c.resolver.type}): ${c.description}${c.examples?.length ? `\n examples: ${c.examples.slice(0, 2).join(', ')}` : ''}`).join('\n');
207
+ // Truncate description and examples prevents context window overflow and
208
+ // reduces prompt injection surface from third-party OpenAPI spec content.
209
+ const MAX_DESC_LEN = 200;
210
+ const MAX_EXAMPLE_LEN = 100;
211
+ const manifestSummary = manifest.capabilities.map(c => `- ${c.id} (${c.resolver.type}): ${c.description.slice(0, MAX_DESC_LEN)}${c.description.length > MAX_DESC_LEN ? '…' : ''}${c.examples?.length
212
+ ? `\n examples: ${c.examples.slice(0, 2).map(e => e.slice(0, MAX_EXAMPLE_LEN)).join(', ')}`
213
+ : ''}`).join('\n');
201
214
  const prompt = `You are an intent matcher for an AI agent system.
202
215
 
203
216
  App: ${manifest.app}
@@ -228,13 +241,13 @@ ${JSON.stringify({ user_query: query })}
228
241
  parsed = JSON.parse(clean);
229
242
  }
230
243
  catch {
231
- throw new Error(`LLM_PARSE_ERROR: LLM returned invalid JSON. First 300 chars: ${clean.slice(0, 300)}`);
244
+ throw new LLMParseError(`LLM returned invalid JSON. First 300 chars: ${clean.slice(0, 300)}`);
232
245
  }
233
246
  if (typeof parsed.matched_capability !== 'string') {
234
- throw new Error(`LLM_PARSE_ERROR: missing "matched_capability" field in response`);
247
+ throw new LLMParseError(`missing "matched_capability" field in response`);
235
248
  }
236
249
  if (typeof parsed.confidence !== 'number') {
237
- throw new Error(`LLM_PARSE_ERROR: missing numeric "confidence" field in response`);
250
+ throw new LLMParseError(`missing numeric "confidence" field in response`);
238
251
  }
239
252
  const isOOS = parsed.matched_capability === 'OUT_OF_SCOPE';
240
253
  const capability = isOOS
@@ -245,16 +258,21 @@ ${JSON.stringify({ user_query: query })}
245
258
  if (!isOOS && capability === null) {
246
259
  logger.warn(`LLM returned unknown capability ID: "${parsed.matched_capability}" — treating as out_of_scope`);
247
260
  }
261
+ // Build full candidate list — all capabilities scored, LLM winner marked as matched.
262
+ // This aligns the shape with keyword match results and allows the learning boost
263
+ // to surface alternatives if the LLM made a wrong call.
264
+ const llmConfidence = effectivelyOOS ? 0 : parsed.confidence;
265
+ const allCandidates = manifest.capabilities.map(c => ({
266
+ capabilityId: c.id,
267
+ score: c.id === capability?.id ? llmConfidence : 0,
268
+ matched: c.id === capability?.id,
269
+ }));
248
270
  return {
249
271
  capability,
250
- confidence: effectivelyOOS ? 0 : parsed.confidence,
272
+ confidence: llmConfidence,
251
273
  intent: effectivelyOOS ? 'out_of_scope' : parsed.intent,
252
274
  extractedParams: (parsed.extracted_params ?? {}),
253
275
  reasoning: parsed.reasoning ?? 'No reasoning provided',
254
- candidates: capability ? [{
255
- capabilityId: capability.id,
256
- score: parsed.confidence,
257
- matched: true,
258
- }] : [],
276
+ candidates: allCandidates,
259
277
  };
260
278
  }
@@ -11,7 +11,7 @@ async function loadSpec(source) {
11
11
  if (source.startsWith('http://') || source.startsWith('https://')) {
12
12
  logger.info(`Fetching OpenAPI spec from: ${source}`);
13
13
  const controller = new AbortController();
14
- const timer = setTimeout(() => controller.abort(), 10000);
14
+ const timer = setTimeout(() => controller.abort(), 10_000);
15
15
  // eslint-disable-next-line prefer-const
16
16
  let res;
17
17
  try {
@@ -36,7 +36,7 @@ async function loadSpec(source) {
36
36
  throw new Error(`Spec file not found: ${resolved}`);
37
37
  }
38
38
  logger.info(`Reading OpenAPI spec from: ${resolved}`);
39
- const text = fs.readFileSync(resolved, 'utf-8');
39
+ const text = await fs.promises.readFile(resolved, 'utf-8');
40
40
  return parseSpecText(text, source);
41
41
  }
42
42
  function parseSpecText(text, source) {
@@ -95,6 +95,20 @@ export async function resolve(matchResult, params = {}, options = {}) {
95
95
  };
96
96
  }
97
97
  }
98
+ /**
99
+ * Resolves an API capability by executing all configured endpoints.
100
+ *
101
+ * ⚠️ PARALLEL EXECUTION: All endpoints are fired simultaneously via Promise.all().
102
+ * If any endpoint fails, the entire result is marked as failed and partial results
103
+ * are discarded — but side effects from successful endpoints cannot be rolled back.
104
+ *
105
+ * Example: a capability with two endpoints [POST /reserve, POST /confirm] will
106
+ * fire both in parallel. If /confirm fails after /reserve succeeded, the reservation
107
+ * exists but the caller receives success: false with no indication that /reserve ran.
108
+ *
109
+ * For capabilities where ordering or rollback matters, define separate capabilities
110
+ * with single endpoints and orchestrate them at the application layer.
111
+ */
98
112
  async function resolveApi(resolver, params, options) {
99
113
  const startTime = Date.now();
100
114
  const retries = options.retries ?? 0;
@@ -102,7 +116,7 @@ async function resolveApi(resolver, params, options) {
102
116
  const apiCalls = resolver.endpoints.map(endpoint => ({
103
117
  method: endpoint.method,
104
118
  url: buildUrl(options.baseUrl ?? '', endpoint.path, params),
105
- params,
119
+ params: Object.fromEntries(Object.entries(params).filter(([, v]) => v !== null && v !== undefined)),
106
120
  }));
107
121
  if (options.dryRun) {
108
122
  return { success: true, resolverType: 'api', apiCalls, durationMs: Date.now() - startTime };
@@ -127,7 +141,7 @@ async function resolveApi(resolver, params, options) {
127
141
  headers: options.headers ?? {},
128
142
  signal: controller.signal,
129
143
  body: ['POST', 'PUT', 'PATCH'].includes(call.method)
130
- ? JSON.stringify(call.params)
144
+ ? JSON.stringify(Object.fromEntries(Object.entries(call.params).filter(([, v]) => v !== null && v !== undefined)))
131
145
  : undefined,
132
146
  });
133
147
  clearTimeout(timer);
@@ -191,10 +205,16 @@ function resolveNav(resolver, params) {
191
205
  continue;
192
206
  const str = String(value);
193
207
  validateNavParam(key, str);
194
- destination = destination.replace(`{${key}}`, encodeURIComponent(str));
208
+ destination = destination.replaceAll(`{${key}}`, encodeURIComponent(str));
195
209
  }
196
210
  return { success: true, resolverType: 'nav', navTarget: destination };
197
211
  }
212
+ // Note: buildUrl does not validate param values against an allowlist.
213
+ // resolveNav() does validate via validateNavParam() because nav destinations
214
+ // are used as deep links where path traversal is a real risk.
215
+ // For API URLs, extractParams() strips most dangerous characters upstream,
216
+ // so the practical risk is low — but any future caller bypassing extractParams
217
+ // should add validation here too.
198
218
  function buildUrl(baseUrl, urlPath, params) {
199
219
  let resolved = urlPath;
200
220
  const unused = {};
@@ -202,7 +222,7 @@ function buildUrl(baseUrl, urlPath, params) {
202
222
  if (value === null || value === undefined)
203
223
  continue; // never write null into URLs
204
224
  if (resolved.includes(`{${key}}`)) {
205
- resolved = resolved.replace(`{${key}}`, encodeURIComponent(String(value)));
225
+ resolved = resolved.replaceAll(`{${key}}`, encodeURIComponent(String(value)));
206
226
  }
207
227
  else {
208
228
  unused[key] = value;
@@ -108,6 +108,7 @@ export declare const CapmanConfigSchema: z.ZodObject<{
108
108
  hint?: string | undefined;
109
109
  }>;
110
110
  }, "strip", z.ZodTypeAny, {
111
+ type: "hybrid";
111
112
  api: {
112
113
  endpoints: {
113
114
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -119,8 +120,8 @@ export declare const CapmanConfigSchema: z.ZodObject<{
119
120
  destination: string;
120
121
  hint?: string | undefined;
121
122
  };
122
- type: "hybrid";
123
123
  }, {
124
+ type: "hybrid";
124
125
  api: {
125
126
  endpoints: {
126
127
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -132,7 +133,6 @@ export declare const CapmanConfigSchema: z.ZodObject<{
132
133
  destination: string;
133
134
  hint?: string | undefined;
134
135
  };
135
- type: "hybrid";
136
136
  }>]>;
137
137
  privacy: z.ZodObject<{
138
138
  level: z.ZodEnum<["public", "user_owned", "admin"]>;
@@ -168,6 +168,7 @@ export declare const CapmanConfigSchema: z.ZodObject<{
168
168
  destination: string;
169
169
  hint?: string | undefined;
170
170
  } | {
171
+ type: "hybrid";
171
172
  api: {
172
173
  endpoints: {
173
174
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -179,7 +180,6 @@ export declare const CapmanConfigSchema: z.ZodObject<{
179
180
  destination: string;
180
181
  hint?: string | undefined;
181
182
  };
182
- type: "hybrid";
183
183
  };
184
184
  privacy: {
185
185
  level: "public" | "user_owned" | "admin";
@@ -210,6 +210,7 @@ export declare const CapmanConfigSchema: z.ZodObject<{
210
210
  destination: string;
211
211
  hint?: string | undefined;
212
212
  } | {
213
+ type: "hybrid";
213
214
  api: {
214
215
  endpoints: {
215
216
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -221,7 +222,6 @@ export declare const CapmanConfigSchema: z.ZodObject<{
221
222
  destination: string;
222
223
  hint?: string | undefined;
223
224
  };
224
- type: "hybrid";
225
225
  };
226
226
  privacy: {
227
227
  level: "public" | "user_owned" | "admin";
@@ -252,6 +252,7 @@ export declare const CapmanConfigSchema: z.ZodObject<{
252
252
  destination: string;
253
253
  hint?: string | undefined;
254
254
  } | {
255
+ type: "hybrid";
255
256
  api: {
256
257
  endpoints: {
257
258
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -263,7 +264,6 @@ export declare const CapmanConfigSchema: z.ZodObject<{
263
264
  destination: string;
264
265
  hint?: string | undefined;
265
266
  };
266
- type: "hybrid";
267
267
  };
268
268
  privacy: {
269
269
  level: "public" | "user_owned" | "admin";
@@ -294,6 +294,7 @@ export declare const CapmanConfigSchema: z.ZodObject<{
294
294
  destination: string;
295
295
  hint?: string | undefined;
296
296
  } | {
297
+ type: "hybrid";
297
298
  api: {
298
299
  endpoints: {
299
300
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -305,7 +306,6 @@ export declare const CapmanConfigSchema: z.ZodObject<{
305
306
  destination: string;
306
307
  hint?: string | undefined;
307
308
  };
308
- type: "hybrid";
309
309
  };
310
310
  privacy: {
311
311
  level: "public" | "user_owned" | "admin";
@@ -339,6 +339,7 @@ export declare const CapmanConfigSchema: z.ZodObject<{
339
339
  destination: string;
340
340
  hint?: string | undefined;
341
341
  } | {
342
+ type: "hybrid";
342
343
  api: {
343
344
  endpoints: {
344
345
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -350,7 +351,6 @@ export declare const CapmanConfigSchema: z.ZodObject<{
350
351
  destination: string;
351
352
  hint?: string | undefined;
352
353
  };
353
- type: "hybrid";
354
354
  };
355
355
  privacy: {
356
356
  level: "public" | "user_owned" | "admin";
@@ -385,6 +385,7 @@ export declare const CapmanConfigSchema: z.ZodObject<{
385
385
  destination: string;
386
386
  hint?: string | undefined;
387
387
  } | {
388
+ type: "hybrid";
388
389
  api: {
389
390
  endpoints: {
390
391
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -396,7 +397,6 @@ export declare const CapmanConfigSchema: z.ZodObject<{
396
397
  destination: string;
397
398
  hint?: string | undefined;
398
399
  };
399
- type: "hybrid";
400
400
  };
401
401
  privacy: {
402
402
  level: "public" | "user_owned" | "admin";
@@ -516,6 +516,7 @@ export declare const ManifestSchema: z.ZodObject<{
516
516
  hint?: string | undefined;
517
517
  }>;
518
518
  }, "strip", z.ZodTypeAny, {
519
+ type: "hybrid";
519
520
  api: {
520
521
  endpoints: {
521
522
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -527,8 +528,8 @@ export declare const ManifestSchema: z.ZodObject<{
527
528
  destination: string;
528
529
  hint?: string | undefined;
529
530
  };
530
- type: "hybrid";
531
531
  }, {
532
+ type: "hybrid";
532
533
  api: {
533
534
  endpoints: {
534
535
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -540,7 +541,6 @@ export declare const ManifestSchema: z.ZodObject<{
540
541
  destination: string;
541
542
  hint?: string | undefined;
542
543
  };
543
- type: "hybrid";
544
544
  }>]>;
545
545
  privacy: z.ZodObject<{
546
546
  level: z.ZodEnum<["public", "user_owned", "admin"]>;
@@ -576,6 +576,7 @@ export declare const ManifestSchema: z.ZodObject<{
576
576
  destination: string;
577
577
  hint?: string | undefined;
578
578
  } | {
579
+ type: "hybrid";
579
580
  api: {
580
581
  endpoints: {
581
582
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -587,7 +588,6 @@ export declare const ManifestSchema: z.ZodObject<{
587
588
  destination: string;
588
589
  hint?: string | undefined;
589
590
  };
590
- type: "hybrid";
591
591
  };
592
592
  privacy: {
593
593
  level: "public" | "user_owned" | "admin";
@@ -618,6 +618,7 @@ export declare const ManifestSchema: z.ZodObject<{
618
618
  destination: string;
619
619
  hint?: string | undefined;
620
620
  } | {
621
+ type: "hybrid";
621
622
  api: {
622
623
  endpoints: {
623
624
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -629,7 +630,6 @@ export declare const ManifestSchema: z.ZodObject<{
629
630
  destination: string;
630
631
  hint?: string | undefined;
631
632
  };
632
- type: "hybrid";
633
633
  };
634
634
  privacy: {
635
635
  level: "public" | "user_owned" | "admin";
@@ -664,6 +664,7 @@ export declare const ManifestSchema: z.ZodObject<{
664
664
  destination: string;
665
665
  hint?: string | undefined;
666
666
  } | {
667
+ type: "hybrid";
667
668
  api: {
668
669
  endpoints: {
669
670
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -675,7 +676,6 @@ export declare const ManifestSchema: z.ZodObject<{
675
676
  destination: string;
676
677
  hint?: string | undefined;
677
678
  };
678
- type: "hybrid";
679
679
  };
680
680
  privacy: {
681
681
  level: "public" | "user_owned" | "admin";
@@ -711,6 +711,7 @@ export declare const ManifestSchema: z.ZodObject<{
711
711
  destination: string;
712
712
  hint?: string | undefined;
713
713
  } | {
714
+ type: "hybrid";
714
715
  api: {
715
716
  endpoints: {
716
717
  method: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
@@ -722,7 +723,6 @@ export declare const ManifestSchema: z.ZodObject<{
722
723
  destination: string;
723
724
  hint?: string | undefined;
724
725
  };
725
- type: "hybrid";
726
726
  };
727
727
  privacy: {
728
728
  level: "public" | "user_owned" | "admin";
@@ -50,8 +50,10 @@ const CapabilitySchema = z.object({
50
50
  id: z.string().min(1, 'capability id is required')
51
51
  .regex(/^[a-z0-9_]+$/, 'id must be snake_case (lowercase, numbers, underscores only)'),
52
52
  name: z.string().min(1, 'capability name is required'),
53
- description: z.string().min(10, 'description must be at least 10 characters for accurate matching'),
54
- examples: z.array(z.string()).optional(),
53
+ description: z.string()
54
+ .min(10, 'description must be at least 10 characters for accurate matching')
55
+ .max(500, 'description must be 500 characters or fewer'),
56
+ examples: z.array(z.string().max(200, 'each example must be 200 characters or fewer')).optional(),
55
57
  params: z.array(CapabilityParamSchema),
56
58
  returns: z.array(z.string()),
57
59
  resolver: ResolverSchema,
@@ -134,3 +134,4 @@ export interface ExplainResult {
134
134
  resolvedVia: 'keyword' | 'llm';
135
135
  durationMs: number;
136
136
  }
137
+ export type MatchMode = 'cheap' | 'balanced' | 'accurate';
@@ -1 +1 @@
1
- export declare const VERSION = "0.5.1";
1
+ export declare const VERSION = "0.5.2";
@@ -1,2 +1,2 @@
1
1
  // Auto-generated by scripts/version.js — do not edit manually
2
- export const VERSION = '0.5.1';
2
+ export const VERSION = '0.5.2';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "capman",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "Capability Manifest Engine — let AI agents interact with your app without navigating the UI",
5
5
  "main": "./dist/cjs/index.js",
6
6
  "module": "./dist/esm/index.js",