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.
Files changed (69) hide show
  1. package/API-BUILD-MANIFEST.txt +339 -0
  2. package/API-SERVER.md +535 -0
  3. package/Architecture-Deck.pptx +0 -0
  4. package/CLAUDE.md +172 -11
  5. package/CLI-BUILD-SUMMARY.md +504 -0
  6. package/CLI-INDEX.md +356 -0
  7. package/CLI-README.md +466 -0
  8. package/COLLABORATION-INTEGRATION-GUIDE.md +325 -0
  9. package/CONNECTED_FABS_GUIDE.md +612 -0
  10. package/CONNECTED_FABS_README.md +310 -0
  11. package/DELIVERABLES.md +343 -0
  12. package/DFM-ANALYZER-INTEGRATION.md +368 -0
  13. package/DFM-QUICK-START.js +253 -0
  14. package/Dockerfile +69 -0
  15. package/IMPLEMENTATION.md +327 -0
  16. package/LICENSE +31 -0
  17. package/MARKETPLACE_QUICK_REFERENCE.txt +294 -0
  18. package/MCP-INDEX.md +264 -0
  19. package/QUICKSTART-API.md +388 -0
  20. package/QUICKSTART-CLI.md +211 -0
  21. package/QUICKSTART-MCP.md +196 -0
  22. package/README-MCP.md +208 -0
  23. package/TEST-TOKEN-ENGINE.md +319 -0
  24. package/TOKEN-ENGINE-SUMMARY.md +266 -0
  25. package/TOKENS-README.md +263 -0
  26. package/TOOLS-REFERENCE.md +254 -0
  27. package/app/index.html +168 -3
  28. package/app/js/TOKEN-INTEGRATION.md +391 -0
  29. package/app/js/agent-api.js +3 -3
  30. package/app/js/ai-copilot.js +1435 -0
  31. package/app/js/cam-pipeline.js +840 -0
  32. package/app/js/collaboration-ui.js +995 -0
  33. package/app/js/collaboration.js +1116 -0
  34. package/app/js/connected-fabs-example.js +404 -0
  35. package/app/js/connected-fabs.js +1449 -0
  36. package/app/js/dfm-analyzer.js +1760 -0
  37. package/app/js/marketplace.js +1994 -0
  38. package/app/js/material-library.js +2115 -0
  39. package/app/js/token-dashboard.js +563 -0
  40. package/app/js/token-engine.js +743 -0
  41. package/app/test-agent.html +1801 -0
  42. package/bin/cyclecad-cli.js +662 -0
  43. package/bin/cyclecad-mcp +2 -0
  44. package/bin/server.js +242 -0
  45. package/cycleCAD-Architecture.pptx +0 -0
  46. package/cycleCAD-Investor-Deck.pptx +0 -0
  47. package/demo-mcp.sh +60 -0
  48. package/docs/API-SERVER-SUMMARY.md +375 -0
  49. package/docs/API-SERVER.md +667 -0
  50. package/docs/CAM-EXAMPLES.md +344 -0
  51. package/docs/CAM-INTEGRATION.md +612 -0
  52. package/docs/CAM-QUICK-REFERENCE.md +199 -0
  53. package/docs/CLI-INTEGRATION.md +510 -0
  54. package/docs/CLI.md +872 -0
  55. package/docs/MARKETPLACE-API-SCHEMA.json +564 -0
  56. package/docs/MARKETPLACE-INTEGRATION.md +467 -0
  57. package/docs/MARKETPLACE-SETUP.html +439 -0
  58. package/docs/MCP-SERVER.md +403 -0
  59. package/examples/api-client-example.js +488 -0
  60. package/examples/api-client-example.py +359 -0
  61. package/examples/batch-manufacturing.txt +28 -0
  62. package/examples/batch-simple.txt +26 -0
  63. package/model-marketplace.html +1273 -0
  64. package/package.json +14 -3
  65. package/server/api-server.js +1120 -0
  66. package/server/mcp-server.js +1161 -0
  67. package/test-api-server.js +432 -0
  68. package/test-mcp.js +198 -0
  69. 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
+ });
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ require('../server/mcp-server.js');