cyclecad 0.2.2 → 0.2.3
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/API-BUILD-MANIFEST.txt +339 -0
- package/API-SERVER.md +535 -0
- package/Architecture-Deck.pptx +0 -0
- package/CLAUDE.md +172 -11
- package/CLI-BUILD-SUMMARY.md +504 -0
- package/CLI-INDEX.md +356 -0
- package/CLI-README.md +466 -0
- package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
- package/CONNECTED_FABS_GUIDE.md +612 -0
- package/CONNECTED_FABS_README.md +310 -0
- package/DELIVERABLES.md +343 -0
- package/DFM-ANALYZER-INTEGRATION.md +368 -0
- package/DFM-QUICK-START.js +253 -0
- package/Dockerfile +69 -0
- package/IMPLEMENTATION.md +327 -0
- package/LICENSE +31 -0
- package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
- package/MCP-INDEX.md +264 -0
- package/QUICKSTART-API.md +388 -0
- package/QUICKSTART-CLI.md +211 -0
- package/QUICKSTART-MCP.md +196 -0
- package/README-MCP.md +208 -0
- package/TEST-TOKEN-ENGINE.md +319 -0
- package/TOKEN-ENGINE-SUMMARY.md +266 -0
- package/TOKENS-README.md +263 -0
- package/TOOLS-REFERENCE.md +254 -0
- package/app/index.html +168 -3
- package/app/js/TOKEN-INTEGRATION.md +391 -0
- package/app/js/agent-api.js +3 -3
- package/app/js/ai-copilot.js +1435 -0
- package/app/js/cam-pipeline.js +840 -0
- package/app/js/collaboration-ui.js +995 -0
- package/app/js/collaboration.js +1116 -0
- package/app/js/connected-fabs-example.js +404 -0
- package/app/js/connected-fabs.js +1449 -0
- package/app/js/dfm-analyzer.js +1760 -0
- package/app/js/marketplace.js +1994 -0
- package/app/js/material-library.js +2115 -0
- package/app/js/token-dashboard.js +563 -0
- package/app/js/token-engine.js +743 -0
- package/app/test-agent.html +1801 -0
- package/bin/cyclecad-cli.js +662 -0
- package/bin/cyclecad-mcp +2 -0
- package/bin/server.js +242 -0
- package/cycleCAD-Architecture.pptx +0 -0
- package/cycleCAD-Investor-Deck.pptx +0 -0
- package/demo-mcp.sh +60 -0
- package/docs/API-SERVER-SUMMARY.md +375 -0
- package/docs/API-SERVER.md +667 -0
- package/docs/CAM-EXAMPLES.md +344 -0
- package/docs/CAM-INTEGRATION.md +612 -0
- package/docs/CAM-QUICK-REFERENCE.md +199 -0
- package/docs/CLI-INTEGRATION.md +510 -0
- package/docs/CLI.md +872 -0
- package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
- package/docs/MARKETPLACE-INTEGRATION.md +467 -0
- package/docs/MARKETPLACE-SETUP.html +439 -0
- package/docs/MCP-SERVER.md +403 -0
- package/examples/api-client-example.js +488 -0
- package/examples/api-client-example.py +359 -0
- package/examples/batch-manufacturing.txt +28 -0
- package/examples/batch-simple.txt +26 -0
- package/model-marketplace.html +1273 -0
- package/package.json +14 -3
- package/server/api-server.js +1120 -0
- package/server/mcp-server.js +1161 -0
- package/test-api-server.js +432 -0
- package/test-mcp.js +198 -0
- package/~$cycleCAD-Investor-Deck.pptx +0 -0
|
@@ -0,0 +1,662 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* cyclecad-cli.js — Command-line interface for cycleCAD Agent API
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* cyclecad shape.cylinder --radius 25 --height 80
|
|
8
|
+
* cyclecad feature.fillet --radius 5 --edges all
|
|
9
|
+
* cyclecad --list
|
|
10
|
+
* cyclecad --describe shape.cylinder
|
|
11
|
+
* cyclecad --interactive
|
|
12
|
+
* cyclecad --batch script.txt
|
|
13
|
+
*
|
|
14
|
+
* Global flags:
|
|
15
|
+
* --help, -h Show usage
|
|
16
|
+
* --version, -v Show version
|
|
17
|
+
* --json Output raw JSON
|
|
18
|
+
* --server <url> Server URL (default: http://localhost:3000)
|
|
19
|
+
* --quiet, -q Suppress status messages
|
|
20
|
+
* --list List all commands
|
|
21
|
+
* --describe <command> Show help for specific command
|
|
22
|
+
* --interactive, -i Interactive REPL mode
|
|
23
|
+
* --batch <file> Batch mode: execute commands from file
|
|
24
|
+
*
|
|
25
|
+
* No external dependencies — pure Node.js built-ins.
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
const http = require('http');
|
|
29
|
+
const https = require('https');
|
|
30
|
+
const readline = require('readline');
|
|
31
|
+
const fs = require('fs');
|
|
32
|
+
const path = require('path');
|
|
33
|
+
const { URL } = require('url');
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// Configuration
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
const VERSION = '0.1.0';
|
|
40
|
+
const DEFAULT_SERVER = 'http://localhost:3000';
|
|
41
|
+
const API_ENDPOINT = '/api/execute';
|
|
42
|
+
|
|
43
|
+
// ANSI colors for terminal output
|
|
44
|
+
const COLORS = {
|
|
45
|
+
reset: '\x1b[0m',
|
|
46
|
+
bright: '\x1b[1m',
|
|
47
|
+
dim: '\x1b[2m',
|
|
48
|
+
green: '\x1b[32m',
|
|
49
|
+
red: '\x1b[31m',
|
|
50
|
+
yellow: '\x1b[33m',
|
|
51
|
+
blue: '\x1b[34m',
|
|
52
|
+
cyan: '\x1b[36m',
|
|
53
|
+
gray: '\x1b[90m',
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
// Command schema (extracted from Agent API)
|
|
57
|
+
const COMMAND_SCHEMA = {
|
|
58
|
+
shape: {
|
|
59
|
+
cylinder: { params: { radius: 'number', height: 'number' }, description: 'Create a cylinder' },
|
|
60
|
+
box: { params: { width: 'number', height: 'number', depth: 'number' }, description: 'Create a box' },
|
|
61
|
+
sphere: { params: { radius: 'number' }, description: 'Create a sphere' },
|
|
62
|
+
cone: { params: { radius: 'number', height: 'number' }, description: 'Create a cone' },
|
|
63
|
+
},
|
|
64
|
+
sketch: {
|
|
65
|
+
start: { params: { plane: 'string?' }, description: 'Start sketch mode' },
|
|
66
|
+
end: { params: {}, description: 'End sketch' },
|
|
67
|
+
line: { params: { x1: 'number', y1: 'number', x2: 'number', y2: 'number' }, description: 'Draw a line' },
|
|
68
|
+
rect: { params: { x: 'number?', y: 'number?', width: 'number', height: 'number' }, description: 'Draw a rectangle' },
|
|
69
|
+
circle: { params: { cx: 'number?', cy: 'number?', radius: 'number' }, description: 'Draw a circle' },
|
|
70
|
+
arc: { params: { cx: 'number?', cy: 'number?', radius: 'number', startAngle: 'number?', endAngle: 'number?' }, description: 'Draw an arc' },
|
|
71
|
+
clear: { params: {}, description: 'Clear sketch' },
|
|
72
|
+
entities: { params: {}, description: 'List sketch entities' },
|
|
73
|
+
},
|
|
74
|
+
feature: {
|
|
75
|
+
extrude: { params: { height: 'number', taper: 'number?' }, description: 'Extrude sketch' },
|
|
76
|
+
revolve: { params: { angle: 'number', axis: 'string?' }, description: 'Revolve sketch' },
|
|
77
|
+
fillet: { params: { radius: 'number', edges: 'string?' }, description: 'Fillet edges' },
|
|
78
|
+
chamfer: { params: { size: 'number', edges: 'string?' }, description: 'Chamfer edges' },
|
|
79
|
+
pattern: { params: { type: 'string', count: 'number', spacing: 'number' }, description: 'Pattern feature' },
|
|
80
|
+
},
|
|
81
|
+
assembly: {
|
|
82
|
+
addComponent: { params: { name: 'string', meshOrFile: 'string|object', position: 'array?' }, description: 'Add component' },
|
|
83
|
+
removeComponent: { params: { target: 'string' }, description: 'Remove component' },
|
|
84
|
+
mate: { params: { target1: 'string', target2: 'string', type: 'string?' }, description: 'Define mate' },
|
|
85
|
+
explode: { params: { target: 'string', distance: 'number?' }, description: 'Explode assembly' },
|
|
86
|
+
bom: { params: { target: 'string?' }, description: 'Generate BOM' },
|
|
87
|
+
},
|
|
88
|
+
render: {
|
|
89
|
+
snapshot: { params: { width: 'number?', height: 'number?' }, description: 'Render snapshot' },
|
|
90
|
+
multiview: { params: { width: 'number?', height: 'number?' }, description: 'Render 6 views' },
|
|
91
|
+
highlight: { params: { target: 'string', color: 'string?' }, description: 'Highlight component' },
|
|
92
|
+
hide: { params: { target: 'string', hidden: 'bool?' }, description: 'Hide/show component' },
|
|
93
|
+
section: { params: { enabled: 'bool?', axis: 'string?', position: 'number?' }, description: 'Section cut' },
|
|
94
|
+
},
|
|
95
|
+
validate: {
|
|
96
|
+
dimensions: { params: { target: 'string' }, description: 'Check dimensions' },
|
|
97
|
+
wallThickness: { params: { target: 'string', minWall: 'number?' }, description: 'Check wall thickness' },
|
|
98
|
+
printability: { params: { target: 'string', process: 'string?' }, description: 'Check printability' },
|
|
99
|
+
cost: { params: { target: 'string', process: 'string?', material: 'string?' }, description: 'Estimate cost' },
|
|
100
|
+
mass: { params: { target: 'string', material: 'string?' }, description: 'Estimate mass' },
|
|
101
|
+
surfaceArea: { params: { target: 'string' }, description: 'Calculate surface area' },
|
|
102
|
+
centerOfMass: { params: { target: 'string' }, description: 'Get center of mass' },
|
|
103
|
+
designReview: { params: { target: 'string' }, description: 'Design review' },
|
|
104
|
+
},
|
|
105
|
+
export: {
|
|
106
|
+
stl: { params: { filename: 'string?', binary: 'bool?' }, description: 'Export STL' },
|
|
107
|
+
obj: { params: { filename: 'string?' }, description: 'Export OBJ' },
|
|
108
|
+
gltf: { params: { filename: 'string?' }, description: 'Export glTF' },
|
|
109
|
+
json: { params: { filename: 'string?' }, description: 'Export JSON' },
|
|
110
|
+
step: { params: { filename: 'string?' }, description: 'Export STEP' },
|
|
111
|
+
},
|
|
112
|
+
marketplace: {
|
|
113
|
+
list: { params: { category: 'string?' }, description: 'List marketplace items' },
|
|
114
|
+
search: { params: { query: 'string', category: 'string?' }, description: 'Search marketplace' },
|
|
115
|
+
publish: { params: { name: 'string', price: 'number?', category: 'string?' }, description: 'Publish to marketplace' },
|
|
116
|
+
},
|
|
117
|
+
cam: {
|
|
118
|
+
slice: { params: { printer: 'string?', layer: 'number?' }, description: 'Slice for 3D printing' },
|
|
119
|
+
toolpath: { params: { tool: 'string', depth: 'number?' }, description: 'Generate CNC toolpath' },
|
|
120
|
+
},
|
|
121
|
+
meta: {
|
|
122
|
+
getSchema: { params: {}, description: 'Get full API schema' },
|
|
123
|
+
getState: { params: {}, description: 'Get session state' },
|
|
124
|
+
version: { params: {}, description: 'Get API version' },
|
|
125
|
+
history: { params: {}, description: 'Get command history' },
|
|
126
|
+
},
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// ============================================================================
|
|
130
|
+
// Utility Functions
|
|
131
|
+
// ============================================================================
|
|
132
|
+
|
|
133
|
+
function color(str, colorName) {
|
|
134
|
+
return COLORS[colorName] + str + COLORS.reset;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function log(msg, level = 'info') {
|
|
138
|
+
const timestamp = new Date().toISOString().substr(11, 8);
|
|
139
|
+
const prefix = {
|
|
140
|
+
info: color(`[${timestamp}]`, 'cyan'),
|
|
141
|
+
success: color(`[${timestamp}] ✓`, 'green'),
|
|
142
|
+
error: color(`[${timestamp}] ✗`, 'red'),
|
|
143
|
+
warn: color(`[${timestamp}] ⚠`, 'yellow'),
|
|
144
|
+
debug: color(`[${timestamp}] ◆`, 'gray'),
|
|
145
|
+
}[level] || `[${timestamp}]`;
|
|
146
|
+
|
|
147
|
+
console.log(`${prefix} ${msg}`);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function spinner(msg) {
|
|
151
|
+
const frames = ['⠋', '⠙', '⠹', '⠸', '⠼', '⠴', '⠦', '⠧', '⠇', '⠏'];
|
|
152
|
+
let frame = 0;
|
|
153
|
+
process.stdout.write(msg + ' ');
|
|
154
|
+
const interval = setInterval(() => {
|
|
155
|
+
process.stdout.write('\b' + frames[frame % frames.length]);
|
|
156
|
+
frame++;
|
|
157
|
+
}, 80);
|
|
158
|
+
return () => {
|
|
159
|
+
clearInterval(interval);
|
|
160
|
+
process.stdout.write('\b');
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function formatTable(headers, rows) {
|
|
165
|
+
const colWidths = headers.map((h, i) => Math.max(h.length, Math.max(...rows.map(r => String(r[i] || '').length))));
|
|
166
|
+
|
|
167
|
+
const sep = '+' + colWidths.map(w => '-'.repeat(w + 2)).join('+') + '+';
|
|
168
|
+
const headerRow = '| ' + headers.map((h, i) => h.padEnd(colWidths[i])).join(' | ') + ' |';
|
|
169
|
+
|
|
170
|
+
const dataRows = rows.map(row =>
|
|
171
|
+
'| ' + row.map((cell, i) => String(cell || '').padEnd(colWidths[i])).join(' | ') + ' |'
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
return [sep, headerRow, sep, ...dataRows, sep].join('\n');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
function formatJSON(obj, indent = 2) {
|
|
178
|
+
return JSON.stringify(obj, null, indent);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
function parseArgs(argv) {
|
|
182
|
+
const args = { _: [], flags: {} };
|
|
183
|
+
|
|
184
|
+
for (let i = 2; i < argv.length; i++) {
|
|
185
|
+
const arg = argv[i];
|
|
186
|
+
|
|
187
|
+
if (arg.startsWith('--')) {
|
|
188
|
+
const [key, val] = arg.slice(2).split('=');
|
|
189
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
|
|
190
|
+
args.flags[key] = argv[++i];
|
|
191
|
+
} else {
|
|
192
|
+
args.flags[key] = val || true;
|
|
193
|
+
}
|
|
194
|
+
} else if (arg.startsWith('-') && arg.length === 2) {
|
|
195
|
+
const key = arg.slice(1);
|
|
196
|
+
if (i + 1 < argv.length && !argv[i + 1].startsWith('-')) {
|
|
197
|
+
args.flags[key] = argv[++i];
|
|
198
|
+
} else {
|
|
199
|
+
args.flags[key] = true;
|
|
200
|
+
}
|
|
201
|
+
} else {
|
|
202
|
+
args._.push(arg);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
return args;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
function parseCommand(cmdStr) {
|
|
210
|
+
const parts = cmdStr.trim().split(/\s+/);
|
|
211
|
+
const method = parts[0];
|
|
212
|
+
const params = {};
|
|
213
|
+
|
|
214
|
+
for (let i = 1; i < parts.length; i++) {
|
|
215
|
+
if (parts[i].startsWith('--')) {
|
|
216
|
+
const key = parts[i].slice(2);
|
|
217
|
+
let value = parts[i + 1];
|
|
218
|
+
|
|
219
|
+
if (value && !value.startsWith('--')) {
|
|
220
|
+
try {
|
|
221
|
+
params[key] = JSON.parse(value);
|
|
222
|
+
} catch {
|
|
223
|
+
params[key] = value;
|
|
224
|
+
}
|
|
225
|
+
i++;
|
|
226
|
+
} else {
|
|
227
|
+
params[key] = true;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
return { method, params };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function validateCommand(method, params) {
|
|
236
|
+
const [ns, cmd] = method.split('.');
|
|
237
|
+
if (!COMMAND_SCHEMA[ns] || !COMMAND_SCHEMA[ns][cmd]) {
|
|
238
|
+
return { ok: false, error: `Unknown command: ${method}` };
|
|
239
|
+
}
|
|
240
|
+
return { ok: true };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// ============================================================================
|
|
244
|
+
// HTTP Communication
|
|
245
|
+
// ============================================================================
|
|
246
|
+
|
|
247
|
+
function makeRequest(serverUrl, method, params) {
|
|
248
|
+
return new Promise((resolve, reject) => {
|
|
249
|
+
const url = new URL(API_ENDPOINT, serverUrl);
|
|
250
|
+
const isHttps = url.protocol === 'https:';
|
|
251
|
+
const client = isHttps ? https : http;
|
|
252
|
+
|
|
253
|
+
const body = JSON.stringify({ method, params });
|
|
254
|
+
const options = {
|
|
255
|
+
method: 'POST',
|
|
256
|
+
headers: {
|
|
257
|
+
'Content-Type': 'application/json',
|
|
258
|
+
'Content-Length': Buffer.byteLength(body),
|
|
259
|
+
},
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
const req = client.request(url, options, (res) => {
|
|
263
|
+
let data = '';
|
|
264
|
+
res.on('data', chunk => data += chunk);
|
|
265
|
+
res.on('end', () => {
|
|
266
|
+
try {
|
|
267
|
+
resolve({ statusCode: res.statusCode, data: JSON.parse(data) });
|
|
268
|
+
} catch {
|
|
269
|
+
resolve({ statusCode: res.statusCode, data: { error: 'Invalid JSON response' } });
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
req.on('error', reject);
|
|
275
|
+
req.write(body);
|
|
276
|
+
req.end();
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// ============================================================================
|
|
281
|
+
// Command Execution
|
|
282
|
+
// ============================================================================
|
|
283
|
+
|
|
284
|
+
async function executeCommand(method, params, serverUrl, quiet = false) {
|
|
285
|
+
const validation = validateCommand(method, params);
|
|
286
|
+
if (!validation.ok) {
|
|
287
|
+
log(validation.error, 'error');
|
|
288
|
+
return { ok: false };
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (!quiet) {
|
|
292
|
+
const stop = spinner(`Executing ${color(method, 'blue')}...`);
|
|
293
|
+
try {
|
|
294
|
+
const result = await makeRequest(serverUrl, method, params);
|
|
295
|
+
stop();
|
|
296
|
+
|
|
297
|
+
if (result.statusCode === 200 && result.data.ok) {
|
|
298
|
+
log(`Command executed: ${color(method, 'green')}`, 'success');
|
|
299
|
+
return { ok: true, result: result.data.result };
|
|
300
|
+
} else {
|
|
301
|
+
log(`Command failed: ${result.data.error || 'Unknown error'}`, 'error');
|
|
302
|
+
return { ok: false, error: result.data.error };
|
|
303
|
+
}
|
|
304
|
+
} catch (err) {
|
|
305
|
+
stop();
|
|
306
|
+
log(`Connection failed: ${err.message}`, 'error');
|
|
307
|
+
return { ok: false, error: err.message };
|
|
308
|
+
}
|
|
309
|
+
} else {
|
|
310
|
+
try {
|
|
311
|
+
const result = await makeRequest(serverUrl, method, params);
|
|
312
|
+
if (result.statusCode === 200 && result.data.ok) {
|
|
313
|
+
return { ok: true, result: result.data.result };
|
|
314
|
+
} else {
|
|
315
|
+
return { ok: false, error: result.data.error };
|
|
316
|
+
}
|
|
317
|
+
} catch (err) {
|
|
318
|
+
return { ok: false, error: err.message };
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
// ============================================================================
|
|
324
|
+
// Output Formatting
|
|
325
|
+
// ============================================================================
|
|
326
|
+
|
|
327
|
+
function displayResult(result, asJson = false) {
|
|
328
|
+
if (asJson) {
|
|
329
|
+
console.log(formatJSON(result));
|
|
330
|
+
return;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
if (!result || typeof result !== 'object') {
|
|
334
|
+
console.log(result);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Handle different result types
|
|
339
|
+
if (result.entityId) {
|
|
340
|
+
console.log(color(` Entity ID: ${result.entityId}`, 'blue'));
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
if (result.dimensions) {
|
|
344
|
+
console.log(color(' Dimensions:', 'cyan'));
|
|
345
|
+
console.log(` Width: ${result.dimensions.width} mm`);
|
|
346
|
+
console.log(` Height: ${result.dimensions.height} mm`);
|
|
347
|
+
console.log(` Depth: ${result.dimensions.depth} mm`);
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (result.mass !== undefined) {
|
|
351
|
+
console.log(color(` Mass: ${result.mass.toFixed(2)} g`, 'cyan'));
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
if (result.surfaceArea !== undefined) {
|
|
355
|
+
console.log(color(` Surface Area: ${result.surfaceArea.toFixed(2)} mm²`, 'cyan'));
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
if (result.cost !== undefined) {
|
|
359
|
+
console.log(color(` Estimated Cost: $${result.cost.toFixed(2)}`, 'cyan'));
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
if (result.score) {
|
|
363
|
+
const scoreColor = result.score === 'A' ? 'green' : result.score === 'F' ? 'red' : 'yellow';
|
|
364
|
+
console.log(color(` Design Review Score: ${result.score}`, scoreColor));
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
if (Array.isArray(result)) {
|
|
368
|
+
result.forEach(item => displayResult(item, asJson));
|
|
369
|
+
}
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// ============================================================================
|
|
373
|
+
// Help & Documentation
|
|
374
|
+
// ============================================================================
|
|
375
|
+
|
|
376
|
+
function showHelp(command = null) {
|
|
377
|
+
if (command) {
|
|
378
|
+
const [ns, cmd] = command.split('.');
|
|
379
|
+
const cmdInfo = COMMAND_SCHEMA[ns]?.[cmd];
|
|
380
|
+
|
|
381
|
+
if (!cmdInfo) {
|
|
382
|
+
log(`Unknown command: ${command}`, 'error');
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
console.log(`\n${color(`Command: ${command}`, 'bright')}`);
|
|
387
|
+
console.log(`Description: ${cmdInfo.description}`);
|
|
388
|
+
console.log(`\nParameters:`);
|
|
389
|
+
|
|
390
|
+
Object.entries(cmdInfo.params || {}).forEach(([key, type]) => {
|
|
391
|
+
console.log(` ${color(key, 'yellow')}: ${type}`);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
console.log(`\nExample:`);
|
|
395
|
+
console.log(` cyclecad ${command} ${Object.keys(cmdInfo.params || {})
|
|
396
|
+
.map(k => `--${k} value`)
|
|
397
|
+
.join(' ')}`);
|
|
398
|
+
console.log('');
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
console.log(`
|
|
403
|
+
${color('cyclecad', 'bright')} — Agent API CLI for cycleCAD
|
|
404
|
+
|
|
405
|
+
${color('Usage:', 'bright')}
|
|
406
|
+
cyclecad <namespace>.<command> [--param value ...]
|
|
407
|
+
cyclecad --interactive
|
|
408
|
+
cyclecad --batch <file>
|
|
409
|
+
cyclecad --list
|
|
410
|
+
cyclecad --describe <command>
|
|
411
|
+
|
|
412
|
+
${color('Global Flags:', 'bright')}
|
|
413
|
+
--help, -h Show this help message
|
|
414
|
+
--version, -v Show version
|
|
415
|
+
--server <url> Server URL (default: ${DEFAULT_SERVER})
|
|
416
|
+
--json Output raw JSON
|
|
417
|
+
--quiet, -q Suppress status messages
|
|
418
|
+
--list List all available commands
|
|
419
|
+
--describe <cmd> Show help for specific command
|
|
420
|
+
--interactive, -i Start interactive REPL
|
|
421
|
+
--batch <file> Execute commands from file
|
|
422
|
+
|
|
423
|
+
${color('Examples:', 'bright')}
|
|
424
|
+
cyclecad shape.cylinder --radius 25 --height 80
|
|
425
|
+
cyclecad feature.fillet --radius 5 --edges all
|
|
426
|
+
cyclecad validate.cost --target extrude_1 --process CNC
|
|
427
|
+
cyclecad --list
|
|
428
|
+
cyclecad --describe shape.cylinder
|
|
429
|
+
cyclecad --interactive
|
|
430
|
+
cyclecad --batch script.txt
|
|
431
|
+
|
|
432
|
+
${color('Namespaces:', 'bright')}
|
|
433
|
+
${Object.keys(COMMAND_SCHEMA).map(ns => color(ns, 'cyan')).join(', ')}
|
|
434
|
+
|
|
435
|
+
For more info: ${color('https://github.com/vvlars-cmd/cyclecad', 'blue')}
|
|
436
|
+
`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function listCommands() {
|
|
440
|
+
console.log(`\n${color('Available Commands', 'bright')}\n`);
|
|
441
|
+
|
|
442
|
+
Object.entries(COMMAND_SCHEMA).forEach(([ns, commands]) => {
|
|
443
|
+
console.log(color(`${ns}/`, 'cyan'));
|
|
444
|
+
Object.entries(commands).forEach(([cmd, info]) => {
|
|
445
|
+
const params = Object.keys(info.params || {}).join(', ');
|
|
446
|
+
console.log(` ${cmd}${params ? ` (${params})` : ''}`);
|
|
447
|
+
console.log(` ${info.description}`);
|
|
448
|
+
});
|
|
449
|
+
console.log('');
|
|
450
|
+
});
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ============================================================================
|
|
454
|
+
// Interactive REPL Mode
|
|
455
|
+
// ============================================================================
|
|
456
|
+
|
|
457
|
+
async function startREPL(serverUrl, asJson = false, quiet = false) {
|
|
458
|
+
const rl = readline.createInterface({
|
|
459
|
+
input: process.stdin,
|
|
460
|
+
output: process.stdout,
|
|
461
|
+
prompt: color('cyclecad> ', 'blue'),
|
|
462
|
+
terminal: true,
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const history = [];
|
|
466
|
+
let historyIndex = -1;
|
|
467
|
+
|
|
468
|
+
// Setup readline history
|
|
469
|
+
rl.on('line', (line) => {
|
|
470
|
+
if (line.trim()) {
|
|
471
|
+
history.push(line);
|
|
472
|
+
historyIndex = history.length;
|
|
473
|
+
}
|
|
474
|
+
});
|
|
475
|
+
|
|
476
|
+
rl.on('SIGINT', () => {
|
|
477
|
+
console.log('');
|
|
478
|
+
log('Goodbye!', 'info');
|
|
479
|
+
rl.close();
|
|
480
|
+
process.exit(0);
|
|
481
|
+
});
|
|
482
|
+
|
|
483
|
+
console.log(color('\ncyclecad Interactive REPL', 'bright'));
|
|
484
|
+
console.log(`Type ${color('help', 'cyan')} for commands, ${color('exit', 'cyan')} to quit.\n`);
|
|
485
|
+
|
|
486
|
+
rl.prompt();
|
|
487
|
+
|
|
488
|
+
rl.on('line', async (line) => {
|
|
489
|
+
const trimmed = line.trim();
|
|
490
|
+
|
|
491
|
+
if (!trimmed) {
|
|
492
|
+
rl.prompt();
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
if (trimmed === 'exit' || trimmed === 'quit') {
|
|
497
|
+
log('Goodbye!', 'info');
|
|
498
|
+
rl.close();
|
|
499
|
+
process.exit(0);
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
if (trimmed === 'help') {
|
|
503
|
+
listCommands();
|
|
504
|
+
rl.prompt();
|
|
505
|
+
return;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (trimmed === 'history') {
|
|
509
|
+
history.forEach((cmd, i) => console.log(` ${i + 1}: ${cmd}`));
|
|
510
|
+
rl.prompt();
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
if (trimmed.startsWith('describe ')) {
|
|
515
|
+
const cmd = trimmed.slice(9);
|
|
516
|
+
showHelp(cmd);
|
|
517
|
+
rl.prompt();
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const { method, params } = parseCommand(trimmed);
|
|
522
|
+
const result = await executeCommand(method, params, serverUrl, quiet);
|
|
523
|
+
|
|
524
|
+
if (result.ok) {
|
|
525
|
+
displayResult(result.result, asJson);
|
|
526
|
+
} else {
|
|
527
|
+
log(result.error, 'error');
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
rl.prompt();
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
// ============================================================================
|
|
535
|
+
// Batch Mode
|
|
536
|
+
// ============================================================================
|
|
537
|
+
|
|
538
|
+
async function executeBatch(filePath, serverUrl, asJson = false, quiet = false) {
|
|
539
|
+
let commands;
|
|
540
|
+
|
|
541
|
+
try {
|
|
542
|
+
commands = fs.readFileSync(filePath, 'utf8').split('\n').filter(l => l.trim() && !l.startsWith('#'));
|
|
543
|
+
} catch (err) {
|
|
544
|
+
log(`Failed to read file: ${err.message}`, 'error');
|
|
545
|
+
process.exit(1);
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
log(`Executing ${commands.length} commands from ${filePath}...`, 'info');
|
|
549
|
+
|
|
550
|
+
let succeeded = 0;
|
|
551
|
+
let failed = 0;
|
|
552
|
+
|
|
553
|
+
for (const cmdStr of commands) {
|
|
554
|
+
const { method, params } = parseCommand(cmdStr);
|
|
555
|
+
const result = await executeCommand(method, params, serverUrl, true);
|
|
556
|
+
|
|
557
|
+
if (result.ok) {
|
|
558
|
+
log(`✓ ${method}`, 'success');
|
|
559
|
+
succeeded++;
|
|
560
|
+
if (asJson) {
|
|
561
|
+
displayResult(result.result, true);
|
|
562
|
+
}
|
|
563
|
+
} else {
|
|
564
|
+
log(`✗ ${method}: ${result.error}`, 'error');
|
|
565
|
+
failed++;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
console.log('');
|
|
570
|
+
log(`Batch complete: ${color(succeeded + ' succeeded', 'green')}, ${color(failed + ' failed', failed > 0 ? 'red' : 'green')}`, 'info');
|
|
571
|
+
|
|
572
|
+
if (failed > 0) {
|
|
573
|
+
process.exit(1);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// ============================================================================
|
|
578
|
+
// Main Entry Point
|
|
579
|
+
// ============================================================================
|
|
580
|
+
|
|
581
|
+
async function main() {
|
|
582
|
+
const args = parseArgs(process.argv);
|
|
583
|
+
|
|
584
|
+
// Global flags
|
|
585
|
+
if (args.flags.help || args.flags.h) {
|
|
586
|
+
showHelp(args._[0]);
|
|
587
|
+
process.exit(0);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
if (args.flags.version || args.flags.v) {
|
|
591
|
+
console.log(`cyclecad-cli v${VERSION}`);
|
|
592
|
+
process.exit(0);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
const serverUrl = args.flags.server || DEFAULT_SERVER;
|
|
596
|
+
const asJson = args.flags.json || false;
|
|
597
|
+
const quiet = args.flags.quiet || args.flags.q || false;
|
|
598
|
+
|
|
599
|
+
if (args.flags.list) {
|
|
600
|
+
listCommands();
|
|
601
|
+
process.exit(0);
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
if (args.flags.describe) {
|
|
605
|
+
showHelp(args.flags.describe);
|
|
606
|
+
process.exit(0);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
if (args.flags.interactive || args.flags.i) {
|
|
610
|
+
await startREPL(serverUrl, asJson, quiet);
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
if (args.flags.batch) {
|
|
615
|
+
await executeBatch(args.flags.batch, serverUrl, asJson, quiet);
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
// Single command execution
|
|
620
|
+
if (args._.length === 0) {
|
|
621
|
+
showHelp();
|
|
622
|
+
process.exit(0);
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const method = args._[0];
|
|
626
|
+
|
|
627
|
+
// Convert remaining args to params
|
|
628
|
+
const params = {};
|
|
629
|
+
for (let i = 1; i < args._.length; i++) {
|
|
630
|
+
if (args._[i].startsWith('--')) {
|
|
631
|
+
const key = args._[i].slice(2);
|
|
632
|
+
const val = args._[i + 1];
|
|
633
|
+
if (val && !val.startsWith('--')) {
|
|
634
|
+
try {
|
|
635
|
+
params[key] = JSON.parse(val);
|
|
636
|
+
} catch {
|
|
637
|
+
params[key] = val;
|
|
638
|
+
}
|
|
639
|
+
i++;
|
|
640
|
+
} else {
|
|
641
|
+
params[key] = true;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
// Merge flags into params
|
|
647
|
+
Object.assign(params, args.flags);
|
|
648
|
+
|
|
649
|
+
const result = await executeCommand(method, params, serverUrl, quiet);
|
|
650
|
+
|
|
651
|
+
if (result.ok) {
|
|
652
|
+
displayResult(result.result, asJson);
|
|
653
|
+
process.exit(0);
|
|
654
|
+
} else {
|
|
655
|
+
process.exit(1);
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
main().catch(err => {
|
|
660
|
+
log(`Fatal error: ${err.message}`, 'error');
|
|
661
|
+
process.exit(1);
|
|
662
|
+
});
|
package/bin/cyclecad-mcp
ADDED