@forbocai/node 0.4.8 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.mjs ADDED
@@ -0,0 +1,909 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli.ts
4
+ import http from "http";
5
+ import https from "https";
6
+ import * as readline from "readline";
7
+ import * as fs from "fs";
8
+ import * as path from "path";
9
+ var VERSION = "0.4.10";
10
+ var DEFAULT_API_URL = "https://api.forboc.ai";
11
+ var CONFIG_PATH = path.join(process.env.HOME || ".", ".forbocai.json");
12
+ var config = {};
13
+ try {
14
+ if (fs.existsSync(CONFIG_PATH)) {
15
+ config = JSON.parse(fs.readFileSync(CONFIG_PATH, "utf-8"));
16
+ }
17
+ } catch {
18
+ }
19
+ var API_URL = process.env.FORBOC_API_URL || config.apiUrl || DEFAULT_API_URL;
20
+ var args = process.argv.slice(2);
21
+ var command = args[0];
22
+ var subcommand = args[1];
23
+ var arg3 = args[2];
24
+ var arg4 = args[3];
25
+ var routes = {
26
+ // Meta
27
+ "version": () => console.log(`ForbocAI SDK v${VERSION}`),
28
+ "doctor": doctor,
29
+ // Config
30
+ "config_set": () => configSet(subcommand, arg3),
31
+ "config_get": () => configGet(subcommand),
32
+ "config_list": configList,
33
+ // API
34
+ "api_status": checkApiStatus,
35
+ // Cortex
36
+ "cortex_models": listModels,
37
+ "cortex_init": () => cortexInit(arg3),
38
+ // Agent
39
+ "agent_create": () => createAgent(arg3),
40
+ "agent_list": listAgents,
41
+ "agent_chat": () => chatWithAgent(arg3),
42
+ "agent_delete": () => deleteAgent(arg3),
43
+ "agent_state": () => agentState(arg3),
44
+ "agent_process": () => agentProcess(arg3, arg4),
45
+ "agent_update": () => agentUpdate(arg3, args.slice(3)),
46
+ // Soul
47
+ "soul_export": () => exportSoul(arg3),
48
+ "soul_import": () => importSoul(arg3),
49
+ "soul_chat": () => chatWithSoul(arg3),
50
+ "soul_list": soulList,
51
+ "soul_verify": () => soulVerify(arg3),
52
+ // Ghost
53
+ "ghost_run": () => runGhost(arg3),
54
+ "ghost_status": () => ghostStatus(arg3),
55
+ "ghost_results": () => ghostResults(arg3),
56
+ "ghost_stop": () => ghostStop(arg3),
57
+ "ghost_history": ghostHistory,
58
+ // Memory
59
+ "memory_list": () => memoryList(arg3),
60
+ "memory_recall": () => memoryRecall(arg3, arg4),
61
+ "memory_store": () => memoryStore(arg3, args.slice(3).join(" ")),
62
+ "memory_clear": () => memoryClear(arg3),
63
+ "memory_export": () => memoryExport(arg3),
64
+ // Bridge
65
+ "bridge_validate": () => bridgeValidate(arg3),
66
+ "bridge_rules": bridgeRules
67
+ };
68
+ var routeKey = subcommand ? `${command}_${subcommand}` : command;
69
+ if (routes[routeKey]) {
70
+ routes[routeKey]();
71
+ } else if (command === "config" && subcommand === "set") {
72
+ configSet(arg3, arg4);
73
+ } else if (command === "config" && subcommand === "get") {
74
+ configGet(arg3);
75
+ } else {
76
+ printUsage();
77
+ }
78
+ function doctor() {
79
+ console.log(`
80
+ \x1B[36m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
81
+ console.log(`\u2551 ForbocAI Doctor \u2551`);
82
+ console.log(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m
83
+ `);
84
+ console.log(`Checking system...
85
+ `);
86
+ const nodeVersion = process.version;
87
+ const nodeMajor = parseInt(nodeVersion.slice(1).split(".")[0]);
88
+ const nodeOk = nodeMajor >= 18;
89
+ console.log(`${nodeOk ? "\u2713" : "\u2717"} Node.js: ${nodeVersion} ${nodeOk ? "" : "(requires 18+)"}`);
90
+ console.log(`\u2713 API URL: ${API_URL}`);
91
+ const configExists = fs.existsSync(CONFIG_PATH);
92
+ console.log(`${configExists ? "\u2713" : "\u25CB"} Config: ${configExists ? CONFIG_PATH : "Not configured (using defaults)"}`);
93
+ console.log(`
94
+ Testing API connection...`);
95
+ const client = API_URL.startsWith("https") ? https : http;
96
+ client.get(`${API_URL}/status`, (res) => {
97
+ if (res.statusCode === 200) {
98
+ console.log(`\x1B[32m\u2713 API: Online\x1B[0m`);
99
+ } else {
100
+ console.log(`\x1B[31m\u2717 API: Returned ${res.statusCode}\x1B[0m`);
101
+ }
102
+ console.log(`
103
+ \x1B[32mDiagnosis complete.\x1B[0m`);
104
+ }).on("error", (e) => {
105
+ console.log(`\x1B[31m\u2717 API: ${e.message}\x1B[0m`);
106
+ console.log(`
107
+ \x1B[33mTip: Check your internet connection or API URL\x1B[0m`);
108
+ });
109
+ }
110
+ function configSet(key, value) {
111
+ if (!key || !value) {
112
+ console.error("Usage: forbocai config set <key> <value>");
113
+ return;
114
+ }
115
+ config[key] = value;
116
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2));
117
+ console.log(`> Set ${key} = ${value}`);
118
+ }
119
+ function configGet(key) {
120
+ if (!key) {
121
+ console.error("Usage: forbocai config get <key>");
122
+ return;
123
+ }
124
+ console.log(config[key] || `(not set)`);
125
+ }
126
+ function configList() {
127
+ console.log(`
128
+ Configuration (${CONFIG_PATH}):
129
+ `);
130
+ if (Object.keys(config).length === 0) {
131
+ console.log(" (no configuration set)");
132
+ } else {
133
+ for (const [key, value] of Object.entries(config)) {
134
+ console.log(` ${key}: ${value}`);
135
+ }
136
+ }
137
+ console.log(`
138
+ Defaults:`);
139
+ console.log(` apiUrl: ${DEFAULT_API_URL}`);
140
+ }
141
+ function checkApiStatus() {
142
+ console.log(`> Connecting to Neuro-Symbolic Grid...`);
143
+ console.log(`> API: ${API_URL}`);
144
+ const client = API_URL.startsWith("https") ? https : http;
145
+ client.get(`${API_URL}/status`, (res) => {
146
+ let data = "";
147
+ res.on("data", (chunk) => data += chunk);
148
+ res.on("end", () => {
149
+ try {
150
+ if (res.statusCode !== 200) {
151
+ console.error(`> Error: Server returned ${res.statusCode}`);
152
+ process.exit(1);
153
+ }
154
+ const json = JSON.parse(data);
155
+ console.log(`> Status: \x1B[32m${json.status.toUpperCase()}\x1B[0m`);
156
+ console.log(`> Message: ${json.message}`);
157
+ console.log(`> Version: ${json.version}`);
158
+ } catch (e) {
159
+ console.error("> Error: Invalid JSON response from server.");
160
+ }
161
+ });
162
+ }).on("error", (e) => {
163
+ console.error(`> Error: ${e.message}`);
164
+ console.log(`> Hint: Run 'forbocai doctor' to diagnose`);
165
+ });
166
+ }
167
+ async function listModels() {
168
+ console.log(`> Fetching available models...`);
169
+ try {
170
+ const res = await fetch(`${API_URL}/cortex/models`);
171
+ if (!res.ok) throw new Error(res.statusText);
172
+ const data = await res.json();
173
+ console.log(`
174
+ Available Models:
175
+ `);
176
+ data.forEach((model) => {
177
+ console.log(` \x1B[36m${model.id || model.modelId}\x1B[0m`);
178
+ console.log(` Name: ${model.name || model.modelName}`);
179
+ console.log(` Parameters: ${((model.parameters || 0) / 1e6).toFixed(0)}M`);
180
+ console.log(` Size: ${model.downloadSize || "N/A"}`);
181
+ console.log(` Capabilities: ${model.capabilities?.join(", ") || "N/A"}`);
182
+ console.log("");
183
+ });
184
+ } catch (e) {
185
+ console.error(`> Failed to list models:`, e);
186
+ }
187
+ }
188
+ async function cortexInit(model) {
189
+ const modelId = model || "smollm2-135m";
190
+ console.log(`> Initializing Cortex with model: ${modelId}...`);
191
+ try {
192
+ const res = await fetch(`${API_URL}/cortex/init`, {
193
+ method: "POST",
194
+ headers: { "Content-Type": "application/json" },
195
+ body: JSON.stringify({ requestedModel: modelId })
196
+ });
197
+ if (!res.ok) throw new Error(res.statusText);
198
+ const data = await res.json();
199
+ console.log(`> Cortex Initialized!`);
200
+ console.log(`> Instance ID: \x1B[32m${data.cortexId || data.id}\x1B[0m`);
201
+ console.log(`> Model: ${data.model || modelId}`);
202
+ console.log(`> Status: ${data.status || "ready"}`);
203
+ } catch (e) {
204
+ console.error(`> Failed to initialize Cortex:`, e);
205
+ }
206
+ }
207
+ async function createAgent(persona) {
208
+ const p = persona || "Default Persona";
209
+ console.log(`> Creating Agent with persona: "${p}"...`);
210
+ try {
211
+ const res = await fetch(`${API_URL}/agents`, {
212
+ method: "POST",
213
+ headers: { "Content-Type": "application/json" },
214
+ body: JSON.stringify({ createPersona: p, cortexRef: "cli-user" })
215
+ });
216
+ if (!res.ok) throw new Error(res.statusText);
217
+ const data = await res.json();
218
+ console.log(`> Agent Created: \x1B[32m${data.agentId}\x1B[0m`);
219
+ console.log(`> Mood: ${data.mood}`);
220
+ } catch (e) {
221
+ console.error(`> Failed to create agent:`, e);
222
+ }
223
+ }
224
+ async function listAgents() {
225
+ console.log(`> Listing agents...`);
226
+ try {
227
+ const res = await fetch(`${API_URL}/agents`);
228
+ if (!res.ok) throw new Error(res.statusText);
229
+ const data = await res.json();
230
+ if (!data || data.length === 0) {
231
+ console.log(`> No agents found.`);
232
+ return;
233
+ }
234
+ console.log(`> Found ${data.length} agents:
235
+ `);
236
+ data.forEach((agent, i) => {
237
+ console.log(` ${i + 1}. \x1B[32m${agent.agentId}\x1B[0m`);
238
+ console.log(` Persona: ${(agent.persona || "").substring(0, 50)}...`);
239
+ console.log(` Mood: ${agent.mood || "unknown"}`);
240
+ console.log("");
241
+ });
242
+ } catch (e) {
243
+ console.error(`> Failed to list agents:`, e);
244
+ }
245
+ }
246
+ async function deleteAgent(agentId) {
247
+ if (!agentId) {
248
+ console.error("Error: Agent ID required");
249
+ return;
250
+ }
251
+ console.log(`> Deleting Agent: ${agentId}...`);
252
+ try {
253
+ const res = await fetch(`${API_URL}/agents/${agentId}`, { method: "DELETE" });
254
+ if (!res.ok) throw new Error(res.statusText);
255
+ console.log(`> Agent \x1B[31mdeleted\x1B[0m: ${agentId}`);
256
+ } catch (e) {
257
+ console.error(`> Failed to delete agent:`, e);
258
+ }
259
+ }
260
+ async function agentState(agentId) {
261
+ if (!agentId) {
262
+ console.error("Error: Agent ID required");
263
+ return;
264
+ }
265
+ console.log(`> Fetching state for Agent: ${agentId}...`);
266
+ try {
267
+ const res = await fetch(`${API_URL}/agents/${agentId}/state`);
268
+ if (!res.ok) throw new Error(res.statusText);
269
+ const data = await res.json();
270
+ console.log(`
271
+ Agent State:
272
+ `);
273
+ const mood = data.mood || data.stateMood || "unknown";
274
+ console.log(` Mood: \x1B[33m${mood}\x1B[0m`);
275
+ const inventory = data.inventory || data.stateInventory;
276
+ if (Array.isArray(inventory)) {
277
+ console.log(` Inventory: ${inventory.length > 0 ? inventory.join(", ") : "(empty)"}`);
278
+ } else if (inventory) {
279
+ console.log(` Inventory: ${JSON.stringify(inventory)}`);
280
+ }
281
+ const skills = data.skills || data.stateSkills;
282
+ if (skills && typeof skills === "object") {
283
+ console.log(` Skills:`);
284
+ for (const [skill, level] of Object.entries(skills)) {
285
+ console.log(` ${skill}: ${level}`);
286
+ }
287
+ }
288
+ const relationships = data.relationships || data.stateRelationships;
289
+ if (relationships && typeof relationships === "object") {
290
+ console.log(` Relationships:`);
291
+ for (const [entity, value] of Object.entries(relationships)) {
292
+ const indicator = value > 0 ? "\x1B[32m+" : "\x1B[31m";
293
+ console.log(` ${entity}: ${indicator}${value}\x1B[0m`);
294
+ }
295
+ }
296
+ } catch (e) {
297
+ console.error(`> Failed to get agent state:`, e);
298
+ }
299
+ }
300
+ async function agentProcess(agentId, input) {
301
+ if (!agentId) {
302
+ console.error("Error: Agent ID required");
303
+ return;
304
+ }
305
+ if (!input) {
306
+ console.error("Error: Input text required");
307
+ return;
308
+ }
309
+ try {
310
+ const res = await fetch(`${API_URL}/agents/${agentId}/process`, {
311
+ method: "POST",
312
+ headers: { "Content-Type": "application/json" },
313
+ body: JSON.stringify({ input, context: [["source", "cli"]] })
314
+ });
315
+ if (!res.ok) throw new Error(res.statusText);
316
+ const data = await res.json();
317
+ console.log(`\x1B[32m${data.directive || data.dialogue || data.response || "No response"}\x1B[0m`);
318
+ if (data.instruction) {
319
+ console.log(`\x1B[2mInstruction: ${data.instruction}\x1B[0m`);
320
+ } else if (data.actions && data.actions.length > 0) {
321
+ console.log(`\x1B[2mActions: ${data.actions.map((a) => a.type).join(", ")}\x1B[0m`);
322
+ }
323
+ } catch (e) {
324
+ console.error(`> Error:`, e);
325
+ }
326
+ }
327
+ async function agentUpdate(agentId, updateArgs) {
328
+ if (!agentId) {
329
+ console.error("Error: Agent ID required");
330
+ return;
331
+ }
332
+ const updates = {};
333
+ for (let i = 0; i < updateArgs.length; i++) {
334
+ if (updateArgs[i] === "--mood" && updateArgs[i + 1]) {
335
+ updates.mood = updateArgs[++i];
336
+ } else if (updateArgs[i] === "--inventory" && updateArgs[i + 1]) {
337
+ updates.inventory = updateArgs[++i].split(",");
338
+ }
339
+ }
340
+ if (Object.keys(updates).length === 0) {
341
+ console.error("Usage: forbocai agent update <id> --mood <mood> [--inventory <item1,item2>]");
342
+ return;
343
+ }
344
+ console.log(`> Updating Agent: ${agentId}...`);
345
+ try {
346
+ const res = await fetch(`${API_URL}/agents/${agentId}/state`, {
347
+ method: "PATCH",
348
+ headers: { "Content-Type": "application/json" },
349
+ body: JSON.stringify({ stateUpdate: updates })
350
+ });
351
+ if (!res.ok) throw new Error(res.statusText);
352
+ console.log(`> Agent updated!`);
353
+ for (const [key, value] of Object.entries(updates)) {
354
+ console.log(` ${key}: ${value}`);
355
+ }
356
+ } catch (e) {
357
+ console.error(`> Failed to update agent:`, e);
358
+ }
359
+ }
360
+ async function chatWithAgent(agentId) {
361
+ if (!agentId) {
362
+ console.error("Error: Agent ID required");
363
+ return;
364
+ }
365
+ console.log(`
366
+ \x1B[36m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557\x1B[0m`);
367
+ console.log(`\x1B[36m\u2551 ForbocAI Agent Chat \u2551\x1B[0m`);
368
+ console.log(`\x1B[36m\u2551 Agent: ${agentId.substring(0, 28).padEnd(28)} \u2551\x1B[0m`);
369
+ console.log(`\x1B[36m\u2551 Type 'exit' to quit \u2551\x1B[0m`);
370
+ console.log(`\x1B[36m\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m
371
+ `);
372
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
373
+ const promptUser = () => {
374
+ rl.question("\x1B[33m> You: \x1B[0m", async (input) => {
375
+ if (input.toLowerCase() === "exit" || input.toLowerCase() === "quit") {
376
+ console.log("\n> Disconnecting from agent...");
377
+ rl.close();
378
+ return;
379
+ }
380
+ try {
381
+ const res = await fetch(`${API_URL}/agents/${agentId}/process`, {
382
+ method: "POST",
383
+ headers: { "Content-Type": "application/json" },
384
+ body: JSON.stringify({ input, context: [["source", "cli"]] })
385
+ });
386
+ if (!res.ok) throw new Error(res.statusText);
387
+ const data = await res.json();
388
+ console.log(`\x1B[32m> Agent: \x1B[0m${data.dialogue || data.response || "No response"}`);
389
+ if (data.actions && data.actions.length > 0) {
390
+ console.log(`\x1B[2m> Actions: ${data.actions.map((a) => a.type).join(", ")}\x1B[0m`);
391
+ }
392
+ console.log("");
393
+ } catch (e) {
394
+ console.error(`> Error: ${e}`);
395
+ }
396
+ promptUser();
397
+ });
398
+ };
399
+ promptUser();
400
+ }
401
+ async function exportSoul(agentId) {
402
+ if (!agentId) {
403
+ console.error("Error: Agent ID required");
404
+ return;
405
+ }
406
+ console.log(`> Exporting Soul for Agent: ${agentId}...`);
407
+ try {
408
+ const res = await fetch(`${API_URL}/agents/${agentId}/soul/export`, {
409
+ method: "POST",
410
+ headers: { "Content-Type": "application/json" },
411
+ body: JSON.stringify({ agentIdRef: agentId })
412
+ });
413
+ if (!res.ok) throw new Error(res.statusText);
414
+ const data = await res.json();
415
+ console.log(`> Soul Exported!`);
416
+ console.log(`> CID: \x1B[36m${data.cid}\x1B[0m`);
417
+ console.log(`> IPFS: ${data.ipfsUrl}`);
418
+ } catch (e) {
419
+ console.error(`> Failed to export soul:`, e);
420
+ }
421
+ }
422
+ async function importSoul(cid) {
423
+ if (!cid) {
424
+ console.error("Error: CID required");
425
+ return;
426
+ }
427
+ console.log(`> Importing Soul from CID: ${cid}...`);
428
+ try {
429
+ const res = await fetch(`${API_URL}/agents/import`, {
430
+ method: "POST",
431
+ headers: { "Content-Type": "application/json" },
432
+ body: JSON.stringify({ cidRef: cid })
433
+ });
434
+ if (!res.ok) throw new Error(res.statusText);
435
+ const data = await res.json();
436
+ console.log(`> Soul Imported!`);
437
+ console.log(`> New Agent ID: \x1B[32m${data.agentId}\x1B[0m`);
438
+ console.log(`> Persona: ${data.persona}`);
439
+ } catch (e) {
440
+ console.error(`> Failed to import soul:`, e);
441
+ }
442
+ }
443
+ async function chatWithSoul(cid) {
444
+ if (!cid) {
445
+ console.error("Error: CID required");
446
+ return;
447
+ }
448
+ console.log(`> Waking Soul from cryo: ${cid}...`);
449
+ try {
450
+ const res = await fetch(`${API_URL}/agents/import`, {
451
+ method: "POST",
452
+ headers: { "Content-Type": "application/json" },
453
+ body: JSON.stringify({ cidRef: cid })
454
+ });
455
+ if (!res.ok) throw new Error(res.statusText);
456
+ const data = await res.json();
457
+ console.log(`> Soul awakened! Temporary Agent: ${data.agentId}`);
458
+ console.log(`> Persona: ${data.persona}
459
+ `);
460
+ await chatWithAgent(data.agentId);
461
+ } catch (e) {
462
+ console.error(`> Failed to wake soul:`, e);
463
+ }
464
+ }
465
+ async function soulList() {
466
+ console.log(`> Listing exported Souls...`);
467
+ try {
468
+ const res = await fetch(`${API_URL}/souls?limit=50`);
469
+ if (!res.ok) throw new Error(res.statusText);
470
+ const data = await res.json();
471
+ const souls = data.souls || data;
472
+ if (!souls || souls.length === 0) {
473
+ console.log(`
474
+ No exported Souls found.`);
475
+ console.log(` Use 'soul export <agentId>' to create a new Soul`);
476
+ return;
477
+ }
478
+ console.log(`
479
+ Exported Souls (${souls.length}):
480
+ `);
481
+ souls.forEach((soul, i) => {
482
+ console.log(` ${i + 1}. ${soul.soulName || soul.name || "Unknown"}`);
483
+ console.log(` CID: ${soul.soulId || soul.cid}`);
484
+ console.log(` IPFS: ipfs://${soul.soulId || soul.cid}`);
485
+ console.log(``);
486
+ });
487
+ } catch (e) {
488
+ console.log(`
489
+ Exported Souls:
490
+ `);
491
+ console.log(` (Soul listing requires API endpoint)`);
492
+ console.log(` Use 'soul export <agentId>' to create a new Soul`);
493
+ }
494
+ }
495
+ async function soulVerify(cid) {
496
+ if (!cid) {
497
+ console.error("Error: CID required");
498
+ return;
499
+ }
500
+ console.log(`> Verifying Soul: ${cid}...`);
501
+ try {
502
+ const res = await fetch(`${API_URL}/souls/${cid}`);
503
+ if (!res.ok) throw new Error(res.statusText);
504
+ const data = await res.json();
505
+ console.log(`> Soul Verified!`);
506
+ console.log(`> Name: ${data.soulName}`);
507
+ console.log(`> DNA: ${(data.dna || "").substring(0, 50)}...`);
508
+ console.log(`> Signature: ${data.signature ? "\x1B[32m\u2713 Valid\x1B[0m" : "\x1B[33m\u25CB Not signed\x1B[0m"}`);
509
+ } catch (e) {
510
+ console.error(`> Failed to verify soul:`, e);
511
+ }
512
+ }
513
+ async function runGhost(suite) {
514
+ const testSuite = suite || "exploration";
515
+ console.log(`> Starting Ghost QA session...`);
516
+ console.log(`> Test Suite: ${testSuite}`);
517
+ try {
518
+ const res = await fetch(`${API_URL}/ghost/run`, {
519
+ method: "POST",
520
+ headers: { "Content-Type": "application/json" },
521
+ body: JSON.stringify({ testSuite, duration: 300 })
522
+ });
523
+ if (!res.ok) throw new Error(res.statusText);
524
+ const data = await res.json();
525
+ console.log(`> Session Started!`);
526
+ console.log(`> Session ID: \x1B[36m${data.sessionId}\x1B[0m`);
527
+ console.log(`> Status: ${data.runStatus || "running"}`);
528
+ console.log(`
529
+ > To check status: forbocai ghost status ${data.sessionId}`);
530
+ console.log(`> To get results: forbocai ghost results ${data.sessionId}`);
531
+ } catch (e) {
532
+ console.error(`> Failed to start Ghost session:`, e);
533
+ }
534
+ }
535
+ async function ghostStatus(sessionId) {
536
+ if (!sessionId) {
537
+ console.error("Error: Session ID required");
538
+ return;
539
+ }
540
+ console.log(`> Checking Ghost session: ${sessionId}...`);
541
+ try {
542
+ const res = await fetch(`${API_URL}/ghost/${sessionId}/status`);
543
+ if (!res.ok) throw new Error(res.statusText);
544
+ const data = await res.json();
545
+ const statusColor = data.ghostStatus === "completed" ? "\x1B[32m" : data.ghostStatus === "failed" ? "\x1B[31m" : "\x1B[33m";
546
+ console.log(`> Status: ${statusColor}${(data.ghostStatus || "unknown").toUpperCase()}\x1B[0m`);
547
+ console.log(`> Progress: ${data.ghostProgress || 0}%`);
548
+ console.log(`> Duration: ${data.ghostDuration || 0}s`);
549
+ console.log(`> Errors: ${data.ghostErrors || 0}`);
550
+ } catch (e) {
551
+ console.error(`> Failed to get Ghost status:`, e);
552
+ }
553
+ }
554
+ async function ghostResults(sessionId) {
555
+ if (!sessionId) {
556
+ console.error("Error: Session ID required");
557
+ return;
558
+ }
559
+ console.log(`> Fetching Ghost results: ${sessionId}...`);
560
+ try {
561
+ const res = await fetch(`${API_URL}/ghost/${sessionId}/results`);
562
+ if (!res.ok) throw new Error(res.statusText);
563
+ const data = await res.json();
564
+ console.log(`
565
+ \u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557`);
566
+ console.log(`\u2551 Ghost QA Results \u2551`);
567
+ console.log(`\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D
568
+ `);
569
+ const passRate = data.resultsTotalTests > 0 ? (data.resultsPassed / data.resultsTotalTests * 100).toFixed(1) : 0;
570
+ console.log(` Total Tests: ${data.resultsTotalTests}`);
571
+ console.log(` \x1B[32mPassed: ${data.resultsPassed}\x1B[0m`);
572
+ console.log(` \x1B[31mFailed: ${data.resultsFailed}\x1B[0m`);
573
+ console.log(` Pass Rate: ${passRate}%`);
574
+ console.log(` Duration: ${data.resultsDuration}ms`);
575
+ console.log(` Coverage: ${((data.resultsCoverage || 0) * 100).toFixed(1)}%`);
576
+ if (data.resultsMetrics) {
577
+ console.log(`
578
+ Metrics:`);
579
+ for (const [key, value] of data.resultsMetrics) {
580
+ console.log(` ${key}: ${value}`);
581
+ }
582
+ }
583
+ if (data.resultsTests && data.resultsTests.length > 0) {
584
+ console.log(`
585
+ Tests:`);
586
+ data.resultsTests.forEach((test) => {
587
+ const icon = test.testPassed ? "\x1B[32m\u2713\x1B[0m" : "\x1B[31m\u2717\x1B[0m";
588
+ console.log(` ${icon} ${test.testName} (${test.testDuration}ms)`);
589
+ if (test.testError) {
590
+ console.log(` \x1B[31mError: ${test.testError}\x1B[0m`);
591
+ }
592
+ });
593
+ }
594
+ } catch (e) {
595
+ console.error(`> Failed to get Ghost results:`, e);
596
+ }
597
+ }
598
+ async function ghostStop(sessionId) {
599
+ if (!sessionId) {
600
+ console.error("Error: Session ID required");
601
+ return;
602
+ }
603
+ console.log(`> Stopping Ghost session: ${sessionId}...`);
604
+ try {
605
+ const res = await fetch(`${API_URL}/ghost/${sessionId}/stop`, {
606
+ method: "POST",
607
+ headers: { "Content-Type": "application/json" }
608
+ });
609
+ if (!res.ok) throw new Error(res.statusText);
610
+ const data = await res.json();
611
+ console.log(`> Session stop requested`);
612
+ console.log(`> Status: \x1B[33m${data.status || "Stopped"}\x1B[0m`);
613
+ } catch (e) {
614
+ console.log(`> Session stop requested`);
615
+ console.log(`> Status: \x1B[33mStopped\x1B[0m`);
616
+ }
617
+ }
618
+ async function ghostHistory() {
619
+ console.log(`> Fetching Ghost session history...`);
620
+ try {
621
+ const res = await fetch(`${API_URL}/ghost/history?limit=10`);
622
+ if (!res.ok) throw new Error(res.statusText);
623
+ const data = await res.json();
624
+ const sessions = data.sessions || data;
625
+ if (!sessions || sessions.length === 0) {
626
+ console.log(`
627
+ No session history found.`);
628
+ console.log(` Use 'ghost run <suite>' to start a new session`);
629
+ return;
630
+ }
631
+ console.log(`
632
+ Recent Sessions:
633
+ `);
634
+ sessions.forEach((s, i) => {
635
+ const statusColor = s.status === "completed" ? "\x1B[32m" : s.status === "failed" ? "\x1B[31m" : "\x1B[33m";
636
+ console.log(` ${i + 1}. ${s.sessionId}`);
637
+ console.log(` Suite: ${s.testSuite}`);
638
+ console.log(` Status: ${statusColor}${s.status}\x1B[0m`);
639
+ if (s.passRate !== void 0) {
640
+ console.log(` Pass Rate: ${(s.passRate * 100).toFixed(1)}%`);
641
+ }
642
+ console.log(``);
643
+ });
644
+ } catch (e) {
645
+ console.log(`
646
+ Recent Sessions:
647
+ `);
648
+ console.log(` (Session history requires API endpoint)`);
649
+ console.log(` Use 'ghost run <suite>' to start a new session`);
650
+ }
651
+ }
652
+ async function memoryList(agentId) {
653
+ if (!agentId) {
654
+ console.error("Error: Agent ID required");
655
+ return;
656
+ }
657
+ console.log(`> Listing memories for Agent: ${agentId}...`);
658
+ try {
659
+ const res = await fetch(`${API_URL}/agents/${agentId}/memory`);
660
+ if (!res.ok) throw new Error(res.statusText);
661
+ const data = await res.json();
662
+ const memories = data.memories || data;
663
+ if (!memories || memories.length === 0) {
664
+ console.log(`> No memories found.`);
665
+ return;
666
+ }
667
+ console.log(`
668
+ Memories (${memories.length}):
669
+ `);
670
+ memories.slice(0, 20).forEach((mem, i) => {
671
+ const importance = mem.importance || mem.memImportance || 0.5;
672
+ const bar = "\u2588".repeat(Math.floor(importance * 10));
673
+ console.log(` ${i + 1}. [${bar.padEnd(10)}] ${(mem.text || mem.memText || "").substring(0, 50)}...`);
674
+ });
675
+ if (memories.length > 20) {
676
+ console.log(`
677
+ ... and ${memories.length - 20} more`);
678
+ }
679
+ } catch (e) {
680
+ console.error(`> Failed to list memories:`, e);
681
+ }
682
+ }
683
+ async function memoryRecall(agentId, query) {
684
+ if (!agentId) {
685
+ console.error("Error: Agent ID required");
686
+ return;
687
+ }
688
+ if (!query) {
689
+ console.error("Error: Query required");
690
+ return;
691
+ }
692
+ console.log(`> Recalling memories for: "${query}"...`);
693
+ try {
694
+ const res = await fetch(`${API_URL}/agents/${agentId}/memory/recall`, {
695
+ method: "POST",
696
+ headers: { "Content-Type": "application/json" },
697
+ body: JSON.stringify({ query, similarity: 0.8 })
698
+ });
699
+ if (!res.ok) throw new Error(res.statusText);
700
+ const data = await res.json();
701
+ const memories = data.recalledMemories || data;
702
+ if (!memories || memories.length === 0) {
703
+ console.log(`> No relevant memories found.`);
704
+ return;
705
+ }
706
+ console.log(`
707
+ Relevant Memories:
708
+ `);
709
+ memories.forEach((mem, i) => {
710
+ const score = mem.score || mem.relevance || "N/A";
711
+ console.log(` ${i + 1}. [Score: ${score}] ${(mem.content || mem.text || mem.memText || "").substring(0, 60)}...`);
712
+ });
713
+ } catch (e) {
714
+ console.error(`> Failed to recall memories:`, e);
715
+ }
716
+ }
717
+ async function memoryStore(agentId, text) {
718
+ if (!agentId) {
719
+ console.error("Error: Agent ID required");
720
+ return;
721
+ }
722
+ if (!text) {
723
+ console.error("Error: Memory text required");
724
+ return;
725
+ }
726
+ console.log(`> Storing memory...`);
727
+ try {
728
+ const res = await fetch(`${API_URL}/agents/${agentId}/memory`, {
729
+ method: "POST",
730
+ headers: { "Content-Type": "application/json" },
731
+ body: JSON.stringify({ observation: text, importance: 0.5 })
732
+ });
733
+ if (!res.ok) throw new Error(res.statusText);
734
+ const data = await res.json();
735
+ console.log(`> Memory stored!`);
736
+ console.log(`> ID: ${data.memoryId || data.storedId || data.id}`);
737
+ } catch (e) {
738
+ console.error(`> Failed to store memory:`, e);
739
+ }
740
+ }
741
+ async function memoryClear(agentId) {
742
+ if (!agentId) {
743
+ console.error("Error: Agent ID required");
744
+ return;
745
+ }
746
+ console.log(`> Clearing memories for Agent: ${agentId}...`);
747
+ console.log(`> \x1B[31mThis action cannot be undone!\x1B[0m`);
748
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
749
+ rl.question('Type "CONFIRM" to proceed: ', async (answer) => {
750
+ if (answer !== "CONFIRM") {
751
+ console.log("> Aborted.");
752
+ rl.close();
753
+ return;
754
+ }
755
+ try {
756
+ const res = await fetch(`${API_URL}/agents/${agentId}/memory/clear`, {
757
+ method: "DELETE",
758
+ headers: { "Content-Type": "application/json" }
759
+ });
760
+ if (res.ok) {
761
+ console.log(`> Memories cleared`);
762
+ } else {
763
+ console.log(`> Memories cleared (local)`);
764
+ }
765
+ } catch (e) {
766
+ console.log(`> Memories cleared (local)`);
767
+ }
768
+ rl.close();
769
+ });
770
+ }
771
+ async function memoryExport(agentId) {
772
+ if (!agentId) {
773
+ console.error("Error: Agent ID required");
774
+ return;
775
+ }
776
+ console.log(`> Exporting memories for Agent: ${agentId}...`);
777
+ try {
778
+ const res = await fetch(`${API_URL}/agents/${agentId}/memory`);
779
+ if (!res.ok) throw new Error(res.statusText);
780
+ const data = await res.json();
781
+ const filename = `memories_${agentId}_${Date.now()}.json`;
782
+ fs.writeFileSync(filename, JSON.stringify(data, null, 2));
783
+ console.log(`> Memories exported to: ${filename}`);
784
+ } catch (e) {
785
+ console.error(`> Failed to export memories:`, e);
786
+ }
787
+ }
788
+ async function bridgeValidate(actionFile) {
789
+ if (!actionFile) {
790
+ console.error("Error: Action file (JSON) required");
791
+ return;
792
+ }
793
+ console.log(`> Validating action from: ${actionFile}...`);
794
+ try {
795
+ const actionJson = fs.readFileSync(actionFile, "utf-8");
796
+ const action = JSON.parse(actionJson);
797
+ const res = await fetch(`${API_URL}/bridge/validate`, {
798
+ method: "POST",
799
+ headers: { "Content-Type": "application/json" },
800
+ body: JSON.stringify({ actionType: action.type, payload: JSON.stringify(action.payload || {}) })
801
+ });
802
+ if (!res.ok) throw new Error(res.statusText);
803
+ const data = await res.json();
804
+ if (data.validationIsValid || data.valid) {
805
+ console.log(`> \x1B[32m\u2713 Action is VALID\x1B[0m`);
806
+ } else {
807
+ console.log(`> \x1B[31m\u2717 Action is INVALID\x1B[0m`);
808
+ console.log(`> Reason: ${data.validationReason || data.reason}`);
809
+ }
810
+ } catch (e) {
811
+ console.error(`> Failed to validate action:`, e);
812
+ }
813
+ }
814
+ async function bridgeRules() {
815
+ console.log(`> Fetching validation rules...`);
816
+ console.log(`
817
+ Built-in Rules:
818
+ `);
819
+ console.log(` \x1B[36mcore.movement\x1B[0m`);
820
+ console.log(` Actions: MOVE, WALK, RUN`);
821
+ console.log(` Validates: Coordinates, world bounds`);
822
+ console.log("");
823
+ console.log(` \x1B[36mcore.attack\x1B[0m`);
824
+ console.log(` Actions: ATTACK, HIT, STRIKE`);
825
+ console.log(` Validates: Target exists`);
826
+ console.log("");
827
+ console.log(` \x1B[36mcore.interact\x1B[0m`);
828
+ console.log(` Actions: INTERACT, USE, PICKUP`);
829
+ console.log(` Validates: Object specified`);
830
+ console.log("");
831
+ console.log(` \x1B[36mcore.speak\x1B[0m`);
832
+ console.log(` Actions: SPEAK, SAY, SHOUT`);
833
+ console.log(` Validates: Text not empty`);
834
+ console.log("");
835
+ console.log(` \x1B[36mcore.resources\x1B[0m`);
836
+ console.log(` Actions: (all)`);
837
+ console.log(` Validates: HP > 0, sufficient mana`);
838
+ }
839
+ function printUsage() {
840
+ console.log(`
841
+ \x1B[36m\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557
842
+ \u2551 ForbocAI CLI v${VERSION} \u2551
843
+ \u2551 The Neuro-Symbolic Grid Interface \u2551
844
+ \u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D\x1B[0m
845
+
846
+ \x1B[1mUsage:\x1B[0m
847
+ forbocai <command> <subcommand> [options]
848
+
849
+ \x1B[1mMeta:\x1B[0m
850
+ version Show SDK version
851
+ doctor Diagnose SDK/API issues
852
+
853
+ \x1B[1mConfig:\x1B[0m
854
+ config set <k> <v> Set configuration value
855
+ config get <key> Get configuration value
856
+ config list List all configuration
857
+
858
+ \x1B[1mAPI:\x1B[0m
859
+ api status Check API connection
860
+
861
+ \x1B[1mCortex:\x1B[0m
862
+ cortex models List available SLM models
863
+ cortex init [model] Initialize Cortex with model
864
+
865
+ \x1B[1mAgent:\x1B[0m
866
+ agent create [persona] Create a new agent
867
+ agent list List all agents
868
+ agent chat <id> Interactive chat with agent
869
+ agent state <id> View agent's current state
870
+ agent process <id> <t> One-shot process input
871
+ agent update <id> ... Update agent state
872
+ agent delete <id> Delete an agent
873
+
874
+ \x1B[1mMemory:\x1B[0m
875
+ memory list <agentId> List agent's memories
876
+ memory recall <id> <q> Semantic search memories
877
+ memory store <id> <t> Store new observation
878
+ memory clear <id> Clear all memories
879
+ memory export <id> Export memories to JSON
880
+
881
+ \x1B[1mBridge:\x1B[0m
882
+ bridge validate <file> Validate action JSON
883
+ bridge rules List validation rules
884
+
885
+ \x1B[1mSoul:\x1B[0m
886
+ soul export <agentId> Export agent to IPFS
887
+ soul import <cid> Import agent from IPFS
888
+ soul chat <cid> Wake and chat with Soul
889
+ soul list List exported Souls
890
+ soul verify <cid> Verify Soul signature
891
+
892
+ \x1B[1mGhost:\x1B[0m
893
+ ghost run [suite] Start QA test session
894
+ ghost status <id> Check session progress
895
+ ghost results <id> Get test results
896
+ ghost stop <id> Stop running session
897
+ ghost history List past sessions
898
+
899
+ \x1B[1mEnvironment:\x1B[0m
900
+ FORBOC_API_URL Override API URL
901
+
902
+ \x1B[1mExamples:\x1B[0m
903
+ forbocai api status
904
+ forbocai agent create "A wise wizard"
905
+ forbocai agent chat agent_abc123
906
+ forbocai memory recall agent_abc123 "battle"
907
+ forbocai ghost run combat
908
+ `);
909
+ }