@openweave/weave-cli 1.0.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.
- package/LICENSE +21 -0
- package/README.md +116 -0
- package/package.json +45 -0
- package/src/cli.test.ts +722 -0
- package/src/cli.ts +155 -0
- package/src/commands/errors.ts +211 -0
- package/src/commands/init.ts +133 -0
- package/src/commands/migrate.ts +239 -0
- package/src/commands/milestones.ts +249 -0
- package/src/commands/orphans.ts +210 -0
- package/src/commands/query.ts +170 -0
- package/src/commands/save-node.ts +161 -0
- package/src/commands/skills.ts +230 -0
- package/src/commands/status.ts +122 -0
- package/src/commands/tools.ts +346 -0
- package/src/index.ts +12 -0
- package/src/types.ts +53 -0
- package/src/utils.ts +19 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { CLIArgs, CommandResult, CliCommand } from '../types.js';
|
|
4
|
+
import { resolveProjectRoot } from '../utils.js';
|
|
5
|
+
import { validateManifest, type ToolManifest } from '@openweave/weave-tools';
|
|
6
|
+
import { ToolStore } from '@openweave/weave-tools';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* ToolsCommand — manage external tool adapters
|
|
10
|
+
*
|
|
11
|
+
* Subcommands:
|
|
12
|
+
* weave tools list List all registered tools
|
|
13
|
+
* weave tools add <url|./path> Register tool from URL or local file
|
|
14
|
+
* weave tools remove <id> Remove a registered tool
|
|
15
|
+
* weave tools info <id> Show tool manifest and status
|
|
16
|
+
* weave tools test <id> <action> [--args={}] Invoke a tool action
|
|
17
|
+
*/
|
|
18
|
+
export const toolsCommand: CliCommand = {
|
|
19
|
+
name: 'tools',
|
|
20
|
+
description: 'Manage external tool adapters (add, remove, list, info, test)',
|
|
21
|
+
usage: 'weave tools <list|add|remove|info|test> [args]',
|
|
22
|
+
flags: {
|
|
23
|
+
json: {
|
|
24
|
+
short: 'j',
|
|
25
|
+
description: 'Output as JSON',
|
|
26
|
+
default: false,
|
|
27
|
+
},
|
|
28
|
+
args: {
|
|
29
|
+
short: 'a',
|
|
30
|
+
description: 'JSON arguments for test subcommand',
|
|
31
|
+
default: '{}',
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
async execute(cliArgs: CLIArgs): Promise<CommandResult> {
|
|
36
|
+
const sub = cliArgs.args[0] ?? 'list';
|
|
37
|
+
const outputJson = Boolean(cliArgs.flags.json);
|
|
38
|
+
const projectRoot = resolveProjectRoot();
|
|
39
|
+
const store = new ToolStore(projectRoot);
|
|
40
|
+
|
|
41
|
+
switch (sub) {
|
|
42
|
+
case 'list':
|
|
43
|
+
return handleList(store, outputJson);
|
|
44
|
+
|
|
45
|
+
case 'add': {
|
|
46
|
+
const source = cliArgs.args[1];
|
|
47
|
+
if (!source) {
|
|
48
|
+
return {
|
|
49
|
+
success: false,
|
|
50
|
+
message: '❌ Usage: weave tools add <url|./path/to/manifest.tool.json>',
|
|
51
|
+
error: 'Missing source',
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
return handleAdd(source, store, projectRoot, outputJson);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
case 'remove': {
|
|
58
|
+
const id = cliArgs.args[1];
|
|
59
|
+
if (!id) {
|
|
60
|
+
return {
|
|
61
|
+
success: false,
|
|
62
|
+
message: '❌ Usage: weave tools remove <tool-id>',
|
|
63
|
+
error: 'Missing tool id',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return handleRemove(id, store, outputJson);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
case 'info': {
|
|
70
|
+
const id = cliArgs.args[1];
|
|
71
|
+
if (!id) {
|
|
72
|
+
return {
|
|
73
|
+
success: false,
|
|
74
|
+
message: '❌ Usage: weave tools info <tool-id>',
|
|
75
|
+
error: 'Missing tool id',
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
return handleInfo(id, store, outputJson);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
case 'test': {
|
|
82
|
+
const id = cliArgs.args[1];
|
|
83
|
+
const action = cliArgs.args[2];
|
|
84
|
+
if (!id || !action) {
|
|
85
|
+
return {
|
|
86
|
+
success: false,
|
|
87
|
+
message: '❌ Usage: weave tools test <tool-id> <action-name> [--args={}]',
|
|
88
|
+
error: 'Missing tool id or action',
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
const rawArgs = String(cliArgs.flags.args ?? '{}');
|
|
92
|
+
return handleTest(id, action, rawArgs, store, outputJson);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
default:
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
message: `❌ Unknown subcommand: "${sub}"\n\nUsage: weave tools <list|add|remove|info|test>`,
|
|
99
|
+
error: `Unknown subcommand: ${sub}`,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
// Handlers
|
|
107
|
+
// ---------------------------------------------------------------------------
|
|
108
|
+
|
|
109
|
+
function handleList(store: ToolStore, outputJson: boolean): CommandResult {
|
|
110
|
+
const tools = store.list();
|
|
111
|
+
|
|
112
|
+
if (outputJson) {
|
|
113
|
+
return {
|
|
114
|
+
success: true,
|
|
115
|
+
message: JSON.stringify({ tools }, null, 2),
|
|
116
|
+
data: { tools },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
if (tools.length === 0) {
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
message: '📭 No external tools registered.\n\nRun: weave tools add <url|./path>',
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const lines = [
|
|
128
|
+
`🔧 External Tools (${tools.length} registered)`,
|
|
129
|
+
'',
|
|
130
|
+
...tools.map((t) =>
|
|
131
|
+
` ${t.id.padEnd(24)} ${t.name.padEnd(30)} [${t.adapter}] v${t.version} (${t.tools.length} action${t.tools.length === 1 ? '' : 's'})`,
|
|
132
|
+
),
|
|
133
|
+
'',
|
|
134
|
+
'Run `weave tools info <id>` for details.',
|
|
135
|
+
];
|
|
136
|
+
|
|
137
|
+
return { success: true, message: lines.join('\n'), data: { tools } };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async function handleAdd(
|
|
141
|
+
source: string,
|
|
142
|
+
store: ToolStore,
|
|
143
|
+
projectRoot: string,
|
|
144
|
+
outputJson: boolean,
|
|
145
|
+
): Promise<CommandResult> {
|
|
146
|
+
let manifest: ToolManifest;
|
|
147
|
+
|
|
148
|
+
// Local file path
|
|
149
|
+
if (source.startsWith('.') || source.startsWith('/')) {
|
|
150
|
+
const filePath = source.startsWith('/') ? source : join(projectRoot, source);
|
|
151
|
+
if (!existsSync(filePath)) {
|
|
152
|
+
return {
|
|
153
|
+
success: false,
|
|
154
|
+
message: `❌ File not found: ${filePath}`,
|
|
155
|
+
error: 'File not found',
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
let raw: string;
|
|
159
|
+
try {
|
|
160
|
+
raw = readFileSync(filePath, 'utf8');
|
|
161
|
+
} catch (err) {
|
|
162
|
+
return {
|
|
163
|
+
success: false,
|
|
164
|
+
message: `❌ Cannot read file: ${err instanceof Error ? err.message : String(err)}`,
|
|
165
|
+
error: String(err),
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
let parsed: unknown;
|
|
169
|
+
try {
|
|
170
|
+
parsed = JSON.parse(raw);
|
|
171
|
+
} catch {
|
|
172
|
+
return { success: false, message: '❌ Invalid JSON in manifest file', error: 'Invalid JSON' };
|
|
173
|
+
}
|
|
174
|
+
const validation = validateManifest(parsed);
|
|
175
|
+
if (!validation.valid) {
|
|
176
|
+
return {
|
|
177
|
+
success: false,
|
|
178
|
+
message: `❌ Invalid manifest:\n${validation.errors.map((e) => ` • ${e}`).join('\n')}`,
|
|
179
|
+
error: validation.errors.join(', '),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
manifest = parsed as ToolManifest;
|
|
183
|
+
} else if (source.startsWith('http://') || source.startsWith('https://')) {
|
|
184
|
+
// Remote URL — fetch the manifest
|
|
185
|
+
let raw: string;
|
|
186
|
+
try {
|
|
187
|
+
const res = await fetch(source, { signal: AbortSignal.timeout(10_000) });
|
|
188
|
+
if (!res.ok) {
|
|
189
|
+
return {
|
|
190
|
+
success: false,
|
|
191
|
+
message: `❌ HTTP ${res.status} fetching manifest from ${source}`,
|
|
192
|
+
error: `HTTP ${res.status}`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
raw = await res.text();
|
|
196
|
+
} catch (err) {
|
|
197
|
+
return {
|
|
198
|
+
success: false,
|
|
199
|
+
message: `❌ Failed to fetch manifest: ${err instanceof Error ? err.message : String(err)}`,
|
|
200
|
+
error: String(err),
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
let parsed: unknown;
|
|
204
|
+
try {
|
|
205
|
+
parsed = JSON.parse(raw);
|
|
206
|
+
} catch {
|
|
207
|
+
return { success: false, message: '❌ Invalid JSON in remote manifest', error: 'Invalid JSON' };
|
|
208
|
+
}
|
|
209
|
+
const validation = validateManifest(parsed);
|
|
210
|
+
if (!validation.valid) {
|
|
211
|
+
return {
|
|
212
|
+
success: false,
|
|
213
|
+
message: `❌ Invalid manifest:\n${validation.errors.map((e) => ` • ${e}`).join('\n')}`,
|
|
214
|
+
error: validation.errors.join(', '),
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
manifest = parsed as ToolManifest;
|
|
218
|
+
|
|
219
|
+
// Copy manifest to .weave/tools/
|
|
220
|
+
const toolsDir = join(projectRoot, '.weave', 'tools');
|
|
221
|
+
mkdirSync(toolsDir, { recursive: true });
|
|
222
|
+
writeFileSync(join(toolsDir, `${manifest.id}.tool.json`), JSON.stringify(manifest, null, 2), 'utf8');
|
|
223
|
+
} else {
|
|
224
|
+
return {
|
|
225
|
+
success: false,
|
|
226
|
+
message: `❌ Unsupported source format: "${source}"\n\nSupported: ./path/to/manifest.tool.json · https://... · .weave/tools/<name>.tool.json`,
|
|
227
|
+
error: 'Unsupported source',
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Save to store
|
|
232
|
+
store.add(manifest);
|
|
233
|
+
|
|
234
|
+
if (outputJson) {
|
|
235
|
+
return { success: true, message: JSON.stringify({ added: manifest }, null, 2), data: { added: manifest } };
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const actions = manifest.tools.map((t) => `${manifest.id}__${t.name}`).join(', ');
|
|
239
|
+
return {
|
|
240
|
+
success: true,
|
|
241
|
+
message: `✅ Tool registered: "${manifest.name}" (${manifest.id})\n Adapter: ${manifest.adapter} | Actions: ${actions}`,
|
|
242
|
+
data: { added: manifest },
|
|
243
|
+
};
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function handleRemove(id: string, store: ToolStore, outputJson: boolean): CommandResult {
|
|
247
|
+
const removed = store.remove(id);
|
|
248
|
+
if (!removed) {
|
|
249
|
+
return {
|
|
250
|
+
success: false,
|
|
251
|
+
message: `❌ Tool not found: "${id}"\n\nRun \`weave tools list\` to see registered tools.`,
|
|
252
|
+
error: `Tool not found: ${id}`,
|
|
253
|
+
};
|
|
254
|
+
}
|
|
255
|
+
if (outputJson) {
|
|
256
|
+
return { success: true, message: JSON.stringify({ removed: id }), data: { removed: id } };
|
|
257
|
+
}
|
|
258
|
+
return { success: true, message: `✅ Tool removed: ${id}`, data: { removed: id } };
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
function handleInfo(id: string, store: ToolStore, outputJson: boolean): CommandResult {
|
|
262
|
+
const manifest = store.get(id);
|
|
263
|
+
if (!manifest) {
|
|
264
|
+
return {
|
|
265
|
+
success: false,
|
|
266
|
+
message: `❌ Tool not found: "${id}"`,
|
|
267
|
+
error: `Tool not found: ${id}`,
|
|
268
|
+
};
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
if (outputJson) {
|
|
272
|
+
return { success: true, message: JSON.stringify(manifest, null, 2), data: manifest };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
const lines = [
|
|
276
|
+
`🔧 ${manifest.name} (${manifest.id})`,
|
|
277
|
+
` Description : ${manifest.description}`,
|
|
278
|
+
` Version : ${manifest.version}`,
|
|
279
|
+
` Adapter : ${manifest.adapter}`,
|
|
280
|
+
manifest.endpoint ? ` Endpoint : ${manifest.endpoint}` : null,
|
|
281
|
+
manifest.scriptPath ? ` Script : ${manifest.scriptPath}` : null,
|
|
282
|
+
` Timeout : ${manifest.timeout_ms ?? 10_000}ms`,
|
|
283
|
+
``,
|
|
284
|
+
` Actions (${manifest.tools.length}):`,
|
|
285
|
+
...manifest.tools.map(
|
|
286
|
+
(t) => ` • ${manifest.id}__${t.name.padEnd(20)} ${t.description}`,
|
|
287
|
+
),
|
|
288
|
+
].filter(Boolean);
|
|
289
|
+
|
|
290
|
+
return { success: true, message: lines.join('\n'), data: manifest };
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
async function handleTest(
|
|
294
|
+
id: string,
|
|
295
|
+
action: string,
|
|
296
|
+
rawArgs: string,
|
|
297
|
+
store: ToolStore,
|
|
298
|
+
outputJson: boolean,
|
|
299
|
+
): Promise<CommandResult> {
|
|
300
|
+
const manifest = store.get(id);
|
|
301
|
+
if (!manifest) {
|
|
302
|
+
return {
|
|
303
|
+
success: false,
|
|
304
|
+
message: `❌ Tool not found: "${id}"`,
|
|
305
|
+
error: `Tool not found: ${id}`,
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
let args: Record<string, unknown>;
|
|
310
|
+
try {
|
|
311
|
+
args = JSON.parse(rawArgs) as Record<string, unknown>;
|
|
312
|
+
} catch {
|
|
313
|
+
return {
|
|
314
|
+
success: false,
|
|
315
|
+
message: `❌ Invalid JSON in --args: ${rawArgs}`,
|
|
316
|
+
error: 'Invalid JSON args',
|
|
317
|
+
};
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const prefixedAction = action.includes('__') ? action : `${id}__${action}`;
|
|
321
|
+
|
|
322
|
+
// Dynamically import the bridge to dispatch the call
|
|
323
|
+
const { ExternalToolBridge } = await import('@openweave/weave-tools');
|
|
324
|
+
const bridge = new ExternalToolBridge(resolveProjectRoot());
|
|
325
|
+
bridge.registerManifest(manifest);
|
|
326
|
+
|
|
327
|
+
const result = await bridge.execute(prefixedAction, args);
|
|
328
|
+
|
|
329
|
+
if (outputJson) {
|
|
330
|
+
return { success: result.success, message: JSON.stringify(result, null, 2), data: result };
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!result.success) {
|
|
334
|
+
return {
|
|
335
|
+
success: false,
|
|
336
|
+
message: `❌ Tool call failed: ${result.error}`,
|
|
337
|
+
error: result.error,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
success: true,
|
|
343
|
+
message: `✅ ${prefixedAction} (${result.durationMs}ms)\n${JSON.stringify(result.data, null, 2)}`,
|
|
344
|
+
data: result,
|
|
345
|
+
};
|
|
346
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weave CLI - Exports
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export type { CLIArgs, CLIConfig, CommandResult, ProjectState, CliCommand } from './types';
|
|
6
|
+
export { initCommand, InitCommand } from './commands/init';
|
|
7
|
+
export { statusCommand, StatusCommand } from './commands/status';
|
|
8
|
+
export { milestonesCommand, MilestonesCommand } from './commands/milestones';
|
|
9
|
+
export { queryCommand, QueryCommand } from './commands/query';
|
|
10
|
+
export { orphansCommand, OrphansCommand } from './commands/orphans';
|
|
11
|
+
export { errorsCommand, ErrorsCommand } from './commands/errors';
|
|
12
|
+
export { saveNodeCommand, SaveNodeCommand } from './commands/save-node';
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Weave CLI Types
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface CLIArgs {
|
|
6
|
+
command: string;
|
|
7
|
+
subcommand?: string;
|
|
8
|
+
args: string[];
|
|
9
|
+
flags: Record<string, string | boolean>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface CLIConfig {
|
|
13
|
+
project_name: string;
|
|
14
|
+
project_root: string;
|
|
15
|
+
knowledge_graph_path: string;
|
|
16
|
+
roadmap_file: string;
|
|
17
|
+
include_tests: boolean;
|
|
18
|
+
max_context_depth: number;
|
|
19
|
+
verbose: boolean;
|
|
20
|
+
debug: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CommandResult {
|
|
24
|
+
success: boolean;
|
|
25
|
+
message: string;
|
|
26
|
+
data?: unknown;
|
|
27
|
+
error?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface ProjectState {
|
|
31
|
+
created_at: Date;
|
|
32
|
+
last_updated: Date;
|
|
33
|
+
session_id: string;
|
|
34
|
+
milestones: number;
|
|
35
|
+
total_nodes: number;
|
|
36
|
+
total_edges: number;
|
|
37
|
+
context_usage_percent: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface CliCommand {
|
|
41
|
+
name: string;
|
|
42
|
+
description: string;
|
|
43
|
+
usage: string;
|
|
44
|
+
flags?: Record<
|
|
45
|
+
string,
|
|
46
|
+
{
|
|
47
|
+
short?: string;
|
|
48
|
+
description: string;
|
|
49
|
+
default?: string | boolean;
|
|
50
|
+
}
|
|
51
|
+
>;
|
|
52
|
+
execute(args: CLIArgs): Promise<CommandResult>;
|
|
53
|
+
}
|
package/src/utils.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Resolves the effective project root directory.
|
|
3
|
+
*
|
|
4
|
+
* When the CLI is invoked through `pnpm --filter weave-cli dev ...` (or any
|
|
5
|
+
* npm/pnpm script), the package manager changes the working directory to the
|
|
6
|
+
* package folder (e.g. apps/weave-cli/). However, npm and pnpm both set the
|
|
7
|
+
* INIT_CWD environment variable to the *original* directory from which the
|
|
8
|
+
* command was run, so we prefer that over process.cwd().
|
|
9
|
+
*
|
|
10
|
+
* In a production global install (`weave init`) both values are identical and
|
|
11
|
+
* INIT_CWD simply may not be set — the fallback to process.cwd() is safe.
|
|
12
|
+
*
|
|
13
|
+
* @param explicitRoot - Optional path supplied via the --root flag. When
|
|
14
|
+
* present it always wins over any automatic detection.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveProjectRoot(explicitRoot?: string): string {
|
|
17
|
+
if (explicitRoot) return explicitRoot;
|
|
18
|
+
return process.env['INIT_CWD'] ?? process.cwd();
|
|
19
|
+
}
|