@bobsworkshop/cli 0.6.2 → 0.7.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/README.md +107 -10
- package/dist/analyse-auto-AU5STJV3.js +529 -0
- package/dist/analyse-auto-GCWXNMNZ.js +529 -0
- package/dist/analyse-auto-PIMZNIOW.js +529 -0
- package/dist/analyse-auto-WJAYO6EW.js +529 -0
- package/dist/analyse-results-DPQN332P.js +8 -0
- package/dist/analyse-results-PFM4IQKY.js +8 -0
- package/dist/analyse-results-TG74PQW7.js +8 -0
- package/dist/analyse-results-TIVOJ5SC.js +8 -0
- package/dist/bob.js +1170 -1233
- package/dist/chunk-DEYW6QHQ.js +1023 -0
- package/dist/chunk-M4GXVZEK.js +1027 -0
- package/dist/chunk-S7UZ5OW3.js +1016 -0
- package/dist/chunk-WXLT3UL7.js +1036 -0
- package/package.json +3 -1
package/dist/bob.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
1
2
|
import {
|
|
2
3
|
buildLocalContext,
|
|
3
4
|
callCloudFunction,
|
|
4
|
-
callHTTPFunction,
|
|
5
5
|
callLocalModel,
|
|
6
6
|
extractAllProposedFiles,
|
|
7
7
|
extractProposedFile,
|
|
@@ -16,11 +16,11 @@ import {
|
|
|
16
16
|
registerLoginCommand,
|
|
17
17
|
setConfigValue,
|
|
18
18
|
stripCodeBlockFromResponse
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-WXLT3UL7.js";
|
|
20
20
|
|
|
21
21
|
// bin/bob.ts
|
|
22
22
|
import { Command } from "commander";
|
|
23
|
-
import
|
|
23
|
+
import chalk25 from "chalk";
|
|
24
24
|
import * as path14 from "path";
|
|
25
25
|
|
|
26
26
|
// src/commands/config.ts
|
|
@@ -292,29 +292,6 @@ async function getTodayMessages(projectName) {
|
|
|
292
292
|
return msg.timestamp >= twentyFourHoursAgo;
|
|
293
293
|
});
|
|
294
294
|
}
|
|
295
|
-
function buildDNAString() {
|
|
296
|
-
const dna = loadCurrentDNA();
|
|
297
|
-
if (!dna) return null;
|
|
298
|
-
const weekly = loadWeeklyProfiles(1)[0] || null;
|
|
299
|
-
const lines = [
|
|
300
|
-
"### USER BEHAVIORAL DNA ###",
|
|
301
|
-
`Archetype: ${dna.archetype || "Unknown"}`,
|
|
302
|
-
`Communication Style: ${dna.communicationStyle || "Unknown"}`,
|
|
303
|
-
`Work Rhythm: ${dna.workRhythm || "Unknown"}`,
|
|
304
|
-
`Decision Making: ${dna.decisionMaking || "Unknown"}`,
|
|
305
|
-
`Emotional State: ${dna.emotionalState || "Unknown"}`
|
|
306
|
-
];
|
|
307
|
-
if (dna.growth) lines.push(`Growth Focus: ${dna.growth}`);
|
|
308
|
-
if (dna.source) lines.push(`Profile Source: ${dna.source} (${dna.lastUpdated || "unknown date"})`);
|
|
309
|
-
if (weekly) {
|
|
310
|
-
lines.push("");
|
|
311
|
-
lines.push("--- WEEKLY TRAJECTORY ---");
|
|
312
|
-
if (weekly.trajectory) lines.push(`Weekly Arc: ${weekly.trajectory}`);
|
|
313
|
-
if (weekly.moodShift) lines.push(`Mood Shift: ${weekly.moodShift}`);
|
|
314
|
-
if (weekly.focusEvolution) lines.push(`Focus Evolution: ${weekly.focusEvolution}`);
|
|
315
|
-
}
|
|
316
|
-
return lines.join("\n");
|
|
317
|
-
}
|
|
318
295
|
|
|
319
296
|
// src/ai/persona.ts
|
|
320
297
|
var STANDARD_STYLE_PROMPT = `You are Bob: friendly, direct, senior-level engineering partner.
|
|
@@ -670,7 +647,7 @@ function renderFrame(frame, frameHeight, statusText) {
|
|
|
670
647
|
}
|
|
671
648
|
}
|
|
672
649
|
function sleep(ms) {
|
|
673
|
-
return new Promise((
|
|
650
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
674
651
|
}
|
|
675
652
|
|
|
676
653
|
// src/ui/animations/deep-dive.ts
|
|
@@ -1153,7 +1130,7 @@ async function typewriterCharByChar(bgColor, textColor, line, maxWidth) {
|
|
|
1153
1130
|
process.stdout.write("\n");
|
|
1154
1131
|
}
|
|
1155
1132
|
function sleep2(ms) {
|
|
1156
|
-
return new Promise((
|
|
1133
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1157
1134
|
}
|
|
1158
1135
|
|
|
1159
1136
|
// src/commands/deepdive.ts
|
|
@@ -1253,8 +1230,8 @@ function registerDeepDiveCommand(program2) {
|
|
|
1253
1230
|
console.log(MODE_DEEPDIVE2(" \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\u2550\u2550\u255D"));
|
|
1254
1231
|
console.log("");
|
|
1255
1232
|
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1256
|
-
const answer = await new Promise((
|
|
1257
|
-
rl.question(MODE_DEEPDIVE2(" Select (1-" + dives.length + ") or 0 to cancel: "),
|
|
1233
|
+
const answer = await new Promise((resolve3) => {
|
|
1234
|
+
rl.question(MODE_DEEPDIVE2(" Select (1-" + dives.length + ") or 0 to cancel: "), resolve3);
|
|
1258
1235
|
});
|
|
1259
1236
|
const selection = parseInt(answer.trim());
|
|
1260
1237
|
if (isNaN(selection) || selection === 0 || selection < 1 || selection > dives.length) {
|
|
@@ -1266,9 +1243,9 @@ function registerDeepDiveCommand(program2) {
|
|
|
1266
1243
|
const parentMessageId = selectedDive.parentMessageId;
|
|
1267
1244
|
const initiatingPrompt = selectedDive.initiatingPrompt || "Deep dive session";
|
|
1268
1245
|
const animation = startDeepDiveAnimation();
|
|
1269
|
-
await new Promise((
|
|
1246
|
+
await new Promise((resolve3) => setTimeout(resolve3, 3e3));
|
|
1270
1247
|
animation.stop();
|
|
1271
|
-
await new Promise((
|
|
1248
|
+
await new Promise((resolve3) => setTimeout(resolve3, 300));
|
|
1272
1249
|
await runDeepDiveSession(config, config.conversationId, parentMessageId, initiatingPrompt, rl);
|
|
1273
1250
|
rl.close();
|
|
1274
1251
|
} catch (error) {
|
|
@@ -1329,8 +1306,8 @@ async function enterDeepDive(config, conversationId, rl) {
|
|
|
1329
1306
|
}
|
|
1330
1307
|
console.log(MODE_DEEPDIVE2(" \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\u2550\u2550\u255D"));
|
|
1331
1308
|
console.log("");
|
|
1332
|
-
const answer = await new Promise((
|
|
1333
|
-
rl.question(MODE_DEEPDIVE2(" Select (1-" + messages.length + ") or 0 to cancel: "),
|
|
1309
|
+
const answer = await new Promise((resolve3) => {
|
|
1310
|
+
rl.question(MODE_DEEPDIVE2(" Select (1-" + messages.length + ") or 0 to cancel: "), resolve3);
|
|
1334
1311
|
});
|
|
1335
1312
|
const selection = parseInt(answer.trim());
|
|
1336
1313
|
if (isNaN(selection) || selection === 0 || selection < 1 || selection > messages.length) {
|
|
@@ -1343,13 +1320,13 @@ async function enterDeepDive(config, conversationId, rl) {
|
|
|
1343
1320
|
const animation = startDeepDiveAnimation();
|
|
1344
1321
|
const divePromise = callCloudFunction("initiateCLIDeepDive", { conversationId, parentMessageId, initiatingPrompt });
|
|
1345
1322
|
try {
|
|
1346
|
-
await Promise.all([divePromise, new Promise((
|
|
1323
|
+
await Promise.all([divePromise, new Promise((resolve3) => setTimeout(resolve3, 3e3))]);
|
|
1347
1324
|
animation.stop();
|
|
1348
|
-
await new Promise((
|
|
1325
|
+
await new Promise((resolve3) => setTimeout(resolve3, 300));
|
|
1349
1326
|
await runDeepDiveSession(config, conversationId, parentMessageId, initiatingPrompt, rl);
|
|
1350
1327
|
} catch (error) {
|
|
1351
1328
|
animation.stop();
|
|
1352
|
-
await new Promise((
|
|
1329
|
+
await new Promise((resolve3) => setTimeout(resolve3, 200));
|
|
1353
1330
|
console.log(ERROR2(` \u274C Could not initiate deep dive: ${error.message}`));
|
|
1354
1331
|
}
|
|
1355
1332
|
}
|
|
@@ -1369,7 +1346,7 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
|
|
|
1369
1346
|
console.log("");
|
|
1370
1347
|
let lastBobResponse = "";
|
|
1371
1348
|
let lastConstraints3 = [];
|
|
1372
|
-
return new Promise((
|
|
1349
|
+
return new Promise((resolve3) => {
|
|
1373
1350
|
const deepDivePrompt = () => {
|
|
1374
1351
|
rl.question(MODE_DEEPDIVE2(" \u{1F93F} You: "), async (input) => {
|
|
1375
1352
|
const trimmed = input.trim();
|
|
@@ -1384,7 +1361,7 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
|
|
|
1384
1361
|
console.log(MODE_DEEPDIVE2(" \u2551") + MUTED2(` Back in: ${conversationId.slice(0, 24)}...`));
|
|
1385
1362
|
console.log(MODE_DEEPDIVE2(" \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\u2550\u2550\u255D"));
|
|
1386
1363
|
console.log("");
|
|
1387
|
-
|
|
1364
|
+
resolve3();
|
|
1388
1365
|
return;
|
|
1389
1366
|
}
|
|
1390
1367
|
if (trimmed === "/promote") {
|
|
@@ -1400,7 +1377,7 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
|
|
|
1400
1377
|
console.log(ERROR2(` \u274C Promote failed: ${error.message}`));
|
|
1401
1378
|
console.log("");
|
|
1402
1379
|
}
|
|
1403
|
-
|
|
1380
|
+
resolve3();
|
|
1404
1381
|
return;
|
|
1405
1382
|
}
|
|
1406
1383
|
if (trimmed === "/clear") {
|
|
@@ -1431,14 +1408,14 @@ async function runDeepDiveSession(config, conversationId, parentMessageId, initi
|
|
|
1431
1408
|
console.log(BORDER(" \u2502"));
|
|
1432
1409
|
console.log(BORDER(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
1433
1410
|
console.log("");
|
|
1434
|
-
const answer = await new Promise((
|
|
1435
|
-
rl.question(MODE_DEEPDIVE2(" Leave deep dive for main conversation? (y/N): "),
|
|
1411
|
+
const answer = await new Promise((resolve4) => {
|
|
1412
|
+
rl.question(MODE_DEEPDIVE2(" Leave deep dive for main conversation? (y/N): "), resolve4);
|
|
1436
1413
|
});
|
|
1437
1414
|
if (answer.trim().toLowerCase() === "y") {
|
|
1438
1415
|
console.log("");
|
|
1439
1416
|
console.log(SUCCESS2(" \u2705 Surfacing from deep dive. Personalization Mode will activate in main conversation."));
|
|
1440
1417
|
console.log("");
|
|
1441
|
-
|
|
1418
|
+
resolve3();
|
|
1442
1419
|
return;
|
|
1443
1420
|
}
|
|
1444
1421
|
console.log(MUTED2(" Staying in deep dive."));
|
|
@@ -1651,7 +1628,7 @@ async function playWelcomeAnimation() {
|
|
|
1651
1628
|
await sleep3(800);
|
|
1652
1629
|
}
|
|
1653
1630
|
function sleep3(ms) {
|
|
1654
|
-
return new Promise((
|
|
1631
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
1655
1632
|
}
|
|
1656
1633
|
|
|
1657
1634
|
// src/core/provider-detect.ts
|
|
@@ -1944,7 +1921,7 @@ ${content}
|
|
|
1944
1921
|
}
|
|
1945
1922
|
rl.pause();
|
|
1946
1923
|
const confirmPromptText = ERROR4(` \u{1F5D1}\uFE0F Delete ${filePath}? (y/n): `);
|
|
1947
|
-
const confirm = await new Promise((
|
|
1924
|
+
const confirm = await new Promise((resolve3) => {
|
|
1948
1925
|
process.stdout.write(confirmPromptText);
|
|
1949
1926
|
process.stdin.resume();
|
|
1950
1927
|
process.stdin.setEncoding("utf-8");
|
|
@@ -1955,7 +1932,7 @@ ${content}
|
|
|
1955
1932
|
inputBuffer += chunk.slice(0, newlineIdx);
|
|
1956
1933
|
process.stdin.removeListener("data", onData);
|
|
1957
1934
|
process.stdin.pause();
|
|
1958
|
-
|
|
1935
|
+
resolve3(inputBuffer.replace(/\r/g, "").trim());
|
|
1959
1936
|
} else {
|
|
1960
1937
|
inputBuffer += chunk;
|
|
1961
1938
|
}
|
|
@@ -2564,8 +2541,8 @@ function registerByokCommand(program2) {
|
|
|
2564
2541
|
return;
|
|
2565
2542
|
}
|
|
2566
2543
|
const rl = readline4.createInterface({ input: process.stdin, output: process.stdout });
|
|
2567
|
-
const answer = await new Promise((
|
|
2568
|
-
rl.question(WARNING9(` Remove ${provider} key? (y/n): `),
|
|
2544
|
+
const answer = await new Promise((resolve3) => {
|
|
2545
|
+
rl.question(WARNING9(` Remove ${provider} key? (y/n): `), resolve3);
|
|
2569
2546
|
});
|
|
2570
2547
|
rl.close();
|
|
2571
2548
|
if (answer.toLowerCase() !== "y" && answer.toLowerCase() !== "yes") {
|
|
@@ -2745,8 +2722,8 @@ function registerConversationsCommand(program2) {
|
|
|
2745
2722
|
}
|
|
2746
2723
|
renderConversationList(conversations, config.conversationId, options.search, result, true);
|
|
2747
2724
|
const rl = readline5.createInterface({ input: process.stdin, output: process.stdout });
|
|
2748
|
-
const answer = await new Promise((
|
|
2749
|
-
rl.question(INFO11(" Select (1-" + conversations.length + ") or 0 to cancel: "),
|
|
2725
|
+
const answer = await new Promise((resolve3) => {
|
|
2726
|
+
rl.question(INFO11(" Select (1-" + conversations.length + ") or 0 to cancel: "), resolve3);
|
|
2750
2727
|
});
|
|
2751
2728
|
rl.close();
|
|
2752
2729
|
const selection = parseInt(answer.trim());
|
|
@@ -2983,7 +2960,7 @@ function truncate(text, max) {
|
|
|
2983
2960
|
return text.length > max ? text.slice(0, max - 3) + "..." : text;
|
|
2984
2961
|
}
|
|
2985
2962
|
function sleep4(ms) {
|
|
2986
|
-
return new Promise((
|
|
2963
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
2987
2964
|
}
|
|
2988
2965
|
|
|
2989
2966
|
// src/commands/fork.ts
|
|
@@ -3027,7 +3004,7 @@ function registerForkCommand(program2) {
|
|
|
3027
3004
|
try {
|
|
3028
3005
|
const result = await forkPromise;
|
|
3029
3006
|
animation.stop();
|
|
3030
|
-
await new Promise((
|
|
3007
|
+
await new Promise((resolve3) => setTimeout(resolve3, 200));
|
|
3031
3008
|
if (result?.conversationId) {
|
|
3032
3009
|
setConfigValue("conversationId", result.conversationId);
|
|
3033
3010
|
console.log("");
|
|
@@ -3061,7 +3038,7 @@ function registerForkCommand(program2) {
|
|
|
3061
3038
|
}
|
|
3062
3039
|
} catch (error) {
|
|
3063
3040
|
animation.stop();
|
|
3064
|
-
await new Promise((
|
|
3041
|
+
await new Promise((resolve3) => setTimeout(resolve3, 200));
|
|
3065
3042
|
console.log("");
|
|
3066
3043
|
console.log(ERROR10(` \u274C Fork failed: ${error.message}`));
|
|
3067
3044
|
console.log("");
|
|
@@ -3131,7 +3108,7 @@ function registerAnalyseCommand(program2) {
|
|
|
3131
3108
|
program2.command("analyse").description("Analyse the current project for bugs, features, improvements, and upgrades").option("--results", "Show analysis dashboard or filtered list").option("--bugs", "Show bugs list (interactive)").option("--features", "Show features list (interactive)").option("--improvements", "Show improvements list (interactive)").option("--upgrades", "Show upgrades list (interactive)").option("--sort <method>", "Sort by: priority (default) or file").option("--search <query>", "Filter results by keyword").option("--status", "Show current analysis job status").option("--auto", "Auto-fix mode: Bob triages and MiniBob implements").option("--confidence <number>", "Confidence gate for auto-fix (default: 90)", "90").option("--priority <level>", "Priority gate for auto-fix: critical, high, medium, low (default: critical)", "critical").action(async (options) => {
|
|
3132
3109
|
const config = getConfig();
|
|
3133
3110
|
if (options.auto) {
|
|
3134
|
-
const { runAutoFix } = await import("./analyse-auto-
|
|
3111
|
+
const { runAutoFix } = await import("./analyse-auto-GCWXNMNZ.js");
|
|
3135
3112
|
const category = options.bugs ? "bugs" : options.features ? "features" : options.improvements ? "improvements" : options.upgrades ? "upgrades" : void 0;
|
|
3136
3113
|
await runAutoFix({
|
|
3137
3114
|
category,
|
|
@@ -3141,7 +3118,7 @@ function registerAnalyseCommand(program2) {
|
|
|
3141
3118
|
return;
|
|
3142
3119
|
}
|
|
3143
3120
|
if (options.bugs || options.features || options.improvements || options.upgrades) {
|
|
3144
|
-
const { showInteractiveResults } = await import("./analyse-results-
|
|
3121
|
+
const { showInteractiveResults } = await import("./analyse-results-TIVOJ5SC.js");
|
|
3145
3122
|
const category = options.bugs ? "bugs" : options.features ? "features" : options.improvements ? "improvements" : "upgrades";
|
|
3146
3123
|
await showInteractiveResults(config, category, options.sort, options.search);
|
|
3147
3124
|
return;
|
|
@@ -3596,8 +3573,8 @@ async function runTier3Autonomy(config) {
|
|
|
3596
3573
|
console.log(GREEN(" \u2705 All tasks complete!"));
|
|
3597
3574
|
console.log(AMBER2(" \u{1F4E4} MiniBob wants to push to GitHub."));
|
|
3598
3575
|
const rl = readline6.createInterface({ input: process.stdin, output: process.stdout });
|
|
3599
|
-
const answer = await new Promise((
|
|
3600
|
-
rl.question(CYAN(" Approve push? (y/n): "),
|
|
3576
|
+
const answer = await new Promise((resolve3) => {
|
|
3577
|
+
rl.question(CYAN(" Approve push? (y/n): "), resolve3);
|
|
3601
3578
|
});
|
|
3602
3579
|
rl.close();
|
|
3603
3580
|
if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
|
|
@@ -3627,7 +3604,7 @@ async function runTier3Autonomy(config) {
|
|
|
3627
3604
|
} catch (pollError) {
|
|
3628
3605
|
}
|
|
3629
3606
|
if (running) {
|
|
3630
|
-
await new Promise((
|
|
3607
|
+
await new Promise((resolve3) => setTimeout(resolve3, 2500));
|
|
3631
3608
|
}
|
|
3632
3609
|
}
|
|
3633
3610
|
console.log("");
|
|
@@ -3945,48 +3922,99 @@ function renderLocalTodoList(queue) {
|
|
|
3945
3922
|
|
|
3946
3923
|
// src/commands/serve.ts
|
|
3947
3924
|
import chalk19 from "chalk";
|
|
3925
|
+
import * as fs10 from "fs";
|
|
3948
3926
|
import * as os3 from "os";
|
|
3949
3927
|
import * as path11 from "path";
|
|
3928
|
+
import * as crypto2 from "crypto";
|
|
3929
|
+
import axios from "axios";
|
|
3950
3930
|
var GREEN2 = chalk19.hex("#66BB6A");
|
|
3951
3931
|
var AMBER3 = chalk19.hex("#FFAB00");
|
|
3952
|
-
var BLUE2 = chalk19.hex("#42A5F5");
|
|
3953
3932
|
var RED2 = chalk19.hex("#EF5350");
|
|
3954
3933
|
var GRAY2 = chalk19.gray;
|
|
3955
3934
|
var CYAN2 = chalk19.cyan;
|
|
3956
3935
|
var BORDER5 = chalk19.hex("#455A64");
|
|
3936
|
+
var BOB_DIR3 = path11.join(os3.homedir(), ".bob");
|
|
3937
|
+
var ALGORITHM = "aes-256-cbc";
|
|
3957
3938
|
var TIER_CONFIGS = {
|
|
3958
3939
|
"Power": {
|
|
3959
3940
|
activeInterval: 2e3,
|
|
3960
|
-
// 2 seconds
|
|
3961
3941
|
sleepInterval: 12e4,
|
|
3962
|
-
// 120 seconds (2 minutes) when sleeping
|
|
3963
3942
|
idleThreshold: 5 * 6e4,
|
|
3964
|
-
// 5 min → enter sleep
|
|
3965
3943
|
extendedIdleTimeout: null
|
|
3966
|
-
// Never auto-exit
|
|
3967
3944
|
},
|
|
3968
3945
|
"Pro": {
|
|
3969
3946
|
activeInterval: 1e4,
|
|
3970
|
-
// 10 seconds
|
|
3971
3947
|
sleepInterval: 3e4,
|
|
3972
|
-
// 30 seconds when sleeping
|
|
3973
3948
|
idleThreshold: 5 * 6e4,
|
|
3974
|
-
// 5 min → enter sleep
|
|
3975
3949
|
extendedIdleTimeout: 60 * 6e4
|
|
3976
|
-
// 1 hour → auto-exit
|
|
3977
3950
|
},
|
|
3978
3951
|
"Starter": {
|
|
3979
3952
|
activeInterval: 15e3,
|
|
3980
|
-
// 15 seconds
|
|
3981
3953
|
sleepInterval: null,
|
|
3982
|
-
// No sleep mode — goes straight to auto-exit
|
|
3983
3954
|
idleThreshold: null,
|
|
3984
|
-
// No idle detection
|
|
3985
3955
|
extendedIdleTimeout: 15 * 6e4
|
|
3986
|
-
// 15 minutes → auto-exit
|
|
3987
3956
|
}
|
|
3988
3957
|
};
|
|
3989
3958
|
var BLOCKED_TIERS = ["Explore", "Free", "free", "explore"];
|
|
3959
|
+
function deriveKey(uid, salt) {
|
|
3960
|
+
return crypto2.pbkdf2Sync(uid, salt, 1e5, 32, "sha256");
|
|
3961
|
+
}
|
|
3962
|
+
function encrypt(inputPath, outputPath, uid) {
|
|
3963
|
+
const salt = crypto2.randomBytes(16).toString("hex");
|
|
3964
|
+
const key = deriveKey(uid, salt);
|
|
3965
|
+
const iv = crypto2.randomBytes(16);
|
|
3966
|
+
const cipher = crypto2.createCipheriv(ALGORITHM, key, iv);
|
|
3967
|
+
const input = fs10.readFileSync(inputPath);
|
|
3968
|
+
const encrypted = Buffer.concat([cipher.update(input), cipher.final()]);
|
|
3969
|
+
const header = Buffer.from(salt + iv.toString("hex"), "utf-8");
|
|
3970
|
+
fs10.writeFileSync(outputPath, Buffer.concat([header, encrypted]));
|
|
3971
|
+
}
|
|
3972
|
+
function decrypt(inputPath, outputPath, uid) {
|
|
3973
|
+
const data = fs10.readFileSync(inputPath);
|
|
3974
|
+
const header = data.slice(0, 64).toString("utf-8");
|
|
3975
|
+
const salt = header.slice(0, 32);
|
|
3976
|
+
const iv = Buffer.from(header.slice(32, 64), "hex");
|
|
3977
|
+
const encrypted = data.slice(64);
|
|
3978
|
+
const key = deriveKey(uid, salt);
|
|
3979
|
+
const decipher = crypto2.createDecipheriv(ALGORITHM, key, iv);
|
|
3980
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
3981
|
+
fs10.writeFileSync(outputPath, decrypted);
|
|
3982
|
+
}
|
|
3983
|
+
function getTempDir() {
|
|
3984
|
+
const tmpDir = path11.join(os3.tmpdir(), `bob-remote-${Date.now()}`);
|
|
3985
|
+
fs10.mkdirSync(tmpDir, { recursive: true });
|
|
3986
|
+
return tmpDir;
|
|
3987
|
+
}
|
|
3988
|
+
function cleanupTemp(tmpDir) {
|
|
3989
|
+
try {
|
|
3990
|
+
fs10.rmSync(tmpDir, { recursive: true, force: true });
|
|
3991
|
+
} catch {
|
|
3992
|
+
}
|
|
3993
|
+
}
|
|
3994
|
+
var IGNORE_DIRS2 = ["node_modules", ".git", "dist", "build", ".dart_tool", ".idea", ".gradle", ".pub-cache", ".bob"];
|
|
3995
|
+
var CODE_EXTENSIONS2 = /* @__PURE__ */ new Set([".dart", ".js", ".ts", ".html", ".css", ".json", ".yaml", ".yml", ".xml", ".sh", ".md"]);
|
|
3996
|
+
function scanProjectFiles2(rootDir, currentDir, depth = 0) {
|
|
3997
|
+
if (depth > 6) return [];
|
|
3998
|
+
const dir = currentDir || rootDir;
|
|
3999
|
+
const files = [];
|
|
4000
|
+
try {
|
|
4001
|
+
const entries = fs10.readdirSync(dir, { withFileTypes: true });
|
|
4002
|
+
for (const entry of entries) {
|
|
4003
|
+
if (IGNORE_DIRS2.includes(entry.name)) continue;
|
|
4004
|
+
if (entry.name.startsWith(".")) continue;
|
|
4005
|
+
const fullPath = path11.join(dir, entry.name);
|
|
4006
|
+
const relativePath = path11.relative(rootDir, fullPath).replace(/\\/g, "/");
|
|
4007
|
+
if (entry.isDirectory()) {
|
|
4008
|
+
files.push(...scanProjectFiles2(rootDir, fullPath, depth + 1));
|
|
4009
|
+
} else {
|
|
4010
|
+
const ext = path11.extname(entry.name).toLowerCase();
|
|
4011
|
+
if (CODE_EXTENSIONS2.has(ext)) files.push(relativePath);
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
} catch {
|
|
4015
|
+
}
|
|
4016
|
+
return files;
|
|
4017
|
+
}
|
|
3990
4018
|
function registerServeCommand(program2) {
|
|
3991
4019
|
program2.command("serve").description("Start an Active Bob \u2014 receive and execute commands from the web app or another device").action(async () => {
|
|
3992
4020
|
const config = getConfig();
|
|
@@ -4067,14 +4095,15 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
|
|
|
4067
4095
|
if (tierConfig.sleepInterval) {
|
|
4068
4096
|
console.log(BORDER5(" \u2551") + GRAY2(` Sleep: every ${tierConfig.sleepInterval / 1e3}s after ${tierConfig.idleThreshold / 6e4} min idle`));
|
|
4069
4097
|
} else {
|
|
4070
|
-
console.log(BORDER5(" \u2551") + GRAY2(
|
|
4098
|
+
console.log(BORDER5(" \u2551") + GRAY2(" Sleep: disabled"));
|
|
4071
4099
|
}
|
|
4072
4100
|
if (tierConfig.extendedIdleTimeout) {
|
|
4073
4101
|
console.log(BORDER5(" \u2551") + GRAY2(` Auto-exit: after ${tierConfig.extendedIdleTimeout / 6e4} min idle`));
|
|
4074
4102
|
} else {
|
|
4075
|
-
console.log(BORDER5(" \u2551") + GREEN2(
|
|
4103
|
+
console.log(BORDER5(" \u2551") + GREEN2(" Auto-exit: never (Power tier)"));
|
|
4076
4104
|
}
|
|
4077
4105
|
console.log(BORDER5(" \u2551"));
|
|
4106
|
+
console.log(BORDER5(" \u2551") + GRAY2(" Capabilities: chat, consult, push, index, analyse, backup, restore"));
|
|
4078
4107
|
console.log(BORDER5(" \u2551") + GRAY2(" Press Ctrl+C to stop."));
|
|
4079
4108
|
console.log(BORDER5(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
4080
4109
|
console.log("");
|
|
@@ -4094,7 +4123,9 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
|
|
|
4094
4123
|
return;
|
|
4095
4124
|
}
|
|
4096
4125
|
let running = true;
|
|
4126
|
+
let sigintHandlerAdded = false;
|
|
4097
4127
|
const cleanup = async () => {
|
|
4128
|
+
if (!running) return;
|
|
4098
4129
|
running = false;
|
|
4099
4130
|
console.log("");
|
|
4100
4131
|
console.log(GRAY2(" \u{1F50C} Shutting down Active Bob..."));
|
|
@@ -4110,8 +4141,11 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
|
|
|
4110
4141
|
console.log("");
|
|
4111
4142
|
process.exit(0);
|
|
4112
4143
|
};
|
|
4113
|
-
|
|
4114
|
-
|
|
4144
|
+
if (!sigintHandlerAdded) {
|
|
4145
|
+
process.once("SIGINT", cleanup);
|
|
4146
|
+
process.once("SIGTERM", cleanup);
|
|
4147
|
+
sigintHandlerAdded = true;
|
|
4148
|
+
}
|
|
4115
4149
|
let lastCommandTime = Date.now();
|
|
4116
4150
|
let isSleeping = false;
|
|
4117
4151
|
while (running) {
|
|
@@ -4154,7 +4188,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
|
|
|
4154
4188
|
}
|
|
4155
4189
|
lastCommandTime = Date.now();
|
|
4156
4190
|
const timestamp = (/* @__PURE__ */ new Date()).toLocaleTimeString();
|
|
4157
|
-
console.log(AMBER3(` [${timestamp}] \u23F3 Received: ${type}
|
|
4191
|
+
console.log(AMBER3(` [${timestamp}] \u23F3 Received: ${type}${payload.message ? ` "${payload.message.slice(0, 40)}${payload.message.length > 40 ? "..." : ""}"` : ""}`));
|
|
4158
4192
|
const commandResult = await executeRemoteCommand(type, payload, config);
|
|
4159
4193
|
await callCloudFunction("completeRemoteCommand", {
|
|
4160
4194
|
conversationId: config.conversationId,
|
|
@@ -4177,7 +4211,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
|
|
|
4177
4211
|
}
|
|
4178
4212
|
}
|
|
4179
4213
|
if (running) {
|
|
4180
|
-
await new Promise((
|
|
4214
|
+
await new Promise((resolve3) => setTimeout(resolve3, currentInterval));
|
|
4181
4215
|
}
|
|
4182
4216
|
}
|
|
4183
4217
|
}
|
|
@@ -4190,9 +4224,13 @@ async function executeRemoteCommand(type, payload, config) {
|
|
|
4190
4224
|
case "push":
|
|
4191
4225
|
return await executePush(payload);
|
|
4192
4226
|
case "index":
|
|
4193
|
-
return
|
|
4227
|
+
return await executeIndex(payload, config);
|
|
4194
4228
|
case "analyse":
|
|
4195
|
-
return
|
|
4229
|
+
return await executeAnalyse(payload, config);
|
|
4230
|
+
case "backup":
|
|
4231
|
+
return await executeBackup(payload, config);
|
|
4232
|
+
case "restore":
|
|
4233
|
+
return await executeRestore(payload, config);
|
|
4196
4234
|
case "autonomy":
|
|
4197
4235
|
return { success: true, message: "Autonomy command received. Feature in progress." };
|
|
4198
4236
|
default:
|
|
@@ -4224,9 +4262,7 @@ ${fullContext}` : "") },
|
|
|
4224
4262
|
];
|
|
4225
4263
|
const response = await callLocalModel(config.localEndpoint, messages);
|
|
4226
4264
|
const proposed = extractProposedFile(response);
|
|
4227
|
-
if (proposed)
|
|
4228
|
-
await proposeAndWriteFile(proposed, true);
|
|
4229
|
-
}
|
|
4265
|
+
if (proposed) await proposeAndWriteFile(proposed, true);
|
|
4230
4266
|
return {
|
|
4231
4267
|
success: true,
|
|
4232
4268
|
text: response,
|
|
@@ -4261,11 +4297,7 @@ ${fullContext}` : "") },
|
|
|
4261
4297
|
{ role: "user", content: message }
|
|
4262
4298
|
];
|
|
4263
4299
|
const response = await callLocalModel(config.localEndpoint, messages);
|
|
4264
|
-
return {
|
|
4265
|
-
success: true,
|
|
4266
|
-
text: response,
|
|
4267
|
-
referencedFiles: selectedFiles
|
|
4268
|
-
};
|
|
4300
|
+
return { success: true, text: response, referencedFiles: selectedFiles };
|
|
4269
4301
|
} catch (error) {
|
|
4270
4302
|
return { success: false, error: error.message };
|
|
4271
4303
|
}
|
|
@@ -4279,7 +4311,9 @@ async function executePush(payload) {
|
|
|
4279
4311
|
const isRepo = await git.checkIsRepo();
|
|
4280
4312
|
if (!isRepo) return { success: false, error: "Not a git repository." };
|
|
4281
4313
|
const status = await git.status();
|
|
4282
|
-
if (status.files.length === 0)
|
|
4314
|
+
if (status.files.length === 0) {
|
|
4315
|
+
return { success: true, message: "Nothing to commit. Working tree clean." };
|
|
4316
|
+
}
|
|
4283
4317
|
await git.add(".");
|
|
4284
4318
|
const commitResult = await git.commit(message);
|
|
4285
4319
|
const branch = (await git.branchLocal()).current;
|
|
@@ -4302,6 +4336,336 @@ async function executePush(payload) {
|
|
|
4302
4336
|
return { success: false, error: error.message };
|
|
4303
4337
|
}
|
|
4304
4338
|
}
|
|
4339
|
+
async function executeIndex(payload, config) {
|
|
4340
|
+
if (!config.localEndpoint) {
|
|
4341
|
+
return { success: false, error: "No local endpoint configured. Index requires a local model." };
|
|
4342
|
+
}
|
|
4343
|
+
const cwd = process.cwd();
|
|
4344
|
+
const projectName = getProjectName(cwd);
|
|
4345
|
+
try {
|
|
4346
|
+
const files = scanProjectFiles2(cwd);
|
|
4347
|
+
if (files.length === 0) {
|
|
4348
|
+
return { success: false, error: "No code files found to index." };
|
|
4349
|
+
}
|
|
4350
|
+
const { runId, runDir, tasksDir } = createAnalysisRun(cwd, files);
|
|
4351
|
+
const summaries = {};
|
|
4352
|
+
let completed = 0;
|
|
4353
|
+
for (const filePath of files) {
|
|
4354
|
+
const absolutePath = path11.join(cwd, filePath);
|
|
4355
|
+
let content;
|
|
4356
|
+
try {
|
|
4357
|
+
content = fs10.readFileSync(absolutePath, "utf-8");
|
|
4358
|
+
} catch {
|
|
4359
|
+
completed++;
|
|
4360
|
+
continue;
|
|
4361
|
+
}
|
|
4362
|
+
if (content.length > 5e4) {
|
|
4363
|
+
summaries[filePath] = `Large file (${Math.round(content.length / 1e3)}KB). Skipped detailed analysis.`;
|
|
4364
|
+
completeTask(tasksDir, filePath, summaries[filePath]);
|
|
4365
|
+
completed++;
|
|
4366
|
+
updateManifestProgress(runDir, completed);
|
|
4367
|
+
continue;
|
|
4368
|
+
}
|
|
4369
|
+
try {
|
|
4370
|
+
const messages = [
|
|
4371
|
+
{
|
|
4372
|
+
role: "system",
|
|
4373
|
+
content: "You are a code analyst. Respond with ONLY a 2-3 sentence summary. No formatting, no headers, no bullets. Just plain sentences."
|
|
4374
|
+
},
|
|
4375
|
+
{
|
|
4376
|
+
role: "user",
|
|
4377
|
+
content: `Summarize this file. What does it do, what does it export, and what does it depend on?
|
|
4378
|
+
|
|
4379
|
+
File: ${filePath}
|
|
4380
|
+
|
|
4381
|
+
${content}`
|
|
4382
|
+
}
|
|
4383
|
+
];
|
|
4384
|
+
const summary = await callLocalModel(config.localEndpoint, messages);
|
|
4385
|
+
summaries[filePath] = summary.trim();
|
|
4386
|
+
completeTask(tasksDir, filePath, summary.trim());
|
|
4387
|
+
} catch {
|
|
4388
|
+
summaries[filePath] = "Could not summarize.";
|
|
4389
|
+
}
|
|
4390
|
+
completed++;
|
|
4391
|
+
updateManifestProgress(runDir, completed);
|
|
4392
|
+
}
|
|
4393
|
+
let dependencies = {};
|
|
4394
|
+
try {
|
|
4395
|
+
const summaryContext = Object.entries(summaries).map(([fp, summary]) => `[${fp}]: ${summary}`).join("\n\n");
|
|
4396
|
+
const messages = [
|
|
4397
|
+
{
|
|
4398
|
+
role: "system",
|
|
4399
|
+
content: "You are a senior software architect. Respond with ONLY a valid JSON object. No explanation, no markdown."
|
|
4400
|
+
},
|
|
4401
|
+
{
|
|
4402
|
+
role: "user",
|
|
4403
|
+
content: `Based on these file summaries, generate a JSON dependency map. Each key is a file path, each value is an array of file paths it depends on.
|
|
4404
|
+
|
|
4405
|
+
FILE SUMMARIES:
|
|
4406
|
+
${summaryContext}
|
|
4407
|
+
|
|
4408
|
+
Respond with ONLY the JSON object:`
|
|
4409
|
+
}
|
|
4410
|
+
];
|
|
4411
|
+
const depResponse = await callLocalModel(config.localEndpoint, messages);
|
|
4412
|
+
const jsonMatch = depResponse.match(/\{[\s\S]*\}/);
|
|
4413
|
+
if (jsonMatch) {
|
|
4414
|
+
dependencies = JSON.parse(jsonMatch[0]);
|
|
4415
|
+
}
|
|
4416
|
+
} catch {
|
|
4417
|
+
dependencies = {};
|
|
4418
|
+
}
|
|
4419
|
+
saveSummaries(cwd, summaries);
|
|
4420
|
+
saveDependencies(cwd, dependencies);
|
|
4421
|
+
updateManifestProgress(runDir, completed, "completed");
|
|
4422
|
+
return {
|
|
4423
|
+
success: true,
|
|
4424
|
+
message: `Indexed ${Object.keys(summaries).length} files in ${projectName}. Dependency map generated.`,
|
|
4425
|
+
fileCount: Object.keys(summaries).length,
|
|
4426
|
+
projectName
|
|
4427
|
+
};
|
|
4428
|
+
} catch (error) {
|
|
4429
|
+
return { success: false, error: error.message };
|
|
4430
|
+
}
|
|
4431
|
+
}
|
|
4432
|
+
async function executeAnalyse(payload, config) {
|
|
4433
|
+
if (!config.localEndpoint) {
|
|
4434
|
+
return { success: false, error: "No local endpoint configured. Analysis requires a local model." };
|
|
4435
|
+
}
|
|
4436
|
+
const cwd = process.cwd();
|
|
4437
|
+
const projectName = getProjectName(cwd);
|
|
4438
|
+
try {
|
|
4439
|
+
const summaries = loadSummaries(cwd);
|
|
4440
|
+
if (!summaries || Object.keys(summaries).length === 0) {
|
|
4441
|
+
return {
|
|
4442
|
+
success: false,
|
|
4443
|
+
error: `Project not indexed. Run \`bob remote index\` first, or \`bob index\` locally.`
|
|
4444
|
+
};
|
|
4445
|
+
}
|
|
4446
|
+
const dependencies = loadDependencies(cwd) || {};
|
|
4447
|
+
const files = Object.keys(summaries);
|
|
4448
|
+
const { analysisDir } = ensureProjectStructure(cwd);
|
|
4449
|
+
const resultsDir = path11.join(analysisDir, "results");
|
|
4450
|
+
if (!fs10.existsSync(resultsDir)) fs10.mkdirSync(resultsDir, { recursive: true });
|
|
4451
|
+
const allResults = {};
|
|
4452
|
+
let totalBugs = 0;
|
|
4453
|
+
let totalFeatures = 0;
|
|
4454
|
+
let totalImprovements = 0;
|
|
4455
|
+
let totalUpgrades = 0;
|
|
4456
|
+
for (const filePath of files) {
|
|
4457
|
+
const absolutePath = path11.join(cwd, filePath);
|
|
4458
|
+
let content;
|
|
4459
|
+
try {
|
|
4460
|
+
content = fs10.readFileSync(absolutePath, "utf-8");
|
|
4461
|
+
} catch {
|
|
4462
|
+
continue;
|
|
4463
|
+
}
|
|
4464
|
+
if (content.length > 3e4) continue;
|
|
4465
|
+
const fileDeps = dependencies[filePath] || [];
|
|
4466
|
+
let depContext = "";
|
|
4467
|
+
if (fileDeps.length > 0) {
|
|
4468
|
+
depContext = `
|
|
4469
|
+
RELATED FILES:
|
|
4470
|
+
${fileDeps.map((d) => `- ${d}: ${summaries[d] || "unknown"}`).join("\n")}
|
|
4471
|
+
`;
|
|
4472
|
+
}
|
|
4473
|
+
const analysisPrompt = `You are the Lead QA Engineer on this project. Perform a thorough code review.
|
|
4474
|
+
|
|
4475
|
+
For each issue provide: title, description (why it's a problem), priority (critical/high/medium/low), implementation (exact fix steps).
|
|
4476
|
+
|
|
4477
|
+
Respond with ONLY a JSON object:
|
|
4478
|
+
{
|
|
4479
|
+
"bugs": [{"title": "...", "description": "...", "priority": "...", "implementation": "..."}],
|
|
4480
|
+
"features": [{"title": "...", "description": "...", "priority": "...", "implementation": "..."}],
|
|
4481
|
+
"improvements": [{"title": "...", "description": "...", "priority": "...", "implementation": "..."}],
|
|
4482
|
+
"upgrades": [{"title": "...", "description": "...", "priority": "...", "implementation": "..."}]
|
|
4483
|
+
}
|
|
4484
|
+
${depContext}
|
|
4485
|
+
FILE: ${filePath}
|
|
4486
|
+
${content}`;
|
|
4487
|
+
try {
|
|
4488
|
+
const messages = [
|
|
4489
|
+
{ role: "system", content: "You are the Lead QA Engineer. Respond with ONLY valid JSON. Quality over quantity." },
|
|
4490
|
+
{ role: "user", content: analysisPrompt }
|
|
4491
|
+
];
|
|
4492
|
+
const responseText = await callLocalModel(config.localEndpoint, messages);
|
|
4493
|
+
const jsonMatch = responseText.match(/\{[\s\S]*\}/);
|
|
4494
|
+
if (jsonMatch) {
|
|
4495
|
+
const parsed = JSON.parse(jsonMatch[0]);
|
|
4496
|
+
for (const cat of ["bugs", "features", "improvements", "upgrades"]) {
|
|
4497
|
+
if (parsed[cat]) {
|
|
4498
|
+
parsed[cat] = parsed[cat].map((item) => ({ ...item, filePath }));
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
allResults[filePath] = parsed;
|
|
4502
|
+
totalBugs += parsed.bugs?.length || 0;
|
|
4503
|
+
totalFeatures += parsed.features?.length || 0;
|
|
4504
|
+
totalImprovements += parsed.improvements?.length || 0;
|
|
4505
|
+
totalUpgrades += parsed.upgrades?.length || 0;
|
|
4506
|
+
}
|
|
4507
|
+
} catch {
|
|
4508
|
+
continue;
|
|
4509
|
+
}
|
|
4510
|
+
}
|
|
4511
|
+
fs10.writeFileSync(
|
|
4512
|
+
path11.join(resultsDir, "analysis.json"),
|
|
4513
|
+
JSON.stringify(allResults, null, 2)
|
|
4514
|
+
);
|
|
4515
|
+
fs10.writeFileSync(
|
|
4516
|
+
path11.join(resultsDir, "counts.json"),
|
|
4517
|
+
JSON.stringify({ bugs: totalBugs, features: totalFeatures, improvements: totalImprovements, upgrades: totalUpgrades }, null, 2)
|
|
4518
|
+
);
|
|
4519
|
+
return {
|
|
4520
|
+
success: true,
|
|
4521
|
+
message: `Analysis complete for ${projectName}. Found: ${totalBugs} bugs, ${totalFeatures} features, ${totalImprovements} improvements, ${totalUpgrades} upgrades.`,
|
|
4522
|
+
counts: { bugs: totalBugs, features: totalFeatures, improvements: totalImprovements, upgrades: totalUpgrades },
|
|
4523
|
+
projectName
|
|
4524
|
+
};
|
|
4525
|
+
} catch (error) {
|
|
4526
|
+
return { success: false, error: error.message };
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
async function executeBackup(payload, config) {
|
|
4530
|
+
const { isSource = false, isGlobal = false, archiveName } = payload;
|
|
4531
|
+
if (!config.uid) {
|
|
4532
|
+
return { success: false, error: "User UID not available. Re-login required." };
|
|
4533
|
+
}
|
|
4534
|
+
const cwd = process.cwd();
|
|
4535
|
+
const projectName = path11.basename(cwd);
|
|
4536
|
+
let sourceDir;
|
|
4537
|
+
let displayLabel;
|
|
4538
|
+
if (isGlobal) {
|
|
4539
|
+
sourceDir = BOB_DIR3;
|
|
4540
|
+
displayLabel = "global (~/.bob/)";
|
|
4541
|
+
} else if (isSource) {
|
|
4542
|
+
sourceDir = cwd;
|
|
4543
|
+
displayLabel = `source: ${projectName}`;
|
|
4544
|
+
} else {
|
|
4545
|
+
sourceDir = path11.join(BOB_DIR3, "projects", projectName);
|
|
4546
|
+
displayLabel = `context: ${projectName}`;
|
|
4547
|
+
}
|
|
4548
|
+
if (!fs10.existsSync(sourceDir)) {
|
|
4549
|
+
return { success: false, error: `Source directory not found: ${sourceDir}` };
|
|
4550
|
+
}
|
|
4551
|
+
const tmpDir = getTempDir();
|
|
4552
|
+
const archivePath = path11.join(tmpDir, "bob-backup.tar.gz");
|
|
4553
|
+
const encryptedPath = path11.join(tmpDir, "bob-backup.bob.enc");
|
|
4554
|
+
try {
|
|
4555
|
+
const tar = await import("tar");
|
|
4556
|
+
const relativeSource = path11.relative(os3.homedir(), sourceDir);
|
|
4557
|
+
await tar.create(
|
|
4558
|
+
{ gzip: true, file: archivePath, cwd: os3.homedir() },
|
|
4559
|
+
[relativeSource]
|
|
4560
|
+
);
|
|
4561
|
+
const archiveStats = fs10.statSync(archivePath);
|
|
4562
|
+
const estimatedSizeGB = archiveStats.size / (1024 * 1024 * 1024);
|
|
4563
|
+
const sizeLabel = archiveStats.size < 1024 * 1024 ? `${(archiveStats.size / 1024).toFixed(1)} KB` : `${(archiveStats.size / (1024 * 1024)).toFixed(1)} MB`;
|
|
4564
|
+
encrypt(archivePath, encryptedPath, config.uid);
|
|
4565
|
+
let uploadResult;
|
|
4566
|
+
const action = archiveName ? isSource ? "requestSourceArchiveUpload" : "requestArchiveUpload" : isSource ? "requestSourceUpload" : "requestUpload";
|
|
4567
|
+
uploadResult = await callCloudFunction("cliBackupLicense", {
|
|
4568
|
+
action,
|
|
4569
|
+
projectName,
|
|
4570
|
+
isGlobal,
|
|
4571
|
+
isSource,
|
|
4572
|
+
archiveName: archiveName || null,
|
|
4573
|
+
estimatedSizeGB
|
|
4574
|
+
});
|
|
4575
|
+
const encryptedData = fs10.readFileSync(encryptedPath);
|
|
4576
|
+
await axios.put(uploadResult.uploadUrl, encryptedData, {
|
|
4577
|
+
headers: {
|
|
4578
|
+
"Content-Type": "application/octet-stream",
|
|
4579
|
+
"Content-Length": encryptedData.length
|
|
4580
|
+
},
|
|
4581
|
+
maxBodyLength: Infinity,
|
|
4582
|
+
maxContentLength: Infinity
|
|
4583
|
+
});
|
|
4584
|
+
const recordAction = archiveName ? isSource ? "recordSourceArchiveUsage" : "recordArchiveUsage" : isSource ? "recordSourceUsage" : "recordUsage";
|
|
4585
|
+
try {
|
|
4586
|
+
await callCloudFunction("cliBackupLicense", {
|
|
4587
|
+
action: recordAction,
|
|
4588
|
+
projectName,
|
|
4589
|
+
isGlobal,
|
|
4590
|
+
isSource,
|
|
4591
|
+
archiveId: uploadResult.archiveId || null
|
|
4592
|
+
});
|
|
4593
|
+
} catch {
|
|
4594
|
+
}
|
|
4595
|
+
const label = archiveName ? `archive "${archiveName}"` : "backup";
|
|
4596
|
+
return {
|
|
4597
|
+
success: true,
|
|
4598
|
+
message: `Remote ${label} complete for ${displayLabel}. Size: ${sizeLabel}.`,
|
|
4599
|
+
sizeBytes: archiveStats.size,
|
|
4600
|
+
projectName,
|
|
4601
|
+
isSource,
|
|
4602
|
+
isGlobal
|
|
4603
|
+
};
|
|
4604
|
+
} catch (error) {
|
|
4605
|
+
return { success: false, error: `Backup failed: ${error.message}` };
|
|
4606
|
+
} finally {
|
|
4607
|
+
cleanupTemp(tmpDir);
|
|
4608
|
+
}
|
|
4609
|
+
}
|
|
4610
|
+
async function executeRestore(payload, config) {
|
|
4611
|
+
const { isSource = false, isGlobal = false } = payload;
|
|
4612
|
+
if (!config.uid) {
|
|
4613
|
+
return { success: false, error: "User UID not available. Re-login required." };
|
|
4614
|
+
}
|
|
4615
|
+
const cwd = process.cwd();
|
|
4616
|
+
const projectName = path11.basename(cwd);
|
|
4617
|
+
const tmpDir = getTempDir();
|
|
4618
|
+
const downloadPath = path11.join(tmpDir, "bob-backup.bob.enc");
|
|
4619
|
+
const decryptedPath = path11.join(tmpDir, "bob-backup.tar.gz");
|
|
4620
|
+
try {
|
|
4621
|
+
const downloadResult = await callCloudFunction("cliBackupLicense", {
|
|
4622
|
+
action: isSource ? "requestSourceDownload" : "requestDownload",
|
|
4623
|
+
projectName,
|
|
4624
|
+
isGlobal,
|
|
4625
|
+
isSource,
|
|
4626
|
+
s3VersionId: null
|
|
4627
|
+
// always latest in headless mode
|
|
4628
|
+
});
|
|
4629
|
+
const response = await axios.get(downloadResult.downloadUrl, {
|
|
4630
|
+
responseType: "arraybuffer",
|
|
4631
|
+
maxContentLength: Infinity
|
|
4632
|
+
});
|
|
4633
|
+
fs10.writeFileSync(downloadPath, Buffer.from(response.data));
|
|
4634
|
+
decrypt(downloadPath, decryptedPath, config.uid);
|
|
4635
|
+
let restoreTarget;
|
|
4636
|
+
if (isGlobal) {
|
|
4637
|
+
restoreTarget = BOB_DIR3;
|
|
4638
|
+
} else if (isSource) {
|
|
4639
|
+
restoreTarget = cwd;
|
|
4640
|
+
} else {
|
|
4641
|
+
restoreTarget = path11.join(BOB_DIR3, "projects", projectName);
|
|
4642
|
+
}
|
|
4643
|
+
const preRestoreBackup = `${restoreTarget}-pre-restore-${Date.now()}`;
|
|
4644
|
+
if (fs10.existsSync(restoreTarget)) {
|
|
4645
|
+
fs10.cpSync(restoreTarget, preRestoreBackup, { recursive: true });
|
|
4646
|
+
}
|
|
4647
|
+
const tar = await import("tar");
|
|
4648
|
+
if (isSource) {
|
|
4649
|
+
const parentDir = path11.dirname(cwd);
|
|
4650
|
+
await tar.extract({ file: decryptedPath, cwd: parentDir });
|
|
4651
|
+
} else {
|
|
4652
|
+
await tar.extract({ file: decryptedPath, cwd: os3.homedir() });
|
|
4653
|
+
}
|
|
4654
|
+
const scopeLabel = isGlobal ? "global (~/.bob/)" : isSource ? `source: ${projectName}` : `context: ${projectName}`;
|
|
4655
|
+
return {
|
|
4656
|
+
success: true,
|
|
4657
|
+
message: `Remote restore complete for ${scopeLabel}. Pre-restore backup saved locally.`,
|
|
4658
|
+
projectName,
|
|
4659
|
+
isSource,
|
|
4660
|
+
isGlobal,
|
|
4661
|
+
preRestoreBackup: path11.basename(preRestoreBackup)
|
|
4662
|
+
};
|
|
4663
|
+
} catch (error) {
|
|
4664
|
+
return { success: false, error: `Restore failed: ${error.message}` };
|
|
4665
|
+
} finally {
|
|
4666
|
+
cleanupTemp(tmpDir);
|
|
4667
|
+
}
|
|
4668
|
+
}
|
|
4305
4669
|
|
|
4306
4670
|
// src/commands/remote.ts
|
|
4307
4671
|
import chalk20 from "chalk";
|
|
@@ -4316,7 +4680,7 @@ var ERROR12 = chalk20.hex("#EF5350");
|
|
|
4316
4680
|
var MUTED14 = chalk20.hex("#78909C");
|
|
4317
4681
|
var BORDER6 = chalk20.hex("#455A64");
|
|
4318
4682
|
function registerRemoteCommand(program2) {
|
|
4319
|
-
program2.command("remote [type] [message]").description("Send commands to an Active Bob on a remote machine").option("--new", "Discover and connect to a different Active Bob").option("--auto", "For analyse: run auto-fix mode").option("-i, --interactive", "Enter interactive remote session").option("--session <id>", "Target a specific Active Bob session").action(async (type, message, options) => {
|
|
4683
|
+
program2.command("remote [type] [message]").description("Send commands to an Active Bob on a remote machine").option("--new", "Discover and connect to a different Active Bob").option("--auto", "For analyse: run auto-fix mode").option("-i, --interactive", "Enter interactive remote session").option("--session <id>", "Target a specific Active Bob session").option("--source", "For backup/restore: use source code mode").option("--global", "For backup/restore: use global mode (Grid only)").option("--archive <name>", "For backup: save as named archive").action(async (type, message, options) => {
|
|
4320
4684
|
const config = getConfig();
|
|
4321
4685
|
if (!config.loggedIn || !config.authToken) {
|
|
4322
4686
|
console.log("");
|
|
@@ -4337,7 +4701,16 @@ function registerRemoteCommand(program2) {
|
|
|
4337
4701
|
await showConnectionStatus(config);
|
|
4338
4702
|
return;
|
|
4339
4703
|
}
|
|
4340
|
-
const validTypes = [
|
|
4704
|
+
const validTypes = [
|
|
4705
|
+
"chat",
|
|
4706
|
+
"consult",
|
|
4707
|
+
"index",
|
|
4708
|
+
"analyse",
|
|
4709
|
+
"push",
|
|
4710
|
+
"autonomy",
|
|
4711
|
+
"backup",
|
|
4712
|
+
"restore"
|
|
4713
|
+
];
|
|
4341
4714
|
if (!validTypes.includes(type)) {
|
|
4342
4715
|
console.log("");
|
|
4343
4716
|
console.log(ERROR12(` \u274C Invalid command type: "${type}"`));
|
|
@@ -4348,6 +4721,9 @@ function registerRemoteCommand(program2) {
|
|
|
4348
4721
|
const payload = { conversationId: config.conversationId };
|
|
4349
4722
|
if (message) payload.message = message;
|
|
4350
4723
|
if (options.auto) payload.auto = true;
|
|
4724
|
+
if (options.source) payload.isSource = true;
|
|
4725
|
+
if (options.global) payload.isGlobal = true;
|
|
4726
|
+
if (options.archive) payload.archiveName = options.archive;
|
|
4351
4727
|
if ((type === "chat" || type === "consult" || type === "push") && !message) {
|
|
4352
4728
|
console.log("");
|
|
4353
4729
|
console.log(ERROR12(` \u274C ${type} requires a message.`));
|
|
@@ -4383,17 +4759,22 @@ async function runInteractiveRemote(config, targetSession) {
|
|
|
4383
4759
|
}
|
|
4384
4760
|
console.log("");
|
|
4385
4761
|
console.log(BORDER6(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
4386
|
-
console.log(BORDER6(" \u2551") + INFO14(
|
|
4762
|
+
console.log(BORDER6(" \u2551") + INFO14(" \u{1F310} Active Bob \u2014 Remote Session") + MUTED14(` (${activeBobName})`));
|
|
4387
4763
|
console.log(BORDER6(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
4388
4764
|
console.log(BORDER6(" \u2551") + MUTED14(` Conversation: ${config.conversationId?.slice(0, 28)}...`));
|
|
4389
4765
|
console.log(BORDER6(" \u2551") + MUTED14(" Commands dispatched to the remote machine."));
|
|
4390
4766
|
console.log(BORDER6(" \u2551"));
|
|
4391
4767
|
console.log(BORDER6(" \u2551") + chalk20.white(" Slash Commands:"));
|
|
4392
|
-
console.log(BORDER6(" \u2551") + MUTED14(' \u25B8 /consult "msg"
|
|
4393
|
-
console.log(BORDER6(" \u2551") + MUTED14(' \u25B8 /push "msg"
|
|
4394
|
-
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /index
|
|
4395
|
-
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /analyse
|
|
4396
|
-
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /
|
|
4768
|
+
console.log(BORDER6(" \u2551") + MUTED14(' \u25B8 /consult "msg" \u2014 Strategic advice'));
|
|
4769
|
+
console.log(BORDER6(" \u2551") + MUTED14(' \u25B8 /push "msg" \u2014 Git commit & push'));
|
|
4770
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /index \u2014 Re-index project"));
|
|
4771
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /analyse \u2014 Run QA analysis"));
|
|
4772
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /backup \u2014 Backup context"));
|
|
4773
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /backup source \u2014 Backup source code"));
|
|
4774
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /backup global \u2014 Backup ~/.bob/ (Grid)"));
|
|
4775
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /restore \u2014 Restore latest context"));
|
|
4776
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /restore source \u2014 Restore latest source"));
|
|
4777
|
+
console.log(BORDER6(" \u2551") + MUTED14(" \u25B8 /exit \u2014 Disconnect"));
|
|
4397
4778
|
console.log(BORDER6(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
4398
4779
|
console.log("");
|
|
4399
4780
|
const rl = readline7.createInterface({
|
|
@@ -4417,7 +4798,7 @@ async function runInteractiveRemote(config, targetSession) {
|
|
|
4417
4798
|
if (trimmed.startsWith("/consult ")) {
|
|
4418
4799
|
const msg = trimmed.slice(9).trim().replace(/^["']|["']$/g, "");
|
|
4419
4800
|
if (msg) {
|
|
4420
|
-
await dispatchAndShow(config, "consult", { message: msg
|
|
4801
|
+
await dispatchAndShow(config, "consult", { message: msg }, targetSession);
|
|
4421
4802
|
} else {
|
|
4422
4803
|
console.log(ERROR12(' \u274C Provide a message: /consult "your question"'));
|
|
4423
4804
|
}
|
|
@@ -4427,7 +4808,7 @@ async function runInteractiveRemote(config, targetSession) {
|
|
|
4427
4808
|
if (trimmed.startsWith("/push ")) {
|
|
4428
4809
|
const msg = trimmed.slice(6).trim().replace(/^["']|["']$/g, "");
|
|
4429
4810
|
if (msg) {
|
|
4430
|
-
await dispatchAndShow(config, "push", { message: msg
|
|
4811
|
+
await dispatchAndShow(config, "push", { message: msg }, targetSession);
|
|
4431
4812
|
} else {
|
|
4432
4813
|
console.log(ERROR12(' \u274C Provide a commit message: /push "your message"'));
|
|
4433
4814
|
}
|
|
@@ -4435,28 +4816,53 @@ async function runInteractiveRemote(config, targetSession) {
|
|
|
4435
4816
|
return;
|
|
4436
4817
|
}
|
|
4437
4818
|
if (trimmed === "/index") {
|
|
4438
|
-
await dispatchAndShow(config, "index", {
|
|
4819
|
+
await dispatchAndShow(config, "index", {}, targetSession);
|
|
4439
4820
|
prompt();
|
|
4440
4821
|
return;
|
|
4441
4822
|
}
|
|
4442
4823
|
if (trimmed === "/analyse" || trimmed === "/analyze") {
|
|
4443
|
-
await dispatchAndShow(config, "analyse", {
|
|
4824
|
+
await dispatchAndShow(config, "analyse", {}, targetSession);
|
|
4825
|
+
prompt();
|
|
4826
|
+
return;
|
|
4827
|
+
}
|
|
4828
|
+
if (trimmed.startsWith("/backup")) {
|
|
4829
|
+
const parts = trimmed.split(" ");
|
|
4830
|
+
const flag = parts[1]?.toLowerCase();
|
|
4831
|
+
const backupPayload = {};
|
|
4832
|
+
if (flag === "source") backupPayload.isSource = true;
|
|
4833
|
+
if (flag === "global") backupPayload.isGlobal = true;
|
|
4834
|
+
await dispatchAndShow(config, "backup", backupPayload, targetSession);
|
|
4835
|
+
prompt();
|
|
4836
|
+
return;
|
|
4837
|
+
}
|
|
4838
|
+
if (trimmed.startsWith("/restore")) {
|
|
4839
|
+
const parts = trimmed.split(" ");
|
|
4840
|
+
const flag = parts[1]?.toLowerCase();
|
|
4841
|
+
const restorePayload = {};
|
|
4842
|
+
if (flag === "source") restorePayload.isSource = true;
|
|
4843
|
+
if (flag === "global") restorePayload.isGlobal = true;
|
|
4844
|
+
await dispatchAndShow(config, "restore", restorePayload, targetSession);
|
|
4444
4845
|
prompt();
|
|
4445
4846
|
return;
|
|
4446
4847
|
}
|
|
4447
|
-
await dispatchAndShow(config, "chat", { message: trimmed
|
|
4848
|
+
await dispatchAndShow(config, "chat", { message: trimmed }, targetSession);
|
|
4448
4849
|
prompt();
|
|
4449
4850
|
});
|
|
4450
4851
|
};
|
|
4451
4852
|
prompt();
|
|
4452
4853
|
}
|
|
4453
4854
|
async function dispatchAndShow(config, type, payload, targetSession) {
|
|
4454
|
-
const spinner = ora7({
|
|
4855
|
+
const spinner = ora7({
|
|
4856
|
+
text: BRAND_SECONDARY13(` \u{1F4E1} Active Bob executing: ${type}...`),
|
|
4857
|
+
spinner: "dots"
|
|
4858
|
+
}).start();
|
|
4859
|
+
let pollCount = 0;
|
|
4860
|
+
const MAX_POLLS = 300;
|
|
4455
4861
|
try {
|
|
4456
4862
|
const result = await callCloudFunction("sendRemoteCommand", {
|
|
4457
4863
|
conversationId: config.conversationId,
|
|
4458
4864
|
type,
|
|
4459
|
-
payload,
|
|
4865
|
+
payload: { ...payload, conversationId: config.conversationId },
|
|
4460
4866
|
targetSession: targetSession || null
|
|
4461
4867
|
});
|
|
4462
4868
|
if (!result?.success) {
|
|
@@ -4466,7 +4872,8 @@ async function dispatchAndShow(config, type, payload, targetSession) {
|
|
|
4466
4872
|
return;
|
|
4467
4873
|
}
|
|
4468
4874
|
const commandId = result.commandId;
|
|
4469
|
-
while (
|
|
4875
|
+
while (pollCount < MAX_POLLS) {
|
|
4876
|
+
pollCount++;
|
|
4470
4877
|
try {
|
|
4471
4878
|
const pollResult = await callCloudFunction("getRemoteCommandResult", {
|
|
4472
4879
|
conversationId: config.conversationId,
|
|
@@ -4491,6 +4898,14 @@ async function dispatchAndShow(config, type, payload, targetSession) {
|
|
|
4491
4898
|
} else if (pollResult.result?.message) {
|
|
4492
4899
|
console.log("");
|
|
4493
4900
|
console.log(SUCCESS14(` \u2705 ${pollResult.result.message}`));
|
|
4901
|
+
if (pollResult.result?.counts) {
|
|
4902
|
+
const c = pollResult.result.counts;
|
|
4903
|
+
console.log(MUTED14(` \u{1F41B} ${c.bugs} bugs \u2B50 ${c.features} features \u{1F527} ${c.improvements} improvements \u2B06\uFE0F ${c.upgrades} upgrades`));
|
|
4904
|
+
}
|
|
4905
|
+
if (pollResult.result?.sizeBytes) {
|
|
4906
|
+
const sizeLabel = pollResult.result.sizeBytes < 1024 * 1024 ? `${(pollResult.result.sizeBytes / 1024).toFixed(1)} KB` : `${(pollResult.result.sizeBytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
4907
|
+
console.log(MUTED14(` \u{1F4E6} Size: ${sizeLabel}`));
|
|
4908
|
+
}
|
|
4494
4909
|
} else if (pollResult.result?.error) {
|
|
4495
4910
|
console.log("");
|
|
4496
4911
|
console.log(ERROR12(` \u274C ${pollResult.result.error}`));
|
|
@@ -4507,8 +4922,13 @@ async function dispatchAndShow(config, type, payload, targetSession) {
|
|
|
4507
4922
|
}
|
|
4508
4923
|
} catch {
|
|
4509
4924
|
}
|
|
4510
|
-
await new Promise((
|
|
4925
|
+
await new Promise((resolve3) => setTimeout(resolve3, 2e3));
|
|
4511
4926
|
}
|
|
4927
|
+
spinner.stop();
|
|
4928
|
+
console.log("");
|
|
4929
|
+
console.log(WARNING13(" \u26A0\uFE0F Command timed out after 10 minutes."));
|
|
4930
|
+
console.log(MUTED14(" The command may still be running on the remote machine."));
|
|
4931
|
+
console.log("");
|
|
4512
4932
|
} catch (error) {
|
|
4513
4933
|
spinner.stop();
|
|
4514
4934
|
console.log(ERROR12(` \u274C ${error.message}`));
|
|
@@ -4551,12 +4971,17 @@ async function showConnectionStatus(config) {
|
|
|
4551
4971
|
console.log("");
|
|
4552
4972
|
if (activeSessions.length > 0) {
|
|
4553
4973
|
console.log(MUTED14(" Commands:"));
|
|
4554
|
-
console.log(MUTED14(" \u25B8 bob remote --interactive
|
|
4555
|
-
console.log(MUTED14(' \u25B8 bob remote chat "message"
|
|
4556
|
-
console.log(MUTED14(' \u25B8 bob remote consult "msg"
|
|
4557
|
-
console.log(MUTED14(' \u25B8 bob remote push "msg"
|
|
4558
|
-
console.log(MUTED14(" \u25B8 bob remote index
|
|
4559
|
-
console.log(MUTED14(" \u25B8 bob remote analyse
|
|
4974
|
+
console.log(MUTED14(" \u25B8 bob remote --interactive \u2014 Persistent session"));
|
|
4975
|
+
console.log(MUTED14(' \u25B8 bob remote chat "message" \u2014 One-shot chat'));
|
|
4976
|
+
console.log(MUTED14(' \u25B8 bob remote consult "msg" \u2014 Strategic advice'));
|
|
4977
|
+
console.log(MUTED14(' \u25B8 bob remote push "msg" \u2014 Git push'));
|
|
4978
|
+
console.log(MUTED14(" \u25B8 bob remote index \u2014 Re-index project"));
|
|
4979
|
+
console.log(MUTED14(" \u25B8 bob remote analyse \u2014 Run QA analysis"));
|
|
4980
|
+
console.log(MUTED14(" \u25B8 bob remote backup \u2014 Backup context"));
|
|
4981
|
+
console.log(MUTED14(" \u25B8 bob remote backup --source \u2014 Backup source code"));
|
|
4982
|
+
console.log(MUTED14(" \u25B8 bob remote backup --global \u2014 Backup ~/.bob/ (Grid)"));
|
|
4983
|
+
console.log(MUTED14(" \u25B8 bob remote restore \u2014 Restore latest"));
|
|
4984
|
+
console.log(MUTED14(" \u25B8 bob remote restore --source \u2014 Restore latest source"));
|
|
4560
4985
|
console.log("");
|
|
4561
4986
|
}
|
|
4562
4987
|
} catch (error) {
|
|
@@ -4591,8 +5016,8 @@ async function discoverAndConnect(config) {
|
|
|
4591
5016
|
console.log(BORDER6(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
4592
5017
|
console.log("");
|
|
4593
5018
|
const rl = readline7.createInterface({ input: process.stdin, output: process.stdout });
|
|
4594
|
-
const answer = await new Promise((
|
|
4595
|
-
rl.question(INFO14(" Select (1-" + bobs.length + ") or 0 to cancel: "),
|
|
5019
|
+
const answer = await new Promise((resolve3) => {
|
|
5020
|
+
rl.question(INFO14(" Select (1-" + bobs.length + ") or 0 to cancel: "), resolve3);
|
|
4596
5021
|
});
|
|
4597
5022
|
rl.close();
|
|
4598
5023
|
const selection = parseInt(answer.trim());
|
|
@@ -5016,7 +5441,7 @@ async function runCloudProfiler(options) {
|
|
|
5016
5441
|
}
|
|
5017
5442
|
}
|
|
5018
5443
|
function sleep5(ms) {
|
|
5019
|
-
return new Promise((
|
|
5444
|
+
return new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
5020
5445
|
}
|
|
5021
5446
|
|
|
5022
5447
|
// src/ui/profile-dashboard.ts
|
|
@@ -5298,7 +5723,7 @@ async function renderProfileDashboard() {
|
|
|
5298
5723
|
import * as path12 from "path";
|
|
5299
5724
|
var AMBER5 = chalk23.hex("#FFAB00");
|
|
5300
5725
|
var GREEN4 = chalk23.hex("#66BB6A");
|
|
5301
|
-
var
|
|
5726
|
+
var BLUE2 = chalk23.hex("#42A5F5");
|
|
5302
5727
|
var GRAY4 = chalk23.gray;
|
|
5303
5728
|
var CYAN4 = chalk23.cyan;
|
|
5304
5729
|
var RED4 = chalk23.hex("#EF5350");
|
|
@@ -5749,1187 +6174,720 @@ function getWeekNumber(date) {
|
|
|
5749
6174
|
return Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
|
|
5750
6175
|
}
|
|
5751
6176
|
|
|
5752
|
-
// src/commands/
|
|
6177
|
+
// src/commands/backup.ts
|
|
5753
6178
|
import chalk24 from "chalk";
|
|
5754
|
-
import
|
|
5755
|
-
import * as
|
|
6179
|
+
import ora11 from "ora";
|
|
6180
|
+
import * as fs11 from "fs";
|
|
5756
6181
|
import * as path13 from "path";
|
|
5757
6182
|
import * as os4 from "os";
|
|
5758
|
-
|
|
5759
|
-
|
|
6183
|
+
import * as crypto3 from "crypto";
|
|
6184
|
+
import axios2 from "axios";
|
|
6185
|
+
import inquirer from "inquirer";
|
|
6186
|
+
var BRAND = chalk24.hex("#E66F24");
|
|
6187
|
+
var CYAN5 = chalk24.cyan;
|
|
5760
6188
|
var GREEN5 = chalk24.hex("#66BB6A");
|
|
5761
|
-
var CYAN5 = chalk24.hex("#26C6DA");
|
|
5762
6189
|
var RED5 = chalk24.hex("#EF5350");
|
|
6190
|
+
var AMBER6 = chalk24.hex("#FFAB00");
|
|
5763
6191
|
var GRAY5 = chalk24.gray;
|
|
5764
|
-
var BLUE4 = chalk24.hex("#42A5F5");
|
|
5765
|
-
var BOB_COLOR = chalk24.hex("#E66F24");
|
|
5766
6192
|
var BORDER10 = chalk24.hex("#455A64");
|
|
5767
|
-
var
|
|
5768
|
-
var
|
|
5769
|
-
function
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
5774
|
-
const
|
|
5775
|
-
const
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
5781
|
-
|
|
6193
|
+
var BOB_DIR4 = path13.join(os4.homedir(), ".bob");
|
|
6194
|
+
var ALGORITHM2 = "aes-256-cbc";
|
|
6195
|
+
function deriveKey2(uid, salt) {
|
|
6196
|
+
return crypto3.pbkdf2Sync(uid, salt, 1e5, 32, "sha256");
|
|
6197
|
+
}
|
|
6198
|
+
function encrypt2(inputPath, outputPath, uid) {
|
|
6199
|
+
const salt = crypto3.randomBytes(16).toString("hex");
|
|
6200
|
+
const key = deriveKey2(uid, salt);
|
|
6201
|
+
const iv = crypto3.randomBytes(16);
|
|
6202
|
+
const cipher = crypto3.createCipheriv(ALGORITHM2, key, iv);
|
|
6203
|
+
const input = fs11.readFileSync(inputPath);
|
|
6204
|
+
const encrypted = Buffer.concat([cipher.update(input), cipher.final()]);
|
|
6205
|
+
const header = Buffer.from(salt + iv.toString("hex"), "utf-8");
|
|
6206
|
+
fs11.writeFileSync(outputPath, Buffer.concat([header, encrypted]));
|
|
6207
|
+
}
|
|
6208
|
+
function decrypt2(inputPath, outputPath, uid) {
|
|
6209
|
+
const data = fs11.readFileSync(inputPath);
|
|
6210
|
+
const header = data.slice(0, 64).toString("utf-8");
|
|
6211
|
+
const salt = header.slice(0, 32);
|
|
6212
|
+
const iv = Buffer.from(header.slice(32, 64), "hex");
|
|
6213
|
+
const encrypted = data.slice(64);
|
|
6214
|
+
const key = deriveKey2(uid, salt);
|
|
6215
|
+
const decipher = crypto3.createDecipheriv(ALGORITHM2, key, iv);
|
|
6216
|
+
const decrypted = Buffer.concat([decipher.update(encrypted), decipher.final()]);
|
|
6217
|
+
fs11.writeFileSync(outputPath, decrypted);
|
|
6218
|
+
}
|
|
6219
|
+
function formatBytes(bytes) {
|
|
6220
|
+
if (bytes < 1024) return `${bytes} B`;
|
|
6221
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
6222
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
6223
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(2)} GB`;
|
|
6224
|
+
}
|
|
6225
|
+
function getTempDir2() {
|
|
6226
|
+
const tmpDir = path13.join(os4.tmpdir(), `bob-backup-${Date.now()}`);
|
|
6227
|
+
fs11.mkdirSync(tmpDir, { recursive: true });
|
|
6228
|
+
return tmpDir;
|
|
6229
|
+
}
|
|
6230
|
+
function cleanupTemp2(tmpDir) {
|
|
5782
6231
|
try {
|
|
5783
|
-
|
|
6232
|
+
fs11.rmSync(tmpDir, { recursive: true, force: true });
|
|
5784
6233
|
} catch {
|
|
5785
|
-
return null;
|
|
5786
6234
|
}
|
|
5787
6235
|
}
|
|
5788
|
-
function
|
|
5789
|
-
|
|
5790
|
-
|
|
6236
|
+
function requireAuth(config) {
|
|
6237
|
+
if (!config.loggedIn || !config.authToken) {
|
|
6238
|
+
console.log("");
|
|
6239
|
+
console.log(RED5(" \u274C Backup requires authentication."));
|
|
6240
|
+
console.log(GRAY5(" Run `bob login` to authenticate."));
|
|
6241
|
+
console.log("");
|
|
6242
|
+
return false;
|
|
6243
|
+
}
|
|
6244
|
+
return true;
|
|
6245
|
+
}
|
|
6246
|
+
function getCurrentProjectName() {
|
|
6247
|
+
return path13.basename(process.cwd());
|
|
5791
6248
|
}
|
|
5792
|
-
function
|
|
5793
|
-
|
|
5794
|
-
console.log("");
|
|
5795
|
-
console.log(BORDER10(" \u2500\u2500\u2500 MISSION CONTROL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5796
|
-
console.log(
|
|
5797
|
-
` SAT: ${satBar} \u2192 ${target}% \u2502 STAG: ${stag}/${stagTarget > 0 ? stagTarget : "\u221E"} \u2502 DIV: ${div}/${divTarget > 0 ? divTarget : "\u221E"} \u2502 GRADE: ${grading}`
|
|
5798
|
-
);
|
|
5799
|
-
console.log(BORDER10(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5800
|
-
console.log("");
|
|
6249
|
+
function getProjectBackupDir(projectName) {
|
|
6250
|
+
return path13.join(BOB_DIR4, "projects", projectName);
|
|
5801
6251
|
}
|
|
5802
|
-
function
|
|
5803
|
-
return
|
|
6252
|
+
function normalizeFilePath(filePath) {
|
|
6253
|
+
return filePath.replace(/\\/g, "/");
|
|
5804
6254
|
}
|
|
5805
|
-
function
|
|
5806
|
-
|
|
5807
|
-
const maxWidth = 70;
|
|
5808
|
-
const lines = wrapText2(cleanMsg, maxWidth - 4);
|
|
5809
|
-
if (sender === "userBob") {
|
|
5810
|
-
const topBar = PURPLE(` \u250C\u2500 UserBob ${"\u2500".repeat(maxWidth - 13)}\u2510`);
|
|
5811
|
-
const bottomBar = PURPLE(` \u2514${"\u2500".repeat(maxWidth - 2)}\u2518`);
|
|
6255
|
+
function handleBackupError(error) {
|
|
6256
|
+
if (error.message?.includes("BOB_BACKUP_LICENSE_REQUIRED")) {
|
|
5812
6257
|
console.log("");
|
|
5813
|
-
console.log(
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
console.log(PURPLE(" \u2502") + ` ${padded}` + PURPLE(" \u2502"));
|
|
5817
|
-
}
|
|
5818
|
-
console.log(bottomBar);
|
|
5819
|
-
if (audit) {
|
|
5820
|
-
const chips = [];
|
|
5821
|
-
if (audit.satisfactionScore !== void 0) chips.push(CYAN5(`[SAT: ${audit.satisfactionScore}%]`));
|
|
5822
|
-
if (audit.resemblanceScore !== void 0) chips.push(BLUE4(`[RES: ${audit.resemblanceScore}%]`));
|
|
5823
|
-
if (audit.reasoning) chips.push(GRAY5(`[${String(audit.reasoning).slice(0, 50)}...]`));
|
|
5824
|
-
if (chips.length > 0) console.log(" " + chips.join(" "));
|
|
5825
|
-
}
|
|
5826
|
-
} else if (sender === "bob") {
|
|
5827
|
-
const indent = " ";
|
|
5828
|
-
const topBar = BOB_COLOR(`${indent}\u250C${"\u2500".repeat(maxWidth - 12)}\u2500 Bob \u2500\u2510`);
|
|
5829
|
-
const bottomBar = BOB_COLOR(`${indent}\u2514${"\u2500".repeat(maxWidth - 2)}\u2518`);
|
|
6258
|
+
console.log(AMBER6(" \u26A0\uFE0F No active backup license found."));
|
|
6259
|
+
console.log(GRAY5(" Purchase one at: app.bobsworkshop.com/iap \u2192 Bob Backup"));
|
|
6260
|
+
} else if (error.message?.includes("STORAGE_QUOTA_EXCEEDED")) {
|
|
5830
6261
|
console.log("");
|
|
5831
|
-
console.log(
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
|
|
5835
|
-
|
|
5836
|
-
console.log(
|
|
5837
|
-
} else if (
|
|
6262
|
+
console.log(AMBER6(" \u26A0\uFE0F Storage quota exceeded."));
|
|
6263
|
+
console.log(GRAY5(" Purchase a storage pack: app.bobsworkshop.com/iap \u2192 Storage Packs"));
|
|
6264
|
+
} else if (error.message?.includes("ARCHIVE_SLOTS_EXHAUSTED")) {
|
|
6265
|
+
console.log("");
|
|
6266
|
+
console.log(AMBER6(" \u26A0\uFE0F All archive slots are used."));
|
|
6267
|
+
console.log(GRAY5(" Upgrade your Workshop SKU for more archive slots."));
|
|
6268
|
+
} else if (error.message?.includes("GRID_REQUIRED")) {
|
|
5838
6269
|
console.log("");
|
|
5839
|
-
console.log(
|
|
5840
|
-
console.log(GRAY5(
|
|
5841
|
-
console.log(CYAN5(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6270
|
+
console.log(AMBER6(" \u26A0\uFE0F Global backup requires the Grid Workshop plan."));
|
|
6271
|
+
console.log(GRAY5(" Upgrade at: app.bobsworkshop.com/iap \u2192 Workshop"));
|
|
5842
6272
|
} else {
|
|
5843
6273
|
console.log("");
|
|
5844
|
-
console.log(
|
|
6274
|
+
console.log(RED5(` \u274C ${error.message}`));
|
|
5845
6275
|
}
|
|
6276
|
+
console.log("");
|
|
5846
6277
|
}
|
|
5847
|
-
function
|
|
5848
|
-
const
|
|
5849
|
-
const
|
|
5850
|
-
|
|
5851
|
-
|
|
5852
|
-
|
|
5853
|
-
|
|
5854
|
-
|
|
5855
|
-
|
|
5856
|
-
|
|
5857
|
-
|
|
5858
|
-
|
|
5859
|
-
|
|
5860
|
-
|
|
6278
|
+
function loadGitignorePatterns(projectDir) {
|
|
6279
|
+
const gitignorePath = path13.join(projectDir, ".gitignore");
|
|
6280
|
+
const defaultIgnore = [
|
|
6281
|
+
"node_modules",
|
|
6282
|
+
".git",
|
|
6283
|
+
"dist",
|
|
6284
|
+
"build",
|
|
6285
|
+
".dart_tool",
|
|
6286
|
+
".idea",
|
|
6287
|
+
".gradle",
|
|
6288
|
+
".pub-cache",
|
|
6289
|
+
"*.log",
|
|
6290
|
+
".env",
|
|
6291
|
+
".env.local",
|
|
6292
|
+
".env.*",
|
|
6293
|
+
"coverage",
|
|
6294
|
+
".nyc_output"
|
|
6295
|
+
];
|
|
6296
|
+
if (!fs11.existsSync(gitignorePath)) return defaultIgnore;
|
|
6297
|
+
try {
|
|
6298
|
+
const lines = fs11.readFileSync(gitignorePath, "utf-8").split("\n").map((l) => l.trim()).filter((l) => l && !l.startsWith("#"));
|
|
6299
|
+
return [...defaultIgnore, ...lines];
|
|
6300
|
+
} catch {
|
|
6301
|
+
return defaultIgnore;
|
|
6302
|
+
}
|
|
6303
|
+
}
|
|
6304
|
+
function shouldIgnore(filePath, patterns) {
|
|
6305
|
+
const parts = filePath.split(/[/\\]/);
|
|
6306
|
+
return patterns.some((pattern) => {
|
|
6307
|
+
const clean = pattern.replace(/^\//, "").replace(/\/$/, "");
|
|
6308
|
+
return parts.some((part) => {
|
|
6309
|
+
if (clean.includes("*")) {
|
|
6310
|
+
const regex = new RegExp("^" + clean.replace(/\./g, "\\.").replace(/\*/g, ".*") + "$");
|
|
6311
|
+
return regex.test(part);
|
|
6312
|
+
}
|
|
6313
|
+
return part === clean;
|
|
6314
|
+
});
|
|
6315
|
+
});
|
|
6316
|
+
}
|
|
6317
|
+
async function runBackup(options) {
|
|
6318
|
+
const { config, projectName, isGlobal, archiveName, sourceDir, displayName } = options;
|
|
6319
|
+
console.log("");
|
|
6320
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
6321
|
+
console.log(BORDER10(" \u2551") + BRAND(" \u2601\uFE0F Bob Backup ") + BORDER10("\u2551"));
|
|
6322
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6323
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Scope: ${isGlobal ? "\u{1F310} Global (~/.bob/)" : `\u{1F4C1} Project: ${projectName}`}`));
|
|
6324
|
+
if (archiveName) console.log(BORDER10(" \u2551") + GRAY5(` Archive: "${archiveName}"`));
|
|
6325
|
+
console.log(BORDER10(" \u2551"));
|
|
6326
|
+
if (!fs11.existsSync(sourceDir)) {
|
|
6327
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6328
|
+
console.log("");
|
|
6329
|
+
console.log(RED5(` \u274C Source directory not found: ${sourceDir}`));
|
|
6330
|
+
console.log(GRAY5(` Run \`bob index\` first to initialize this project.`));
|
|
6331
|
+
console.log("");
|
|
6332
|
+
return;
|
|
6333
|
+
}
|
|
6334
|
+
const tmpDir = getTempDir2();
|
|
6335
|
+
const archivePath = path13.join(tmpDir, "bob-backup.tar.gz");
|
|
6336
|
+
const encryptedPath = path13.join(tmpDir, "bob-backup.bob.enc");
|
|
6337
|
+
try {
|
|
6338
|
+
const compressSpinner = ora11({ text: GRAY5(" Compressing ..."), spinner: "dots" }).start();
|
|
6339
|
+
const tar = await import("tar");
|
|
6340
|
+
const relativeSource = path13.relative(os4.homedir(), sourceDir);
|
|
6341
|
+
await tar.create(
|
|
6342
|
+
{ gzip: true, file: archivePath, cwd: os4.homedir() },
|
|
6343
|
+
[relativeSource]
|
|
6344
|
+
);
|
|
6345
|
+
const archiveStats = fs11.statSync(archivePath);
|
|
6346
|
+
const estimatedSizeGB = archiveStats.size / (1024 * 1024 * 1024);
|
|
6347
|
+
compressSpinner.succeed(GREEN5(` Compressing ${displayName} ...`) + GRAY5(` ${formatBytes(archiveStats.size)}`));
|
|
6348
|
+
const encryptSpinner = ora11({ text: GRAY5(" Encrypting archive ..."), spinner: "dots" }).start();
|
|
6349
|
+
encrypt2(archivePath, encryptedPath, config.uid);
|
|
6350
|
+
const encryptedStats = fs11.statSync(encryptedPath);
|
|
6351
|
+
encryptSpinner.succeed(GREEN5(" Encrypting archive ...") + GRAY5(` ${formatBytes(encryptedStats.size)}`));
|
|
6352
|
+
const urlSpinner = ora11({ text: GRAY5(" Requesting upload authorization ..."), spinner: "dots" }).start();
|
|
6353
|
+
let uploadResult;
|
|
6354
|
+
try {
|
|
6355
|
+
if (archiveName) {
|
|
6356
|
+
uploadResult = await callCloudFunction("cliBackupLicense", {
|
|
6357
|
+
action: "requestArchiveUpload",
|
|
6358
|
+
projectName,
|
|
6359
|
+
isGlobal,
|
|
6360
|
+
isSource: false,
|
|
6361
|
+
archiveName,
|
|
6362
|
+
estimatedSizeGB
|
|
6363
|
+
});
|
|
5861
6364
|
} else {
|
|
5862
|
-
|
|
6365
|
+
uploadResult = await callCloudFunction("cliBackupLicense", {
|
|
6366
|
+
action: "requestUpload",
|
|
6367
|
+
projectName,
|
|
6368
|
+
isGlobal,
|
|
6369
|
+
isSource: false,
|
|
6370
|
+
estimatedSizeGB
|
|
6371
|
+
});
|
|
5863
6372
|
}
|
|
6373
|
+
urlSpinner.succeed(GREEN5(" Requesting upload authorization ..."));
|
|
6374
|
+
} catch (error) {
|
|
6375
|
+
urlSpinner.fail(RED5(" \u274C Authorization failed."));
|
|
6376
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6377
|
+
handleBackupError(error);
|
|
6378
|
+
return;
|
|
5864
6379
|
}
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
}
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
6380
|
+
const uploadSpinner = ora11({ text: GRAY5(" Uploading to S3 ..."), spinner: "dots" }).start();
|
|
6381
|
+
const encryptedData = fs11.readFileSync(encryptedPath);
|
|
6382
|
+
await axios2.put(uploadResult.uploadUrl, encryptedData, {
|
|
6383
|
+
headers: { "Content-Type": "application/octet-stream", "Content-Length": encryptedData.length },
|
|
6384
|
+
maxBodyLength: Infinity,
|
|
6385
|
+
maxContentLength: Infinity
|
|
6386
|
+
});
|
|
6387
|
+
uploadSpinner.succeed(GREEN5(" Uploading to S3 ..."));
|
|
6388
|
+
const recordSpinner = ora11({ text: GRAY5(" Recording usage ..."), spinner: "dots" }).start();
|
|
5872
6389
|
try {
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
5879
|
-
|
|
5880
|
-
|
|
5881
|
-
|
|
5882
|
-
|
|
5883
|
-
|
|
5884
|
-
|
|
6390
|
+
if (archiveName && uploadResult.archiveId) {
|
|
6391
|
+
await callCloudFunction("cliBackupLicense", {
|
|
6392
|
+
action: "recordArchiveUsage",
|
|
6393
|
+
projectName,
|
|
6394
|
+
isGlobal,
|
|
6395
|
+
isSource: false,
|
|
6396
|
+
archiveId: uploadResult.archiveId
|
|
6397
|
+
});
|
|
6398
|
+
} else {
|
|
6399
|
+
await callCloudFunction("cliBackupLicense", {
|
|
6400
|
+
action: "recordUsage",
|
|
6401
|
+
projectName,
|
|
6402
|
+
isGlobal,
|
|
6403
|
+
isSource: false
|
|
6404
|
+
});
|
|
6405
|
+
}
|
|
6406
|
+
recordSpinner.succeed(GREEN5(" Recording usage ..."));
|
|
5885
6407
|
} catch {
|
|
5886
|
-
|
|
6408
|
+
recordSpinner.warn(AMBER6(" Usage recording failed (non-fatal). Backup was saved."));
|
|
6409
|
+
}
|
|
6410
|
+
const now = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
6411
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6412
|
+
if (archiveName) {
|
|
6413
|
+
console.log(BORDER10(" \u2551") + GREEN5(` \u2705 Archive saved: "${archiveName}" \u2014 ${now}`));
|
|
6414
|
+
} else {
|
|
6415
|
+
console.log(BORDER10(" \u2551") + GREEN5(` \u2705 Backup complete \u2014 ${now}`));
|
|
6416
|
+
}
|
|
6417
|
+
console.log(BORDER10(" \u2551") + GRAY5(" Run `bob backup list` to see all revisions."));
|
|
6418
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6419
|
+
console.log("");
|
|
6420
|
+
} catch (error) {
|
|
6421
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6422
|
+
handleBackupError(error);
|
|
6423
|
+
} finally {
|
|
6424
|
+
cleanupTemp2(tmpDir);
|
|
6425
|
+
}
|
|
6426
|
+
}
|
|
6427
|
+
async function runSourceBackup(options) {
|
|
6428
|
+
const { config, projectName, projectDir, archiveName } = options;
|
|
6429
|
+
const filePath = options.filePath ? normalizeFilePath(options.filePath) : void 0;
|
|
6430
|
+
const isFileMode = !!filePath;
|
|
6431
|
+
const scopeLabel = isFileMode ? `\u{1F4C4} File: ${filePath}` : `\u{1F4BE} Source: ${projectName}`;
|
|
6432
|
+
console.log("");
|
|
6433
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
6434
|
+
console.log(BORDER10(" \u2551") + BRAND(" \u2601\uFE0F Bob Source Backup ") + BORDER10("\u2551"));
|
|
6435
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6436
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Scope: ${scopeLabel}`));
|
|
6437
|
+
if (archiveName) console.log(BORDER10(" \u2551") + GRAY5(` Archive: "${archiveName}"`));
|
|
6438
|
+
console.log(BORDER10(" \u2551"));
|
|
6439
|
+
if (isFileMode) {
|
|
6440
|
+
const absoluteFilePath = path13.resolve(projectDir, filePath);
|
|
6441
|
+
if (!fs11.existsSync(absoluteFilePath)) {
|
|
6442
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6443
|
+
console.log("");
|
|
6444
|
+
console.log(RED5(` \u274C File not found: ${filePath}`));
|
|
6445
|
+
console.log("");
|
|
6446
|
+
return;
|
|
6447
|
+
}
|
|
6448
|
+
} else {
|
|
6449
|
+
if (!fs11.existsSync(projectDir)) {
|
|
6450
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6451
|
+
console.log("");
|
|
6452
|
+
console.log(RED5(` \u274C Project directory not found: ${projectDir}`));
|
|
6453
|
+
console.log("");
|
|
6454
|
+
return;
|
|
5887
6455
|
}
|
|
5888
|
-
return;
|
|
5889
6456
|
}
|
|
5890
|
-
const
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
const
|
|
5895
|
-
|
|
5896
|
-
|
|
5897
|
-
|
|
5898
|
-
|
|
6457
|
+
const tmpDir = getTempDir2();
|
|
6458
|
+
const archivePath = path13.join(tmpDir, "bob-source.tar.gz");
|
|
6459
|
+
const encryptedPath = path13.join(tmpDir, "bob-source.bob.enc");
|
|
6460
|
+
try {
|
|
6461
|
+
const compressSpinner = ora11({ text: GRAY5(" Compressing source ..."), spinner: "dots" }).start();
|
|
6462
|
+
const tar = await import("tar");
|
|
6463
|
+
if (isFileMode) {
|
|
6464
|
+
const relativeFilePath = path13.normalize(filePath).replace(/\\/g, "/");
|
|
6465
|
+
await tar.create(
|
|
6466
|
+
{ gzip: true, file: archivePath, cwd: projectDir },
|
|
6467
|
+
[relativeFilePath]
|
|
6468
|
+
);
|
|
6469
|
+
} else {
|
|
6470
|
+
const ignorePatterns = loadGitignorePatterns(projectDir);
|
|
6471
|
+
const parentDir = path13.dirname(projectDir);
|
|
6472
|
+
const projectDirName = path13.basename(projectDir);
|
|
6473
|
+
await tar.create(
|
|
6474
|
+
{
|
|
6475
|
+
gzip: true,
|
|
6476
|
+
file: archivePath,
|
|
6477
|
+
cwd: parentDir,
|
|
6478
|
+
filter: (fp) => !shouldIgnore(fp, ignorePatterns)
|
|
6479
|
+
},
|
|
6480
|
+
[projectDirName]
|
|
6481
|
+
);
|
|
6482
|
+
}
|
|
6483
|
+
const archiveStats = fs11.statSync(archivePath);
|
|
6484
|
+
const estimatedSizeGB = archiveStats.size / (1024 * 1024 * 1024);
|
|
6485
|
+
compressSpinner.succeed(
|
|
6486
|
+
GREEN5(` Compressing ${isFileMode ? filePath : projectName} ...`) + GRAY5(` ${formatBytes(archiveStats.size)}`)
|
|
6487
|
+
);
|
|
6488
|
+
const encryptSpinner = ora11({ text: GRAY5(" Encrypting ..."), spinner: "dots" }).start();
|
|
6489
|
+
encrypt2(archivePath, encryptedPath, config.uid);
|
|
6490
|
+
const encryptedStats = fs11.statSync(encryptedPath);
|
|
6491
|
+
encryptSpinner.succeed(GREEN5(" Encrypting ...") + GRAY5(` ${formatBytes(encryptedStats.size)}`));
|
|
6492
|
+
const urlSpinner = ora11({ text: GRAY5(" Requesting upload authorization ..."), spinner: "dots" }).start();
|
|
6493
|
+
let uploadResult;
|
|
6494
|
+
const uploadPayload = {
|
|
6495
|
+
action: archiveName ? "requestSourceArchiveUpload" : "requestSourceUpload",
|
|
6496
|
+
projectName,
|
|
6497
|
+
isGlobal: false,
|
|
6498
|
+
isSource: true,
|
|
6499
|
+
filePath: filePath || null,
|
|
6500
|
+
archiveName: archiveName || null,
|
|
6501
|
+
estimatedSizeGB
|
|
5899
6502
|
};
|
|
5900
6503
|
try {
|
|
5901
|
-
await
|
|
5902
|
-
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
console.log(GREEN5(` \u2705 ${param} updated to ${value}`));
|
|
5909
|
-
} catch (e) {
|
|
5910
|
-
console.log(RED5(` \u274C Failed to update ${param}: ${e.message}`));
|
|
6504
|
+
uploadResult = await callCloudFunction("cliBackupLicense", uploadPayload);
|
|
6505
|
+
urlSpinner.succeed(GREEN5(" Requesting upload authorization ..."));
|
|
6506
|
+
} catch (error) {
|
|
6507
|
+
urlSpinner.fail(RED5(" \u274C Authorization failed."));
|
|
6508
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6509
|
+
handleBackupError(error);
|
|
6510
|
+
return;
|
|
5911
6511
|
}
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
6512
|
+
const uploadSpinner = ora11({ text: GRAY5(" Uploading to S3 ..."), spinner: "dots" }).start();
|
|
6513
|
+
const encryptedData = fs11.readFileSync(encryptedPath);
|
|
6514
|
+
await axios2.put(uploadResult.uploadUrl, encryptedData, {
|
|
6515
|
+
headers: { "Content-Type": "application/octet-stream", "Content-Length": encryptedData.length },
|
|
6516
|
+
maxBodyLength: Infinity,
|
|
6517
|
+
maxContentLength: Infinity
|
|
6518
|
+
});
|
|
6519
|
+
uploadSpinner.succeed(GREEN5(" Uploading to S3 ..."));
|
|
6520
|
+
const recordSpinner = ora11({ text: GRAY5(" Recording usage ..."), spinner: "dots" }).start();
|
|
6521
|
+
const recordPayload = {
|
|
6522
|
+
action: archiveName ? "recordSourceArchiveUsage" : "recordSourceUsage",
|
|
6523
|
+
projectName,
|
|
6524
|
+
isGlobal: false,
|
|
6525
|
+
isSource: true,
|
|
6526
|
+
filePath: filePath || null,
|
|
6527
|
+
archiveId: uploadResult.archiveId || null
|
|
6528
|
+
};
|
|
5917
6529
|
try {
|
|
5918
|
-
await
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
email: config.email,
|
|
5923
|
-
note
|
|
5924
|
-
});
|
|
5925
|
-
console.log(GREEN5(` \u2705 Director's note injected.`));
|
|
5926
|
-
} catch (e) {
|
|
5927
|
-
console.log(RED5(` \u274C Failed to inject note: ${e.message}`));
|
|
6530
|
+
await callCloudFunction("cliBackupLicense", recordPayload);
|
|
6531
|
+
recordSpinner.succeed(GREEN5(" Recording usage ..."));
|
|
6532
|
+
} catch {
|
|
6533
|
+
recordSpinner.warn(AMBER6(" Usage recording failed (non-fatal). Backup was saved."));
|
|
5928
6534
|
}
|
|
5929
|
-
|
|
6535
|
+
const now = (/* @__PURE__ */ new Date()).toLocaleString();
|
|
6536
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6537
|
+
if (archiveName) {
|
|
6538
|
+
console.log(BORDER10(" \u2551") + GREEN5(` \u2705 Source archive saved: "${archiveName}" \u2014 ${now}`));
|
|
6539
|
+
} else {
|
|
6540
|
+
console.log(BORDER10(" \u2551") + GREEN5(` \u2705 Source backup complete \u2014 ${now}`));
|
|
6541
|
+
}
|
|
6542
|
+
console.log(BORDER10(" \u2551") + GRAY5(` ${isFileMode ? `File: ${filePath}` : `Project: ${projectName} (gitignore respected)`}`));
|
|
6543
|
+
console.log(BORDER10(" \u2551") + GRAY5(" Run `bob backup list --source` to see all source revisions."));
|
|
6544
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6545
|
+
console.log("");
|
|
6546
|
+
} catch (error) {
|
|
6547
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6548
|
+
handleBackupError(error);
|
|
6549
|
+
} finally {
|
|
6550
|
+
cleanupTemp2(tmpDir);
|
|
5930
6551
|
}
|
|
5931
|
-
console.log(GRAY5(' Commands: /set grading|target|stag|div <n> /inject "note" /status /abort'));
|
|
5932
6552
|
}
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
5936
|
-
|
|
5937
|
-
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
action: "injectNote",
|
|
5948
|
-
conversationId,
|
|
5949
|
-
uid: config.uid,
|
|
5950
|
-
email: config.email,
|
|
5951
|
-
note: mission
|
|
5952
|
-
});
|
|
5953
|
-
console.log(GREEN5(" \u2705 Mission injected. Simulation is running."));
|
|
5954
|
-
console.log("");
|
|
5955
|
-
console.log(BORDER10(" \u2500\u2500\u2500 LIVE SIMULATION \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5956
|
-
console.log(GRAY5(" Messages will stream below as Bob and UserBob interact."));
|
|
5957
|
-
console.log(GRAY5(" You can type commands at any time:"));
|
|
5958
|
-
console.log("");
|
|
5959
|
-
console.log(AMBER6(" /abort") + GRAY5(" \u2014 Stop the simulation immediately"));
|
|
5960
|
-
console.log(AMBER6(" /set target 90") + GRAY5(" \u2014 Update satisfaction target"));
|
|
5961
|
-
console.log(AMBER6(" /set grading 70") + GRAY5(" \u2014 Update Teacher's Curve"));
|
|
5962
|
-
console.log(AMBER6(" /set stag 5") + GRAY5(" \u2014 Update stalemate threshold"));
|
|
5963
|
-
console.log(AMBER6(" /set div 3") + GRAY5(" \u2014 Update divergence threshold"));
|
|
5964
|
-
console.log(AMBER6(' /inject "note"') + GRAY5(" \u2014 Inject a director's note mid-session"));
|
|
5965
|
-
console.log(AMBER6(" /status") + GRAY5(" \u2014 Show current simulation parameters"));
|
|
5966
|
-
console.log("");
|
|
5967
|
-
console.log(BORDER10(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
5968
|
-
console.log("");
|
|
5969
|
-
let running = true;
|
|
5970
|
-
let lastMessageTimestamp = 0;
|
|
5971
|
-
let hudState = { sat: 0, target: params.target, stag: 0, stagTarget: params.stag, div: 0, divTarget: params.div, grading: params.grading };
|
|
5972
|
-
const sigintHandler = async () => {
|
|
5973
|
-
if (!running) return;
|
|
5974
|
-
running = false;
|
|
5975
|
-
console.log("\n");
|
|
5976
|
-
console.log(AMBER6(" \u{1F6D1} Aborting simulation..."));
|
|
5977
|
-
try {
|
|
5978
|
-
await callHTTPFunction("userSimManagerService", {
|
|
5979
|
-
action: "abortMission",
|
|
5980
|
-
conversationId,
|
|
5981
|
-
uid: config.uid,
|
|
5982
|
-
email: config.email
|
|
6553
|
+
function registerBackupCommand(program2) {
|
|
6554
|
+
const backupCmd = program2.command("backup").description("Encrypt and upload your Bob CLI data to secure cloud storage");
|
|
6555
|
+
backupCmd.command("create").description("Create a new encrypted backup").option("--archive <name>", "Save as a named archive snapshot").option("--global", "Back up entire ~/.bob/ (Grid Workshop SKU required)").option("--source", "Back up actual project source code (gitignore respected)").option("--file <path>", "Back up a single file (use with --source)").action(async (options) => {
|
|
6556
|
+
const config = getConfig();
|
|
6557
|
+
if (!requireAuth(config)) return;
|
|
6558
|
+
const projectName = getCurrentProjectName();
|
|
6559
|
+
const projectDir = process.cwd();
|
|
6560
|
+
if (options.source) {
|
|
6561
|
+
await runSourceBackup({
|
|
6562
|
+
config,
|
|
6563
|
+
projectName,
|
|
6564
|
+
projectDir,
|
|
6565
|
+
filePath: options.file,
|
|
6566
|
+
archiveName: options.archive
|
|
5983
6567
|
});
|
|
5984
|
-
|
|
5985
|
-
} catch {
|
|
6568
|
+
return;
|
|
5986
6569
|
}
|
|
5987
|
-
|
|
5988
|
-
|
|
5989
|
-
|
|
5990
|
-
|
|
5991
|
-
|
|
5992
|
-
|
|
5993
|
-
|
|
5994
|
-
|
|
5995
|
-
|
|
5996
|
-
running = false;
|
|
5997
|
-
console.log(AMBER6(" \u{1F6D1} Aborting simulation..."));
|
|
5998
|
-
try {
|
|
5999
|
-
await callHTTPFunction("userSimManagerService", {
|
|
6000
|
-
action: "abortMission",
|
|
6001
|
-
conversationId,
|
|
6002
|
-
uid: config.uid,
|
|
6003
|
-
email: config.email
|
|
6004
|
-
});
|
|
6005
|
-
console.log(GREEN5(" \u2705 Simulation aborted."));
|
|
6006
|
-
} catch {
|
|
6007
|
-
}
|
|
6008
|
-
rl.close();
|
|
6009
|
-
process.exit(0);
|
|
6570
|
+
const isGlobal = options.global || false;
|
|
6571
|
+
let sourceDir;
|
|
6572
|
+
let displayName;
|
|
6573
|
+
if (isGlobal) {
|
|
6574
|
+
sourceDir = BOB_DIR4;
|
|
6575
|
+
displayName = "~/.bob/ (global)";
|
|
6576
|
+
} else {
|
|
6577
|
+
sourceDir = getProjectBackupDir(projectName);
|
|
6578
|
+
displayName = `~/.bob/projects/${projectName}/`;
|
|
6010
6579
|
}
|
|
6011
|
-
await
|
|
6580
|
+
await runBackup({
|
|
6581
|
+
config,
|
|
6582
|
+
projectName,
|
|
6583
|
+
isGlobal,
|
|
6584
|
+
archiveName: options.archive,
|
|
6585
|
+
sourceDir,
|
|
6586
|
+
displayName
|
|
6587
|
+
});
|
|
6012
6588
|
});
|
|
6013
|
-
|
|
6014
|
-
|
|
6589
|
+
backupCmd.command("list").description("List all backup revisions and named archives").option("--global", "Show global backup revisions").option("--source", "Show source code backup revisions").option("--file <path>", "Show revisions for a specific file (use with --source)").action(async (options) => {
|
|
6590
|
+
const config = getConfig();
|
|
6591
|
+
if (!requireAuth(config)) return;
|
|
6592
|
+
const isGlobal = options.global || false;
|
|
6593
|
+
const isSource = options.source || false;
|
|
6594
|
+
const projectName = getCurrentProjectName();
|
|
6595
|
+
const filePath = options.file ? normalizeFilePath(options.file) : null;
|
|
6596
|
+
const spinner = ora11({ text: CYAN5(" Loading backup history..."), spinner: "dots" }).start();
|
|
6015
6597
|
try {
|
|
6016
|
-
const
|
|
6017
|
-
|
|
6018
|
-
|
|
6598
|
+
const result = await callCloudFunction("getCLIBackupStatus", {
|
|
6599
|
+
projectName,
|
|
6600
|
+
isGlobal,
|
|
6601
|
+
isSource,
|
|
6602
|
+
filePath
|
|
6019
6603
|
});
|
|
6020
|
-
|
|
6021
|
-
const
|
|
6022
|
-
|
|
6023
|
-
|
|
6024
|
-
|
|
6025
|
-
|
|
6604
|
+
spinner.stop();
|
|
6605
|
+
const { storage, retention, lastBackedUpAt, versions, archives } = result;
|
|
6606
|
+
const scopeLabel = isGlobal ? "\u{1F310} Global" : isSource ? filePath ? `\u{1F4C4} ${filePath}` : `\u{1F4BE} Source: ${projectName}` : `\u{1F4C1} ${projectName}`;
|
|
6607
|
+
console.log("");
|
|
6608
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
6609
|
+
console.log(BORDER10(" \u2551") + BRAND(" \u2601\uFE0F Bob Backup Status ") + BORDER10("\u2551"));
|
|
6610
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6611
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Scope: ${scopeLabel}`));
|
|
6612
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Storage: ${storage.usedGB.toFixed(2)} GB / ${storage.totalGB} GB (${storage.usedPercent}% used)`));
|
|
6613
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Retention: ${retention.months} months`));
|
|
6614
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Archives: ${retention.usedArchiveSlots}/${retention.archiveSlots} slots used`));
|
|
6615
|
+
if (lastBackedUpAt) {
|
|
6616
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Last backup: ${new Date(lastBackedUpAt).toLocaleString()}`));
|
|
6617
|
+
}
|
|
6618
|
+
if (versions.length > 0) {
|
|
6619
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6620
|
+
console.log(BORDER10(" \u2551") + CYAN5(" Revisions"));
|
|
6621
|
+
console.log(BORDER10(" \u2551") + GRAY5(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6622
|
+
for (const v of versions) {
|
|
6623
|
+
const date = new Date(v.lastModified).toLocaleString();
|
|
6624
|
+
const label = v.isLatest ? GREEN5(` ${v.label.padEnd(10)}`) : GRAY5(` ${v.label.padEnd(10)}`);
|
|
6625
|
+
console.log(BORDER10(" \u2551") + `${label} ${GRAY5(formatBytes(v.sizeBytes).padEnd(10))} ${GRAY5(date)}`);
|
|
6026
6626
|
}
|
|
6027
|
-
}
|
|
6028
|
-
|
|
6029
|
-
|
|
6030
|
-
|
|
6031
|
-
if (
|
|
6032
|
-
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6039
|
-
|
|
6040
|
-
if (messages.length > 0) {
|
|
6041
|
-
renderHUD(hudState.sat, hudState.target, hudState.stag, hudState.stagTarget, hudState.div, hudState.divTarget, hudState.grading);
|
|
6627
|
+
} else {
|
|
6628
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6629
|
+
console.log(BORDER10(" \u2551") + GRAY5(` No revisions found.`));
|
|
6630
|
+
}
|
|
6631
|
+
if (archives.length > 0) {
|
|
6632
|
+
console.log(BORDER10(" \u2560\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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2563"));
|
|
6633
|
+
console.log(BORDER10(" \u2551") + AMBER6(" Named Archives"));
|
|
6634
|
+
console.log(BORDER10(" \u2551") + GRAY5(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6635
|
+
for (const a of archives) {
|
|
6636
|
+
const date = new Date(a.createdAt).toLocaleString();
|
|
6637
|
+
const expires = new Date(a.expiresAt).toLocaleString();
|
|
6638
|
+
console.log(BORDER10(" \u2551") + ` \u{1F4CC} ${AMBER6(a.name.padEnd(24))} ${GRAY5(formatBytes(a.sizeGB * 1024 * 1024 * 1024))}`);
|
|
6639
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Created: ${date} | Expires: ${expires}`));
|
|
6042
6640
|
}
|
|
6043
|
-
console.log("");
|
|
6044
|
-
console.log(AMBER6(` \u{1F3C1} Simulation ended: ${state.simulationStatus || "INACTIVE"}`));
|
|
6045
|
-
console.log("");
|
|
6046
|
-
running = false;
|
|
6047
|
-
break;
|
|
6048
6641
|
}
|
|
6049
|
-
|
|
6050
|
-
renderHUD(hudState.sat, hudState.target, hudState.stag, hudState.stagTarget, hudState.div, hudState.divTarget, hudState.grading);
|
|
6051
|
-
}
|
|
6052
|
-
} catch (e) {
|
|
6053
|
-
console.log(RED5(` \u274C Poll error: ${e.message}`));
|
|
6054
|
-
}
|
|
6055
|
-
}
|
|
6056
|
-
rl.close();
|
|
6057
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
6058
|
-
}
|
|
6059
|
-
async function runLocalSimulation(config, dnaString, mission, params) {
|
|
6060
|
-
writeSessionFile({ active: true, turns: 0, mission });
|
|
6061
|
-
let running = true;
|
|
6062
|
-
let turns = 0;
|
|
6063
|
-
let conversationHistory = [];
|
|
6064
|
-
let sat = 0;
|
|
6065
|
-
let stalemateCurrent = 0;
|
|
6066
|
-
let divergenceCurrent = 0;
|
|
6067
|
-
let lastStatus = "";
|
|
6068
|
-
const sigintHandler = () => {
|
|
6069
|
-
running = false;
|
|
6070
|
-
writeSessionFile({ active: false });
|
|
6071
|
-
clearSessionFile();
|
|
6072
|
-
console.log("\n" + AMBER6(" \u{1F6D1} Simulation stopped."));
|
|
6073
|
-
process.exit(0);
|
|
6074
|
-
};
|
|
6075
|
-
process.on("SIGINT", sigintHandler);
|
|
6076
|
-
const rl = readline8.createInterface({ input: process.stdin, output: process.stdout, terminal: true });
|
|
6077
|
-
rl.setPrompt("");
|
|
6078
|
-
rl.on("line", (line) => {
|
|
6079
|
-
const t = line.trim();
|
|
6080
|
-
if (t === "/abort" || t === "abort") {
|
|
6081
|
-
running = false;
|
|
6082
|
-
writeSessionFile({ active: false });
|
|
6083
|
-
clearSessionFile();
|
|
6084
|
-
console.log(AMBER6(" \u{1F6D1} Simulation stopped."));
|
|
6085
|
-
rl.close();
|
|
6086
|
-
process.exit(0);
|
|
6087
|
-
}
|
|
6088
|
-
if (t.startsWith("/set ")) {
|
|
6089
|
-
const m = t.match(/^\/set\s+(grading|target|stag|div)\s+(\d+)$/i);
|
|
6090
|
-
if (m) {
|
|
6091
|
-
const val = parseInt(m[2], 10);
|
|
6092
|
-
if (m[1] === "grading") params.grading = val;
|
|
6093
|
-
if (m[1] === "target") params.target = val;
|
|
6094
|
-
if (m[1] === "stag") params.stag = val;
|
|
6095
|
-
if (m[1] === "div") params.div = val;
|
|
6096
|
-
console.log(GREEN5(` \u2705 ${m[1]} updated to ${val} (local)`));
|
|
6097
|
-
}
|
|
6098
|
-
}
|
|
6099
|
-
if (t === "/status") {
|
|
6642
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6100
6643
|
console.log("");
|
|
6101
|
-
console.log(
|
|
6102
|
-
console.log(GRAY5(
|
|
6103
|
-
console.log(GRAY5(
|
|
6644
|
+
console.log(GRAY5(" Commands:"));
|
|
6645
|
+
console.log(GRAY5(" bob backup create \u2014 Context backup"));
|
|
6646
|
+
console.log(GRAY5(" bob backup create --source \u2014 Full source backup"));
|
|
6647
|
+
console.log(GRAY5(" bob backup create --source --file <path> \u2014 Single file backup"));
|
|
6648
|
+
console.log(GRAY5(" bob backup create --global \u2014 Full ~/.bob/ (Grid)"));
|
|
6649
|
+
console.log(GRAY5(" bob backup restore \u2014 Restore context"));
|
|
6650
|
+
console.log(GRAY5(" bob backup restore --source \u2014 Restore source"));
|
|
6651
|
+
console.log(GRAY5(" bob backup restore --source --file <path> \u2014 Restore single file"));
|
|
6104
6652
|
console.log("");
|
|
6653
|
+
} catch (error) {
|
|
6654
|
+
spinner.stop();
|
|
6655
|
+
handleBackupError(error);
|
|
6105
6656
|
}
|
|
6106
6657
|
});
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6110
|
-
|
|
6111
|
-
|
|
6112
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
Mission: ${mission}
|
|
6118
|
-
|
|
6119
|
-
After each Bob response, evaluate it 0-100 on mission alignment. Reply with your reaction, then append exactly one JSON footer on its own line:
|
|
6120
|
-
{"satisfactionScore": <0-100>, "status": "CONVERGING|STAGNATING|DIVERGING"}`;
|
|
6121
|
-
console.log(BORDER10(" \u2500\u2500\u2500 LIVE LOCAL SIMULATION \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6122
|
-
console.log(GRAY5(" Bob and UserBob will converse autonomously below."));
|
|
6123
|
-
console.log(GRAY5(" Commands:"));
|
|
6124
|
-
console.log(AMBER6(" /abort") + GRAY5(" \u2014 Stop the simulation"));
|
|
6125
|
-
console.log(AMBER6(" /set target 90") + GRAY5(" \u2014 Update satisfaction target"));
|
|
6126
|
-
console.log(AMBER6(" /set grading 70") + GRAY5(" \u2014 Update Teacher's Curve"));
|
|
6127
|
-
console.log(AMBER6(" /set stag 5") + GRAY5(" \u2014 Update stalemate threshold"));
|
|
6128
|
-
console.log(AMBER6(" /set div 3") + GRAY5(" \u2014 Update divergence threshold"));
|
|
6129
|
-
console.log(AMBER6(" /status") + GRAY5(" \u2014 Show current parameters"));
|
|
6130
|
-
console.log(BORDER10(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6131
|
-
console.log("");
|
|
6132
|
-
const kickstart = `Mission received: "${mission}". Bob, what's your first move?`;
|
|
6133
|
-
console.log(PURPLE(" UserBob > ") + WHITE3(kickstart));
|
|
6134
|
-
conversationHistory.push({ role: "user", content: kickstart });
|
|
6135
|
-
while (running) {
|
|
6136
|
-
const session = readSessionFile();
|
|
6137
|
-
if (!session?.active) {
|
|
6138
|
-
running = false;
|
|
6139
|
-
break;
|
|
6140
|
-
}
|
|
6141
|
-
turns++;
|
|
6658
|
+
backupCmd.command("restore").description("Restore from a backup revision or named archive").option("--global", "Restore from global backup").option("--source", "Restore source code backup").option("--file <path>", "Restore a single file (use with --source)").action(async (options) => {
|
|
6659
|
+
const config = getConfig();
|
|
6660
|
+
if (!requireAuth(config)) return;
|
|
6661
|
+
const isGlobal = options.global || false;
|
|
6662
|
+
const isSource = options.source || false;
|
|
6663
|
+
const projectName = getCurrentProjectName();
|
|
6664
|
+
const projectDir = process.cwd();
|
|
6665
|
+
const filePath = options.file ? normalizeFilePath(options.file) : null;
|
|
6666
|
+
const spinner = ora11({ text: CYAN5(" Loading available backups..."), spinner: "dots" }).start();
|
|
6667
|
+
let statusResult;
|
|
6142
6668
|
try {
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
const ubResponse = await callLocalModel(config.localEndpoint, ubMessages);
|
|
6155
|
-
const jsonMatch = ubResponse.match(/\{[^}]*"satisfactionScore"[^}]*\}/);
|
|
6156
|
-
const cleanResponse = ubResponse.replace(/\{[^}]*"satisfactionScore"[^}]*\}/, "").trim();
|
|
6157
|
-
console.log(PURPLE(" UserBob > ") + WHITE3(cleanResponse));
|
|
6158
|
-
let auditChips = [];
|
|
6159
|
-
if (jsonMatch) {
|
|
6160
|
-
try {
|
|
6161
|
-
const audit = JSON.parse(jsonMatch[0]);
|
|
6162
|
-
const rawScore = audit.satisfactionScore || 0;
|
|
6163
|
-
sat = Math.round(rawScore * (params.grading / 100));
|
|
6164
|
-
lastStatus = audit.status || "";
|
|
6165
|
-
auditChips = [CYAN5(`[SAT: ${sat}%]`), BLUE4(`[RAW: ${rawScore}]`), GRAY5(`[${lastStatus}]`)];
|
|
6166
|
-
if (lastStatus === "STAGNATING") {
|
|
6167
|
-
stalemateCurrent++;
|
|
6168
|
-
if (params.stag > 0 && stalemateCurrent >= params.stag) {
|
|
6169
|
-
console.log(" " + auditChips.join(" "));
|
|
6170
|
-
renderHUD(sat, params.target, stalemateCurrent, params.stag, divergenceCurrent, params.div, params.grading);
|
|
6171
|
-
console.log(AMBER6(` \u{1F3C1} Stalemate threshold reached (${stalemateCurrent}/${params.stag}). Simulation ended.`));
|
|
6172
|
-
running = false;
|
|
6173
|
-
break;
|
|
6174
|
-
}
|
|
6175
|
-
} else if (lastStatus === "DIVERGING") {
|
|
6176
|
-
divergenceCurrent++;
|
|
6177
|
-
stalemateCurrent = 0;
|
|
6178
|
-
if (params.div > 0 && divergenceCurrent >= params.div) {
|
|
6179
|
-
console.log(" " + auditChips.join(" "));
|
|
6180
|
-
renderHUD(sat, params.target, stalemateCurrent, params.stag, divergenceCurrent, params.div, params.grading);
|
|
6181
|
-
console.log(AMBER6(` \u{1F3C1} Divergence threshold reached (${divergenceCurrent}/${params.div}). Simulation ended.`));
|
|
6182
|
-
running = false;
|
|
6183
|
-
break;
|
|
6184
|
-
}
|
|
6185
|
-
} else if (lastStatus === "CONVERGING") {
|
|
6186
|
-
stalemateCurrent = 0;
|
|
6187
|
-
divergenceCurrent = 0;
|
|
6188
|
-
}
|
|
6189
|
-
} catch {
|
|
6190
|
-
}
|
|
6191
|
-
}
|
|
6192
|
-
if (auditChips.length) console.log(" " + auditChips.join(" "));
|
|
6193
|
-
conversationHistory.push({ role: "user", content: ubResponse });
|
|
6194
|
-
writeSessionFile({ active: true, turns, mission, sat });
|
|
6195
|
-
renderHUD(sat, params.target, stalemateCurrent, params.stag, divergenceCurrent, params.div, params.grading);
|
|
6196
|
-
if (sat >= params.target) {
|
|
6197
|
-
console.log(GREEN5(` \u{1F3AF} Target satisfaction ${params.target}% reached! Mission complete.`));
|
|
6198
|
-
running = false;
|
|
6199
|
-
break;
|
|
6200
|
-
}
|
|
6201
|
-
await new Promise((r) => setTimeout(r, 1e3));
|
|
6202
|
-
} catch (e) {
|
|
6203
|
-
console.log(RED5(` \u274C Local model error: ${e.message}`));
|
|
6204
|
-
console.log(GRAY5(" Retrying in 3 seconds..."));
|
|
6205
|
-
await new Promise((r) => setTimeout(r, 3e3));
|
|
6669
|
+
statusResult = await callCloudFunction("getCLIBackupStatus", {
|
|
6670
|
+
projectName,
|
|
6671
|
+
isGlobal,
|
|
6672
|
+
isSource,
|
|
6673
|
+
filePath
|
|
6674
|
+
});
|
|
6675
|
+
spinner.stop();
|
|
6676
|
+
} catch (error) {
|
|
6677
|
+
spinner.stop();
|
|
6678
|
+
handleBackupError(error);
|
|
6679
|
+
return;
|
|
6206
6680
|
}
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
rl.close();
|
|
6210
|
-
process.removeListener("SIGINT", sigintHandler);
|
|
6211
|
-
console.log("");
|
|
6212
|
-
console.log(GRAY5(` Session complete. ${turns} turns processed.`));
|
|
6213
|
-
console.log("");
|
|
6214
|
-
}
|
|
6215
|
-
function registerUserBobCommand(program2) {
|
|
6216
|
-
program2.command("userbob [mission...]").description("Launch your UserBob digital twin simulation").option("--local", "Force local Ollama mode (Tier 1)").option("--target <number>", "Satisfaction target (default: 85)", "85").option("--grading <number>", "Teacher's curve grading standard (default: 50)", "50").option("--stag <number>", "Stalemate threshold \u2014 0 = infinite (default: 0)", "0").option("--div <number>", "Divergence threshold \u2014 0 = infinite (default: 0)", "0").option("--resume", "Resume without injecting a new mission note").action(async (missionArgs, options) => {
|
|
6217
|
-
const config = getConfig();
|
|
6218
|
-
const params = {
|
|
6219
|
-
target: parseInt(options.target, 10),
|
|
6220
|
-
grading: parseInt(options.grading, 10),
|
|
6221
|
-
stag: parseInt(options.stag, 10),
|
|
6222
|
-
div: parseInt(options.div, 10)
|
|
6223
|
-
};
|
|
6224
|
-
const usePlatform = !options.local && isAuthenticated();
|
|
6225
|
-
console.log("");
|
|
6226
|
-
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
6227
|
-
console.log(BORDER10(" \u2551") + PURPLE(" \u{1F916} UserBob \u2014 Digital Twin Simulation"));
|
|
6228
|
-
console.log(BORDER10(" \u2551") + GRAY5(` Mode: ${usePlatform ? "Platform (Tier 3)" : "Local Ollama (Tier 1)"}`));
|
|
6229
|
-
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6230
|
-
console.log("");
|
|
6231
|
-
const dna = buildDNAString();
|
|
6232
|
-
if (dna) {
|
|
6233
|
-
console.log(GREEN5(" \u2705 Behavioral DNA loaded."));
|
|
6234
|
-
} else {
|
|
6235
|
-
console.log(AMBER6(" \u26A0\uFE0F No behavioral profile found."));
|
|
6236
|
-
console.log(GRAY5(" UserBob performs significantly better with your DNA loaded."));
|
|
6681
|
+
const { versions, archives } = statusResult;
|
|
6682
|
+
if (versions.length === 0 && archives.length === 0) {
|
|
6237
6683
|
console.log("");
|
|
6238
|
-
const
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
|
|
6242
|
-
console.log("");
|
|
6243
|
-
console.log(GRAY5(" Run `bob profile --today` in a separate terminal, then re-run `bob userbob`."));
|
|
6244
|
-
process.exit(0);
|
|
6245
|
-
} else {
|
|
6246
|
-
console.log("");
|
|
6247
|
-
console.log(RED5(" \u26A0\uFE0F Running in Generic Mode \u2014 no behavioral profile loaded."));
|
|
6248
|
-
console.log(RED5(" UserBob will respond using project context only."));
|
|
6249
|
-
console.log(RED5(" Responses won't reflect your personal communication style,"));
|
|
6250
|
-
console.log(RED5(" decision patterns, or engineering philosophy."));
|
|
6251
|
-
console.log(GRAY5(" Run `bob profile --today` anytime to unlock full personalization."));
|
|
6252
|
-
console.log("");
|
|
6253
|
-
}
|
|
6684
|
+
const scopeMsg = isSource ? filePath ? `file "${filePath}"` : `source of ${projectName}` : isGlobal ? "global" : projectName;
|
|
6685
|
+
console.log(AMBER6(` \u26A0\uFE0F No backups found for ${scopeMsg}.`));
|
|
6686
|
+
console.log("");
|
|
6687
|
+
return;
|
|
6254
6688
|
}
|
|
6255
|
-
|
|
6256
|
-
if (
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
|
|
6261
|
-
|
|
6262
|
-
|
|
6689
|
+
const choices = [];
|
|
6690
|
+
if (versions.length > 0) {
|
|
6691
|
+
choices.push(new inquirer.Separator(CYAN5(" \u2500\u2500 Revisions \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")));
|
|
6692
|
+
for (const v of versions) {
|
|
6693
|
+
const date = new Date(v.lastModified).toLocaleString();
|
|
6694
|
+
choices.push({
|
|
6695
|
+
name: ` ${v.isLatest ? GREEN5("\u25CF ") : " "}${v.label.padEnd(12)} ${GRAY5(formatBytes(v.sizeBytes).padEnd(10))} ${GRAY5(date)}`,
|
|
6696
|
+
value: { type: "revision", versionId: v.versionId, label: v.label }
|
|
6697
|
+
});
|
|
6263
6698
|
}
|
|
6264
|
-
mission = mission.trim();
|
|
6265
6699
|
}
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
process.exit(1);
|
|
6274
|
-
}
|
|
6275
|
-
if (options.resume) {
|
|
6276
|
-
console.log(AMBER6(" \u{1F504} Resuming simulation (no new mission note)..."));
|
|
6277
|
-
await callHTTPFunction("userSimManagerService", {
|
|
6278
|
-
action: "resumeMission",
|
|
6279
|
-
conversationId,
|
|
6280
|
-
uid: config.uid,
|
|
6281
|
-
email: config.email
|
|
6700
|
+
if (archives.length > 0) {
|
|
6701
|
+
choices.push(new inquirer.Separator(AMBER6(" \u2500\u2500 Named Archives \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500")));
|
|
6702
|
+
for (const a of archives) {
|
|
6703
|
+
const date = new Date(a.createdAt).toLocaleString();
|
|
6704
|
+
choices.push({
|
|
6705
|
+
name: ` \u{1F4CC} ${AMBER6(a.name.padEnd(24))} ${GRAY5(date)}`,
|
|
6706
|
+
value: { type: "archive", archiveId: a.archiveId, name: a.name }
|
|
6282
6707
|
});
|
|
6283
|
-
console.log(GREEN5(" \u2705 Simulation resumed. Entering watch mode..."));
|
|
6284
|
-
console.log("");
|
|
6285
|
-
await runPlatformSimulation(config, conversationId, mission || "Resumed session", params);
|
|
6286
|
-
} else {
|
|
6287
|
-
await runPlatformSimulation(config, conversationId, mission, params);
|
|
6288
6708
|
}
|
|
6289
|
-
return;
|
|
6290
6709
|
}
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
}
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
}
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
var RED6 = chalk25.hex("#EF5350");
|
|
6307
|
-
var BLUE5 = chalk25.hex("#42A5F5");
|
|
6308
|
-
var PURPLE2 = chalk25.hex("#AB47BC");
|
|
6309
|
-
var CYAN6 = chalk25.hex("#26C6DA");
|
|
6310
|
-
var TEAL = chalk25.hex("#26A69A");
|
|
6311
|
-
var GRAY6 = chalk25.gray;
|
|
6312
|
-
var WHITE4 = chalk25.white;
|
|
6313
|
-
var BORDER11 = chalk25.hex("#455A64");
|
|
6314
|
-
function statusColor(status) {
|
|
6315
|
-
switch (status) {
|
|
6316
|
-
case "queued":
|
|
6317
|
-
return GRAY6;
|
|
6318
|
-
case "awaiting_approval":
|
|
6319
|
-
return AMBER7;
|
|
6320
|
-
case "in_progress":
|
|
6321
|
-
return BLUE5;
|
|
6322
|
-
case "completed":
|
|
6323
|
-
return GREEN6;
|
|
6324
|
-
case "failed":
|
|
6325
|
-
return RED6;
|
|
6326
|
-
case "denied":
|
|
6327
|
-
return RED6;
|
|
6328
|
-
default:
|
|
6329
|
-
return GRAY6;
|
|
6330
|
-
}
|
|
6331
|
-
}
|
|
6332
|
-
function statusLabel(status) {
|
|
6333
|
-
switch (status) {
|
|
6334
|
-
case "queued":
|
|
6335
|
-
return "QUEUED";
|
|
6336
|
-
case "awaiting_approval":
|
|
6337
|
-
return "NEEDS APPROVAL";
|
|
6338
|
-
case "in_progress":
|
|
6339
|
-
return "IN PROGRESS";
|
|
6340
|
-
case "completed":
|
|
6341
|
-
return "COMPLETE";
|
|
6342
|
-
case "failed":
|
|
6343
|
-
return "FAILED";
|
|
6344
|
-
case "denied":
|
|
6345
|
-
return "DENIED";
|
|
6346
|
-
default:
|
|
6347
|
-
return status.toUpperCase();
|
|
6348
|
-
}
|
|
6349
|
-
}
|
|
6350
|
-
function categoryColor(category) {
|
|
6351
|
-
const map = {
|
|
6352
|
-
security: RED6,
|
|
6353
|
-
frontend: BLUE5,
|
|
6354
|
-
backend: GREEN6,
|
|
6355
|
-
cloud_functions: PURPLE2,
|
|
6356
|
-
documentation: TEAL,
|
|
6357
|
-
testing: AMBER7,
|
|
6358
|
-
review: CYAN6,
|
|
6359
|
-
fullstack: ORANGE2
|
|
6360
|
-
};
|
|
6361
|
-
return map[category] || GRAY6;
|
|
6362
|
-
}
|
|
6363
|
-
function confidenceColor(confidence) {
|
|
6364
|
-
if (confidence >= 80) return GREEN6;
|
|
6365
|
-
if (confidence >= 50) return AMBER7;
|
|
6366
|
-
return RED6;
|
|
6367
|
-
}
|
|
6368
|
-
function formatTimestamp(ms) {
|
|
6369
|
-
if (!ms) return "\u2014";
|
|
6370
|
-
return new Date(ms).toLocaleString("en-US", {
|
|
6371
|
-
month: "short",
|
|
6372
|
-
day: "numeric",
|
|
6373
|
-
hour: "numeric",
|
|
6374
|
-
minute: "2-digit",
|
|
6375
|
-
hour12: true
|
|
6376
|
-
});
|
|
6377
|
-
}
|
|
6378
|
-
function stripMarkdown2(text) {
|
|
6379
|
-
return text.replace(/\*\*(.+?)\*\*/g, "$1").replace(/\*(.+?)\*/g, "$1").replace(/^#{1,6}\s+/gm, "").replace(/^---+$/gm, "").replace(/`([^`]+)`/g, "$1").replace(/^\s*[-*+]\s+/gm, " \u2022 ").replace(/\n{3,}/g, "\n\n").trim();
|
|
6380
|
-
}
|
|
6381
|
-
function renderStats(stats) {
|
|
6382
|
-
console.log("");
|
|
6383
|
-
console.log(BORDER11(" \u2500\u2500\u2500 COMMAND CENTER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6384
|
-
const parts = [];
|
|
6385
|
-
if (stats.awaiting_approval > 0) parts.push(AMBER7(`${stats.awaiting_approval} PENDING`));
|
|
6386
|
-
if (stats.in_progress > 0) parts.push(BLUE5(`${stats.in_progress} RUNNING`));
|
|
6387
|
-
if (stats.completed > 0) parts.push(GREEN6(`${stats.completed} DONE`));
|
|
6388
|
-
if (stats.failed > 0) parts.push(RED6(`${stats.failed} FAILED`));
|
|
6389
|
-
if (stats.denied > 0) parts.push(GRAY6(`${stats.denied} DENIED`));
|
|
6390
|
-
parts.push(GRAY6(`${stats.total} TOTAL`));
|
|
6391
|
-
console.log(" " + parts.join(GRAY6(" \u2502 ")));
|
|
6392
|
-
console.log(BORDER11(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6393
|
-
console.log("");
|
|
6394
|
-
}
|
|
6395
|
-
function renderTaskDetail(task) {
|
|
6396
|
-
const catColor = categoryColor(task.request.category);
|
|
6397
|
-
const confColor = confidenceColor(task.request.confidence);
|
|
6398
|
-
const sColor = statusColor(task.outcome.status);
|
|
6399
|
-
console.log("");
|
|
6400
|
-
console.log(BORDER11(" \u250C\u2500 TASK DETAIL \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510"));
|
|
6401
|
-
console.log(BORDER11(" \u2502"));
|
|
6402
|
-
console.log(BORDER11(" \u2502 ") + WHITE4(task.request.description));
|
|
6403
|
-
console.log(BORDER11(" \u2502"));
|
|
6404
|
-
console.log(BORDER11(" \u2502 ") + catColor(`[${task.request.category.toUpperCase()}]`) + " " + WHITE4(`${task.request.taskType.toUpperCase()}`) + " " + confColor(`${task.request.confidence}% confidence`) + " " + sColor(`\u25CF ${statusLabel(task.outcome.status)}`));
|
|
6405
|
-
if (task.request.targetFile) {
|
|
6406
|
-
console.log(BORDER11(" \u2502 ") + GRAY6("File: ") + CYAN6(task.request.targetFile));
|
|
6407
|
-
}
|
|
6408
|
-
console.log(BORDER11(" \u2502"));
|
|
6409
|
-
console.log(BORDER11(" \u2502 ") + GRAY6("Priority: ") + WHITE4(`P${task.priority}`) + GRAY6(" \u2502 Difficulty: ") + WHITE4(task.difficulty.toUpperCase()) + GRAY6(" \u2502 Created: ") + WHITE4(formatTimestamp(task.createdAt)));
|
|
6410
|
-
console.log(BORDER11(" \u2502"));
|
|
6411
|
-
console.log(BORDER11(" \u2502 ") + AMBER7("TRIGGER"));
|
|
6412
|
-
console.log(BORDER11(" \u2502 ") + GRAY6(stripMarkdown2(task.trigger.reasoning).slice(0, 120) + "..."));
|
|
6413
|
-
if (task.trigger.turnSatisfaction !== null) {
|
|
6414
|
-
console.log(BORDER11(" \u2502 ") + GRAY6("SAT at dispatch: ") + CYAN6(`${task.trigger.turnSatisfaction}%`));
|
|
6415
|
-
}
|
|
6416
|
-
if (task.outcome.resultSummary) {
|
|
6417
|
-
console.log(BORDER11(" \u2502"));
|
|
6418
|
-
console.log(BORDER11(" \u2502 ") + GREEN6("RESULT"));
|
|
6419
|
-
console.log(BORDER11(" \u2502 ") + GRAY6(stripMarkdown2(task.outcome.resultSummary).slice(0, 200) + "..."));
|
|
6420
|
-
}
|
|
6421
|
-
if (task.outcome.filesModified && task.outcome.filesModified.length > 0) {
|
|
6422
|
-
console.log(BORDER11(" \u2502"));
|
|
6423
|
-
console.log(BORDER11(" \u2502 ") + AMBER7("FILES MODIFIED"));
|
|
6424
|
-
for (const file of task.outcome.filesModified) {
|
|
6425
|
-
const icon = file.action === "created" ? GREEN6("+") : AMBER7("~");
|
|
6426
|
-
console.log(BORDER11(" \u2502 ") + icon + " " + CYAN6(file.path || file));
|
|
6427
|
-
}
|
|
6428
|
-
}
|
|
6429
|
-
if (task.outcome.error) {
|
|
6430
|
-
console.log(BORDER11(" \u2502"));
|
|
6431
|
-
console.log(BORDER11(" \u2502 ") + RED6("ERROR"));
|
|
6432
|
-
console.log(BORDER11(" \u2502 ") + RED6(task.outcome.error.slice(0, 150)));
|
|
6433
|
-
}
|
|
6434
|
-
if (task.isDenied && task.denyReason) {
|
|
6435
|
-
console.log(BORDER11(" \u2502"));
|
|
6436
|
-
console.log(BORDER11(" \u2502 ") + RED6("DENIED BY: ") + GRAY6(task.deniedBy || "Unknown"));
|
|
6437
|
-
console.log(BORDER11(" \u2502 ") + RED6("REASON: ") + GRAY6(task.denyReason));
|
|
6438
|
-
}
|
|
6439
|
-
if (task.outcome.turnsUsed || task.outcome.tokensConsumed) {
|
|
6440
|
-
console.log(BORDER11(" \u2502"));
|
|
6441
|
-
if (task.outcome.turnsUsed) {
|
|
6442
|
-
console.log(BORDER11(" \u2502 ") + GRAY6(`Turns: ${task.outcome.turnsUsed} \u2502 Tokens: ${task.outcome.tokensConsumed || 0} \u2502 Provider: ${task.outcome.provider || "unknown"}`));
|
|
6443
|
-
}
|
|
6444
|
-
}
|
|
6445
|
-
console.log(BORDER11(" \u2502"));
|
|
6446
|
-
console.log(BORDER11(" \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518"));
|
|
6447
|
-
console.log("");
|
|
6448
|
-
}
|
|
6449
|
-
async function runDecisionStream(conversationId) {
|
|
6450
|
-
console.log("");
|
|
6451
|
-
console.log(ORANGE2(" \u2500\u2500\u2500 DECISION STREAM \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6452
|
-
console.log(GRAY6(" Live feed of all autonomous decisions. Ctrl+C to exit.\n"));
|
|
6453
|
-
let lastCount = 0;
|
|
6454
|
-
let running = true;
|
|
6455
|
-
process.on("SIGINT", () => {
|
|
6456
|
-
running = false;
|
|
6457
|
-
console.log("\n" + AMBER7(" Stream ended."));
|
|
6458
|
-
process.exit(0);
|
|
6459
|
-
});
|
|
6460
|
-
while (running) {
|
|
6461
|
-
try {
|
|
6462
|
-
const response = await callCloudFunction("getCLIAutonomousTasks", { conversationId });
|
|
6463
|
-
const tasks = response?.tasks || [];
|
|
6464
|
-
if (tasks.length > lastCount) {
|
|
6465
|
-
const newTasks = tasks.slice(0, tasks.length - lastCount);
|
|
6466
|
-
for (const task of newTasks.reverse()) {
|
|
6467
|
-
const sColor = statusColor(task.outcome.status);
|
|
6468
|
-
const catColor = categoryColor(task.request.category);
|
|
6469
|
-
console.log(
|
|
6470
|
-
GRAY6(` ${formatTimestamp(task.createdAt)} `) + catColor(`[${task.request.category.toUpperCase()}]`) + " " + WHITE4(task.request.description.slice(0, 60) + (task.request.description.length > 60 ? "..." : "")) + " " + sColor(`\u25CF ${statusLabel(task.outcome.status)}`)
|
|
6471
|
-
);
|
|
6472
|
-
}
|
|
6473
|
-
lastCount = tasks.length;
|
|
6474
|
-
}
|
|
6475
|
-
} catch (e) {
|
|
6476
|
-
console.log(RED6(` \u274C Poll error: ${e.message}`));
|
|
6710
|
+
choices.push(new inquirer.Separator());
|
|
6711
|
+
choices.push({ name: GRAY5(" \u2190 Cancel"), value: null });
|
|
6712
|
+
console.log("");
|
|
6713
|
+
const scopeLabel = isSource ? filePath ? `file "${filePath}"` : `source: ${projectName}` : isGlobal ? "global" : projectName;
|
|
6714
|
+
const { selected } = await inquirer.prompt([{
|
|
6715
|
+
type: "select",
|
|
6716
|
+
name: "selected",
|
|
6717
|
+
message: CYAN5(` Select a backup to restore (${scopeLabel}):`),
|
|
6718
|
+
choices,
|
|
6719
|
+
pageSize: 12
|
|
6720
|
+
}]);
|
|
6721
|
+
if (!selected) {
|
|
6722
|
+
console.log(GRAY5(" Cancelled."));
|
|
6723
|
+
console.log("");
|
|
6724
|
+
return;
|
|
6477
6725
|
}
|
|
6478
|
-
|
|
6479
|
-
|
|
6480
|
-
|
|
6481
|
-
|
|
6482
|
-
|
|
6483
|
-
|
|
6484
|
-
|
|
6485
|
-
|
|
6486
|
-
console.log(RED6(` \u274C Failed to fetch settings: ${e.message}`));
|
|
6487
|
-
return;
|
|
6488
|
-
}
|
|
6489
|
-
const settings = response?.settings || {};
|
|
6490
|
-
console.log("");
|
|
6491
|
-
console.log(ORANGE2(" \u2500\u2500\u2500 AUTONOMY SETTINGS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6492
|
-
console.log(GRAY6(` Current threshold: ${settings.autonomousConfidenceThreshold ?? 75}%`));
|
|
6493
|
-
console.log(GRAY6(" Tasks below this confidence level require your approval."));
|
|
6494
|
-
console.log("");
|
|
6495
|
-
const { threshold } = await inquirer.prompt([
|
|
6496
|
-
{
|
|
6497
|
-
type: "number",
|
|
6498
|
-
name: "threshold",
|
|
6499
|
-
message: AMBER7(" Confidence threshold (0-100):"),
|
|
6500
|
-
default: settings.autonomousConfidenceThreshold ?? 75,
|
|
6501
|
-
validate: (val) => val >= 0 && val <= 100 ? true : "Must be 0-100"
|
|
6502
|
-
}
|
|
6503
|
-
]);
|
|
6504
|
-
const categories = ["security", "frontend", "backend", "cloud_functions", "documentation", "testing", "review", "fullstack"];
|
|
6505
|
-
const overrides = { ...settings.autonomousCategoryOverrides || {} };
|
|
6506
|
-
console.log("");
|
|
6507
|
-
console.log(GRAY6(" Category overrides:"));
|
|
6508
|
-
console.log("");
|
|
6509
|
-
for (const cat of categories) {
|
|
6510
|
-
const current = overrides[cat] || "threshold";
|
|
6511
|
-
const { override } = await inquirer.prompt([
|
|
6512
|
-
{
|
|
6513
|
-
type: "select",
|
|
6514
|
-
name: "override",
|
|
6515
|
-
message: categoryColor(cat)(` ${cat.padEnd(20)}`),
|
|
6516
|
-
default: current,
|
|
6517
|
-
choices: [
|
|
6518
|
-
{ name: GRAY6("Use Threshold"), value: "threshold" },
|
|
6519
|
-
{ name: GREEN6("Always Auto-Execute"), value: "autonomous" },
|
|
6520
|
-
{ name: RED6("Always Require Approval"), value: "approval_required" }
|
|
6521
|
-
]
|
|
6522
|
-
}
|
|
6523
|
-
]);
|
|
6524
|
-
if (override === "threshold") {
|
|
6525
|
-
delete overrides[cat];
|
|
6726
|
+
const label = selected.type === "archive" ? `archive "${selected.name}"` : selected.label;
|
|
6727
|
+
console.log("");
|
|
6728
|
+
if (isSource && filePath) {
|
|
6729
|
+
console.log(AMBER6(` \u26A0\uFE0F This will restore ${filePath} from ${label}.`));
|
|
6730
|
+
console.log(GRAY5(" Current file will be backed up to .bob-backups/ first."));
|
|
6731
|
+
} else if (isSource) {
|
|
6732
|
+
console.log(AMBER6(` \u26A0\uFE0F This will restore source code of ${projectName} from ${label}.`));
|
|
6733
|
+
console.log(GRAY5(" Current project will be backed up locally first."));
|
|
6526
6734
|
} else {
|
|
6527
|
-
|
|
6735
|
+
console.log(AMBER6(` \u26A0\uFE0F This will restore ${isGlobal ? "~/.bob/" : `~/.bob/projects/${projectName}/`} from ${label}.`));
|
|
6736
|
+
console.log(GRAY5(" Your current data will be backed up locally first."));
|
|
6528
6737
|
}
|
|
6529
|
-
}
|
|
6530
|
-
try {
|
|
6531
|
-
await callCloudFunction("updateCLIAutonomySettings", {
|
|
6532
|
-
conversationId,
|
|
6533
|
-
autonomousConfidenceThreshold: threshold,
|
|
6534
|
-
autonomousCategoryOverrides: overrides
|
|
6535
|
-
});
|
|
6536
6738
|
console.log("");
|
|
6537
|
-
|
|
6538
|
-
|
|
6539
|
-
|
|
6540
|
-
|
|
6541
|
-
|
|
6542
|
-
|
|
6543
|
-
|
|
6544
|
-
|
|
6545
|
-
async function runTaskBoard(conversationId) {
|
|
6546
|
-
let continueLoop = true;
|
|
6547
|
-
while (continueLoop) {
|
|
6548
|
-
let response;
|
|
6549
|
-
try {
|
|
6550
|
-
response = await callCloudFunction("getCLIAutonomousTasks", { conversationId });
|
|
6551
|
-
} catch (e) {
|
|
6552
|
-
console.log(RED6(` \u274C Failed to fetch tasks: ${e.message}`));
|
|
6553
|
-
return;
|
|
6554
|
-
}
|
|
6555
|
-
const tasks = response?.tasks || [];
|
|
6556
|
-
const stats = response?.stats || {};
|
|
6557
|
-
renderStats(stats);
|
|
6558
|
-
if (tasks.length === 0) {
|
|
6559
|
-
console.log(GRAY6(" No autonomous tasks found for this conversation."));
|
|
6560
|
-
console.log(GRAY6(" Tasks appear here when UserBob dispatches work to Mini Bob."));
|
|
6739
|
+
const { confirmed } = await inquirer.prompt([{
|
|
6740
|
+
type: "confirm",
|
|
6741
|
+
name: "confirmed",
|
|
6742
|
+
message: AMBER6(" Continue with restore?"),
|
|
6743
|
+
default: false
|
|
6744
|
+
}]);
|
|
6745
|
+
if (!confirmed) {
|
|
6746
|
+
console.log(GRAY5(" Cancelled."));
|
|
6561
6747
|
console.log("");
|
|
6562
6748
|
return;
|
|
6563
6749
|
}
|
|
6564
|
-
const
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6581
|
-
|
|
6582
|
-
|
|
6583
|
-
|
|
6584
|
-
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
|
|
6590
|
-
|
|
6591
|
-
|
|
6592
|
-
|
|
6593
|
-
|
|
6594
|
-
|
|
6595
|
-
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
|
|
6604
|
-
|
|
6605
|
-
|
|
6606
|
-
|
|
6607
|
-
|
|
6608
|
-
|
|
6609
|
-
const actions = [];
|
|
6610
|
-
if (selectedTask.outcome.status === "awaiting_approval") {
|
|
6611
|
-
actions.push({ name: GREEN6(" \u2705 Approve \u2014 execute this task now"), value: "approve", short: "Approve" });
|
|
6612
|
-
actions.push({ name: RED6(" \u274C Deny \u2014 reject this task"), value: "deny", short: "Deny" });
|
|
6613
|
-
}
|
|
6614
|
-
actions.push({ name: AMBER7(" \u21A9 Back to task list"), value: "back", short: "Back" });
|
|
6615
|
-
actions.push({ name: GRAY6(" \u2715 Exit Command Center"), value: "exit", short: "Exit" });
|
|
6616
|
-
const { action } = await inquirer.prompt([
|
|
6617
|
-
{
|
|
6618
|
-
type: "select",
|
|
6619
|
-
name: "action",
|
|
6620
|
-
message: ORANGE2(" What would you like to do?"),
|
|
6621
|
-
choices: actions
|
|
6622
|
-
}
|
|
6623
|
-
]);
|
|
6624
|
-
if (action === "approve") {
|
|
6625
|
-
const { confirmed } = await inquirer.prompt([
|
|
6626
|
-
{
|
|
6627
|
-
type: "confirm",
|
|
6628
|
-
name: "confirmed",
|
|
6629
|
-
message: AMBER7(" Approve task and execute autonomously?"),
|
|
6630
|
-
default: true
|
|
6750
|
+
const tmpDir = getTempDir2();
|
|
6751
|
+
const downloadPath = path13.join(tmpDir, "bob-backup.bob.enc");
|
|
6752
|
+
const decryptedPath = path13.join(tmpDir, "bob-backup.tar.gz");
|
|
6753
|
+
try {
|
|
6754
|
+
const dlSpinner = ora11({
|
|
6755
|
+
text: GRAY5(` Downloading ${label} ...`),
|
|
6756
|
+
spinner: "dots"
|
|
6757
|
+
}).start();
|
|
6758
|
+
let downloadAction;
|
|
6759
|
+
if (isSource) {
|
|
6760
|
+
downloadAction = selected.type === "archive" ? "requestSourceArchiveDownload" : "requestSourceDownload";
|
|
6761
|
+
} else {
|
|
6762
|
+
downloadAction = selected.type === "archive" ? "requestArchiveDownload" : "requestDownload";
|
|
6763
|
+
}
|
|
6764
|
+
const downloadResult = await callCloudFunction("cliBackupLicense", {
|
|
6765
|
+
action: downloadAction,
|
|
6766
|
+
projectName,
|
|
6767
|
+
isGlobal,
|
|
6768
|
+
isSource,
|
|
6769
|
+
filePath,
|
|
6770
|
+
s3VersionId: selected.type === "revision" ? selected.versionId : null,
|
|
6771
|
+
archiveId: selected.type === "archive" ? selected.archiveId : null
|
|
6772
|
+
});
|
|
6773
|
+
const response = await axios2.get(downloadResult.downloadUrl, {
|
|
6774
|
+
responseType: "arraybuffer",
|
|
6775
|
+
maxContentLength: Infinity
|
|
6776
|
+
});
|
|
6777
|
+
fs11.writeFileSync(downloadPath, Buffer.from(response.data));
|
|
6778
|
+
dlSpinner.succeed(GREEN5(` Downloading ${label} ...`));
|
|
6779
|
+
const decryptSpinner = ora11({ text: GRAY5(" Decrypting ..."), spinner: "dots" }).start();
|
|
6780
|
+
decrypt2(downloadPath, decryptedPath, config.uid);
|
|
6781
|
+
decryptSpinner.succeed(GREEN5(" Decrypting ..."));
|
|
6782
|
+
const backupSpinner = ora11({
|
|
6783
|
+
text: GRAY5(" Backing up current state ..."),
|
|
6784
|
+
spinner: "dots"
|
|
6785
|
+
}).start();
|
|
6786
|
+
let preRestoreBackup;
|
|
6787
|
+
if (isSource && filePath) {
|
|
6788
|
+
const absoluteFilePath = path13.resolve(projectDir, filePath);
|
|
6789
|
+
const backupDir = path13.join(projectDir, ".bob-backups");
|
|
6790
|
+
if (!fs11.existsSync(backupDir)) fs11.mkdirSync(backupDir, { recursive: true });
|
|
6791
|
+
const backupFileName = filePath.replace(/[\/\\]/g, "_") + `.${Date.now()}.bak`;
|
|
6792
|
+
preRestoreBackup = path13.join(backupDir, backupFileName);
|
|
6793
|
+
if (fs11.existsSync(absoluteFilePath)) {
|
|
6794
|
+
fs11.copyFileSync(absoluteFilePath, preRestoreBackup);
|
|
6631
6795
|
}
|
|
6632
|
-
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
console.log(GRAY6(" Mini Bob is executing. Streaming live logs...\n"));
|
|
6637
|
-
callCloudFunction("approveAutonomousTask", {
|
|
6638
|
-
conversationId,
|
|
6639
|
-
taskId: selectedTask.id,
|
|
6640
|
-
action: "approve"
|
|
6641
|
-
}).then(() => {
|
|
6642
|
-
}).catch((e) => {
|
|
6643
|
-
console.log(RED6(`
|
|
6644
|
-
\u274C Execution error: ${e.message}`));
|
|
6645
|
-
});
|
|
6646
|
-
let running = true;
|
|
6647
|
-
let seenLogIds = /* @__PURE__ */ new Set();
|
|
6648
|
-
let pollErrors = 0;
|
|
6649
|
-
const maxPollErrors = 5;
|
|
6650
|
-
const sigintHandler = () => {
|
|
6651
|
-
running = false;
|
|
6652
|
-
console.log("\n" + AMBER7(" Stream ended. Task continues in background."));
|
|
6653
|
-
process.exit(0);
|
|
6654
|
-
};
|
|
6655
|
-
process.on("SIGINT", sigintHandler);
|
|
6656
|
-
while (running) {
|
|
6657
|
-
await new Promise((r) => setTimeout(r, 2e3));
|
|
6658
|
-
try {
|
|
6659
|
-
const taskResponse = await callCloudFunction("getCLIAutonomousTasks", {
|
|
6660
|
-
conversationId,
|
|
6661
|
-
statusFilter: null
|
|
6662
|
-
});
|
|
6663
|
-
const updatedTask = taskResponse?.tasks?.find((t) => t.id === selectedTask.id);
|
|
6664
|
-
const logResponse = await callCloudFunction("getCLITaskExecutionLog", {
|
|
6665
|
-
conversationId,
|
|
6666
|
-
taskId: selectedTask.id
|
|
6667
|
-
});
|
|
6668
|
-
const logEntries = logResponse?.entries || [];
|
|
6669
|
-
for (const entry of logEntries) {
|
|
6670
|
-
if (seenLogIds.has(entry.id)) continue;
|
|
6671
|
-
seenLogIds.add(entry.id);
|
|
6672
|
-
const stage = entry.stage || "EXECUTION";
|
|
6673
|
-
let prefix;
|
|
6674
|
-
switch (stage) {
|
|
6675
|
-
case "INIT":
|
|
6676
|
-
prefix = CYAN6(" [INIT] ");
|
|
6677
|
-
break;
|
|
6678
|
-
case "TOOL_CALL":
|
|
6679
|
-
prefix = AMBER7(" [TOOL] ");
|
|
6680
|
-
break;
|
|
6681
|
-
case "FALLBACK":
|
|
6682
|
-
prefix = AMBER7(" [FALLBACK] ");
|
|
6683
|
-
break;
|
|
6684
|
-
case "COMPLETE":
|
|
6685
|
-
prefix = GREEN6(" [DONE] ");
|
|
6686
|
-
break;
|
|
6687
|
-
case "ERROR":
|
|
6688
|
-
prefix = RED6(" [ERROR] ");
|
|
6689
|
-
break;
|
|
6690
|
-
case "APPROVED":
|
|
6691
|
-
prefix = GREEN6(" [APPROVED] ");
|
|
6692
|
-
break;
|
|
6693
|
-
case "DENIED":
|
|
6694
|
-
prefix = RED6(" [DENIED] ");
|
|
6695
|
-
break;
|
|
6696
|
-
case "AWAITING_APPROVAL":
|
|
6697
|
-
prefix = AMBER7(" [PENDING] ");
|
|
6698
|
-
break;
|
|
6699
|
-
default:
|
|
6700
|
-
prefix = GRAY6(" [LOG] ");
|
|
6701
|
-
break;
|
|
6702
|
-
}
|
|
6703
|
-
console.log(prefix + WHITE4(entry.text));
|
|
6704
|
-
}
|
|
6705
|
-
if (updatedTask) {
|
|
6706
|
-
const status = updatedTask.outcome.status;
|
|
6707
|
-
if (status === "completed") {
|
|
6708
|
-
console.log("");
|
|
6709
|
-
console.log(GREEN6(" \u2705 Task complete!"));
|
|
6710
|
-
if (updatedTask.outcome.filesModified?.length > 0) {
|
|
6711
|
-
console.log("");
|
|
6712
|
-
console.log(AMBER7(" Files modified:"));
|
|
6713
|
-
for (const file of updatedTask.outcome.filesModified) {
|
|
6714
|
-
const icon = file.action === "created" ? GREEN6("+") : AMBER7("~");
|
|
6715
|
-
console.log(` ${icon} ${CYAN6(file.path || file)}`);
|
|
6716
|
-
}
|
|
6717
|
-
}
|
|
6718
|
-
if (updatedTask.outcome.resultSummary) {
|
|
6719
|
-
console.log("");
|
|
6720
|
-
console.log(AMBER7(" Summary:"));
|
|
6721
|
-
const summary = stripMarkdown2(updatedTask.outcome.resultSummary);
|
|
6722
|
-
const lines = summary.split("\n").slice(0, 8);
|
|
6723
|
-
for (const line of lines) {
|
|
6724
|
-
console.log(GRAY6(` ${line}`));
|
|
6725
|
-
}
|
|
6726
|
-
}
|
|
6727
|
-
if (updatedTask.outcome.turnsUsed) {
|
|
6728
|
-
console.log("");
|
|
6729
|
-
console.log(GRAY6(` Turns: ${updatedTask.outcome.turnsUsed} \u2502 Tokens: ${updatedTask.outcome.tokensConsumed || 0} \u2502 Provider: ${updatedTask.outcome.provider || "unknown"}`));
|
|
6730
|
-
}
|
|
6731
|
-
running = false;
|
|
6732
|
-
} else if (status === "failed") {
|
|
6733
|
-
console.log("");
|
|
6734
|
-
console.log(RED6(` \u274C Task failed: ${updatedTask.outcome.error || "Unknown error"}`));
|
|
6735
|
-
running = false;
|
|
6736
|
-
} else if (status === "denied") {
|
|
6737
|
-
console.log("");
|
|
6738
|
-
console.log(RED6(" \u274C Task was denied."));
|
|
6739
|
-
running = false;
|
|
6740
|
-
}
|
|
6741
|
-
}
|
|
6742
|
-
pollErrors = 0;
|
|
6743
|
-
} catch (e) {
|
|
6744
|
-
pollErrors++;
|
|
6745
|
-
if (pollErrors >= maxPollErrors) {
|
|
6746
|
-
console.log(RED6(` \u274C Lost connection after ${maxPollErrors} errors. Task continues in background.`));
|
|
6747
|
-
running = false;
|
|
6748
|
-
}
|
|
6749
|
-
}
|
|
6796
|
+
} else if (isSource) {
|
|
6797
|
+
preRestoreBackup = `${projectDir}-pre-restore-${Date.now()}`;
|
|
6798
|
+
if (fs11.existsSync(projectDir)) {
|
|
6799
|
+
fs11.cpSync(projectDir, preRestoreBackup, { recursive: true });
|
|
6750
6800
|
}
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
{
|
|
6757
|
-
type: "input",
|
|
6758
|
-
name: "reason",
|
|
6759
|
-
message: AMBER7(" Denial reason (optional):"),
|
|
6760
|
-
default: ""
|
|
6801
|
+
} else {
|
|
6802
|
+
const restoreTarget = isGlobal ? BOB_DIR4 : getProjectBackupDir(projectName);
|
|
6803
|
+
preRestoreBackup = `${restoreTarget}-pre-restore-${Date.now()}`;
|
|
6804
|
+
if (fs11.existsSync(restoreTarget)) {
|
|
6805
|
+
fs11.cpSync(restoreTarget, preRestoreBackup, { recursive: true });
|
|
6761
6806
|
}
|
|
6762
|
-
]);
|
|
6763
|
-
try {
|
|
6764
|
-
console.log(GRAY6(" Denying task..."));
|
|
6765
|
-
await callCloudFunction("approveAutonomousTask", {
|
|
6766
|
-
conversationId,
|
|
6767
|
-
taskId: selectedTask.id,
|
|
6768
|
-
action: "deny",
|
|
6769
|
-
reason: reason.trim() || null
|
|
6770
|
-
});
|
|
6771
|
-
console.log("");
|
|
6772
|
-
console.log(RED6(" \u274C Task denied."));
|
|
6773
|
-
console.log("");
|
|
6774
|
-
} catch (e) {
|
|
6775
|
-
console.log(RED6(` \u274C Failed to deny: ${e.message}`));
|
|
6776
6807
|
}
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6808
|
+
backupSpinner.succeed(
|
|
6809
|
+
GREEN5(" Backing up current state ...") + GRAY5(` \u2192 ${path13.basename(preRestoreBackup)}`)
|
|
6810
|
+
);
|
|
6811
|
+
const extractSpinner = ora11({ text: GRAY5(" Extracting ..."), spinner: "dots" }).start();
|
|
6812
|
+
const tar = await import("tar");
|
|
6813
|
+
if (isSource && filePath) {
|
|
6814
|
+
await tar.extract({ file: decryptedPath, cwd: projectDir });
|
|
6815
|
+
} else if (isSource) {
|
|
6816
|
+
const parentDir = path13.dirname(projectDir);
|
|
6817
|
+
await tar.extract({ file: decryptedPath, cwd: parentDir });
|
|
6818
|
+
} else {
|
|
6819
|
+
await tar.extract({ file: decryptedPath, cwd: os4.homedir() });
|
|
6820
|
+
}
|
|
6821
|
+
extractSpinner.succeed(GREEN5(" Extracting ..."));
|
|
6786
6822
|
console.log("");
|
|
6787
|
-
console.log(
|
|
6788
|
-
console.log(
|
|
6823
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
6824
|
+
console.log(BORDER10(" \u2551") + GREEN5(" \u2705 Restore complete. ") + BORDER10("\u2551"));
|
|
6825
|
+
if (isSource && filePath) {
|
|
6826
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Restored: ${filePath}`));
|
|
6827
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Backup: .bob-backups/${path13.basename(preRestoreBackup)}`));
|
|
6828
|
+
} else if (isSource) {
|
|
6829
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Restored: ${projectName}/ source`));
|
|
6830
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Backup: ${path13.basename(preRestoreBackup)}/`));
|
|
6831
|
+
} else {
|
|
6832
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Restored: ${isGlobal ? "~/.bob/" : `~/.bob/projects/${projectName}/`}`));
|
|
6833
|
+
console.log(BORDER10(" \u2551") + GRAY5(` Backup: ${path13.basename(preRestoreBackup)}`));
|
|
6834
|
+
}
|
|
6835
|
+
console.log(BORDER10(" \u2551") + GRAY5(` From: ${label}`));
|
|
6836
|
+
console.log(BORDER10(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6789
6837
|
console.log("");
|
|
6790
|
-
|
|
6791
|
-
}
|
|
6792
|
-
const config = getConfig();
|
|
6793
|
-
const conversationId = config.conversationId;
|
|
6794
|
-
if (!conversationId) {
|
|
6838
|
+
} catch (error) {
|
|
6795
6839
|
console.log("");
|
|
6796
|
-
console.log(
|
|
6797
|
-
console.log(
|
|
6840
|
+
console.log(RED5(` \u274C Restore failed: ${error.message}`));
|
|
6841
|
+
console.log(GRAY5(" Your original data was not modified."));
|
|
6798
6842
|
console.log("");
|
|
6799
|
-
|
|
6843
|
+
} finally {
|
|
6844
|
+
cleanupTemp2(tmpDir);
|
|
6800
6845
|
}
|
|
6846
|
+
});
|
|
6847
|
+
backupCmd.action(async () => {
|
|
6848
|
+
const config = getConfig();
|
|
6849
|
+
if (!requireAuth(config)) return;
|
|
6850
|
+
const projectName = getCurrentProjectName();
|
|
6851
|
+
console.log("");
|
|
6852
|
+
console.log(GRAY5(` Current project: ${projectName}`));
|
|
6853
|
+
console.log("");
|
|
6854
|
+
console.log(GRAY5(" Bob Backup commands:"));
|
|
6855
|
+
console.log("");
|
|
6856
|
+
console.log(GRAY5(" bob backup create \u2014 Context backup (Bob data)"));
|
|
6857
|
+
console.log(GRAY5(" bob backup create --source \u2014 Source code backup"));
|
|
6858
|
+
console.log(GRAY5(" bob backup create --source --file <path> \u2014 Single file backup"));
|
|
6859
|
+
console.log(GRAY5(' bob backup create --archive "name" \u2014 Named archive'));
|
|
6860
|
+
console.log(GRAY5(" bob backup create --global \u2014 Full ~/.bob/ (Grid only)"));
|
|
6861
|
+
console.log(GRAY5(" bob backup list \u2014 List context revisions"));
|
|
6862
|
+
console.log(GRAY5(" bob backup list --source \u2014 List source revisions"));
|
|
6863
|
+
console.log(GRAY5(" bob backup list --source --file <path> \u2014 List file revisions"));
|
|
6864
|
+
console.log(GRAY5(" bob backup restore \u2014 Restore context"));
|
|
6865
|
+
console.log(GRAY5(" bob backup restore --source \u2014 Restore source code"));
|
|
6866
|
+
console.log(GRAY5(" bob backup restore --source --file <path> \u2014 Restore single file"));
|
|
6867
|
+
console.log(GRAY5(" bob backup restore --global \u2014 Restore full ~/.bob/"));
|
|
6801
6868
|
console.log("");
|
|
6802
|
-
console.log(BORDER11(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557"));
|
|
6803
|
-
console.log(BORDER11(" \u2551") + ORANGE2(" \u25C9 AUTONOMOUS COMMAND CENTER"));
|
|
6804
|
-
console.log(BORDER11(" \u2551") + GRAY6(` Conversation: ${conversationId.slice(0, 24)}...`));
|
|
6805
|
-
console.log(BORDER11(" \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\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D"));
|
|
6806
|
-
if (options.stream) {
|
|
6807
|
-
await runDecisionStream(conversationId);
|
|
6808
|
-
return;
|
|
6809
|
-
}
|
|
6810
|
-
if (options.settings) {
|
|
6811
|
-
await runSettings(conversationId);
|
|
6812
|
-
return;
|
|
6813
|
-
}
|
|
6814
|
-
await runTaskBoard(conversationId);
|
|
6815
6869
|
});
|
|
6816
6870
|
}
|
|
6817
6871
|
|
|
6818
6872
|
// bin/bob.ts
|
|
6819
|
-
var BRAND_PRIMARY11 = chalk26.hex("#E66F24");
|
|
6820
|
-
var BRAND_SECONDARY15 = chalk26.hex("#FFAB00");
|
|
6821
|
-
var SUCCESS16 = chalk26.hex("#66BB6A");
|
|
6822
|
-
var INFO16 = chalk26.hex("#26C6DA");
|
|
6823
|
-
var MUTED16 = chalk26.hex("#78909C");
|
|
6824
|
-
var MODE_CONSULTANT9 = chalk26.hex("#AB47BC");
|
|
6825
6873
|
var program = new Command();
|
|
6826
|
-
program.name("bob").description("Bob's CLI \u2014 AI coding assistant and Forge orchestrator").version("0.
|
|
6827
|
-
program.option("-h, --help", "Print this usage information").on("option:help", () => {
|
|
6828
|
-
printCustomHelp();
|
|
6829
|
-
process.exit(0);
|
|
6830
|
-
});
|
|
6831
|
-
program.command("help").description("Display help for Bob's CLI").action(() => {
|
|
6832
|
-
printCustomHelp();
|
|
6833
|
-
});
|
|
6834
|
-
function printCustomHelp() {
|
|
6835
|
-
console.log("");
|
|
6836
|
-
console.log(BRAND_PRIMARY11(" \u25C9 Bob's CLI") + MUTED16(" \u2014 Your AI Engineering Partner, In Your Terminal."));
|
|
6837
|
-
console.log("");
|
|
6838
|
-
console.log(chalk26.white(" Common commands:"));
|
|
6839
|
-
console.log("");
|
|
6840
|
-
console.log(BRAND_SECONDARY15(' bob chat "message"'));
|
|
6841
|
-
console.log(MUTED16(" Chat with Bob \u2014 code-friendly engineering partner with file awareness."));
|
|
6842
|
-
console.log("");
|
|
6843
|
-
console.log(BRAND_SECONDARY15(' bob consult "message"'));
|
|
6844
|
-
console.log(MUTED16(" Strategic advice only \u2014 no code, just architectural guidance."));
|
|
6845
|
-
console.log("");
|
|
6846
|
-
console.log(BRAND_SECONDARY15(" bob index"));
|
|
6847
|
-
console.log(MUTED16(" Index your project \u2014 generates summaries and dependency map for context."));
|
|
6848
|
-
console.log("");
|
|
6849
|
-
console.log(chalk26.white(" Usage: ") + INFO16("bob <command> [arguments]"));
|
|
6850
|
-
console.log("");
|
|
6851
|
-
console.log(chalk26.white(" Global options:"));
|
|
6852
|
-
console.log(MUTED16(" -h, --help Print this usage information."));
|
|
6853
|
-
console.log(MUTED16(" -V, --version Output the version number."));
|
|
6854
|
-
console.log("");
|
|
6855
|
-
console.log(chalk26.white(" Available commands:"));
|
|
6856
|
-
console.log("");
|
|
6857
|
-
console.log(INFO16(" Conversation"));
|
|
6858
|
-
printCmd("chat [message]", "Chat with Bob \u2014 code-friendly engineering partner");
|
|
6859
|
-
printCmd("consult [message]", "Strategic advice only, no code output");
|
|
6860
|
-
printCmd("conversations", "List, search, and join existing conversations");
|
|
6861
|
-
printCmd("fork <title>", "Branch conversation into a focused sub-project");
|
|
6862
|
-
printCmd("forks", "List all forks of the current conversation");
|
|
6863
|
-
printCmd("deepdive", "Sandboxed exploration on a specific Bob message");
|
|
6864
|
-
printCmd("deepdives", "List all deep dives in the current conversation");
|
|
6865
|
-
printCmd("deepdives-join", "Re-enter an existing deep dive");
|
|
6866
|
-
console.log("");
|
|
6867
|
-
console.log(SUCCESS16(" Project Tools"));
|
|
6868
|
-
printCmd("index", "Index your project \u2014 AI-powered summaries + dependency map");
|
|
6869
|
-
printCmd("analyse", "Full QA code review \u2014 bugs, features, improvements, upgrades");
|
|
6870
|
-
printCmd("analyse --auto", "Auto-fix mode \u2014 Bob triages, MiniBob implements");
|
|
6871
|
-
printCmd("autonomy", "Full autonomous repair across entire codebase");
|
|
6872
|
-
printCmd("push <message>", "Git stage + commit + push in one command");
|
|
6873
|
-
console.log("");
|
|
6874
|
-
console.log(BRAND_PRIMARY11(" Remote (SovereignLink)"));
|
|
6875
|
-
printCmd("serve", "Start Active Bob \u2014 receive commands from any device");
|
|
6876
|
-
printCmd("remote [type] [msg]", "Send commands to a remote Active Bob");
|
|
6877
|
-
console.log("");
|
|
6878
|
-
console.log(MODE_CONSULTANT9(" Profile & Identity"));
|
|
6879
|
-
printCmd("profile", "View your behavioral DNA dashboard");
|
|
6880
|
-
printCmd("profile --cloud", "Generate cloud-powered daily profile");
|
|
6881
|
-
printCmd("userbob", "Launch your UserBob digital twin");
|
|
6882
|
-
printCmd("byok", "Manage Bring Your Own Key configuration");
|
|
6883
|
-
printCmd("command-center", "Autonomous Command Center \u2014 inspect and approve tasks");
|
|
6884
|
-
printCmd("cc", "Alias for command-center");
|
|
6885
|
-
console.log("");
|
|
6886
|
-
console.log(MUTED16(" Configuration"));
|
|
6887
|
-
printCmd("config show", "Display current configuration");
|
|
6888
|
-
printCmd("config set <key> <val>", "Update a configuration value");
|
|
6889
|
-
printCmd("login", "Authenticate with Bob's Workshop via browser");
|
|
6890
|
-
printCmd("logout", "Sign out and clear stored credentials");
|
|
6891
|
-
printCmd("whoami", "Show current auth status and project info");
|
|
6892
|
-
console.log("");
|
|
6893
|
-
console.log(chalk26.white(" Interactive slash commands ") + MUTED16("(inside a chat/consult session):"));
|
|
6894
|
-
console.log("");
|
|
6895
|
-
printCmd("/exit", "End the session");
|
|
6896
|
-
printCmd("/new", "Start a fresh conversation");
|
|
6897
|
-
printCmd("/clear", "Clear the terminal");
|
|
6898
|
-
printCmd("/include <path>", "Load a file into active context");
|
|
6899
|
-
printCmd("/delete <path>", "Delete a file (with backup)");
|
|
6900
|
-
printCmd("/deepdive", "Deep dive on the last Bob message");
|
|
6901
|
-
printCmd("/constraints", "View active negative constraints");
|
|
6902
|
-
console.log("");
|
|
6903
|
-
console.log(MUTED16(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6904
|
-
console.log("");
|
|
6905
|
-
console.log(MUTED16(" Run ") + INFO16("bob <command> --help") + MUTED16(" for details on a specific command."));
|
|
6906
|
-
console.log("");
|
|
6907
|
-
console.log(MUTED16(" \u{1F4D6} Full docs:"));
|
|
6908
|
-
console.log(INFO16(" https://seedling-io.gitbook.io/bob-cli/bobs-cli-product-wiki-and-user-guide/command-reference"));
|
|
6909
|
-
console.log("");
|
|
6910
|
-
console.log(MUTED16(" Built by ") + BRAND_PRIMARY11("Bob's Workshop") + MUTED16(" \u2014 A Seedling Company."));
|
|
6911
|
-
console.log("");
|
|
6912
|
-
}
|
|
6913
|
-
function printCmd(cmd, desc) {
|
|
6914
|
-
const padded = cmd.padEnd(24);
|
|
6915
|
-
console.log(BRAND_SECONDARY15(` ${padded}`) + MUTED16(desc));
|
|
6916
|
-
}
|
|
6874
|
+
program.name("bob").description("Bob's CLI \u2014 AI coding assistant and Forge orchestrator").version("0.1.3");
|
|
6917
6875
|
program.command("whoami").description("Show current authentication status and configuration").action(() => {
|
|
6918
6876
|
const config = getConfig();
|
|
6919
6877
|
const projectName = path14.basename(process.cwd());
|
|
6920
6878
|
console.log("");
|
|
6921
|
-
console.log(
|
|
6922
|
-
console.log(
|
|
6923
|
-
console.log(` ${
|
|
6924
|
-
console.log(` ${
|
|
6925
|
-
console.log(` ${
|
|
6926
|
-
console.log(` ${
|
|
6927
|
-
console.log(` ${
|
|
6928
|
-
console.log(` ${
|
|
6929
|
-
console.log(` ${
|
|
6879
|
+
console.log(chalk25.bold(" \u{1F916} Bob's CLI"));
|
|
6880
|
+
console.log(chalk25.gray(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500"));
|
|
6881
|
+
console.log(` ${chalk25.cyan("Status:")} ${config.loggedIn ? chalk25.green("Logged in as " + config.email) : "Not logged in"}`);
|
|
6882
|
+
console.log(` ${chalk25.cyan("Tier:")} ${config.tier === "platform" ? "Platform (Tier 3)" : "Local-first (Tier 1)"}`);
|
|
6883
|
+
console.log(` ${chalk25.cyan("Provider:")} ${config.provider || "Not configured"}`);
|
|
6884
|
+
console.log(` ${chalk25.cyan("Mode:")} ${config.personalizationMode ? "Personalized" : config.consultantMode ? "Consultant" : "Standard"}`);
|
|
6885
|
+
console.log(` ${chalk25.cyan("IDRP:")} ${config.idrp ? "Enabled" : "Disabled"}`);
|
|
6886
|
+
console.log(` ${chalk25.cyan("Project:")} ${projectName} (${process.cwd()})`);
|
|
6887
|
+
console.log(` ${chalk25.cyan("Session:")} ${config.conversationId ? config.conversationId.slice(0, 20) + "..." : "None"}`);
|
|
6930
6888
|
console.log("");
|
|
6931
6889
|
if (!config.loggedIn) {
|
|
6932
|
-
console.log(
|
|
6890
|
+
console.log(chalk25.gray(" Run `bob login` to authenticate."));
|
|
6933
6891
|
console.log("");
|
|
6934
6892
|
}
|
|
6935
6893
|
});
|
|
@@ -6948,26 +6906,5 @@ registerAutonomyCommand(program);
|
|
|
6948
6906
|
registerServeCommand(program);
|
|
6949
6907
|
registerRemoteCommand(program);
|
|
6950
6908
|
registerProfileCommand(program);
|
|
6951
|
-
|
|
6952
|
-
registerCommandCenterCommand(program);
|
|
6953
|
-
process.on("uncaughtException", (error) => {
|
|
6954
|
-
console.error("");
|
|
6955
|
-
console.error(chalk26.hex("#EF5350")(" \u274C An unexpected error occurred."));
|
|
6956
|
-
console.error(chalk26.hex("#78909C")(` ${error.message || "Unknown error"}`));
|
|
6957
|
-
console.error("");
|
|
6958
|
-
console.error(chalk26.hex("#78909C")(" If this persists, please report it:"));
|
|
6959
|
-
console.error(chalk26.hex("#26C6DA")(" https://github.com/bobsworkshop/bob-cli/issues"));
|
|
6960
|
-
console.error("");
|
|
6961
|
-
process.exit(1);
|
|
6962
|
-
});
|
|
6963
|
-
process.on("unhandledRejection", (reason) => {
|
|
6964
|
-
console.error("");
|
|
6965
|
-
console.error(chalk26.hex("#EF5350")(" \u274C An unexpected error occurred."));
|
|
6966
|
-
console.error(chalk26.hex("#78909C")(` ${reason?.message || reason || "Unknown error"}`));
|
|
6967
|
-
console.error("");
|
|
6968
|
-
console.error(chalk26.hex("#78909C")(" If this persists, please report it:"));
|
|
6969
|
-
console.error(chalk26.hex("#26C6DA")(" https://github.com/bobsworkshop/bob-cli/issues"));
|
|
6970
|
-
console.error("");
|
|
6971
|
-
process.exit(1);
|
|
6972
|
-
});
|
|
6909
|
+
registerBackupCommand(program);
|
|
6973
6910
|
program.parse();
|