@newsails/veil-cli 1.0.1
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/.veil/agents/analyst/AGENT.md +21 -0
- package/.veil/agents/analyst/agent.json +23 -0
- package/.veil/agents/assistant/AGENT.md +15 -0
- package/.veil/agents/assistant/agent.json +19 -0
- package/.veil/agents/coder/AGENT.md +18 -0
- package/.veil/agents/coder/agent.json +19 -0
- package/.veil/agents/hello/AGENT.md +5 -0
- package/.veil/agents/hello/agent.json +13 -0
- package/.veil/agents/writer/AGENT.md +12 -0
- package/.veil/agents/writer/agent.json +17 -0
- package/.veil/memory/MEMORY.md +343 -0
- package/.veil/memory/agents/analyst/MEMORY.md +55 -0
- package/.veil/memory/agents/hello/MEMORY.md +12 -0
- package/.veil/runtime.pid +1 -0
- package/.veil/settings.json +10 -0
- package/.veil-studio/studio.db +0 -0
- package/.veil-studio/studio.db-shm +0 -0
- package/.veil-studio/studio.db-wal +0 -0
- package/PLAN/01-vision.md +26 -0
- package/PLAN/02-tech-stack.md +94 -0
- package/PLAN/03-agents.md +232 -0
- package/PLAN/04-runtime.md +171 -0
- package/PLAN/05-tools.md +211 -0
- package/PLAN/06-communication.md +243 -0
- package/PLAN/07-storage.md +218 -0
- package/PLAN/08-api-cli.md +153 -0
- package/PLAN/09-permissions.md +108 -0
- package/PLAN/10-ably.md +105 -0
- package/PLAN/11-file-formats.md +442 -0
- package/PLAN/12-folder-structure.md +205 -0
- package/PLAN/13-operations.md +212 -0
- package/PLAN/README.md +23 -0
- package/README.md +128 -0
- package/REPORT.md +174 -0
- package/TODO.md +45 -0
- package/ai-tests/FRONTEND_PROMPT.md +220 -0
- package/ai-tests/Research & Planning.md +814 -0
- package/ai-tests/prompt-001-basic-api.md +230 -0
- package/ai-tests/prompt-002-basic-flows.md +230 -0
- package/ai-tests/prompt-003-agent-behaviors.md +220 -0
- package/api/middleware.js +60 -0
- package/api/routes/agents.js +193 -0
- package/api/routes/chat.js +93 -0
- package/api/routes/completions.js +122 -0
- package/api/routes/daemons.js +80 -0
- package/api/routes/memory.js +169 -0
- package/api/routes/models.js +40 -0
- package/api/routes/remote-methods.js +74 -0
- package/api/routes/sessions.js +208 -0
- package/api/routes/settings.js +108 -0
- package/api/routes/system.js +50 -0
- package/api/routes/tasks.js +270 -0
- package/api/server.js +120 -0
- package/cli/formatter.js +70 -0
- package/cli/index.js +443 -0
- package/cli/parser.js +113 -0
- package/config/config.json +10 -0
- package/config/models.json +6826 -0
- package/core/agent.js +329 -0
- package/core/cancel.js +38 -0
- package/core/compaction.js +176 -0
- package/core/events.js +13 -0
- package/core/loop.js +564 -0
- package/core/memory.js +51 -0
- package/core/prompt.js +185 -0
- package/core/queue.js +96 -0
- package/core/registry.js +291 -0
- package/core/remote-methods.js +124 -0
- package/core/router.js +386 -0
- package/core/running-sessions.js +18 -0
- package/docs/api/01-system.md +84 -0
- package/docs/api/02-agents.md +374 -0
- package/docs/api/03-chat.md +269 -0
- package/docs/api/04-tasks.md +470 -0
- package/docs/api/05-sessions.md +444 -0
- package/docs/api/06-daemons.md +142 -0
- package/docs/api/07-memory.md +186 -0
- package/docs/api/08-settings.md +133 -0
- package/docs/api/09-models.md +119 -0
- package/docs/api/09-websocket.md +350 -0
- package/docs/api/10-completions.md +134 -0
- package/docs/api/README.md +116 -0
- package/docs/guide/01-quickstart.md +220 -0
- package/docs/guide/02-folder-structure.md +185 -0
- package/docs/guide/03-configuration.md +252 -0
- package/docs/guide/04-agents.md +267 -0
- package/docs/guide/05-cli.md +290 -0
- package/docs/guide/06-tools.md +643 -0
- package/docs/guide/07-permissions.md +236 -0
- package/docs/guide/08-memory.md +139 -0
- package/docs/guide/09-multi-agent.md +271 -0
- package/docs/guide/10-daemons.md +226 -0
- package/docs/guide/README.md +53 -0
- package/docs/index.html +623 -0
- package/examples/README.md +151 -0
- package/examples/agents/assistant/AGENT.md +31 -0
- package/examples/agents/assistant/SOUL.md +9 -0
- package/examples/agents/assistant/agent.json +74 -0
- package/examples/agents/hello/AGENT.md +15 -0
- package/examples/agents/hello/agent.json +14 -0
- package/examples/agents/monitor/AGENT.md +51 -0
- package/examples/agents/monitor/agent.json +33 -0
- package/examples/agents/monitor/heartbeats/monitor.md +24 -0
- package/examples/agents/orchestrator/AGENT.md +70 -0
- package/examples/agents/orchestrator/agent.json +30 -0
- package/examples/agents/researcher/AGENT.md +52 -0
- package/examples/agents/researcher/agent.json +49 -0
- package/examples/agents/researcher/skills/web-research.md +28 -0
- package/examples/skills/code-review.md +72 -0
- package/examples/skills/summarise.md +59 -0
- package/examples/skills/web-research.md +42 -0
- package/examples/tools/word-count/index.js +27 -0
- package/examples/tools/word-count/tool.json +18 -0
- package/infrastructure/database.js +563 -0
- package/infrastructure/scheduler.js +122 -0
- package/llm/client.js +206 -0
- package/migrations/001-initial.sql +121 -0
- package/migrations/002-debuggability.sql +13 -0
- package/migrations/003-drop-orphaned-columns.sql +72 -0
- package/migrations/004-session-message-token-fields.sql +78 -0
- package/migrations/005-session-thinking.sql +5 -0
- package/package.json +30 -0
- package/schemas/agent.json +143 -0
- package/schemas/settings.json +111 -0
- package/scripts/fetch-models.js +93 -0
- package/session-debug-scenario.md +248 -0
- package/settings/fields.js +52 -0
- package/system-prompts/base-core.md +7 -0
- package/system-prompts/environment.md +13 -0
- package/system-prompts/reminders/anti-drift.md +6 -0
- package/system-prompts/reminders/stall-recovery.md +10 -0
- package/system-prompts/safety-rules.md +25 -0
- package/system-prompts/task-heuristics.md +27 -0
- package/test/client.js +71 -0
- package/test/integration/01-health.test.js +25 -0
- package/test/integration/02-agents.test.js +80 -0
- package/test/integration/03-chat-hello.test.js +48 -0
- package/test/integration/04-chat-multiturn.test.js +61 -0
- package/test/integration/05-chat-writer.test.js +48 -0
- package/test/integration/06-task-basic.test.js +68 -0
- package/test/integration/07-task-tools.test.js +74 -0
- package/test/integration/08-task-code-analysis.test.js +69 -0
- package/test/integration/09-memory-analyst.test.js +63 -0
- package/test/integration/10-task-advanced.test.js +85 -0
- package/test/integration/11-sessions-advanced.test.js +84 -0
- package/test/integration/12-assistant-chat-tools.test.js +75 -0
- package/test/integration/13-edge-cases.test.js +99 -0
- package/test/integration/14-cancel.test.js +62 -0
- package/test/integration/15-debug.test.js +106 -0
- package/test/integration/16-memory-api.test.js +83 -0
- package/test/integration/17-settings-api.test.js +41 -0
- package/test/integration/18-tool-search-activation.test.js +119 -0
- package/test/results/.gitkeep +0 -0
- package/test/runner.js +206 -0
- package/test/smoke.js +216 -0
- package/tools/agent_message.js +85 -0
- package/tools/agent_send.js +80 -0
- package/tools/agent_spawn.js +44 -0
- package/tools/bash.js +49 -0
- package/tools/edit_file.js +41 -0
- package/tools/glob.js +64 -0
- package/tools/grep.js +82 -0
- package/tools/list_dir.js +63 -0
- package/tools/log_write.js +31 -0
- package/tools/memory_read.js +38 -0
- package/tools/memory_search.js +65 -0
- package/tools/memory_write.js +42 -0
- package/tools/read_file.js +48 -0
- package/tools/sleep.js +22 -0
- package/tools/task_create.js +41 -0
- package/tools/task_respond.js +37 -0
- package/tools/task_spawn.js +64 -0
- package/tools/task_status.js +39 -0
- package/tools/task_subscribe.js +37 -0
- package/tools/todo_read.js +26 -0
- package/tools/todo_write.js +38 -0
- package/tools/tool_activate.js +24 -0
- package/tools/tool_search.js +24 -0
- package/tools/web_fetch.js +50 -0
- package/tools/web_search.js +52 -0
- package/tools/write_file.js +28 -0
- package/ui/api.js +190 -0
- package/ui/app.js +281 -0
- package/ui/index.html +382 -0
- package/ui/views/agents.js +377 -0
- package/ui/views/chat.js +610 -0
- package/ui/views/connection.js +96 -0
- package/ui/views/daemons.js +129 -0
- package/ui/views/feed.js +194 -0
- package/ui/views/memory.js +263 -0
- package/ui/views/models.js +146 -0
- package/ui/views/sessions.js +314 -0
- package/ui/views/settings.js +142 -0
- package/ui/views/tasks.js +415 -0
- package/utils/context.js +49 -0
- package/utils/id.js +16 -0
- package/utils/models.js +88 -0
- package/utils/paths.js +213 -0
- package/utils/settings.js +172 -0
package/cli/index.js
ADDED
|
@@ -0,0 +1,443 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
// ── Node version check (must be first) ───────────────────────────────────────
|
|
5
|
+
const [major] = process.version.slice(1).split('.').map(Number);
|
|
6
|
+
if (major < 18) {
|
|
7
|
+
console.error(`VeilCLI requires Node.js 18+. Current: ${process.version}`);
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// ── Set CWD and version in context singleton ──────────────────────────────────
|
|
12
|
+
const context = require('../utils/context');
|
|
13
|
+
const pkg = require('../package.json');
|
|
14
|
+
context.setVersion(pkg.version);
|
|
15
|
+
|
|
16
|
+
const { parseCliArgs, USAGE } = require('./parser');
|
|
17
|
+
const fmt = require('./formatter');
|
|
18
|
+
const path = require('path');
|
|
19
|
+
const fs = require('fs');
|
|
20
|
+
|
|
21
|
+
const args = parseCliArgs(process.argv.slice(2));
|
|
22
|
+
|
|
23
|
+
// ── Command dispatch ──────────────────────────────────────────────────────────
|
|
24
|
+
(async () => {
|
|
25
|
+
switch (args.command) {
|
|
26
|
+
case 'help':
|
|
27
|
+
console.log(USAGE);
|
|
28
|
+
process.exit(0);
|
|
29
|
+
break;
|
|
30
|
+
|
|
31
|
+
case 'version':
|
|
32
|
+
console.log(`VeilCLI v${pkg.version}`);
|
|
33
|
+
process.exit(0);
|
|
34
|
+
break;
|
|
35
|
+
|
|
36
|
+
case 'check-for-update':
|
|
37
|
+
await cmdCheckForUpdate();
|
|
38
|
+
break;
|
|
39
|
+
|
|
40
|
+
case 'start':
|
|
41
|
+
await cmdStart(args.args);
|
|
42
|
+
break;
|
|
43
|
+
|
|
44
|
+
case 'run':
|
|
45
|
+
await cmdRun(args.args);
|
|
46
|
+
break;
|
|
47
|
+
|
|
48
|
+
case 'stop':
|
|
49
|
+
await cmdStop(args.args);
|
|
50
|
+
break;
|
|
51
|
+
|
|
52
|
+
case 'status':
|
|
53
|
+
await cmdStatus(args.args);
|
|
54
|
+
break;
|
|
55
|
+
|
|
56
|
+
case 'agents':
|
|
57
|
+
await cmdAgents(args.subcommand, args.args);
|
|
58
|
+
break;
|
|
59
|
+
|
|
60
|
+
case 'login':
|
|
61
|
+
await cmdLogin(args.args);
|
|
62
|
+
break;
|
|
63
|
+
|
|
64
|
+
case 'import':
|
|
65
|
+
await cmdImport(args.args);
|
|
66
|
+
break;
|
|
67
|
+
|
|
68
|
+
case 'dashboard':
|
|
69
|
+
await cmdDashboard(args.args);
|
|
70
|
+
break;
|
|
71
|
+
|
|
72
|
+
default:
|
|
73
|
+
fmt.error(`Unknown command: ${args.command}`);
|
|
74
|
+
console.log('\n' + USAGE);
|
|
75
|
+
process.exit(1);
|
|
76
|
+
}
|
|
77
|
+
})().catch(err => {
|
|
78
|
+
fmt.error(err.message);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ── Command implementations ───────────────────────────────────────────────────
|
|
83
|
+
|
|
84
|
+
async function cmdCheckForUpdate() {
|
|
85
|
+
const https = require('https');
|
|
86
|
+
const current = pkg.version;
|
|
87
|
+
|
|
88
|
+
const latest = await new Promise((resolve, reject) => {
|
|
89
|
+
const req = https.get(
|
|
90
|
+
`https://registry.npmjs.org/${pkg.name}/latest`,
|
|
91
|
+
{ headers: { Accept: 'application/json' } },
|
|
92
|
+
(res) => {
|
|
93
|
+
let body = '';
|
|
94
|
+
res.on('data', (chunk) => { body += chunk; });
|
|
95
|
+
res.on('end', () => {
|
|
96
|
+
try { resolve(JSON.parse(body).version); }
|
|
97
|
+
catch { reject(new Error('Failed to parse npm registry response')); }
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
req.on('error', reject);
|
|
102
|
+
req.setTimeout(8000, () => { req.destroy(new Error('Request timed out')); });
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
if (latest === current) {
|
|
106
|
+
console.log(`No updates available (current: v${current})`);
|
|
107
|
+
} else {
|
|
108
|
+
console.log(`New update exists: v${latest} (current: v${current})`);
|
|
109
|
+
}
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async function cmdStart({ port, folder, secret, ably }) {
|
|
114
|
+
const cwd = folder ? path.resolve(folder) : process.cwd();
|
|
115
|
+
context.setCwd(cwd);
|
|
116
|
+
|
|
117
|
+
const { loadSettings } = require('../utils/settings');
|
|
118
|
+
const cliOverrides = {};
|
|
119
|
+
if (port) cliOverrides.port = parseInt(port, 10);
|
|
120
|
+
if (secret) cliOverrides.secret = secret;
|
|
121
|
+
if (ably) cliOverrides.ably = { enabled: true };
|
|
122
|
+
|
|
123
|
+
const settings = loadSettings({ cwd, cliOverrides });
|
|
124
|
+
|
|
125
|
+
// Startup recovery: mark stale processing tasks as failed
|
|
126
|
+
const db = require('../infrastructure/database');
|
|
127
|
+
db.recoverStaleTasks(cwd);
|
|
128
|
+
|
|
129
|
+
// Start scheduler
|
|
130
|
+
const { createScheduler } = require('../infrastructure/scheduler');
|
|
131
|
+
const scheduler = createScheduler();
|
|
132
|
+
|
|
133
|
+
// Auto-start daemons
|
|
134
|
+
const { listAgents, loadAgent } = require('../core/agent');
|
|
135
|
+
const agents = listAgents({ cwd });
|
|
136
|
+
for (const agentSummary of agents) {
|
|
137
|
+
if (agentSummary.modes.includes('daemon')) {
|
|
138
|
+
try {
|
|
139
|
+
const agent = loadAgent({ cwd, name: agentSummary.name });
|
|
140
|
+
if (agent.modes?.daemon?.enabled && agent.modes?.daemon?.cron) {
|
|
141
|
+
scheduler.startDaemon({ agentName: agentSummary.name, agent, cwd, settings });
|
|
142
|
+
fmt.info(`Auto-started daemon: ${agentSummary.name}`);
|
|
143
|
+
}
|
|
144
|
+
} catch (err) {
|
|
145
|
+
fmt.warn(`Failed to auto-start daemon ${agentSummary.name}: ${err.message}`);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Start REST server
|
|
151
|
+
const { startServer } = require('../api/server');
|
|
152
|
+
const listenPort = settings.port || 5050;
|
|
153
|
+
|
|
154
|
+
const server = await startServer({ settings, scheduler, port: listenPort });
|
|
155
|
+
|
|
156
|
+
// Write PID file
|
|
157
|
+
const paths = require('../utils/paths');
|
|
158
|
+
const pidFile = paths.getPidFilePath(cwd);
|
|
159
|
+
fs.mkdirSync(path.dirname(pidFile), { recursive: true });
|
|
160
|
+
fs.writeFileSync(pidFile, String(process.pid), 'utf8');
|
|
161
|
+
|
|
162
|
+
fmt.success(`VeilCLI v${pkg.version} started on port ${listenPort}`);
|
|
163
|
+
fmt.info(`Project: ${cwd}`);
|
|
164
|
+
fmt.info(`Agents: ${agents.length} | PID: ${process.pid}`);
|
|
165
|
+
|
|
166
|
+
// Graceful shutdown
|
|
167
|
+
const shutdown = () => {
|
|
168
|
+
fmt.info('Shutting down...');
|
|
169
|
+
scheduler.stopAll();
|
|
170
|
+
server.close(() => {
|
|
171
|
+
try { fs.unlinkSync(pidFile); } catch {}
|
|
172
|
+
db.closeDb();
|
|
173
|
+
fmt.success('Shutdown complete');
|
|
174
|
+
process.exit(0);
|
|
175
|
+
});
|
|
176
|
+
setTimeout(() => process.exit(1), 5000);
|
|
177
|
+
};
|
|
178
|
+
process.on('SIGINT', shutdown);
|
|
179
|
+
process.on('SIGTERM', shutdown);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
async function cmdRun({ agent, input, timeout, session, output, 'output-format': outputFormat }) {
|
|
183
|
+
if (!agent) { fmt.error('--agent is required'); process.exit(1); }
|
|
184
|
+
if (!input) { fmt.error('--input is required'); process.exit(1); }
|
|
185
|
+
|
|
186
|
+
const cwd = process.cwd();
|
|
187
|
+
context.setCwd(cwd);
|
|
188
|
+
|
|
189
|
+
const { loadSettings } = require('../utils/settings');
|
|
190
|
+
const settings = loadSettings({ cwd });
|
|
191
|
+
if (timeout) settings.maxDurationSeconds = parseInt(timeout, 10);
|
|
192
|
+
|
|
193
|
+
const db = require('../infrastructure/database');
|
|
194
|
+
const { runTask } = require('../core/router');
|
|
195
|
+
|
|
196
|
+
const taskId = db.createTask({
|
|
197
|
+
agentName: agent,
|
|
198
|
+
input,
|
|
199
|
+
priority: 'normal',
|
|
200
|
+
instanceFolder: cwd,
|
|
201
|
+
sessionId: session || null,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
fmt.info(`Running task ${taskId} with agent "${agent}"${session ? ` (continuing session ${session})` : ''}...`);
|
|
205
|
+
|
|
206
|
+
try {
|
|
207
|
+
const result = await runTask({ taskId, cwd, settings });
|
|
208
|
+
const data = {
|
|
209
|
+
taskId,
|
|
210
|
+
sessionId: result.sessionId,
|
|
211
|
+
agent,
|
|
212
|
+
status: result.status,
|
|
213
|
+
output: result.output,
|
|
214
|
+
timestamp: new Date().toISOString(),
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
if (result.status === 'finished') {
|
|
218
|
+
writeOutput(data, output, outputFormat || 'json');
|
|
219
|
+
process.exit(0);
|
|
220
|
+
} else {
|
|
221
|
+
data.error = result.error || 'Task did not finish';
|
|
222
|
+
writeOutput(data, output, outputFormat || 'json');
|
|
223
|
+
process.exit(1);
|
|
224
|
+
}
|
|
225
|
+
} catch (err) {
|
|
226
|
+
const data = { error: err.message, timestamp: new Date().toISOString() };
|
|
227
|
+
writeOutput(data, output, outputFormat || 'json');
|
|
228
|
+
process.exit(1);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function writeOutput(data, outputFile, format) {
|
|
233
|
+
let content;
|
|
234
|
+
if (format === 'md') {
|
|
235
|
+
const { output, ...meta } = data;
|
|
236
|
+
const yamlLines = Object.entries(meta)
|
|
237
|
+
.filter(([, v]) => v !== undefined && v !== null)
|
|
238
|
+
.map(([k, v]) => `${k}: ${typeof v === 'string' ? v : JSON.stringify(v)}`);
|
|
239
|
+
content = `---\n${yamlLines.join('\n')}\n---\n\n${output || ''}`;
|
|
240
|
+
} else {
|
|
241
|
+
content = JSON.stringify(data, null, 2);
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
if (outputFile) {
|
|
245
|
+
fs.writeFileSync(outputFile, content, 'utf8');
|
|
246
|
+
fmt.success(`Output written to ${outputFile}`);
|
|
247
|
+
} else {
|
|
248
|
+
console.log(content);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
async function cmdStop() {
|
|
253
|
+
const cwd = process.cwd();
|
|
254
|
+
const paths = require('../utils/paths');
|
|
255
|
+
const pidFile = paths.getPidFilePath(cwd);
|
|
256
|
+
|
|
257
|
+
if (!fs.existsSync(pidFile)) {
|
|
258
|
+
fmt.error('No running server found (no PID file at .veil/runtime.pid)');
|
|
259
|
+
process.exit(1);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
263
|
+
try {
|
|
264
|
+
process.kill(pid, 'SIGTERM');
|
|
265
|
+
fmt.success(`Sent SIGTERM to process ${pid}`);
|
|
266
|
+
} catch (err) {
|
|
267
|
+
fmt.error(`Failed to stop server: ${err.message}`);
|
|
268
|
+
process.exit(1);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function cmdStatus() {
|
|
273
|
+
const cwd = process.cwd();
|
|
274
|
+
const paths = require('../utils/paths');
|
|
275
|
+
const pidFile = paths.getPidFilePath(cwd);
|
|
276
|
+
|
|
277
|
+
if (!fs.existsSync(pidFile)) {
|
|
278
|
+
fmt.warn('No running server (no PID file found)');
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const pid = parseInt(fs.readFileSync(pidFile, 'utf8').trim(), 10);
|
|
283
|
+
try {
|
|
284
|
+
process.kill(pid, 0); // Existence check
|
|
285
|
+
fmt.success(`Server is running (PID: ${pid})`);
|
|
286
|
+
} catch {
|
|
287
|
+
fmt.warn(`PID file exists but process ${pid} is not running (stale PID file)`);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async function cmdAgents(subcommand, { name }) {
|
|
292
|
+
const cwd = process.cwd();
|
|
293
|
+
context.setCwd(cwd);
|
|
294
|
+
|
|
295
|
+
const { listAgents, loadAgent } = require('../core/agent');
|
|
296
|
+
|
|
297
|
+
if (subcommand === 'list' || !subcommand) {
|
|
298
|
+
const agents = listAgents({ cwd });
|
|
299
|
+
fmt.header(`Agents (${agents.length})`);
|
|
300
|
+
if (agents.length === 0) {
|
|
301
|
+
fmt.info('No agents found. Add agents to .veil/agents/<name>/');
|
|
302
|
+
} else {
|
|
303
|
+
fmt.table(agents, ['name', 'description', 'model', 'source']);
|
|
304
|
+
}
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
if (subcommand === 'inspect') {
|
|
309
|
+
if (!name) { fmt.error('Agent name required: veil agents inspect <name>'); process.exit(1); }
|
|
310
|
+
try {
|
|
311
|
+
const agent = loadAgent({ cwd, name });
|
|
312
|
+
fmt.header(`Agent: ${agent.name}`);
|
|
313
|
+
fmt.info(`Description: ${agent.description || '(none)'}`);
|
|
314
|
+
fmt.info(`Model: ${agent.model}`);
|
|
315
|
+
fmt.info(`Folder: ${agent.agentFolder}`);
|
|
316
|
+
fmt.info(`Modes: ${Object.keys(agent.modes || {}).filter(m => agent.modes[m]?.enabled).join(', ') || '(none enabled)'}`);
|
|
317
|
+
if (agent.agentMd) {
|
|
318
|
+
fmt.header('AGENT.md');
|
|
319
|
+
console.log(agent.agentMd.slice(0, 500) + (agent.agentMd.length > 500 ? '\n...(truncated)' : ''));
|
|
320
|
+
}
|
|
321
|
+
} catch (err) {
|
|
322
|
+
fmt.error(err.message);
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
if (subcommand === 'create') {
|
|
329
|
+
fmt.info('Interactive agent creation is not yet implemented. Create .veil/agents/<name>/agent.json manually.');
|
|
330
|
+
fmt.info('See examples/agents/hello/ for a minimal valid example.');
|
|
331
|
+
return;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
fmt.error(`Unknown agents subcommand: ${subcommand}`);
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function cmdLogin({ key, global: isGlobal }) {
|
|
339
|
+
if (!key) { fmt.error('--key is required'); process.exit(1); }
|
|
340
|
+
|
|
341
|
+
const cwd = process.cwd();
|
|
342
|
+
const paths = require('../utils/paths');
|
|
343
|
+
const targetDir = isGlobal ? paths.getGlobalConfigDir() : paths.getProjectConfigDir(cwd);
|
|
344
|
+
const authPath = path.join(targetDir, 'auth.json');
|
|
345
|
+
|
|
346
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
347
|
+
|
|
348
|
+
let existing = {};
|
|
349
|
+
try { existing = JSON.parse(fs.readFileSync(authPath, 'utf8')); } catch {}
|
|
350
|
+
|
|
351
|
+
existing.models = existing.models || {};
|
|
352
|
+
existing.models.main = existing.models.main || {};
|
|
353
|
+
existing.models.main.api_key = key;
|
|
354
|
+
|
|
355
|
+
fs.writeFileSync(authPath, JSON.stringify(existing, null, 2), 'utf8');
|
|
356
|
+
fmt.success(`API key saved to ${authPath}`);
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
async function cmdImport({ type, source }) {
|
|
360
|
+
if (!type || !source) { fmt.error('--type and --source are required'); process.exit(1); }
|
|
361
|
+
fmt.info(`Import adapter for "${type}" is not yet fully implemented.`);
|
|
362
|
+
fmt.info(`Source: ${source}`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
async function cmdDashboard({ port, api, secret }) {
|
|
366
|
+
const listenPort = port ? parseInt(port, 10) : 8080;
|
|
367
|
+
if (isNaN(listenPort) || listenPort < 1 || listenPort > 65535) {
|
|
368
|
+
fmt.error(`Invalid port: ${port}`);
|
|
369
|
+
process.exit(1);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
const http = require('http');
|
|
373
|
+
const uiDir = path.join(__dirname, '..', 'ui');
|
|
374
|
+
|
|
375
|
+
if (!fs.existsSync(uiDir)) {
|
|
376
|
+
fmt.error(`UI folder not found: ${uiDir}`);
|
|
377
|
+
fmt.info('Make sure the ui/ folder exists at the repo root.');
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
const MIME = {
|
|
382
|
+
'.html': 'text/html; charset=utf-8',
|
|
383
|
+
'.js': 'application/javascript; charset=utf-8',
|
|
384
|
+
'.css': 'text/css; charset=utf-8',
|
|
385
|
+
'.json': 'application/json',
|
|
386
|
+
'.png': 'image/png',
|
|
387
|
+
'.ico': 'image/x-icon',
|
|
388
|
+
'.svg': 'image/svg+xml',
|
|
389
|
+
'.woff2':'font/woff2',
|
|
390
|
+
};
|
|
391
|
+
|
|
392
|
+
const server = http.createServer((req, res) => {
|
|
393
|
+
let urlPath = req.url.split('?')[0];
|
|
394
|
+
if (urlPath === '/') urlPath = '/index.html';
|
|
395
|
+
|
|
396
|
+
const safePath = path.normalize(urlPath).replace(/^(\.\.[\/\\])+/, '');
|
|
397
|
+
let filePath = path.join(uiDir, safePath);
|
|
398
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
399
|
+
|
|
400
|
+
if (!ext || !fs.existsSync(filePath)) {
|
|
401
|
+
filePath = path.join(uiDir, 'index.html');
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
try {
|
|
405
|
+
let content = fs.readFileSync(filePath);
|
|
406
|
+
const mime = MIME[path.extname(filePath).toLowerCase()] || 'text/plain';
|
|
407
|
+
|
|
408
|
+
// Inject API pre-connect config into index.html when --api is provided
|
|
409
|
+
if (api && path.extname(filePath).toLowerCase() === '.html') {
|
|
410
|
+
const inject = `<script>window.__VEIL_API__=${JSON.stringify(api)};window.__VEIL_SECRET__=${JSON.stringify(secret || '')};</script>`;
|
|
411
|
+
content = Buffer.from(content.toString().replace('</head>', inject + '</head>'));
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
res.writeHead(200, {
|
|
415
|
+
'Content-Type': mime,
|
|
416
|
+
'Cache-Control': 'no-cache',
|
|
417
|
+
});
|
|
418
|
+
res.end(content);
|
|
419
|
+
} catch {
|
|
420
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
421
|
+
res.end('Not found');
|
|
422
|
+
}
|
|
423
|
+
});
|
|
424
|
+
|
|
425
|
+
server.on('error', (err) => {
|
|
426
|
+
if (err.code === 'EADDRINUSE') {
|
|
427
|
+
fmt.error(`Port ${listenPort} is already in use. Use --port to specify a different port.`);
|
|
428
|
+
} else {
|
|
429
|
+
fmt.error(`Server error: ${err.message}`);
|
|
430
|
+
}
|
|
431
|
+
process.exit(1);
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
server.listen(listenPort, '127.0.0.1', () => {
|
|
435
|
+
fmt.success(`Dashboard available at http://localhost:${listenPort}`);
|
|
436
|
+
fmt.info(`Serving: ${uiDir}`);
|
|
437
|
+
if (api) fmt.info(`Pre-connecting to API: ${api}`);
|
|
438
|
+
fmt.info('Press Ctrl+C to stop.');
|
|
439
|
+
});
|
|
440
|
+
|
|
441
|
+
process.on('SIGINT', () => { server.close(); process.exit(0); });
|
|
442
|
+
process.on('SIGTERM', () => { server.close(); process.exit(0); });
|
|
443
|
+
}
|
package/cli/parser.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { parseArgs } = require('util');
|
|
4
|
+
|
|
5
|
+
const USAGE = `
|
|
6
|
+
VeilCLI — Enterprise autonomous agent runtime
|
|
7
|
+
|
|
8
|
+
Usage:
|
|
9
|
+
veil start [--port <n>] [--folder <path>] [--secret <s>] [--ably]
|
|
10
|
+
veil run --agent <name> --input "<text>" [--session <id>] [--timeout <s>] [--output <file>] [--output-format json|md]
|
|
11
|
+
veil stop
|
|
12
|
+
veil status
|
|
13
|
+
veil agents list
|
|
14
|
+
veil agents inspect <name>
|
|
15
|
+
veil agents create
|
|
16
|
+
veil login --key <token> [--global]
|
|
17
|
+
veil import --type <type> --source <path>
|
|
18
|
+
veil dashboard [--port <n>] [--api <url>] [--secret <s>]
|
|
19
|
+
|
|
20
|
+
Commands:
|
|
21
|
+
start Start the REST API server
|
|
22
|
+
run Run a one-shot task and exit
|
|
23
|
+
stop Gracefully stop the running server
|
|
24
|
+
status Show running server info
|
|
25
|
+
agents Manage agents (list, inspect, create)
|
|
26
|
+
login Store API credentials
|
|
27
|
+
import Import agents from Claude Code / other formats
|
|
28
|
+
dashboard Launch the browser-based GUI (default port: 8080)
|
|
29
|
+
Use --api to pre-connect to a specific Veil API URL
|
|
30
|
+
|
|
31
|
+
Options:
|
|
32
|
+
--port Port to listen on (default: 5050 for start, 8080 for dashboard)
|
|
33
|
+
--api Veil API base URL to pre-connect (e.g. http://localhost:5051)
|
|
34
|
+
--folder Project folder (default: current directory)
|
|
35
|
+
--secret API secret for auth header
|
|
36
|
+
--ably Enable Ably remote access
|
|
37
|
+
--agent Agent name (for run command)
|
|
38
|
+
--input Task input text (for run command)
|
|
39
|
+
--session Continue from existing session ID (for run command)
|
|
40
|
+
--timeout Timeout in seconds (for run command)
|
|
41
|
+
--output Write output to file instead of stdout (for run command)
|
|
42
|
+
--output-format Output format: json (default) or md (for run command)
|
|
43
|
+
--key API key (for login command)
|
|
44
|
+
--global Apply globally to ~/.veil/ (for login command)
|
|
45
|
+
--type Import type: claude-code|raw (for import command)
|
|
46
|
+
--source Source path (for import command)
|
|
47
|
+
--help, -h Show this help
|
|
48
|
+
--version Show version
|
|
49
|
+
--check-for-update Check if a newer version is available on npm
|
|
50
|
+
`.trim();
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Parse CLI arguments.
|
|
54
|
+
* @param {string[]} argv - process.argv.slice(2)
|
|
55
|
+
* @returns {{ command: string, subcommand?: string, args: Object }}
|
|
56
|
+
*/
|
|
57
|
+
function parseCliArgs(argv) {
|
|
58
|
+
if (!argv || argv.length === 0 || argv[0] === '--help' || argv[0] === '-h') {
|
|
59
|
+
return { command: 'help', args: {} };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (argv[0] === '--version') {
|
|
63
|
+
return { command: 'version', args: {} };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (argv[0] === '--check-for-update') {
|
|
67
|
+
return { command: 'check-for-update', args: {} };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const command = argv[0];
|
|
71
|
+
const rest = argv.slice(1);
|
|
72
|
+
|
|
73
|
+
// Handle subcommands (e.g. `agents list`, `agents inspect <name>`)
|
|
74
|
+
if (command === 'agents') {
|
|
75
|
+
const subcommand = rest[0] || 'list';
|
|
76
|
+
const argName = rest[1]; // e.g. agent name for inspect
|
|
77
|
+
return { command: 'agents', subcommand, args: { name: argName } };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Parse flags for remaining commands
|
|
81
|
+
let parsed;
|
|
82
|
+
try {
|
|
83
|
+
parsed = parseArgs({
|
|
84
|
+
args: rest,
|
|
85
|
+
options: {
|
|
86
|
+
port: { type: 'string' },
|
|
87
|
+
folder: { type: 'string' },
|
|
88
|
+
secret: { type: 'string' },
|
|
89
|
+
api: { type: 'string' },
|
|
90
|
+
ably: { type: 'boolean', default: false },
|
|
91
|
+
agent: { type: 'string' },
|
|
92
|
+
input: { type: 'string' },
|
|
93
|
+
timeout: { type: 'string' },
|
|
94
|
+
session: { type: 'string' },
|
|
95
|
+
output: { type: 'string' },
|
|
96
|
+
'output-format': { type: 'string' },
|
|
97
|
+
key: { type: 'string' },
|
|
98
|
+
global: { type: 'boolean', default: false },
|
|
99
|
+
type: { type: 'string' },
|
|
100
|
+
source: { type: 'string' },
|
|
101
|
+
help: { type: 'boolean', short: 'h', default: false },
|
|
102
|
+
},
|
|
103
|
+
allowPositionals: true,
|
|
104
|
+
strict: false,
|
|
105
|
+
});
|
|
106
|
+
} catch {
|
|
107
|
+
return { command, args: {} };
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return { command, args: parsed.values };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
module.exports = { parseCliArgs, USAGE };
|