explorbot 0.4.0 → 0.4.1

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 (73) hide show
  1. package/bin/explorbot-cli.ts +5 -3
  2. package/boat/api-tester/src/ai/chief.ts +7 -1
  3. package/boat/api-tester/src/ai/curler.ts +7 -1
  4. package/boat/api-tester/src/apibot.ts +10 -4
  5. package/boat/api-tester/src/cli.ts +12 -2
  6. package/boat/api-tester/src/config.ts +28 -8
  7. package/boat/doc-collector/bin/doc-collector-cli.ts +3 -2
  8. package/boat/doc-collector/src/cli.ts +3 -1
  9. package/boat/doc-collector/src/docbot.ts +1 -1
  10. package/boat/prima/bin/prima-cli.ts +2 -0
  11. package/boat/prima/src/cli.ts +2 -0
  12. package/dist/bin/explorbot-cli.js +5 -3
  13. package/dist/boat/api-tester/bin/apibot-cli.js +3 -2
  14. package/dist/boat/api-tester/src/ai/chief.js +6 -1
  15. package/dist/boat/api-tester/src/ai/curler.js +6 -1
  16. package/dist/boat/api-tester/src/apibot.js +7 -3
  17. package/dist/boat/api-tester/src/cli.js +12 -2
  18. package/dist/boat/api-tester/src/config.js +31 -8
  19. package/dist/boat/doc-collector/bin/doc-collector-cli.js +3 -2
  20. package/dist/boat/doc-collector/src/cli.js +3 -1
  21. package/dist/boat/doc-collector/src/docbot.js +1 -1
  22. package/dist/boat/prima/bin/prima-cli.js +2 -0
  23. package/dist/boat/prima/src/cli.js +3 -0
  24. package/dist/package.json +1 -1
  25. package/dist/rules/planner/styles/normal.md +1 -1
  26. package/dist/src/ai/captain.js +1 -1
  27. package/dist/src/ai/navigator.js +1 -1
  28. package/dist/src/ai/planner.js +9 -7
  29. package/dist/src/ai/researcher.js +2 -0
  30. package/dist/src/ai/rules.js +3 -3
  31. package/dist/src/api/spec-reader.js +1 -1
  32. package/dist/src/commands/config-command.js +1 -1
  33. package/dist/src/commands/drill-command.js +1 -1
  34. package/dist/src/commands/explore-command.js +12 -1
  35. package/dist/src/commands/options/base-option.d.ts +8 -0
  36. package/dist/src/commands/options/base-option.js +12 -0
  37. package/dist/src/commands/options/index.d.ts +5 -0
  38. package/dist/src/commands/options/index.js +5 -0
  39. package/dist/src/commands/options/knowledge-option.d.ts +7 -0
  40. package/dist/src/commands/options/knowledge-option.js +12 -0
  41. package/dist/src/commands/options/ws-option.d.ts +7 -0
  42. package/dist/src/commands/options/ws-option.js +21 -0
  43. package/dist/src/config.d.ts +1 -0
  44. package/dist/src/config.js +11 -0
  45. package/dist/src/explorbot.js +1 -1
  46. package/dist/src/knowledge-tracker.d.ts +20 -7
  47. package/dist/src/knowledge-tracker.js +69 -31
  48. package/dist/src/remote.d.ts +0 -3
  49. package/dist/src/remote.js +0 -18
  50. package/docs/api-testing/basics.md +15 -0
  51. package/docs/api-testing/planning.md +10 -1
  52. package/docs/reference/commands.md +24 -4
  53. package/docs/workflow/agentic-usage.md +11 -2
  54. package/docs/workflow/knowledge.md +46 -2
  55. package/package.json +1 -1
  56. package/rules/planner/styles/normal.md +1 -1
  57. package/src/ai/captain.ts +1 -1
  58. package/src/ai/navigator.ts +1 -1
  59. package/src/ai/planner.ts +9 -8
  60. package/src/ai/researcher.ts +1 -0
  61. package/src/ai/rules.ts +3 -3
  62. package/src/api/spec-reader.ts +1 -1
  63. package/src/commands/config-command.ts +1 -1
  64. package/src/commands/drill-command.ts +1 -1
  65. package/src/commands/explore-command.ts +12 -1
  66. package/src/commands/options/base-option.ts +18 -0
  67. package/src/commands/options/index.ts +7 -0
  68. package/src/commands/options/knowledge-option.ts +14 -0
  69. package/src/commands/options/ws-option.ts +24 -0
  70. package/src/config.ts +11 -0
  71. package/src/explorbot.ts +1 -1
  72. package/src/knowledge-tracker.ts +94 -36
  73. package/src/remote.ts +0 -20
@@ -11,36 +11,54 @@ import { loadMarkdownFiles } from './utils/markdown-files.js';
11
11
  import { mdq } from './utils/markdown-query.js';
12
12
  import { isSecretName, registerSecret } from './utils/secrets.js';
13
13
  import { slugify } from './utils/strings.js';
14
+ import { extractStatePath, matchesUrl } from './utils/url-matcher.js';
14
15
 
15
16
  const debugLog = createDebug('explorbot:knowledge-tracker');
16
17
 
17
- export interface Knowledge {
18
- filePath: string;
19
- url: string;
20
- content: string;
21
- [key: string]: any;
22
- }
18
+ const sessionEntries: string[] = [];
23
19
 
24
20
  export class KnowledgeTracker {
25
21
  private knowledgeDir: string;
26
22
  private knowledgeFiles: Knowledge[] = [];
23
+ private sessionKnowledge: Knowledge[] = [];
27
24
  private isLoaded = false;
28
25
  private applicationSpec?: ApplicationSpec;
29
26
 
30
- constructor(applicationSpecPath?: string) {
31
- const configParser = ConfigParser.getInstance();
32
- const config = configParser.getConfig();
33
- this.knowledgeDir = configParser.resolveProjectDir(config.dirs?.knowledge || 'knowledge');
27
+ static appendSessionKnowledge(text: string): void {
28
+ sessionEntries.push(text);
29
+ }
30
+
31
+ static resetSessionKnowledge(): void {
32
+ sessionEntries.length = 0;
33
+ }
34
+
35
+ constructor(options: KnowledgeTrackerOptions = {}) {
36
+ let knowledgeDir = options.knowledgeDir;
37
+ let specPath = options.applicationSpec;
38
+
39
+ if (!knowledgeDir) {
40
+ const configParser = ConfigParser.getInstance();
41
+ const config = configParser.getConfig();
42
+ knowledgeDir = configParser.resolveProjectDir(config.dirs?.knowledge || 'knowledge');
43
+ specPath ||= config.dirs?.spec;
44
+ }
45
+
46
+ this.knowledgeDir = knowledgeDir;
34
47
 
35
48
  if (!existsSync(this.knowledgeDir)) {
36
49
  mkdirSync(this.knowledgeDir, { recursive: true });
37
50
  }
38
51
 
39
- const specPath = applicationSpecPath || config.dirs?.spec;
40
52
  if (specPath) {
41
53
  this.applicationSpec = new ApplicationSpec(specPath);
42
54
  tag('info').log(`Loaded application spec with ${this.applicationSpec.pageCount} documented pages`);
43
55
  }
56
+
57
+ this.sessionKnowledge = sessionEntries.map((entry, index) => {
58
+ const parsed = matter(entry);
59
+ debugLog(`Session knowledge #${index + 1}`);
60
+ return this.toKnowledge(`--knowledge #${index + 1}`, parsed.data, parsed.content.trim());
61
+ });
44
62
  }
45
63
 
46
64
  private loadKnowledgeFiles(): void {
@@ -49,12 +67,7 @@ export class KnowledgeTracker {
49
67
  this.knowledgeFiles = [];
50
68
 
51
69
  for (const entry of loadMarkdownFiles(this.knowledgeDir, { recursive: true })) {
52
- this.knowledgeFiles.push({
53
- filePath: entry.filePath,
54
- url: entry.data.url || entry.data.path || '*',
55
- content: this.interpolateVars(entry.content),
56
- ...entry.data,
57
- });
70
+ this.knowledgeFiles.push(this.toKnowledge(entry.filePath, entry.data, entry.content));
58
71
  }
59
72
 
60
73
  this.isLoaded = true;
@@ -63,28 +76,22 @@ export class KnowledgeTracker {
63
76
  getRelevantKnowledge(state: ActionResult): Knowledge[] {
64
77
  this.loadKnowledgeFiles();
65
78
 
66
- return this.knowledgeFiles.filter((knowledge) => {
67
- return state.isMatchedBy(knowledge);
68
- });
79
+ return this.allKnowledge().filter((knowledge) => knowledge.url && state.isMatchedBy(knowledge));
69
80
  }
70
81
 
71
- renderRelevantKnowledge(state: ActionResult): string {
72
- const knowledgeFiles = this.getRelevantKnowledge(state);
73
- if (knowledgeFiles.length === 0) return '';
82
+ getEndpointKnowledge(endpoint: string): Knowledge[] {
83
+ this.loadKnowledgeFiles();
84
+ const path = extractStatePath(endpoint);
74
85
 
75
- const knowledgeContent = knowledgeFiles
76
- .map((k) => k.content)
77
- .filter((k) => !!k)
78
- .join('\n\n');
86
+ return this.allKnowledge().filter((knowledge) => knowledge.endpoint && matchesUrl(knowledge.endpoint, path));
87
+ }
79
88
 
80
- tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
81
- return dedent`
82
- <knowledge>
83
- Here is relevant knowledge for this page:
89
+ renderRelevantKnowledge(state: ActionResult): string {
90
+ return this.renderKnowledge(this.getRelevantKnowledge(state), 'page');
91
+ }
84
92
 
85
- ${knowledgeContent}
86
- </knowledge>
87
- `;
93
+ renderEndpointKnowledge(endpoint: string): string {
94
+ return this.renderKnowledge(this.getEndpointKnowledge(endpoint), 'endpoint');
88
95
  }
89
96
 
90
97
  renderRelevantContext(state: ActionResult): string {
@@ -188,7 +195,7 @@ export class KnowledgeTracker {
188
195
  getExistingUrls(): string[] {
189
196
  this.loadKnowledgeFiles();
190
197
 
191
- return this.knowledgeFiles.map((knowledge) => knowledge.url).filter((url) => url && url !== '*');
198
+ return this.knowledgeFiles.map((knowledge) => knowledge.url || '').filter((url) => url && url !== '*');
192
199
  }
193
200
 
194
201
  getKnowledgeForUrl(urlPattern: string): string[] {
@@ -205,7 +212,7 @@ export class KnowledgeTracker {
205
212
  const content = knowledge.content.trim();
206
213
  const firstLine = mdq(content).meta()[0]?.text.split('\n')[0]?.trim() || '';
207
214
  return {
208
- url: knowledge.url,
215
+ url: knowledge.url || knowledge.endpoint || '',
209
216
  firstLine,
210
217
  filePath: knowledge.filePath,
211
218
  };
@@ -242,4 +249,55 @@ export class KnowledgeTracker {
242
249
 
243
250
  return result;
244
251
  }
252
+
253
+ private allKnowledge(): Knowledge[] {
254
+ return [...this.knowledgeFiles, ...this.sessionKnowledge];
255
+ }
256
+
257
+ private toKnowledge(filePath: string, data: Record<string, any>, content: string): Knowledge {
258
+ const knowledge: Knowledge = {
259
+ ...data,
260
+ filePath,
261
+ url: data.url || data.path,
262
+ content: this.interpolateVars(content),
263
+ };
264
+
265
+ if (!data.url && !data.path && !data.endpoint) {
266
+ knowledge.url = '*';
267
+ knowledge.endpoint = '*';
268
+ }
269
+
270
+ return knowledge;
271
+ }
272
+
273
+ private renderKnowledge(knowledgeFiles: Knowledge[], scope: string): string {
274
+ if (knowledgeFiles.length === 0) return '';
275
+
276
+ const knowledgeContent = knowledgeFiles
277
+ .map((k) => k.content)
278
+ .filter((k) => !!k)
279
+ .join('\n\n');
280
+
281
+ tag('operation').log(`Found ${knowledgeFiles.length} relevant knowledge ${pluralize(knowledgeFiles.length, 'file')}`);
282
+ return dedent`
283
+ <knowledge>
284
+ Here is relevant knowledge for this ${scope}:
285
+
286
+ ${knowledgeContent}
287
+ </knowledge>
288
+ `;
289
+ }
290
+ }
291
+
292
+ export interface Knowledge {
293
+ filePath: string;
294
+ url?: string;
295
+ endpoint?: string;
296
+ content: string;
297
+ [key: string]: any;
298
+ }
299
+
300
+ export interface KnowledgeTrackerOptions {
301
+ applicationSpec?: string;
302
+ knowledgeDir?: string;
245
303
  }
package/src/remote.ts CHANGED
@@ -1,4 +1,3 @@
1
- import type { Command } from 'commander';
2
1
  import stripAnsi from 'strip-ansi';
3
2
  import { type ActivityEntry, addActivityListener } from './activity.ts';
4
3
  import { executionController } from './execution-controller.ts';
@@ -30,15 +29,6 @@ export class Remote implements LogDestination {
30
29
  private askCounter = 0;
31
30
  private lastActivity: string | null = null;
32
31
 
33
- registerOption(program: Command): void {
34
- program.option('--ws <url>', 'Stream this run to a remote UI over WebSocket');
35
- program.hook('preAction', (_thisCommand, actionCommand) => {
36
- const url = actionCommand.optsWithGlobals().ws || process.env.EXPLORBOT_WS_URL;
37
- if (!url) return;
38
- this.attach(String(url), this.commandPath(actionCommand));
39
- });
40
- }
41
-
42
32
  attach(url: string, command: string): void {
43
33
  if (this.url) return;
44
34
  this.url = url;
@@ -225,16 +215,6 @@ export class Remote implements LogDestination {
225
215
  if (typeof failure.message === 'string') return failure.message;
226
216
  return undefined;
227
217
  }
228
-
229
- private commandPath(command: Command): string {
230
- const parts: string[] = [];
231
- let node: Command | null = command;
232
- while (node) {
233
- parts.unshift(node.name());
234
- node = node.parent;
235
- }
236
- return parts.slice(1).join(' ') || parts.join(' ');
237
- }
238
218
  }
239
219
 
240
220
  export const remote = new Remote();