capman 0.4.2 → 0.4.4

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 (61) hide show
  1. package/CHANGELOG.md +153 -0
  2. package/CODEBASE.md +393 -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 +77 -0
  14. package/dist/cjs/cache.d.ts.map +1 -1
  15. package/dist/cjs/cache.js +8 -2
  16. package/dist/cjs/cache.js.map +1 -1
  17. package/dist/cjs/engine.d.ts +58 -1
  18. package/dist/cjs/engine.d.ts.map +1 -1
  19. package/dist/cjs/engine.js +312 -12
  20. package/dist/cjs/engine.js.map +1 -1
  21. package/dist/cjs/generator.d.ts.map +1 -1
  22. package/dist/cjs/generator.js +4 -0
  23. package/dist/cjs/generator.js.map +1 -1
  24. package/dist/cjs/index.d.ts +1 -1
  25. package/dist/cjs/index.d.ts.map +1 -1
  26. package/dist/cjs/index.js.map +1 -1
  27. package/dist/cjs/learning.d.ts.map +1 -1
  28. package/dist/cjs/learning.js +7 -2
  29. package/dist/cjs/learning.js.map +1 -1
  30. package/dist/cjs/matcher.d.ts.map +1 -1
  31. package/dist/cjs/matcher.js +23 -27
  32. package/dist/cjs/matcher.js.map +1 -1
  33. package/dist/cjs/parser.js +2 -1
  34. package/dist/cjs/parser.js.map +1 -1
  35. package/dist/cjs/resolver.js +6 -2
  36. package/dist/cjs/resolver.js.map +1 -1
  37. package/dist/cjs/types.d.ts +27 -0
  38. package/dist/cjs/types.d.ts.map +1 -1
  39. package/dist/cjs/version.d.ts +1 -1
  40. package/dist/cjs/version.js +1 -1
  41. package/dist/esm/cache.d.ts +49 -0
  42. package/dist/esm/cache.js +8 -2
  43. package/dist/esm/engine.d.ts +138 -0
  44. package/dist/esm/engine.js +312 -12
  45. package/dist/esm/generator.d.ts +7 -0
  46. package/dist/esm/generator.js +4 -0
  47. package/dist/esm/index.d.ts +47 -0
  48. package/dist/esm/learning.d.ts +55 -0
  49. package/dist/esm/learning.js +7 -2
  50. package/dist/esm/logger.d.ts +21 -0
  51. package/dist/esm/matcher.d.ts +6 -0
  52. package/dist/esm/matcher.js +23 -27
  53. package/dist/esm/parser.d.ts +10 -0
  54. package/dist/esm/parser.js +2 -1
  55. package/dist/esm/resolver.d.ts +21 -0
  56. package/dist/esm/resolver.js +6 -2
  57. package/dist/esm/schema.d.ts +740 -0
  58. package/dist/esm/types.d.ts +136 -0
  59. package/dist/esm/version.d.ts +1 -0
  60. package/dist/esm/version.js +1 -1
  61. package/package.json +5 -3
@@ -0,0 +1,21 @@
1
+ export type LogLevel = 'silent' | 'error' | 'warn' | 'info' | 'debug';
2
+ export declare class Logger {
3
+ private level;
4
+ constructor(level?: LogLevel);
5
+ setLevel(level: LogLevel): void;
6
+ error(msg: string, ...args: unknown[]): void;
7
+ warn(msg: string, ...args: unknown[]): void;
8
+ info(msg: string, ...args: unknown[]): void;
9
+ debug(msg: string, ...args: unknown[]): void;
10
+ }
11
+ export declare const logger: Logger;
12
+ /**
13
+ * Set the global log level for capman.
14
+ *
15
+ * @example
16
+ * import { setLogLevel } from 'capman'
17
+ * setLogLevel('debug') // see everything
18
+ * setLogLevel('info') // see key steps
19
+ * setLogLevel('silent') // no output (default)
20
+ */
21
+ export declare function setLogLevel(level: LogLevel): void;
@@ -0,0 +1,6 @@
1
+ import type { Manifest, MatchResult } from './types';
2
+ export declare function match(query: string, manifest: Manifest): MatchResult;
3
+ export interface LLMMatcherOptions {
4
+ llm: (prompt: string) => Promise<string>;
5
+ }
6
+ export declare function matchWithLLM(query: string, manifest: Manifest, options: LLMMatcherOptions): Promise<MatchResult>;
@@ -76,9 +76,6 @@ function extractParams(query, cap) {
76
76
  // e.g. "profile for johndoe" → johndoe
77
77
  // "articles by jane" → jane
78
78
  // "tag javascript" → javascript
79
- // Use param name and description as hints for what to look for
80
- const paramHints = [param.name, ...param.description.toLowerCase().split(/\s+/)]
81
- .filter(w => w.length > 2);
82
79
  // Try keyword-based extraction first
83
80
  const keywords = [
84
81
  `for `, `by `, `about `, `named `, `called `,
@@ -203,29 +200,28 @@ export async function matchWithLLM(query, manifest, options) {
203
200
  "reasoning": "<one sentence>",
204
201
  "extracted_params": { "<param_name>": "<value or null>" }
205
202
  }`;
206
- try {
207
- const raw = await options.llm(prompt);
208
- const clean = raw.replace(/```json|```/g, '').trim();
209
- const parsed = JSON.parse(clean);
210
- const isOOS = parsed.matched_capability === 'OUT_OF_SCOPE';
211
- const capability = isOOS
212
- ? null
213
- : manifest.capabilities.find(c => c.id === parsed.matched_capability) ?? null;
214
- return {
215
- capability,
216
- confidence: parsed.confidence,
217
- intent: isOOS ? 'out_of_scope' : parsed.intent,
218
- extractedParams: parsed.extracted_params ?? {},
219
- reasoning: parsed.reasoning,
220
- candidates: capability ? [{
221
- capabilityId: capability.id,
222
- score: parsed.confidence,
223
- matched: true,
224
- }] : [],
225
- };
226
- }
227
- catch (err) {
228
- logger.warn(`LLM match failed, falling back to keyword matcher: ${err}`);
229
- return match(query, manifest);
203
+ const raw = await options.llm(prompt);
204
+ const clean = raw.replace(/```json|```/g, '').trim();
205
+ const parsed = JSON.parse(clean);
206
+ const isOOS = parsed.matched_capability === 'OUT_OF_SCOPE';
207
+ const capability = isOOS
208
+ ? null
209
+ : manifest.capabilities.find(c => c.id === parsed.matched_capability) ?? null;
210
+ // If LLM returned an unknown capability ID, treat as out of scope
211
+ const effectivelyOOS = isOOS || capability === null;
212
+ if (!effectivelyOOS && capability === null) {
213
+ logger.warn(`LLM returned unknown capability ID: "${parsed.matched_capability}" — treating as out_of_scope`);
230
214
  }
215
+ return {
216
+ capability,
217
+ confidence: effectivelyOOS ? 0 : parsed.confidence,
218
+ intent: effectivelyOOS ? 'out_of_scope' : parsed.intent,
219
+ extractedParams: parsed.extracted_params ?? {},
220
+ reasoning: parsed.reasoning ?? 'No reasoning provided',
221
+ candidates: capability ? [{
222
+ capabilityId: capability.id,
223
+ score: parsed.confidence,
224
+ matched: true,
225
+ }] : [],
226
+ };
231
227
  }
@@ -0,0 +1,10 @@
1
+ import type { CapmanConfig } from './types';
2
+ export interface ParseResult {
3
+ config: CapmanConfig;
4
+ stats: {
5
+ total: number;
6
+ skipped: number;
7
+ warnings: string[];
8
+ };
9
+ }
10
+ export declare function parseOpenAPI(specPathOrUrl: string): Promise<ParseResult>;
@@ -245,7 +245,8 @@ function toSnakeCase(str) {
245
245
  .replace(/[-\s]+/g, '_')
246
246
  .toLowerCase()
247
247
  .replace(/^_/, '')
248
- .replace(/__+/g, '_');
248
+ .replace(/__+/g, '_')
249
+ .replace(/_$/, '');
249
250
  }
250
251
  function toHumanName(id) {
251
252
  return id
@@ -0,0 +1,21 @@
1
+ import type { MatchResult, ResolveResult } from './types';
2
+ export interface AuthContext {
3
+ /** Whether the current request is authenticated */
4
+ isAuthenticated: boolean;
5
+ /** Current user's role */
6
+ role?: 'user' | 'admin';
7
+ /** Current user's ID — injected into session params */
8
+ userId?: string;
9
+ }
10
+ export interface ResolveOptions {
11
+ baseUrl?: string;
12
+ fetch?: typeof globalThis.fetch;
13
+ dryRun?: boolean;
14
+ headers?: Record<string, string>;
15
+ auth?: AuthContext;
16
+ /** Number of retries on failure (default: 0) */
17
+ retries?: number;
18
+ /** Timeout in milliseconds (default: 5000) */
19
+ timeoutMs?: number;
20
+ }
21
+ export declare function resolve(matchResult: MatchResult, params?: Record<string, unknown>, options?: ResolveOptions): Promise<ResolveResult>;
@@ -43,7 +43,7 @@ export async function resolve(matchResult, params = {}, options = {}) {
43
43
  // ── Session param injection ───────────────────────────────────────────────
44
44
  // Inject auth.userId into any params marked as source: 'session'
45
45
  const enrichedParams = { ...params };
46
- if (options.auth?.userId) {
46
+ if (options.auth?.userId !== undefined) {
47
47
  for (const param of capability.params) {
48
48
  if (param.source === 'session') {
49
49
  enrichedParams[param.name] = options.auth.userId;
@@ -172,7 +172,9 @@ async function resolveApi(resolver, params, options) {
172
172
  function resolveNav(resolver, params) {
173
173
  let destination = resolver.destination;
174
174
  for (const [key, value] of Object.entries(params)) {
175
- destination = destination.replace(`{${key}}`, String(value));
175
+ if (value === null || value === undefined)
176
+ continue;
177
+ destination = destination.replace(`{${key}}`, encodeURIComponent(String(value)));
176
178
  }
177
179
  return { success: true, resolverType: 'nav', navTarget: destination };
178
180
  }
@@ -180,6 +182,8 @@ function buildUrl(baseUrl, urlPath, params) {
180
182
  let resolved = urlPath;
181
183
  const unused = {};
182
184
  for (const [key, value] of Object.entries(params)) {
185
+ if (value === null || value === undefined)
186
+ continue; // never write null into URLs
183
187
  if (resolved.includes(`{${key}}`)) {
184
188
  resolved = resolved.replace(`{${key}}`, encodeURIComponent(String(value)));
185
189
  }