@dot-ai/cli 0.5.2 → 0.7.0

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/src/index.ts CHANGED
@@ -1,17 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
  import { argv, cwd, exit } from 'node:process';
3
- import { mkdir, writeFile, access } from 'node:fs/promises';
4
- import { join } from 'node:path';
3
+ import { mkdir, writeFile, access, stat } from 'node:fs/promises';
4
+ import { join, resolve } from 'node:path';
5
5
  import {
6
- loadConfig,
7
- resolveConfig,
8
- injectRoot,
9
- registerDefaults,
10
- clearProviders,
11
- createProviders,
12
- boot,
13
- enrich,
14
- formatContext,
6
+ DotAiRuntime,
7
+ clearBootCache,
15
8
  } from '@dot-ai/core';
16
9
 
17
10
  const args = argv.slice(2);
@@ -28,23 +21,282 @@ async function main(): Promise<void> {
28
21
  case 'trace':
29
22
  await cmdTrace(args.slice(1));
30
23
  break;
24
+ case 'cache':
25
+ await cmdCache(args.slice(1));
26
+ break;
27
+ case 'tools':
28
+ await cmdTools(args.slice(1));
29
+ break;
30
+ case 'commands':
31
+ await cmdCommands();
32
+ break;
33
+ case 'migrate':
34
+ await cmdMigrate();
35
+ break;
36
+ case '--version':
37
+ case '-v':
38
+ console.log('dot-ai v0.6.0');
39
+ break;
40
+ default:
41
+ // Try to execute as a tool: dot-ai <domain> <action> [args...]
42
+ if (command && !command.startsWith('-')) {
43
+ await cmdExecTool(args);
44
+ } else {
45
+ printHelp();
46
+ exit(command ? 1 : 0);
47
+ }
48
+ }
49
+ }
50
+
51
+ function printHelp(): void {
52
+ console.log('dot-ai v0.6.0\n');
53
+ console.log('Commands:');
54
+ console.log(' init Create .ai/ directory with defaults');
55
+ console.log(' boot Run boot and show workspace info');
56
+ console.log(' trace "<prompt>" Dry-run enrich pipeline with token estimates');
57
+ console.log(' tools [--json] List registered tools from extensions');
58
+ console.log(' commands List registered commands from extensions');
59
+ console.log(' cache clear Clear boot cache');
60
+ console.log(' cache status Show cache status');
61
+ console.log(' migrate Migrate dot-ai.yml to settings.json');
62
+ console.log('');
63
+ console.log('Tool execution:');
64
+ console.log(' dot-ai <domain> <action> [args...]');
65
+ console.log(' dot-ai memory recall "query" Execute memory_recall tool');
66
+ console.log(' dot-ai tasks list Execute task_list tool');
67
+ console.log('');
68
+ console.log('Flags:');
69
+ console.log(' --json Output as JSON');
70
+ console.log(' --workspace <path> Override workspace root');
71
+ }
72
+
73
+ // ── Workspace Detection ──
74
+
75
+ async function resolveWorkspace(): Promise<string> {
76
+ const wsIdx = args.indexOf('--workspace');
77
+ if (wsIdx !== -1 && args[wsIdx + 1]) {
78
+ return resolve(args[wsIdx + 1]);
79
+ }
80
+
81
+ let dir = cwd();
82
+ const root = resolve('/');
83
+
84
+ while (dir !== root) {
85
+ try {
86
+ await access(join(dir, '.ai'));
87
+ return dir;
88
+ } catch {
89
+ dir = resolve(dir, '..');
90
+ }
91
+ }
92
+
93
+ return cwd();
94
+ }
95
+
96
+ // ── Boot Runtime ──
97
+
98
+ async function bootRuntime(workspaceRoot?: string): Promise<DotAiRuntime> {
99
+ const ws = workspaceRoot ?? await resolveWorkspace();
100
+ const runtime = new DotAiRuntime({ workspaceRoot: ws });
101
+ await runtime.boot();
102
+ return runtime;
103
+ }
104
+
105
+ // ── Tool Execution ──
106
+
107
+ async function cmdExecTool(rawArgs: string[]): Promise<void> {
108
+ const jsonMode = rawArgs.includes('--json');
109
+ const cleanArgs = rawArgs.filter(a => a !== '--json' && a !== '--workspace').filter((_, i, arr) => {
110
+ if (i > 0 && arr[i - 1] === '--workspace') return false;
111
+ return true;
112
+ });
113
+
114
+ const domain = cleanArgs[0];
115
+ const action = cleanArgs[1];
116
+
117
+ if (!domain || !action) {
118
+ console.error(`Unknown command: ${domain ?? ''}`);
119
+ console.error('Run "dot-ai" for help.');
120
+ exit(1);
121
+ }
122
+
123
+ const toolName = `${domain}_${action}`;
124
+ const toolArgs = cleanArgs.slice(2);
125
+
126
+ const runtime = await bootRuntime();
127
+ const tools = runtime.capabilities;
128
+
129
+ const tool = tools.find(t => t.name === toolName);
130
+ if (!tool) {
131
+ console.error(`Unknown tool: ${toolName}`);
132
+ console.error('Available tools:');
133
+ for (const t of tools) {
134
+ console.error(` ${t.name} — ${t.description}`);
135
+ }
136
+ exit(1);
137
+ }
138
+
139
+ const input = parseToolArgs(toolArgs, tool.parameters);
140
+
141
+ try {
142
+ const result = await tool.execute(input);
143
+ if (jsonMode) {
144
+ console.log(JSON.stringify({ content: result.text, details: result.details }, null, 2));
145
+ } else {
146
+ console.log(result.text);
147
+ }
148
+ } catch (err) {
149
+ console.error(`Tool execution error: ${err instanceof Error ? err.message : err}`);
150
+ exit(1);
151
+ } finally {
152
+ await runtime.flush();
153
+ }
154
+ }
155
+
156
+ function parseToolArgs(rawArgs: string[], schema: Record<string, unknown>): Record<string, unknown> {
157
+ const input: Record<string, unknown> = {};
158
+ const props = (schema as { properties?: Record<string, { type?: string }> }).properties ?? {};
159
+ const required = (schema as { required?: string[] }).required ?? [];
160
+ const propNames = Object.keys(props);
161
+
162
+ let positionalIdx = 0;
163
+ let i = 0;
164
+
165
+ while (i < rawArgs.length) {
166
+ const arg = rawArgs[i];
167
+
168
+ if (arg.startsWith('--')) {
169
+ const eqIdx = arg.indexOf('=');
170
+ if (eqIdx !== -1) {
171
+ const key = arg.slice(2, eqIdx);
172
+ const value = arg.slice(eqIdx + 1);
173
+ input[key] = coerceValue(value, props[key]?.type);
174
+ } else {
175
+ const key = arg.slice(2);
176
+ const nextArg = rawArgs[i + 1];
177
+ if (nextArg && !nextArg.startsWith('--')) {
178
+ input[key] = coerceValue(nextArg, props[key]?.type);
179
+ i++;
180
+ } else {
181
+ input[key] = true;
182
+ }
183
+ }
184
+ } else {
185
+ const targetKey = required[positionalIdx] ?? propNames.find(p => !(p in input));
186
+ if (targetKey) {
187
+ input[targetKey] = coerceValue(arg, props[targetKey]?.type);
188
+ }
189
+ positionalIdx++;
190
+ }
191
+ i++;
192
+ }
193
+
194
+ return input;
195
+ }
196
+
197
+ function coerceValue(value: string, type?: string): unknown {
198
+ if (type === 'number') return Number(value);
199
+ if (type === 'boolean') return value === 'true';
200
+ if (type === 'array') {
201
+ try { return JSON.parse(value); } catch { return value.split(','); }
202
+ }
203
+ return value;
204
+ }
205
+
206
+ // ── Tools List ──
207
+
208
+ async function cmdTools(rawArgs: string[]): Promise<void> {
209
+ const jsonMode = rawArgs.includes('--json');
210
+ const runtime = await bootRuntime();
211
+ const tools = runtime.capabilities;
212
+
213
+ if (jsonMode) {
214
+ const output = tools.map(t => ({
215
+ name: t.name,
216
+ description: t.description,
217
+ category: t.category,
218
+ readOnly: t.readOnly,
219
+ }));
220
+ console.log(JSON.stringify(output, null, 2));
221
+ } else {
222
+ if (tools.length === 0) {
223
+ console.log('No tools registered.');
224
+ } else {
225
+ console.log(`${tools.length} tool(s) registered:\n`);
226
+ for (const t of tools) {
227
+ const cat = t.category ? ` [${t.category}]` : '';
228
+ const ro = t.readOnly ? ' (read-only)' : '';
229
+ console.log(` ${t.name}${cat}${ro}`);
230
+ console.log(` ${t.description}`);
231
+ }
232
+ }
233
+ }
234
+
235
+ await runtime.flush();
236
+ }
237
+
238
+ // ── Commands List ──
239
+
240
+ async function cmdCommands(): Promise<void> {
241
+ const runtime = await bootRuntime();
242
+ const runner = runtime.runner;
243
+ const commands = runner?.commands ?? [];
244
+
245
+ if (commands.length === 0) {
246
+ console.log('No commands registered.');
247
+ } else {
248
+ console.log(`${commands.length} command(s) registered:\n`);
249
+ for (const cmd of commands) {
250
+ const params = cmd.parameters?.map((p: { name: string; required?: boolean }) =>
251
+ p.required ? `<${p.name}>` : `[${p.name}]`
252
+ ).join(' ') ?? '';
253
+ console.log(` /${cmd.name} ${params}`);
254
+ console.log(` ${cmd.description}`);
255
+ }
256
+ }
257
+
258
+ await runtime.flush();
259
+ }
260
+
261
+ // ── Cache Management ──
262
+
263
+ async function cmdCache(rawArgs: string[]): Promise<void> {
264
+ const subcommand = rawArgs[0];
265
+ const ws = await resolveWorkspace();
266
+ const cacheDir = join(ws, '.ai', '.cache');
267
+
268
+ switch (subcommand) {
269
+ case 'clear': {
270
+ await clearBootCache(ws);
271
+ console.log('Cache cleared.');
272
+ break;
273
+ }
274
+ case 'status': {
275
+ try {
276
+ const bootJson = join(cacheDir, 'boot.json');
277
+ const s = await stat(bootJson);
278
+ console.log(`Cache: ${cacheDir}`);
279
+ console.log(` boot.json: ${s.size} bytes, modified ${s.mtime.toISOString()}`);
280
+ } catch {
281
+ console.log('No cache found.');
282
+ }
283
+ break;
284
+ }
31
285
  default:
32
- console.log('dot-ai v0.4.0\n');
33
- console.log('Commands:');
34
- console.log(' init Create .ai/ directory with defaults');
35
- console.log(' boot Run boot and show workspace info');
36
- console.log(' trace "<prompt>" Dry-run enrich pipeline with token estimates');
37
- exit(command ? 1 : 0);
286
+ console.log('Usage: dot-ai cache <clear|status>');
287
+ exit(1);
38
288
  }
39
289
  }
40
290
 
291
+ // ── Init ──
292
+
41
293
  async function cmdInit(): Promise<void> {
42
294
  const root = cwd();
43
295
  const aiDir = join(root, '.ai');
44
296
 
45
297
  try {
46
- await access(join(aiDir, 'dot-ai.yml'));
47
- console.log('.ai/dot-ai.yml already exists. Nothing to do.');
298
+ await access(join(aiDir, 'settings.json'));
299
+ console.log('.ai/settings.json already exists. Nothing to do.');
48
300
  return;
49
301
  } catch {
50
302
  // Doesn't exist, create it
@@ -52,21 +304,10 @@ async function cmdInit(): Promise<void> {
52
304
 
53
305
  await mkdir(aiDir, { recursive: true });
54
306
 
55
- await writeFile(join(aiDir, 'dot-ai.yml'), [
56
- '# dot-ai configuration',
57
- '# Uncomment and customize providers as needed.',
58
- '# Default: file-based providers reading from .ai/ directory.',
59
- '#',
60
- '# memory:',
61
- '# use: "@dot-ai/provider-file-memory"',
62
- '#',
63
- '# skills:',
64
- '# use: "@dot-ai/provider-file-skills"',
65
- '#',
66
- '# routing:',
67
- '# use: "@dot-ai/provider-rules-routing"',
68
- '',
69
- ].join('\n'));
307
+ await writeFile(join(aiDir, 'settings.json'), JSON.stringify({
308
+ extensions: [],
309
+ packages: [],
310
+ }, null, 2) + '\n');
70
311
 
71
312
  await writeFile(join(aiDir, 'AGENTS.md'), [
72
313
  '# AGENTS.md',
@@ -80,32 +321,34 @@ async function cmdInit(): Promise<void> {
80
321
  ].join('\n'));
81
322
 
82
323
  console.log('Created:');
83
- console.log(' .ai/dot-ai.yml (config)');
84
- console.log(' .ai/AGENTS.md (template)');
85
- console.log('\nNext: add SOUL.md, USER.md, skills/, memory/ as needed.');
324
+ console.log(' .ai/settings.json (config)');
325
+ console.log(' .ai/AGENTS.md (template)');
326
+ console.log('\nNext: add SOUL.md, USER.md, skills/, extensions/ as needed.');
86
327
  }
87
328
 
329
+ // ── Boot ──
330
+
88
331
  async function cmdBoot(): Promise<void> {
89
- const root = cwd();
332
+ const root = await resolveWorkspace();
90
333
  const start = performance.now();
91
334
 
92
- clearProviders();
93
- registerDefaults();
94
-
95
- const rawConfig = await loadConfig(root);
96
- const config = injectRoot(rawConfig, root);
97
- const providers = await createProviders(config);
98
- const cache = await boot(providers);
99
-
335
+ const runtime = new DotAiRuntime({ workspaceRoot: root });
336
+ await runtime.boot();
100
337
  const duration = Math.round(performance.now() - start);
338
+ const diag = runtime.diagnostics;
101
339
 
102
340
  console.log(`dot-ai boot — ${root}\n`);
103
- console.log(`Identities: ${cache.identities.length}${cache.identities.length > 0 ? ` (${cache.identities.map(i => i.type).join(', ')})` : ''}`);
104
- console.log(`Skills: ${cache.skills.length}${cache.skills.length > 0 ? ` (${cache.skills.map(s => s.name).join(', ')})` : ''}`);
105
- console.log(`Vocabulary: ${cache.vocabulary.length} labels`);
341
+ console.log(`Extensions: ${diag.extensions.length}`);
342
+ console.log(`Vocabulary: ${diag.vocabularySize} labels`);
343
+ console.log(`Tools: ${diag.capabilityCount}`);
344
+ console.log(`Tiers: ${diag.usedTiers.join(', ') || 'none'}`);
106
345
  console.log(`\nBoot complete in ${duration}ms`);
346
+
347
+ await runtime.flush();
107
348
  }
108
349
 
350
+ // ── Trace ──
351
+
109
352
  async function cmdTrace(rawArgs: string[]): Promise<void> {
110
353
  const flags = new Set(rawArgs.filter(a => a.startsWith('--')));
111
354
  const prompt = rawArgs.filter(a => !a.startsWith('--')).join(' ');
@@ -117,109 +360,73 @@ async function cmdTrace(rawArgs: string[]): Promise<void> {
117
360
  exit(1);
118
361
  }
119
362
 
120
- const root = cwd();
363
+ const root = await resolveWorkspace();
121
364
  const start = performance.now();
122
365
 
123
- clearProviders();
124
- registerDefaults();
125
-
126
- const rawConfig = await loadConfig(root);
127
- const config = injectRoot(rawConfig, root);
128
- const resolved = resolveConfig(rawConfig);
129
- const providers = await createProviders(config);
130
- const cache = await boot(providers);
131
- const ctx = await enrich(prompt, providers, cache);
132
-
133
- // Load skill content for matched skills
134
- for (const skill of ctx.skills) {
135
- if (!skill.content && skill.name) {
136
- skill.content = await providers.skills.load(skill.name) ?? undefined;
137
- }
138
- }
139
-
366
+ const runtime = new DotAiRuntime({ workspaceRoot: root });
367
+ await runtime.boot();
368
+ const result = await runtime.processPrompt(prompt);
140
369
  const duration = Math.round(performance.now() - start);
141
370
 
142
- // Compute token estimates
143
- const identityChars = cache.identities.reduce((sum, i) => sum + (i.content?.length ?? 0), 0);
144
- const skillChars = ctx.skills.reduce((sum, s) => sum + (s.content?.length ?? 0), 0);
145
- const memoryChars = ctx.memories.reduce((sum, m) => sum + m.content.length, 0);
146
- const totalChars = skillChars + memoryChars;
147
-
148
- // Check for disabled skills
149
- const disabledList = typeof resolved.skills?.with?.disabled === 'string'
150
- ? resolved.skills.with.disabled.split(',').map((s: string) => s.trim()).filter(Boolean)
151
- : [];
152
-
153
371
  if (jsonMode) {
154
372
  const output = {
155
373
  prompt,
156
- sessionStart: {
157
- identityCount: cache.identities.length,
158
- identityChars,
159
- estimatedTokens: Math.round(identityChars / 4),
160
- },
161
- userPromptSubmit: {
162
- labels: ctx.labels.map(l => l.name),
163
- skills: ctx.skills.map(s => ({
164
- name: s.name,
165
- chars: s.content?.length ?? 0,
166
- })),
167
- memoryCount: ctx.memories.length,
168
- memoryChars,
169
- toolCount: ctx.tools.length,
170
- routing: ctx.routing,
171
- totalChars,
172
- estimatedTokens: Math.round(totalChars / 4),
173
- },
174
- disabled: disabledList,
175
- vocabularySize: cache.vocabulary.length,
374
+ labels: result.labels?.map(l => l.name),
375
+ routing: result.routing,
376
+ sections: result.sections?.map(s => ({
377
+ id: s.id,
378
+ title: s.title,
379
+ priority: s.priority,
380
+ source: s.source,
381
+ chars: s.content.length,
382
+ })),
383
+ totalChars: result.formatted.length,
384
+ estimatedTokens: Math.round(result.formatted.length / 4),
385
+ toolCount: result.capabilities.length,
176
386
  durationMs: duration,
177
387
  };
178
388
  console.log(JSON.stringify(output, null, 2));
179
- return;
180
- }
389
+ } else {
390
+ console.log(`dot-ai trace — "${prompt}"\n`);
181
391
 
182
- // Human-readable output
183
- console.log(`dot-ai trace "${prompt}"\n`);
392
+ const labels = result.labels ?? result.enriched.labels;
393
+ console.log(`Labels: [${labels.map(l => l.name).join(', ')}]`);
184
394
 
185
- // SessionStart section
186
- console.log(`── SessionStart (one-time) ──`);
187
- console.log(` ${cache.identities.length} identities: ${identityChars.toLocaleString()} chars (~${Math.round(identityChars / 4).toLocaleString()} tokens)`);
395
+ if (result.sections && result.sections.length > 0) {
396
+ console.log(`\nSections (${result.sections.length}):`);
397
+ for (const s of result.sections) {
398
+ console.log(` [${s.priority}] ${s.title} (${s.source}, ${s.content.length} chars)`);
399
+ }
400
+ }
188
401
 
189
- // UserPromptSubmit section
190
- console.log(`\n── UserPromptSubmit ──`);
191
- console.log(` Labels: [${ctx.labels.map(l => l.name).join(', ')}]`);
402
+ const routing = result.routing ?? result.enriched.routing;
403
+ console.log(`\nRouting: ${routing?.model ?? 'default'} (${routing?.reason ?? 'none'})`);
404
+ console.log(`Total: ${result.formatted.length.toLocaleString()} chars (~${Math.round(result.formatted.length / 4).toLocaleString()} tokens)`);
405
+ console.log(`Tools: ${result.capabilities.length}`);
192
406
 
193
- if (ctx.skills.length > 0) {
194
- const skillDetails = ctx.skills.map(s => `${s.name} (${(s.content?.length ?? 0).toLocaleString()} chars)`);
195
- console.log(` Skills: ${skillDetails.join(', ')}`);
196
- } else {
197
- console.log(` Skills: none matched`);
407
+ if (verbose) {
408
+ console.log(`\n── Injected markdown (${result.formatted.length.toLocaleString()} chars) ──`);
409
+ console.log(result.formatted);
410
+ }
411
+
412
+ console.log(`\nTrace complete in ${duration}ms`);
198
413
  }
199
414
 
200
- console.log(` Memory: ${ctx.memories.length} entries (${memoryChars.toLocaleString()} chars)`);
201
- console.log(` Tools: ${ctx.tools.length} matched`);
202
- console.log(` Routing: ${ctx.routing.model} (${ctx.routing.reason})`);
203
- console.log(` Total: ${totalChars.toLocaleString()} chars (~${Math.round(totalChars / 4).toLocaleString()} tokens)`);
415
+ await runtime.flush();
416
+ }
204
417
 
205
- // Disabled skills
206
- if (disabledList.length > 0) {
207
- console.log(`\n── Disabled skills ──`);
208
- console.log(` ${disabledList.join(', ')} (via config)`);
209
- }
418
+ // ── Migrate ──
210
419
 
211
- // Verbose: show the actual markdown that would be injected
212
- if (verbose) {
213
- const formatted = formatContext(ctx, {
214
- skipIdentities: true,
215
- maxSkillLength: 3000,
216
- maxSkills: 5,
217
- });
218
- console.log(`\n── Injected markdown (${formatted.length.toLocaleString()} chars) ──`);
219
- console.log(formatted);
420
+ async function cmdMigrate(): Promise<void> {
421
+ const ws = await resolveWorkspace();
422
+ const { migrateConfig } = await import('@dot-ai/core');
423
+ const result = await migrateConfig(ws);
424
+ if (result) {
425
+ console.log(`Migrated config to ${result}`);
426
+ console.log('You can now delete .ai/dot-ai.yml');
427
+ } else {
428
+ console.log('No dot-ai.yml found to migrate.');
220
429
  }
221
-
222
- console.log(`\nTrace complete in ${duration}ms`);
223
430
  }
224
431
 
225
432
  main().catch((err) => {