@bobsworkshop/cli 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +7 -0
  2. package/dist/bob.js +35 -41
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -621,6 +621,13 @@ Tier 1 — Local (Free) Tier 3 — Platform (Subscription)
621
621
  Same commands. Scale without changing tools.
622
622
 
623
623
  ---
624
+ ## What's New in v1.2.0
625
+
626
+ - **Per-project conversation scoping** — `conversationId` is now stored in each project's `~/.bob/projects/{name}/project.json` instead of the global config. When you `cd` between projects, `bob chat` automatically resumes the correct conversation for that project. No more cross-contamination between codebases.
627
+ - **Backward compatible migration** — Existing installs are unaffected. If no project-level `conversationId` exists yet, Bob falls back to the global config value seamlessly. The new value is written to `project.json` on first interaction.
628
+ - **`project.json` now tracks `lastActive`** — Every time a conversation ID is written to a project, the `lastActive` timestamp is updated. Useful for future project-aware features.
629
+ - **Bug fix: `remote.ts` conversation ID** — `bob remote` now reads the active conversation ID from project scope, not the global config. Connecting to an Active Bob correctly reflects the current project's conversation.
630
+ - **Bug fix: `serve.ts` conversation ID** — `bob serve` now resolves the conversation ID from project scope before registering the daemon session, ensuring SovereignLink binds to the correct project conversation.
624
631
 
625
632
  ## What's New in v1.1.0
626
633
 
package/dist/bob.js CHANGED
@@ -4721,7 +4721,8 @@ function registerServeCommand(program2) {
4721
4721
  console.log("");
4722
4722
  return;
4723
4723
  }
4724
- if (!config.conversationId) {
4724
+ const conversationId = getActiveConversationId(process.cwd()) || config.conversationId;
4725
+ if (!conversationId) {
4725
4726
  console.log("");
4726
4727
  console.log(RED2(" \u274C No active conversation."));
4727
4728
  console.log(GRAY2(" Active Bob must be bound to a conversation."));
@@ -4773,10 +4774,10 @@ function registerServeCommand(program2) {
4773
4774
  const machineId = os2.hostname();
4774
4775
  const projectName = path10.basename(process.cwd());
4775
4776
  const sessionId = `${machineId}_${Date.now()}`;
4776
- await startActiveBob(config, sessionId, machineId, projectName, tierConfig, userTier);
4777
+ await startActiveBob(config, conversationId, sessionId, machineId, projectName, tierConfig, userTier);
4777
4778
  });
4778
4779
  }
4779
- async function startActiveBob(config, sessionId, machineId, projectName, tierConfig, userTier) {
4780
+ async function startActiveBob(config, conversationId, sessionId, machineId, projectName, tierConfig, userTier) {
4780
4781
  console.log("");
4781
4782
  console.log(BORDER5(" \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"));
4782
4783
  console.log(BORDER5(" \u2551") + CYAN2(" \u{1F310} Bob Serve \u2014 Active Bob Online ") + BORDER5("\u2551"));
@@ -4785,7 +4786,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
4785
4786
  console.log(BORDER5(" \u2551") + GRAY2(` Machine: ${machineId}`));
4786
4787
  console.log(BORDER5(" \u2551") + GRAY2(` Project: ${projectName} (${process.cwd()})`));
4787
4788
  console.log(BORDER5(" \u2551") + GRAY2(` Session: ${sessionId.slice(0, 30)}...`));
4788
- console.log(BORDER5(" \u2551") + GRAY2(` Convo: ${config.conversationId?.slice(0, 24)}...`));
4789
+ console.log(BORDER5(" \u2551") + GRAY2(` Convo: ${conversationId?.slice(0, 24)}...`));
4789
4790
  console.log(BORDER5(" \u2551") + AMBER4(` Tier: ${userTier}`));
4790
4791
  console.log(BORDER5(" \u2551") + GRAY2(` Polling: every ${tierConfig.activeInterval / 1e3}s`));
4791
4792
  if (tierConfig.sleepInterval) {
@@ -4805,7 +4806,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
4805
4806
  console.log("");
4806
4807
  try {
4807
4808
  await callCloudFunction("registerRemoteDaemonSession", {
4808
- conversationId: config.conversationId,
4809
+ conversationId,
4809
4810
  sessionId,
4810
4811
  machineId,
4811
4812
  projectName,
@@ -4827,7 +4828,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
4827
4828
  console.log(GRAY2(" \u{1F50C} Shutting down Active Bob..."));
4828
4829
  try {
4829
4830
  await callCloudFunction("deregisterRemoteDaemonSession", {
4830
- conversationId: config.conversationId,
4831
+ conversationId,
4831
4832
  sessionId
4832
4833
  });
4833
4834
  console.log(GRAY2(" \u2705 Session deregistered. Bob is offline."));
@@ -4853,7 +4854,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
4853
4854
  console.log("");
4854
4855
  try {
4855
4856
  await callCloudFunction("deregisterRemoteDaemonSession", {
4856
- conversationId: config.conversationId,
4857
+ conversationId,
4857
4858
  sessionId
4858
4859
  });
4859
4860
  } catch {
@@ -4871,7 +4872,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
4871
4872
  const currentInterval = isSleeping && tierConfig.sleepInterval ? tierConfig.sleepInterval : tierConfig.activeInterval;
4872
4873
  try {
4873
4874
  const result = await callCloudFunction("pollRemoteCommands", {
4874
- conversationId: config.conversationId,
4875
+ conversationId,
4875
4876
  sessionId
4876
4877
  });
4877
4878
  if (result?.command) {
@@ -4887,7 +4888,7 @@ async function startActiveBob(config, sessionId, machineId, projectName, tierCon
4887
4888
  console.log(AMBER4(` [${timestamp}] \u23F3 Received: ${type}${payload.message ? ` "${payload.message.slice(0, 40)}${payload.message.length > 40 ? "..." : ""}"` : ""}`));
4888
4889
  const commandResult = await executeRemoteCommand(type, payload, config);
4889
4890
  await callCloudFunction("completeRemoteCommand", {
4890
- conversationId: config.conversationId,
4891
+ conversationId,
4891
4892
  commandId: cmd.id,
4892
4893
  sessionId,
4893
4894
  result: commandResult
@@ -5320,7 +5321,6 @@ async function executeRestore(payload, config) {
5320
5321
  isGlobal,
5321
5322
  isSource,
5322
5323
  s3VersionId: null
5323
- // always latest in headless mode
5324
5324
  });
5325
5325
  const response = await axios.get(downloadResult.downloadUrl, {
5326
5326
  responseType: "arraybuffer",
@@ -5385,16 +5385,17 @@ function registerRemoteCommand(program2) {
5385
5385
  console.log("");
5386
5386
  return;
5387
5387
  }
5388
- if (options.new || !config.conversationId) {
5388
+ const activeConvoId = getActiveConversationId(process.cwd()) || config.conversationId;
5389
+ if (options.new || !activeConvoId) {
5389
5390
  await discoverAndConnect(config);
5390
5391
  return;
5391
5392
  }
5392
5393
  if (options.interactive || !type && !options.new) {
5393
- await runInteractiveRemote(config, options.session);
5394
+ await runInteractiveRemote(config, activeConvoId, options.session);
5394
5395
  return;
5395
5396
  }
5396
5397
  if (!type) {
5397
- await showConnectionStatus(config);
5398
+ await showConnectionStatus(config, activeConvoId);
5398
5399
  return;
5399
5400
  }
5400
5401
  const validTypes = [
@@ -5414,7 +5415,7 @@ function registerRemoteCommand(program2) {
5414
5415
  console.log("");
5415
5416
  return;
5416
5417
  }
5417
- const payload = { conversationId: config.conversationId };
5418
+ const payload = { conversationId: activeConvoId };
5418
5419
  if (message) payload.message = message;
5419
5420
  if (options.auto) payload.auto = true;
5420
5421
  if (options.source) payload.isSource = true;
@@ -5427,15 +5428,15 @@ function registerRemoteCommand(program2) {
5427
5428
  console.log("");
5428
5429
  return;
5429
5430
  }
5430
- await dispatchCommand(config, type, payload, options.session);
5431
+ await dispatchCommand(config, activeConvoId, type, payload, options.session);
5431
5432
  });
5432
5433
  }
5433
- async function runInteractiveRemote(config, targetSession) {
5434
+ async function runInteractiveRemote(config, activeConvoId, targetSession) {
5434
5435
  const spinner = ora7({ text: INFO14(" Connecting to Active Bob..."), spinner: "dots" }).start();
5435
5436
  let activeBobName = "Unknown";
5436
5437
  try {
5437
5438
  const result = await callCloudFunction("listActiveBobs", {
5438
- conversationId: config.conversationId
5439
+ conversationId: activeConvoId
5439
5440
  });
5440
5441
  const sessions = (result?.sessions || []).filter((s) => s.active);
5441
5442
  if (sessions.length === 0) {
@@ -5457,7 +5458,7 @@ async function runInteractiveRemote(config, targetSession) {
5457
5458
  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"));
5458
5459
  console.log(BORDER6(" \u2551") + INFO14(" \u{1F310} Active Bob \u2014 Remote Session") + MUTED14(` (${activeBobName})`));
5459
5460
  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"));
5460
- console.log(BORDER6(" \u2551") + MUTED14(` Conversation: ${config.conversationId?.slice(0, 28)}...`));
5461
+ console.log(BORDER6(" \u2551") + MUTED14(` Conversation: ${activeConvoId?.slice(0, 28)}...`));
5461
5462
  console.log(BORDER6(" \u2551") + MUTED14(" Commands dispatched to the remote machine."));
5462
5463
  console.log(BORDER6(" \u2551"));
5463
5464
  console.log(BORDER6(" \u2551") + chalk20.white(" Slash Commands:"));
@@ -5494,7 +5495,7 @@ async function runInteractiveRemote(config, targetSession) {
5494
5495
  if (trimmed.startsWith("/consult ")) {
5495
5496
  const msg = trimmed.slice(9).trim().replace(/^["']|["']$/g, "");
5496
5497
  if (msg) {
5497
- await dispatchAndShow(config, "consult", { message: msg }, targetSession);
5498
+ await dispatchAndShow(config, activeConvoId, "consult", { message: msg }, targetSession);
5498
5499
  } else {
5499
5500
  console.log(ERROR12(' \u274C Provide a message: /consult "your question"'));
5500
5501
  }
@@ -5504,7 +5505,7 @@ async function runInteractiveRemote(config, targetSession) {
5504
5505
  if (trimmed.startsWith("/push ")) {
5505
5506
  const msg = trimmed.slice(6).trim().replace(/^["']|["']$/g, "");
5506
5507
  if (msg) {
5507
- await dispatchAndShow(config, "push", { message: msg }, targetSession);
5508
+ await dispatchAndShow(config, activeConvoId, "push", { message: msg }, targetSession);
5508
5509
  } else {
5509
5510
  console.log(ERROR12(' \u274C Provide a commit message: /push "your message"'));
5510
5511
  }
@@ -5512,12 +5513,12 @@ async function runInteractiveRemote(config, targetSession) {
5512
5513
  return;
5513
5514
  }
5514
5515
  if (trimmed === "/index") {
5515
- await dispatchAndShow(config, "index", {}, targetSession);
5516
+ await dispatchAndShow(config, activeConvoId, "index", {}, targetSession);
5516
5517
  prompt();
5517
5518
  return;
5518
5519
  }
5519
5520
  if (trimmed === "/analyse" || trimmed === "/analyze") {
5520
- await dispatchAndShow(config, "analyse", {}, targetSession);
5521
+ await dispatchAndShow(config, activeConvoId, "analyse", {}, targetSession);
5521
5522
  prompt();
5522
5523
  return;
5523
5524
  }
@@ -5527,7 +5528,7 @@ async function runInteractiveRemote(config, targetSession) {
5527
5528
  const backupPayload = {};
5528
5529
  if (flag === "source") backupPayload.isSource = true;
5529
5530
  if (flag === "global") backupPayload.isGlobal = true;
5530
- await dispatchAndShow(config, "backup", backupPayload, targetSession);
5531
+ await dispatchAndShow(config, activeConvoId, "backup", backupPayload, targetSession);
5531
5532
  prompt();
5532
5533
  return;
5533
5534
  }
@@ -5537,17 +5538,17 @@ async function runInteractiveRemote(config, targetSession) {
5537
5538
  const restorePayload = {};
5538
5539
  if (flag === "source") restorePayload.isSource = true;
5539
5540
  if (flag === "global") restorePayload.isGlobal = true;
5540
- await dispatchAndShow(config, "restore", restorePayload, targetSession);
5541
+ await dispatchAndShow(config, activeConvoId, "restore", restorePayload, targetSession);
5541
5542
  prompt();
5542
5543
  return;
5543
5544
  }
5544
- await dispatchAndShow(config, "chat", { message: trimmed }, targetSession);
5545
+ await dispatchAndShow(config, activeConvoId, "chat", { message: trimmed }, targetSession);
5545
5546
  prompt();
5546
5547
  });
5547
5548
  };
5548
5549
  prompt();
5549
5550
  }
5550
- async function dispatchAndShow(config, type, payload, targetSession) {
5551
+ async function dispatchAndShow(config, activeConvoId, type, payload, targetSession) {
5551
5552
  const spinner = ora7({
5552
5553
  text: BRAND_SECONDARY13(` \u{1F4E1} Active Bob executing: ${type}...`),
5553
5554
  spinner: "dots"
@@ -5556,9 +5557,9 @@ async function dispatchAndShow(config, type, payload, targetSession) {
5556
5557
  const MAX_POLLS = 300;
5557
5558
  try {
5558
5559
  const result = await callCloudFunction("sendRemoteCommand", {
5559
- conversationId: config.conversationId,
5560
+ conversationId: activeConvoId,
5560
5561
  type,
5561
- payload: { ...payload, conversationId: config.conversationId },
5562
+ payload: { ...payload, conversationId: activeConvoId },
5562
5563
  targetSession: targetSession || null
5563
5564
  });
5564
5565
  if (!result?.success) {
@@ -5572,7 +5573,7 @@ async function dispatchAndShow(config, type, payload, targetSession) {
5572
5573
  pollCount++;
5573
5574
  try {
5574
5575
  const pollResult = await callCloudFunction("getRemoteCommandResult", {
5575
- conversationId: config.conversationId,
5576
+ conversationId: activeConvoId,
5576
5577
  commandId
5577
5578
  });
5578
5579
  if (pollResult?.status === "completed") {
@@ -5631,17 +5632,10 @@ async function dispatchAndShow(config, type, payload, targetSession) {
5631
5632
  console.log("");
5632
5633
  }
5633
5634
  }
5634
- async function showConnectionStatus(config) {
5635
- if (!config.conversationId) {
5636
- console.log("");
5637
- console.log(ERROR12(" \u{1F534} No conversation selected."));
5638
- console.log(MUTED14(" Run `bob remote --new` to find and connect to an Active Bob."));
5639
- console.log("");
5640
- return;
5641
- }
5635
+ async function showConnectionStatus(config, activeConvoId) {
5642
5636
  const spinner = ora7({ text: INFO14(" Checking Active Bob status..."), spinner: "dots" }).start();
5643
5637
  try {
5644
- const result = await callCloudFunction("listActiveBobs", { conversationId: config.conversationId });
5638
+ const result = await callCloudFunction("listActiveBobs", { conversationId: activeConvoId });
5645
5639
  spinner.stop();
5646
5640
  const sessions = result?.sessions || [];
5647
5641
  const activeSessions = sessions.filter((s) => s.active);
@@ -5649,7 +5643,7 @@ async function showConnectionStatus(config) {
5649
5643
  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"));
5650
5644
  console.log(BORDER6(" \u2551") + INFO14(" \u{1F310} Remote Connection Status"));
5651
5645
  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"));
5652
- console.log(BORDER6(" \u2551") + MUTED14(` Conversation: ${config.conversationId?.slice(0, 28)}...`));
5646
+ console.log(BORDER6(" \u2551") + MUTED14(` Conversation: ${activeConvoId?.slice(0, 28)}...`));
5653
5647
  console.log(BORDER6(" \u2551"));
5654
5648
  if (activeSessions.length === 0) {
5655
5649
  console.log(BORDER6(" \u2551") + ERROR12(" \u{1F534} No Active Bob found on this conversation."));
@@ -5733,8 +5727,8 @@ async function discoverAndConnect(config) {
5733
5727
  console.log("");
5734
5728
  }
5735
5729
  }
5736
- async function dispatchCommand(config, type, payload, targetSession) {
5737
- await dispatchAndShow(config, type, payload, targetSession);
5730
+ async function dispatchCommand(config, activeConvoId, type, payload, targetSession) {
5731
+ await dispatchAndShow(config, activeConvoId, type, payload, targetSession);
5738
5732
  }
5739
5733
  function getTimeAgo2(isoDate) {
5740
5734
  const now = Date.now();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bobsworkshop/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Bob's CLI — AI coding assistant and Forge orchestrator",
5
5
  "type": "module",
6
6
  "files": [