uda-cli 0.2.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/LICENSE +21 -0
- package/README.md +85 -0
- package/bin/uda.js +4 -0
- package/package.json +47 -0
- package/src/adapters/agents-md.js +22 -0
- package/src/adapters/base.js +14 -0
- package/src/adapters/claude.js +138 -0
- package/src/adapters/cursor.js +46 -0
- package/src/adapters/raw.js +34 -0
- package/src/adapters/registry.js +18 -0
- package/src/cli.js +106 -0
- package/src/commands/config.js +55 -0
- package/src/commands/export.js +77 -0
- package/src/commands/init.js +62 -0
- package/src/commands/learn.js +82 -0
- package/src/commands/logs.js +70 -0
- package/src/commands/plugin.js +100 -0
- package/src/commands/scan.js +71 -0
- package/src/commands/search.js +69 -0
- package/src/commands/status.js +74 -0
- package/src/commands/sync.js +84 -0
- package/src/core/config.js +47 -0
- package/src/core/constants.js +34 -0
- package/src/core/init.js +86 -0
- package/src/core/knowledge-loader.js +93 -0
- package/src/core/validators.js +123 -0
- package/src/plugins/manager.js +160 -0
- package/src/rag/chunker.js +86 -0
- package/src/rag/embedder.js +29 -0
- package/src/rag/manager.js +56 -0
- package/src/rag/store.js +77 -0
- package/src/workflows/parser.js +49 -0
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { initProject } from '../core/init.js';
|
|
2
|
+
import { handleScan } from './scan.js';
|
|
3
|
+
import { handleSync } from './sync.js';
|
|
4
|
+
import { validateEngine } from '../core/validators.js';
|
|
5
|
+
|
|
6
|
+
export async function handleInit(options) {
|
|
7
|
+
const root = process.cwd();
|
|
8
|
+
|
|
9
|
+
// Validate engine option if provided
|
|
10
|
+
if (options.engine) {
|
|
11
|
+
const v = validateEngine(options.engine);
|
|
12
|
+
if (!v.valid) {
|
|
13
|
+
console.error(`✘ ${v.error}`);
|
|
14
|
+
process.exitCode = 1;
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
console.log('UDA v0.2.0\n');
|
|
20
|
+
|
|
21
|
+
// Step 1: Create directory structure
|
|
22
|
+
console.log('Creating project structure...');
|
|
23
|
+
try {
|
|
24
|
+
await initProject(root);
|
|
25
|
+
} catch (err) {
|
|
26
|
+
console.error(`✘ Failed to create project structure: ${err.message}`);
|
|
27
|
+
process.exitCode = 1;
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
console.log('✔ .uda/ directory created\n');
|
|
31
|
+
|
|
32
|
+
// Step 2: Engine detection
|
|
33
|
+
const engine = options.engine || await detectEngine(root);
|
|
34
|
+
if (engine) {
|
|
35
|
+
console.log(`✔ Engine detected: ${engine}`);
|
|
36
|
+
console.log(` Run \`uda plugin add <repo>\` to install ${engine} plugin\n`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Step 3: Initial scan
|
|
40
|
+
console.log('Scanning knowledge base...');
|
|
41
|
+
await handleScan();
|
|
42
|
+
|
|
43
|
+
// Step 4: Generate AI tool files
|
|
44
|
+
console.log('\nGenerating AI tool files...');
|
|
45
|
+
await handleSync();
|
|
46
|
+
|
|
47
|
+
console.log('\n✔ UDA is ready!');
|
|
48
|
+
console.log(' uda search "your query"');
|
|
49
|
+
console.log(' uda learn <file.md>');
|
|
50
|
+
console.log(' uda plugin add <git-repo>');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function detectEngine(root) {
|
|
54
|
+
const { existsSync } = await import('fs');
|
|
55
|
+
const { join } = await import('path');
|
|
56
|
+
|
|
57
|
+
if (existsSync(join(root, 'ProjectSettings', 'ProjectVersion.txt'))) return 'unity';
|
|
58
|
+
if (existsSync(join(root, 'project.godot'))) return 'godot';
|
|
59
|
+
if (existsSync(join(root, 'Config', 'DefaultEngine.ini'))) return 'unreal';
|
|
60
|
+
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// src/commands/learn.js
|
|
2
|
+
import { resolve, extname } from 'path';
|
|
3
|
+
import { stat, readFile } from 'fs/promises';
|
|
4
|
+
import { RagManager } from '../rag/manager.js';
|
|
5
|
+
import { udaPaths } from '../core/constants.js';
|
|
6
|
+
import { validateLearnType } from '../core/validators.js';
|
|
7
|
+
|
|
8
|
+
export async function handleLearn(source, options) {
|
|
9
|
+
// Guard against missing source argument
|
|
10
|
+
if (!source || (typeof source === 'string' && source.trim() === '')) {
|
|
11
|
+
console.error('✘ Source file path is required.');
|
|
12
|
+
process.exitCode = 1;
|
|
13
|
+
return;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Validate learn type if provided
|
|
17
|
+
if (options.type) {
|
|
18
|
+
const tv = validateLearnType(options.type);
|
|
19
|
+
if (!tv.valid) {
|
|
20
|
+
console.error(`✘ ${tv.error}`);
|
|
21
|
+
process.exitCode = 1;
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const paths = udaPaths(process.cwd());
|
|
27
|
+
|
|
28
|
+
let rag;
|
|
29
|
+
try {
|
|
30
|
+
rag = new RagManager(paths.rag.lancedb);
|
|
31
|
+
await rag.init();
|
|
32
|
+
} catch (err) {
|
|
33
|
+
console.error(`✘ Failed to initialize RAG engine: ${err.message}`);
|
|
34
|
+
process.exitCode = 1;
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const sourcePath = resolve(source);
|
|
39
|
+
|
|
40
|
+
let info;
|
|
41
|
+
try {
|
|
42
|
+
info = await stat(sourcePath);
|
|
43
|
+
} catch (err) {
|
|
44
|
+
if (err.code === 'ENOENT') {
|
|
45
|
+
console.error(`✘ File not found: ${sourcePath}`);
|
|
46
|
+
} else {
|
|
47
|
+
console.error(`✘ Cannot access file "${sourcePath}": ${err.message}`);
|
|
48
|
+
}
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (!info.isFile()) {
|
|
54
|
+
console.error(`✘ Source must be a file path, not a directory: ${sourcePath}`);
|
|
55
|
+
process.exitCode = 1;
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Warn if file is not markdown
|
|
60
|
+
const ext = extname(sourcePath).toLowerCase();
|
|
61
|
+
if (ext && ext !== '.md' && ext !== '.markdown') {
|
|
62
|
+
console.log(`⚠ Warning: "${source}" is not a markdown file. Results may vary.`);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Check for empty files
|
|
66
|
+
if (info.size === 0) {
|
|
67
|
+
console.error(`✘ File is empty: ${sourcePath}`);
|
|
68
|
+
process.exitCode = 1;
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const count = await rag.learnFile(sourcePath, {
|
|
74
|
+
type: options.type || 'knowledge',
|
|
75
|
+
tags: options.tags ? options.tags.split(',') : [],
|
|
76
|
+
});
|
|
77
|
+
console.log(`✔ Learned ${count} chunks from ${source}`);
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(`✘ Failed to learn from "${source}": ${err.message}`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { readFile, readdir } from 'fs/promises';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { udaPaths } from '../core/constants.js';
|
|
4
|
+
|
|
5
|
+
export async function handleLogs(options) {
|
|
6
|
+
const root = process.cwd();
|
|
7
|
+
const paths = udaPaths(root);
|
|
8
|
+
const logsDir = paths.logs;
|
|
9
|
+
|
|
10
|
+
// Find log files
|
|
11
|
+
let logFiles;
|
|
12
|
+
try {
|
|
13
|
+
const files = await readdir(logsDir);
|
|
14
|
+
logFiles = files.filter(f => f.endsWith('.jsonl'));
|
|
15
|
+
} catch (err) {
|
|
16
|
+
if (err.code === 'ENOENT') {
|
|
17
|
+
console.log('No log files found. Is a log bridge plugin installed?');
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
throw err;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (logFiles.length === 0) {
|
|
24
|
+
console.log('No log files found. Is a log bridge plugin installed?');
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Read and parse all log entries
|
|
29
|
+
let entries = [];
|
|
30
|
+
for (const file of logFiles) {
|
|
31
|
+
const content = await readFile(join(logsDir, file), 'utf8');
|
|
32
|
+
const lines = content.split('\n').filter(l => l.trim());
|
|
33
|
+
for (const line of lines) {
|
|
34
|
+
try {
|
|
35
|
+
entries.push(JSON.parse(line));
|
|
36
|
+
} catch {
|
|
37
|
+
// skip malformed lines
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Filter by level
|
|
43
|
+
if (options.errors) {
|
|
44
|
+
entries = entries.filter(e => e.level === 'Error' || e.level === 'Exception');
|
|
45
|
+
} else if (options.warnings) {
|
|
46
|
+
entries = entries.filter(e => e.level === 'Warning');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Limit results
|
|
50
|
+
if (options.last) {
|
|
51
|
+
const n = parseInt(options.last, 10) || 20;
|
|
52
|
+
entries = entries.slice(-n);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (entries.length === 0) {
|
|
56
|
+
console.log('No matching log entries.');
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Display
|
|
61
|
+
console.log(`Showing ${entries.length} log entries:\n`);
|
|
62
|
+
for (const entry of entries) {
|
|
63
|
+
const time = entry.timestamp ? new Date(entry.timestamp).toLocaleTimeString() : '??:??';
|
|
64
|
+
const level = (entry.level || 'Log').padEnd(9);
|
|
65
|
+
console.log(`[${time}] ${level} ${entry.message}`);
|
|
66
|
+
if (entry.stackTrace) {
|
|
67
|
+
console.log(` ${entry.stackTrace.split('\n')[0]}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { PluginManager } from '../plugins/manager.js';
|
|
2
|
+
import { validatePluginRepo, validatePluginName } from '../core/validators.js';
|
|
3
|
+
|
|
4
|
+
export async function handlePluginAdd(repo) {
|
|
5
|
+
const rv = validatePluginRepo(repo);
|
|
6
|
+
if (!rv.valid) {
|
|
7
|
+
console.error(`✘ ${rv.error}`);
|
|
8
|
+
process.exitCode = 1;
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const pm = new PluginManager(process.cwd());
|
|
13
|
+
try {
|
|
14
|
+
const manifest = await pm.add(repo);
|
|
15
|
+
console.log(`✔ Plugin "${manifest.name}" v${manifest.version} installed`);
|
|
16
|
+
console.log(` Engine: ${manifest.engine || 'generic'}`);
|
|
17
|
+
console.log(` Run \`uda scan\` to index new knowledge`);
|
|
18
|
+
} catch (err) {
|
|
19
|
+
console.error(`✘ Failed to install plugin from "${repo}": ${err.message}`);
|
|
20
|
+
process.exitCode = 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export async function handlePluginList() {
|
|
25
|
+
const pm = new PluginManager(process.cwd());
|
|
26
|
+
let plugins;
|
|
27
|
+
try {
|
|
28
|
+
plugins = await pm.list();
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`✘ Failed to list plugins: ${err.message}`);
|
|
31
|
+
process.exitCode = 1;
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (plugins.length === 0) {
|
|
35
|
+
console.log('No plugins installed. Run `uda plugin add <repo>` to install one.');
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
for (const p of plugins) {
|
|
39
|
+
console.log(` ✔ ${p.name || 'unknown'} (v${p.version || '?'}) — ${p.engine || 'generic'}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export async function handlePluginRemove(name) {
|
|
44
|
+
const nv = validatePluginName(name);
|
|
45
|
+
if (!nv.valid) {
|
|
46
|
+
console.error(`✘ ${nv.error}`);
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const pm = new PluginManager(process.cwd());
|
|
52
|
+
try {
|
|
53
|
+
const meta = await pm.remove(name);
|
|
54
|
+
console.log(`✔ Plugin "${meta.name}" removed`);
|
|
55
|
+
} catch (err) {
|
|
56
|
+
if (err.code === 'ENOENT') {
|
|
57
|
+
console.error(`✘ Plugin "${name}" is not installed`);
|
|
58
|
+
} else {
|
|
59
|
+
console.error(`✘ Failed to remove plugin "${name}": ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
process.exitCode = 1;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function handlePluginUpdate(name) {
|
|
66
|
+
if (name) {
|
|
67
|
+
const nv = validatePluginName(name);
|
|
68
|
+
if (!nv.valid) {
|
|
69
|
+
console.error(`✘ ${nv.error}`);
|
|
70
|
+
process.exitCode = 1;
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const pm = new PluginManager(process.cwd());
|
|
76
|
+
|
|
77
|
+
try {
|
|
78
|
+
if (name) {
|
|
79
|
+
const result = await pm.update(name);
|
|
80
|
+
console.log(`✔ Plugin "${result.name}" updated (${result.commitHash?.slice(0, 7) || 'latest'})`);
|
|
81
|
+
} else {
|
|
82
|
+
const results = await pm.updateAll();
|
|
83
|
+
if (results.length === 0) {
|
|
84
|
+
console.log('No plugins installed.');
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
for (const r of results) {
|
|
88
|
+
console.log(` ✔ ${r.name} updated (${r.commitHash?.slice(0, 7) || 'latest'})`);
|
|
89
|
+
}
|
|
90
|
+
console.log(`\n✔ ${results.length} plugin(s) updated`);
|
|
91
|
+
}
|
|
92
|
+
} catch (err) {
|
|
93
|
+
if (err.code === 'ENOENT' && name) {
|
|
94
|
+
console.error(`✘ Plugin "${name}" is not installed`);
|
|
95
|
+
} else {
|
|
96
|
+
console.error(`✘ Failed to update plugin${name ? ` "${name}"` : 's'}: ${err.message}`);
|
|
97
|
+
}
|
|
98
|
+
process.exitCode = 1;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// src/commands/scan.js
|
|
2
|
+
import { readdir, stat, readFile } from 'fs/promises';
|
|
3
|
+
import { join, relative, extname } from 'path';
|
|
4
|
+
import { RagManager } from '../rag/manager.js';
|
|
5
|
+
import { udaPaths } from '../core/constants.js';
|
|
6
|
+
|
|
7
|
+
export async function handleScan() {
|
|
8
|
+
const root = process.cwd();
|
|
9
|
+
const paths = udaPaths(root);
|
|
10
|
+
|
|
11
|
+
let rag;
|
|
12
|
+
try {
|
|
13
|
+
rag = new RagManager(paths.rag.lancedb);
|
|
14
|
+
await rag.init();
|
|
15
|
+
} catch (err) {
|
|
16
|
+
console.error(`✘ Failed to initialize RAG engine: ${err.message}`);
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Scan .uda/knowledge directory
|
|
22
|
+
const knowledgeDir = paths.knowledge.root;
|
|
23
|
+
let files;
|
|
24
|
+
try {
|
|
25
|
+
files = await collectMdFiles(knowledgeDir);
|
|
26
|
+
} catch (err) {
|
|
27
|
+
console.error(`✘ Failed to collect files from knowledge directory: ${err.message}`);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
if (files.length === 0) {
|
|
33
|
+
console.log('No markdown files found in knowledge directory.');
|
|
34
|
+
console.log(' Add .md files to .uda/knowledge/ or install a plugin.');
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
let totalChunks = 0;
|
|
39
|
+
for (const file of files) {
|
|
40
|
+
const relPath = relative(root, file);
|
|
41
|
+
try {
|
|
42
|
+
const count = await rag.learnFile(file, { source: relPath });
|
|
43
|
+
totalChunks += count;
|
|
44
|
+
console.log(` ✔ ${relPath} (${count} chunks)`);
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.error(` ✘ Failed to index ${relPath}: ${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
console.log(`\n✔ Scan complete: ${files.length} files, ${totalChunks} chunks indexed`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function collectMdFiles(dir) {
|
|
54
|
+
const results = [];
|
|
55
|
+
try {
|
|
56
|
+
const entries = await readdir(dir, { withFileTypes: true });
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const fullPath = join(dir, entry.name);
|
|
59
|
+
if (entry.isDirectory()) {
|
|
60
|
+
results.push(...await collectMdFiles(fullPath));
|
|
61
|
+
} else if (extname(entry.name) === '.md') {
|
|
62
|
+
results.push(fullPath);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
if (err.code !== 'ENOENT') {
|
|
67
|
+
console.error(`✘ Failed to read directory ${dir}: ${err.message}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return results;
|
|
71
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
// src/commands/search.js
|
|
2
|
+
import { RagManager } from '../rag/manager.js';
|
|
3
|
+
import { udaPaths } from '../core/constants.js';
|
|
4
|
+
import { validateSearchQuery, validatePositiveInt, validateSearchFormat } from '../core/validators.js';
|
|
5
|
+
|
|
6
|
+
export async function handleSearch(query, options) {
|
|
7
|
+
// Validate search query
|
|
8
|
+
const qv = validateSearchQuery(query);
|
|
9
|
+
if (!qv.valid) {
|
|
10
|
+
console.error(`✘ ${qv.error}`);
|
|
11
|
+
process.exitCode = 1;
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Validate --top option
|
|
16
|
+
const tv = validatePositiveInt(options.top, '--top');
|
|
17
|
+
if (!tv.valid) {
|
|
18
|
+
console.error(`✘ ${tv.error}`);
|
|
19
|
+
process.exitCode = 1;
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Validate --format option
|
|
24
|
+
if (options.format) {
|
|
25
|
+
const fv = validateSearchFormat(options.format);
|
|
26
|
+
if (!fv.valid) {
|
|
27
|
+
console.error(`✘ ${fv.error}`);
|
|
28
|
+
process.exitCode = 1;
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const paths = udaPaths(process.cwd());
|
|
34
|
+
|
|
35
|
+
let rag;
|
|
36
|
+
try {
|
|
37
|
+
rag = new RagManager(paths.rag.lancedb);
|
|
38
|
+
await rag.init();
|
|
39
|
+
} catch (err) {
|
|
40
|
+
console.error(`✘ Failed to initialize RAG engine: ${err.message}`);
|
|
41
|
+
process.exitCode = 1;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const limit = tv.value;
|
|
46
|
+
|
|
47
|
+
let results;
|
|
48
|
+
try {
|
|
49
|
+
results = await rag.search(query, limit);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
console.error(`✘ Search failed: ${err.message}`);
|
|
52
|
+
process.exitCode = 1;
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (results.length === 0) {
|
|
57
|
+
console.log('No results found.');
|
|
58
|
+
return;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
results.forEach((r, i) => {
|
|
62
|
+
const distance = typeof r.score === 'number' && !isNaN(r.score) ? r.score : 0;
|
|
63
|
+
const score = (1 - distance).toFixed(2); // convert distance to similarity
|
|
64
|
+
console.log(`\n${i + 1}. [${(score * 100).toFixed(0)}%] ${r.source || 'unknown'}`);
|
|
65
|
+
console.log(` ${(r.content || '').slice(0, 120)}...`);
|
|
66
|
+
const tags = Array.isArray(r.tags) ? r.tags : [];
|
|
67
|
+
if (tags.length > 0) console.log(` Tags: ${tags.join(', ')}`);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
// src/commands/status.js
|
|
2
|
+
import { readFile, readdir, access } from 'fs/promises';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import { udaPaths } from '../core/constants.js';
|
|
5
|
+
import { loadConfig } from '../core/config.js';
|
|
6
|
+
import { VectorStore } from '../rag/store.js';
|
|
7
|
+
|
|
8
|
+
export async function handleStatus() {
|
|
9
|
+
const root = process.cwd();
|
|
10
|
+
const paths = udaPaths(root);
|
|
11
|
+
|
|
12
|
+
// Check if UDA is initialized
|
|
13
|
+
try {
|
|
14
|
+
await access(paths.config);
|
|
15
|
+
} catch {
|
|
16
|
+
console.error('✘ UDA is not initialized. Run `uda init` first.');
|
|
17
|
+
process.exitCode = 1;
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
let config;
|
|
22
|
+
try {
|
|
23
|
+
config = await loadConfig(root);
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`✘ Failed to read config file: ${err.message}`);
|
|
26
|
+
process.exitCode = 1;
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
console.log(`UDA v${config.version || 'unknown'}\n`);
|
|
30
|
+
|
|
31
|
+
// Plugins
|
|
32
|
+
try {
|
|
33
|
+
const pluginFiles = await readdir(paths.plugins);
|
|
34
|
+
const plugins = pluginFiles.filter(f => f.endsWith('.json'));
|
|
35
|
+
console.log(`Plugins: ${plugins.length > 0 ? plugins.map(f => f.replace('.json', '')).join(', ') : 'none'}`);
|
|
36
|
+
} catch (err) {
|
|
37
|
+
if (err.code === 'ENOENT') {
|
|
38
|
+
console.log('Plugins: none');
|
|
39
|
+
} else {
|
|
40
|
+
console.error(`Plugins: failed to read (${err.message})`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// RAG stats
|
|
45
|
+
try {
|
|
46
|
+
const store = new VectorStore(paths.rag.lancedb);
|
|
47
|
+
await store.init();
|
|
48
|
+
const count = await store.count();
|
|
49
|
+
console.log(`RAG index: ${count} chunks`);
|
|
50
|
+
} catch (err) {
|
|
51
|
+
if (err.code === 'ENOENT' || err.message?.includes('Table') || err.message?.includes('does not exist')) {
|
|
52
|
+
console.log('RAG index: not initialized');
|
|
53
|
+
} else {
|
|
54
|
+
console.error(`RAG index: failed to read (${err.message})`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Adapters
|
|
59
|
+
const adapters = Array.isArray(config.adapters) ? config.adapters : [];
|
|
60
|
+
console.log(`Adapters: ${adapters.length > 0 ? adapters.join(', ') : 'none configured'}`);
|
|
61
|
+
|
|
62
|
+
// State
|
|
63
|
+
try {
|
|
64
|
+
const state = await readFile(paths.state.current, 'utf8');
|
|
65
|
+
const lines = state.split('\n').slice(0, 5).join('\n');
|
|
66
|
+
console.log(`\n--- Current State ---\n${lines}`);
|
|
67
|
+
} catch (err) {
|
|
68
|
+
if (err.code === 'ENOENT') {
|
|
69
|
+
console.log('\nNo state file.');
|
|
70
|
+
} else {
|
|
71
|
+
console.error(`\n✘ Failed to read state file: ${err.message}`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
// src/commands/sync.js
|
|
2
|
+
import { readFile, writeFile, mkdir, readdir } from 'fs/promises';
|
|
3
|
+
import { join, dirname } from 'path';
|
|
4
|
+
import { udaPaths } from '../core/constants.js';
|
|
5
|
+
import { loadConfig } from '../core/config.js';
|
|
6
|
+
import { loadKnowledge, loadWorkflows, loadAgents } from '../core/knowledge-loader.js';
|
|
7
|
+
import { ClaudeAdapter } from '../adapters/claude.js';
|
|
8
|
+
import { CursorAdapter } from '../adapters/cursor.js';
|
|
9
|
+
import { RawAdapter } from '../adapters/raw.js';
|
|
10
|
+
import { AgentsMdAdapter } from '../adapters/agents-md.js';
|
|
11
|
+
|
|
12
|
+
const ALL_ADAPTERS = [
|
|
13
|
+
new ClaudeAdapter(),
|
|
14
|
+
new CursorAdapter(),
|
|
15
|
+
new RawAdapter(),
|
|
16
|
+
new AgentsMdAdapter(),
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export async function handleSync() {
|
|
20
|
+
const root = process.cwd();
|
|
21
|
+
const paths = udaPaths(root);
|
|
22
|
+
|
|
23
|
+
let config, knowledge, workflows, agents;
|
|
24
|
+
try {
|
|
25
|
+
config = await loadConfig(root);
|
|
26
|
+
knowledge = await loadKnowledge(paths);
|
|
27
|
+
workflows = await loadWorkflows(paths);
|
|
28
|
+
agents = await loadAgents(paths);
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.error(`✘ Failed to load project data: ${err.message}`);
|
|
31
|
+
console.error(' Run `uda init` to initialize the project.');
|
|
32
|
+
process.exitCode = 1;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Load merged capabilities from all installed plugins
|
|
37
|
+
const capabilities = await loadCapabilities(paths);
|
|
38
|
+
|
|
39
|
+
const adapterList = Array.isArray(config.adapters) ? config.adapters : [];
|
|
40
|
+
const activeAdapters = adapterList.length > 0
|
|
41
|
+
? ALL_ADAPTERS.filter(a => adapterList.includes(a.name))
|
|
42
|
+
: ALL_ADAPTERS;
|
|
43
|
+
|
|
44
|
+
if (activeAdapters.length === 0) {
|
|
45
|
+
console.log('No matching adapters found. Check config.adapters setting.');
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
let totalFiles = 0;
|
|
50
|
+
|
|
51
|
+
for (const adapter of activeAdapters) {
|
|
52
|
+
try {
|
|
53
|
+
const files = adapter.generate(knowledge, workflows, agents, root, capabilities);
|
|
54
|
+
for (const [filePath, content] of Object.entries(files)) {
|
|
55
|
+
const fullPath = join(root, filePath);
|
|
56
|
+
await mkdir(dirname(fullPath), { recursive: true });
|
|
57
|
+
await writeFile(fullPath, content);
|
|
58
|
+
totalFiles++;
|
|
59
|
+
}
|
|
60
|
+
console.log(` ✔ ${adapter.name} adapter — ${Object.keys(files).length} files`);
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error(` ✘ ${adapter.name} adapter failed: ${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(`\n✔ Sync complete: ${totalFiles} files generated`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async function loadCapabilities(paths) {
|
|
70
|
+
const merged = {};
|
|
71
|
+
try {
|
|
72
|
+
const files = await readdir(paths.plugins);
|
|
73
|
+
for (const f of files) {
|
|
74
|
+
if (!f.endsWith('.json')) continue;
|
|
75
|
+
try {
|
|
76
|
+
const meta = JSON.parse(await readFile(join(paths.plugins, f), 'utf8'));
|
|
77
|
+
if (meta.capabilities) {
|
|
78
|
+
Object.assign(merged, meta.capabilities);
|
|
79
|
+
}
|
|
80
|
+
} catch { /* skip malformed */ }
|
|
81
|
+
}
|
|
82
|
+
} catch { /* no plugins dir */ }
|
|
83
|
+
return merged;
|
|
84
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
// src/core/config.js
|
|
2
|
+
import { readFile, writeFile } from 'fs/promises';
|
|
3
|
+
import { udaPaths } from './constants.js';
|
|
4
|
+
|
|
5
|
+
export async function loadConfig(root) {
|
|
6
|
+
const paths = udaPaths(root);
|
|
7
|
+
return JSON.parse(await readFile(paths.config, 'utf8'));
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export async function saveConfig(root, config) {
|
|
11
|
+
const paths = udaPaths(root);
|
|
12
|
+
await writeFile(paths.config, JSON.stringify(config, null, 2));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getConfigValue(config, key) {
|
|
16
|
+
const parts = key.split('.');
|
|
17
|
+
let current = config;
|
|
18
|
+
for (const part of parts) {
|
|
19
|
+
if (current == null || typeof current !== 'object') return undefined;
|
|
20
|
+
current = current[part];
|
|
21
|
+
}
|
|
22
|
+
return current;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function setConfigValue(config, key, value) {
|
|
26
|
+
const parts = key.split('.');
|
|
27
|
+
let current = config;
|
|
28
|
+
for (let i = 0; i < parts.length - 1; i++) {
|
|
29
|
+
if (current[parts[i]] == null || typeof current[parts[i]] !== 'object') {
|
|
30
|
+
current[parts[i]] = {};
|
|
31
|
+
}
|
|
32
|
+
current = current[parts[i]];
|
|
33
|
+
}
|
|
34
|
+
current[parts[parts.length - 1]] = parseValue(value);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function parseValue(value) {
|
|
38
|
+
if (value === 'true') return true;
|
|
39
|
+
if (value === 'false') return false;
|
|
40
|
+
if (value === 'null') return null;
|
|
41
|
+
if (/^\d+$/.test(value)) return parseInt(value, 10);
|
|
42
|
+
if (/^\d+\.\d+$/.test(value)) return parseFloat(value);
|
|
43
|
+
if (value.startsWith('[') && value.endsWith(']')) {
|
|
44
|
+
return value.slice(1, -1).split(',').map(s => s.trim());
|
|
45
|
+
}
|
|
46
|
+
return value;
|
|
47
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
// src/core/constants.js
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
|
|
4
|
+
export const UDA_DIR = '.uda';
|
|
5
|
+
|
|
6
|
+
export function udaPaths(root) {
|
|
7
|
+
const uda = join(root, UDA_DIR);
|
|
8
|
+
return {
|
|
9
|
+
root: uda,
|
|
10
|
+
config: join(uda, 'config.json'),
|
|
11
|
+
knowledge: {
|
|
12
|
+
root: join(uda, 'knowledge'),
|
|
13
|
+
engine: join(uda, 'knowledge', 'engine'),
|
|
14
|
+
project: join(uda, 'knowledge', 'project'),
|
|
15
|
+
community: join(uda, 'knowledge', 'community'),
|
|
16
|
+
},
|
|
17
|
+
workflows: join(uda, 'workflows'),
|
|
18
|
+
agents: join(uda, 'agents'),
|
|
19
|
+
state: {
|
|
20
|
+
root: join(uda, 'state'),
|
|
21
|
+
current: join(uda, 'state', 'current.md'),
|
|
22
|
+
features: join(uda, 'state', 'features'),
|
|
23
|
+
history: join(uda, 'state', 'history'),
|
|
24
|
+
},
|
|
25
|
+
rag: {
|
|
26
|
+
root: join(uda, 'rag'),
|
|
27
|
+
lancedb: join(uda, 'rag', 'lancedb'),
|
|
28
|
+
cache: join(uda, 'rag', 'cache'),
|
|
29
|
+
},
|
|
30
|
+
plugins: join(uda, 'plugins'),
|
|
31
|
+
generated: join(uda, '.generated'),
|
|
32
|
+
logs: join(uda, 'logs'),
|
|
33
|
+
};
|
|
34
|
+
}
|