@letterblack/lbe-exec 1.2.15 → 1.2.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -404,7 +404,7 @@ var init_localPolicy = __esm({
404
404
  "src/core/localPolicy.js"() {
405
405
  init_auditLog();
406
406
  init_atomicWrite();
407
- POLICY_FILE = "lbe.policy.json";
407
+ POLICY_FILE = ".lbe/policy.json";
408
408
  AUDIT_FILE = ".lbe/audit.jsonl";
409
409
  }
410
410
  });
@@ -2363,12 +2363,8 @@ This project has LetterBlack LBE (Local-first execution Governance) active.
2363
2363
 
2364
2364
  ## Your role as an agent
2365
2365
 
2366
- You do not have direct access to the file system or shell in this workspace.
2367
- Every file write, file read, file delete, and shell command must go through the
2368
- LBE executor. The executor validates every action against the project policy,
2369
- signs it, audits it, and either allows or blocks it \u2014 transparently.
2370
-
2371
- You do not need to know how this works. You only need to use the right methods.
2366
+ Every file write, delete, and shell command must go through the LBE executor.
2367
+ The executor validates each action against the project policy and audits it.
2372
2368
 
2373
2369
  ## How to perform actions
2374
2370
 
@@ -2376,143 +2372,83 @@ You do not need to know how this works. You only need to use the right methods.
2376
2372
  import { createLocalExecutor } from '@letterblack/lbe-exec';
2377
2373
  const lbe = createLocalExecutor({ rootDir: process.cwd() });
2378
2374
 
2379
- // File operations
2380
2375
  await lbe.writeFile('output/report.md', content);
2381
2376
  await lbe.readFile('src/config.json');
2382
2377
  await lbe.patchFile('src/index.js', patch);
2383
2378
  await lbe.deleteFile('tmp/scratch.txt');
2384
-
2385
- // Shell (only explicitly allowlisted commands run)
2386
2379
  await lbe.runShell('node', ['scripts/build.js']);
2387
2380
  \`\`\`
2388
2381
 
2389
2382
  ## What you must never do
2390
2383
 
2391
- - Call file system APIs directly (fs.writeFile, fs.readFile, etc.)
2392
- - Call child_process or exec directly
2393
- - Modify \`lbe.policy.json\` directly \u2014 propose a rule via \`lbe.policy.proposeRule()\`
2394
- - Attempt to bypass, patch, or disable the executor
2395
-
2396
- ## Proposing a new policy rule
2384
+ - Call fs.* or child_process.* directly
2385
+ - Modify \`.lbe/policy.json\` directly \u2014 propose a rule via \`lbe.policy.proposeRule()\`
2386
+ - Attempt to bypass or disable the executor
2397
2387
 
2398
- If you believe a rule should be added or removed, propose it and let the
2399
- controller decide:
2388
+ ## Proposing a policy rule
2400
2389
 
2401
2390
  \`\`\`js
2402
2391
  const proposal = lbe.policy.proposeRule({
2403
- effect: 'deny',
2404
- type: 'path',
2405
- pattern: 'secrets/**',
2392
+ effect: 'deny', type: 'path', pattern: 'secrets/**',
2406
2393
  from: 'agent: these files should never be modified'
2407
2394
  });
2408
- // Return proposal to the user. Never call lbe.policy.addRule() yourself.
2395
+ // Return proposal to the user \u2014 never call lbe.policy.addRule() yourself.
2409
2396
  \`\`\`
2410
2397
 
2411
2398
  ## Result shape
2412
2399
 
2413
- Every method returns:
2414
- \`\`\`
2415
- { ok: boolean, decision: 'allow' | 'deny' | 'observe', executed: boolean }
2416
- \`\`\`
2417
-
2418
- If \`ok\` is false, the action was blocked. Check \`result.error.message\` for reason.
2419
-
2420
- ## Current policy
2421
-
2422
- See \`lbe.policy.json\` in the project root.
2423
- See \`.lbe/audit.jsonl\` for the execution audit trail.
2424
-
2425
- ## CLI
2426
-
2427
- \`\`\`bash
2428
- npx lbe-exec status # mode, rule count, audit entry count
2429
- npx lbe-exec policy # list active rules
2430
- npx lbe-exec observe # switch to observer mode
2431
- npx lbe-exec enforce # switch to enforcement mode
2432
- \`\`\`
2433
- `;
2434
- }
2435
- function claudeMdSection() {
2436
- return `<!-- lbe-governance -->
2437
- ## LBE Execution Governance
2438
-
2439
- This project has LetterBlack LBE active. All file and shell operations must
2440
- route through the LBE executor \u2014 do not use file system tools directly.
2441
-
2442
- ### Use these methods
2443
-
2444
- \`\`\`js
2445
- import { createLocalExecutor } from '@letterblack/lbe-exec';
2446
- const lbe = createLocalExecutor({ rootDir: process.cwd() });
2447
-
2448
- await lbe.writeFile('path/to/file', content);
2449
- await lbe.readFile('path/to/file');
2450
- await lbe.patchFile('path/to/file', patch);
2451
- await lbe.deleteFile('path/to/file');
2452
- await lbe.runShell('node', ['script.js']);
2453
- \`\`\`
2454
-
2455
- ### Rules
2456
-
2457
- - Never call \`fs.*\` or \`child_process.*\` directly
2458
- - Never write to \`lbe.policy.json\` directly \u2014 use \`lbe.policy.proposeRule()\`
2459
- - Every blocked action has a reason in \`result.error.message\`
2460
- - Audit trail is in \`.lbe/audit.jsonl\`
2400
+ \`{ ok: boolean, decision: 'allow' | 'deny' | 'observe', executed: boolean }\`
2461
2401
 
2462
- See \`.lbe/AGENT_CONTRACT.md\` for the full agent contract.
2463
- <!-- /lbe-governance -->`;
2464
- }
2465
- function copilotInstructionsContent() {
2466
- return `# LBE Governance \u2014 Copilot Instructions
2467
-
2468
- This project has LetterBlack LBE active. All file and shell operations must
2469
- route through the LBE executor.
2470
-
2471
- ## Required
2472
-
2473
- Use \`@letterblack/lbe-exec\` for all file and shell actions:
2474
-
2475
- \`\`\`js
2476
- const lbe = createLocalExecutor({ rootDir: process.cwd() });
2477
- await lbe.writeFile(path, content);
2478
- await lbe.readFile(path);
2479
- await lbe.runShell(cmd, args);
2480
- \`\`\`
2481
-
2482
- ## Forbidden
2402
+ ## Files
2483
2403
 
2484
- - Direct \`fs.*\` calls
2485
- - Direct \`child_process.*\` or \`exec\` calls
2486
- - Writing to \`lbe.policy.json\` directly
2487
-
2488
- See \`.lbe/AGENT_CONTRACT.md\` for the full contract.
2404
+ - Policy: \`.lbe/policy.json\`
2405
+ - Audit: \`.lbe/audit.jsonl\`
2406
+ - Status: \`npx lbe-exec status\`
2489
2407
  `;
2490
2408
  }
2491
- function writeAgentGovernanceFiles(cwd) {
2409
+ function writeAgentContract(cwd) {
2492
2410
  const lbeDir = path4.join(cwd, ".lbe");
2493
2411
  fs4.mkdirSync(lbeDir, { recursive: true });
2494
2412
  fs4.writeFileSync(path4.join(lbeDir, "AGENT_CONTRACT.md"), agentContractContent());
2495
- const claudePath = path4.join(cwd, "CLAUDE.md");
2496
- const section = claudeMdSection();
2497
- if (fs4.existsSync(claudePath)) {
2498
- const existing = fs4.readFileSync(claudePath, "utf8");
2499
- if (!existing.includes("<!-- lbe-governance -->")) {
2500
- fs4.appendFileSync(claudePath, "\n\n" + section + "\n");
2413
+ }
2414
+ function migrateLegacyRootFiles(cwd) {
2415
+ const lbeDir = path4.join(cwd, ".lbe");
2416
+ fs4.mkdirSync(lbeDir, { recursive: true });
2417
+ const migrations = [
2418
+ ["lbe.policy.json", ".lbe/policy.json"],
2419
+ ["lbe.workspace.json", ".lbe/workspace.json"]
2420
+ ];
2421
+ const removed = [];
2422
+ for (const [src, dest] of migrations) {
2423
+ const srcPath = path4.join(cwd, src);
2424
+ const destPath = path4.join(cwd, dest);
2425
+ if (fs4.existsSync(srcPath) && !fs4.existsSync(destPath)) {
2426
+ fs4.renameSync(srcPath, destPath);
2427
+ removed.push(src + " \u2192 " + dest);
2428
+ } else if (fs4.existsSync(srcPath)) {
2429
+ fs4.unlinkSync(srcPath);
2430
+ removed.push(src + " (removed \u2014 .lbe/ version exists)");
2431
+ }
2432
+ }
2433
+ const toDelete = ["CLAUDE.md", path4.join(".github", "copilot-instructions.md")];
2434
+ for (const rel of toDelete) {
2435
+ const p = path4.join(cwd, rel);
2436
+ if (fs4.existsSync(p)) {
2437
+ const content = fs4.readFileSync(p, "utf8");
2438
+ if (content.includes("lbe-governance") || content.includes("LetterBlack LBE")) {
2439
+ fs4.unlinkSync(p);
2440
+ removed.push(rel + " (removed \u2014 LBE-generated file)");
2441
+ }
2501
2442
  }
2502
- } else {
2503
- fs4.writeFileSync(claudePath, section + "\n");
2504
- }
2505
- const githubDir = path4.join(cwd, ".github");
2506
- fs4.mkdirSync(githubDir, { recursive: true });
2507
- const copilotPath = path4.join(githubDir, "copilot-instructions.md");
2508
- if (!fs4.existsSync(copilotPath)) {
2509
- fs4.writeFileSync(copilotPath, copilotInstructionsContent());
2510
2443
  }
2444
+ return removed;
2511
2445
  }
2512
2446
  async function initCommand(opts2 = {}) {
2513
2447
  const cwd = process.cwd();
2514
2448
  const yes = opts2.yes || opts2.y || !process.stdin.isTTY;
2515
- const outPath = path4.join(cwd, "lbe.workspace.json");
2449
+ const lbeDir = path4.join(cwd, ".lbe");
2450
+ fs4.mkdirSync(lbeDir, { recursive: true });
2451
+ const outPath = path4.join(lbeDir, "workspace.json");
2516
2452
  console.log("\nScanning workspace...\n");
2517
2453
  const { projectTypes, primaryType: primaryType2, semantics, enforcement } = scanWorkspace(cwd);
2518
2454
  console.log(formatSummary(projectTypes, semantics, enforcement));
@@ -2537,21 +2473,24 @@ async function initCommand(opts2 = {}) {
2537
2473
  enforcement: finalEnforcement
2538
2474
  };
2539
2475
  fs4.writeFileSync(outPath, JSON.stringify(contract, null, 2));
2540
- console.log("\u2713 Wrote lbe.workspace.json");
2476
+ console.log("\u2713 Wrote .lbe/workspace.json");
2541
2477
  setupCrypto(cwd);
2542
- const localPolicyPath = path4.join(cwd, "lbe.policy.json");
2478
+ const localPolicyPath = path4.join(lbeDir, "policy.json");
2543
2479
  if (!fs4.existsSync(localPolicyPath)) {
2544
2480
  fs4.writeFileSync(localPolicyPath, JSON.stringify({ version: 1, mode: "observe", workspace: cwd, rules: [] }, null, 2) + "\n");
2545
2481
  }
2546
- const localAuditPath = path4.join(cwd, ".lbe", "audit.jsonl");
2482
+ const localAuditPath = path4.join(lbeDir, "audit.jsonl");
2547
2483
  if (!fs4.existsSync(localAuditPath)) fs4.writeFileSync(localAuditPath, "");
2548
- console.log("\u2713 Keys and policy ready");
2549
- writeAgentGovernanceFiles(cwd);
2484
+ console.log("\u2713 Keys and policy ready (.lbe/)");
2485
+ writeAgentContract(cwd);
2550
2486
  console.log("\u2713 Agent contract written \u2192 .lbe/AGENT_CONTRACT.md");
2551
- console.log("\u2713 CLAUDE.md updated with LBE governance section");
2552
- console.log("\u2713 .github/copilot-instructions.md ready\n");
2553
- console.log("Done. Any AI agent that reads project context will follow LBE governance automatically.");
2554
- console.log("Run npx lbe status to see mode, rules, and audit entry count.\n");
2487
+ const migrated = migrateLegacyRootFiles(cwd);
2488
+ if (migrated.length) {
2489
+ console.log("\n\u2713 Migrated legacy files:");
2490
+ for (const m of migrated) console.log(" " + m);
2491
+ }
2492
+ console.log("\nDone. All LBE state is in .lbe/");
2493
+ console.log("Run npx lbe-exec status to verify.\n");
2555
2494
  return { success: true, contract };
2556
2495
  }
2557
2496
 
@@ -2571,14 +2510,10 @@ var opts = Object.fromEntries(
2571
2510
  var positional = rest.filter((v) => !v.startsWith("--") && rest[rest.indexOf(v) - 1]?.startsWith("--") === false);
2572
2511
  var __dir = path15.dirname(fileURLToPath2(import.meta.url));
2573
2512
  function loadPolicy() {
2574
- const p = path15.join(process.cwd(), "lbe.policy.json");
2513
+ const cwd = process.cwd();
2514
+ const p = fs14.existsSync(path15.join(cwd, ".lbe", "policy.json")) ? path15.join(cwd, ".lbe", "policy.json") : path15.join(cwd, "lbe.policy.json");
2575
2515
  return fs14.existsSync(p) ? JSON.parse(fs14.readFileSync(p, "utf8")) : null;
2576
2516
  }
2577
- function countAudit() {
2578
- const p = path15.join(process.cwd(), ".lbe", "audit.jsonl");
2579
- if (!fs14.existsSync(p)) return 0;
2580
- return fs14.readFileSync(p, "utf8").split("\n").filter((l) => l.trim()).length;
2581
- }
2582
2517
  function findHookPath() {
2583
2518
  const candidates = [
2584
2519
  path15.resolve(__dir, "../hooks/register.cjs"),
@@ -2684,8 +2619,9 @@ switch (cmd) {
2684
2619
  process.exit(1);
2685
2620
  }
2686
2621
  const existing = process.env.NODE_OPTIONS || "";
2687
- const hookFlag = "--require " + hookPath;
2688
- const nodeOptions = existing.includes(hookFlag) ? existing : (existing + " " + hookFlag).trim();
2622
+ const hookPathFwd = hookPath.replace(/\\/g, "/");
2623
+ const hookFlag = '--require "' + hookPathFwd + '"';
2624
+ const nodeOptions = existing.includes(hookPathFwd) ? existing : (existing + " " + hookFlag).trim();
2689
2625
  const npmArgs = rest.filter((v) => !v.startsWith("--mode") && v !== opts.mode);
2690
2626
  const child = spawn("npm", npmArgs, {
2691
2627
  stdio: "inherit",
@@ -2696,59 +2632,63 @@ switch (cmd) {
2696
2632
  break;
2697
2633
  }
2698
2634
  case "status": {
2699
- const policy = loadPolicy();
2635
+ const root = process.cwd();
2700
2636
  console.log("\u2500\u2500 LBE Status \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2701
- console.log("workspace: " + process.cwd());
2702
- if (policy) {
2703
- console.log("mode: " + policy.mode);
2704
- console.log("rules: " + (policy.rules?.length ?? 0));
2705
- console.log("audit: " + countAudit() + " entries (.lbe/audit.jsonl)");
2706
- } else {
2707
- console.log("policy: not found \u2014 run: npx lbe-exec init");
2708
- }
2709
- const statusFile = path15.join(process.cwd(), ".lbe", "runtime", "hook-status.json");
2710
- if (!fs14.existsSync(statusFile)) {
2711
- console.log("\nhook: inactive \u2014 use: npx lbe-exec run-node ./agent.js");
2712
- break;
2713
- }
2714
- let hookStatus;
2715
- try {
2716
- hookStatus = JSON.parse(fs14.readFileSync(statusFile, "utf8"));
2717
- } catch (e) {
2718
- console.log("\nhook: status file unreadable \u2014 " + e.message);
2719
- break;
2720
- }
2721
- let pidAlive = false;
2722
- try {
2723
- process.kill(hookStatus.pid, 0);
2724
- pidAlive = true;
2725
- } catch (_) {
2726
- }
2727
- console.log("\nhook: " + (pidAlive ? "ACTIVE" : "stale (process exited)"));
2728
- console.log("hook pid: " + hookStatus.pid + (pidAlive ? " (alive)" : " (gone)"));
2729
- console.log("hook mode: " + hookStatus.mode);
2730
- console.log("hook root: " + hookStatus.root);
2731
- console.log("hook start: " + hookStatus.started_at);
2732
- if (hookStatus.patched) {
2733
- console.log("\nPatched functions:");
2734
- for (const [fn, active] of Object.entries(hookStatus.patched)) {
2735
- console.log(" " + (active ? "\u2713" : "\u2013") + " " + fn);
2637
+ console.log("workspace: " + root);
2638
+ const hookPath = findHookPath();
2639
+ console.log("hook file: " + hookPath + (fs14.existsSync(hookPath) ? " (found)" : " (MISSING)"));
2640
+ const lbeRoot = process.env.LBE_ROOT || "";
2641
+ console.log("LBE_ROOT: " + (lbeRoot || "(not set)"));
2642
+ const nodeOpts = process.env.NODE_OPTIONS || "";
2643
+ const hookInPath = nodeOpts.includes("register.cjs");
2644
+ console.log("NODE_OPTIONS contains hook: " + (hookInPath ? "yes" : "no"));
2645
+ const eventsFile = path15.join(root, ".lbe", "events.jsonl");
2646
+ const auditExists = fs14.existsSync(eventsFile);
2647
+ console.log("audit log: " + (auditExists ? eventsFile : "(none yet)"));
2648
+ if (auditExists) {
2649
+ try {
2650
+ const lines = fs14.readFileSync(eventsFile, "utf8").split("\n").filter((l) => l.trim());
2651
+ if (lines.length) {
2652
+ const last = JSON.parse(lines[lines.length - 1]);
2653
+ const ts = new Date((last.ts || 0) * 1e3).toISOString().replace("T", " ").slice(0, 19);
2654
+ const target = last.path || last.cmd || "?";
2655
+ console.log("last event: " + ts + " " + last.action + " " + target + " \u2192 " + (last.decision || "?"));
2656
+ } else {
2657
+ console.log("last event: (none)");
2658
+ }
2659
+ } catch (_) {
2660
+ console.log("last event: (unreadable)");
2736
2661
  }
2737
2662
  }
2738
- const activationFile = path15.join(process.cwd(), ".lbe", "activation.json");
2739
- const sessionActive = (process.env.NODE_OPTIONS || "").includes("register.cjs");
2740
- if (fs14.existsSync(activationFile)) {
2663
+ const statusFile = path15.join(root, ".lbe", "runtime", "hook-status.json");
2664
+ if (fs14.existsSync(statusFile)) {
2665
+ let h;
2741
2666
  try {
2742
- const act = JSON.parse(fs14.readFileSync(activationFile, "utf8"));
2743
- console.log("\nactivation: " + (act.permanent ? "permanent" : "session") + " (" + act.mode + ")" + (sessionActive ? " NODE_OPTIONS \u2713" : " restart terminal to apply"));
2667
+ h = JSON.parse(fs14.readFileSync(statusFile, "utf8"));
2744
2668
  } catch (_) {
2745
2669
  }
2746
- } else if (sessionActive) {
2747
- console.log("\nactivation: session active NODE_OPTIONS \u2713");
2670
+ if (h) {
2671
+ let pidAlive = false;
2672
+ try {
2673
+ process.kill(h.pid, 0);
2674
+ pidAlive = true;
2675
+ } catch (_) {
2676
+ }
2677
+ console.log("\nhook process: " + (pidAlive ? "ACTIVE" : "stale (process exited)"));
2678
+ console.log("hook pid: " + h.pid + (pidAlive ? " (alive)" : " (gone)"));
2679
+ console.log("hook mode: " + h.mode);
2680
+ console.log("hook started: " + h.started_at);
2681
+ if (h.patched) {
2682
+ console.log("\nPatched functions:");
2683
+ for (const [fn, active] of Object.entries(h.patched)) {
2684
+ console.log(" " + (active ? "\u2713" : "\u2013") + " " + fn);
2685
+ }
2686
+ }
2687
+ }
2748
2688
  } else {
2749
- console.log("\nactivation: none \u2014 run: lbe-exec activate");
2689
+ console.log("\nhook process: inactive \u2014 run: lbe-exec run-node ./agent.js");
2690
+ console.log(" or: lbe-exec activate then lbe-exec shell");
2750
2691
  }
2751
- console.log("\nevents log: .lbe/events.jsonl");
2752
2692
  break;
2753
2693
  }
2754
2694
  case "audit": {
@@ -2793,12 +2733,12 @@ switch (cmd) {
2793
2733
  case "activate": {
2794
2734
  const hookPath = findHookPath();
2795
2735
  if (!fs14.existsSync(hookPath)) {
2796
- console.error("Hook not found: " + hookPath + "\nRun: npm install @letterblack/lbe-exec");
2736
+ console.error("Hook not found: " + hookPath);
2737
+ console.error("Run: npm install @letterblack/lbe-exec");
2797
2738
  process.exit(1);
2798
2739
  }
2799
2740
  const mode = opts.mode || "observe";
2800
2741
  const root = process.cwd();
2801
- const reqFlag = '--require "' + hookPath + '"';
2802
2742
  const lbeDir = path15.join(root, ".lbe");
2803
2743
  fs14.mkdirSync(lbeDir, { recursive: true });
2804
2744
  fs14.writeFileSync(path15.join(lbeDir, "activation.json"), JSON.stringify({
@@ -2806,111 +2746,83 @@ switch (cmd) {
2806
2746
  activatedAt: (/* @__PURE__ */ new Date()).toISOString(),
2807
2747
  hookPath,
2808
2748
  mode,
2809
- root,
2810
- permanent: !!opts.permanent
2749
+ root
2811
2750
  }, null, 2) + "\n");
2812
- if (opts.permanent) {
2813
- if (process.platform === "win32") {
2814
- const psRead = spawnSync2("powershell.exe", [
2815
- "-NoProfile",
2816
- "-NonInteractive",
2817
- "-Command",
2818
- '[System.Environment]::GetEnvironmentVariable("NODE_OPTIONS","User")'
2819
- ], { encoding: "utf8" });
2820
- const current = (psRead.stdout || "").trim();
2821
- const merged = current.includes(hookPath) ? current : (current + " " + reqFlag).trim();
2822
- spawnSync2("powershell.exe", [
2823
- "-NoProfile",
2824
- "-NonInteractive",
2825
- "-Command",
2826
- `[System.Environment]::SetEnvironmentVariable('NODE_OPTIONS','${merged}','User');[System.Environment]::SetEnvironmentVariable('LBE_ROOT','${root}','User');[System.Environment]::SetEnvironmentVariable('LBE_MODE','${mode}','User')`
2827
- ], { stdio: "inherit" });
2828
- console.log("\n\u2713 LBE permanently activated \u2014 Windows user environment updated.");
2829
- console.log(" Restart your terminal or IDE to apply.\n");
2830
- console.log(" Every node / npm / npx process will be governed automatically.");
2831
- } else {
2832
- const home = process.env.HOME || "";
2833
- const rcFiles = [".bashrc", ".zshrc"].map((f) => path15.join(home, f)).filter((f) => fs14.existsSync(f));
2834
- const block = `
2835
- # LBE Agent Governance
2836
- export NODE_OPTIONS='${reqFlag}'
2837
- export LBE_ROOT="${root}"
2838
- export LBE_MODE="${mode}"
2839
- `;
2840
- for (const rc of rcFiles) {
2841
- fs14.appendFileSync(rc, block);
2842
- console.log("\u2713 Appended to " + rc);
2843
- }
2844
- if (!rcFiles.length) console.log("No .bashrc/.zshrc found \u2014 add these manually:\n" + block);
2845
- console.log(" Run: source ~/.bashrc (or restart terminal)");
2751
+ console.log("\u2500\u2500 LBE workspace activated \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2752
+ console.log("workspace: " + root);
2753
+ console.log("hook: " + hookPath);
2754
+ console.log("mode: " + mode);
2755
+ console.log("\nNext: open a governed shell session:");
2756
+ console.log(" lbe-exec shell");
2757
+ console.log("\nAny Node.js agent run inside that shell is intercepted.");
2758
+ console.log("Python, Go, native binaries, and PowerShell are NOT governed.");
2759
+ break;
2760
+ }
2761
+ case "shell": {
2762
+ const activationFile = path15.join(process.cwd(), ".lbe", "activation.json");
2763
+ let activation = null;
2764
+ if (fs14.existsSync(activationFile)) {
2765
+ try {
2766
+ activation = JSON.parse(fs14.readFileSync(activationFile, "utf8"));
2767
+ } catch (_) {
2846
2768
  }
2769
+ }
2770
+ const hookPath = activation && activation.hookPath || findHookPath();
2771
+ if (!fs14.existsSync(hookPath)) {
2772
+ console.error("Hook not found. Run: lbe-exec activate");
2773
+ process.exit(1);
2774
+ }
2775
+ const mode = opts.mode || activation && activation.mode || "observe";
2776
+ const root = activation && activation.root || process.cwd();
2777
+ const hookPathFwd = hookPath.replace(/\\/g, "/");
2778
+ const nodeOpts = '--require "' + hookPathFwd + '"';
2779
+ const shellEnv = { ...process.env, NODE_OPTIONS: nodeOpts, LBE_ROOT: root, LBE_MODE: mode };
2780
+ console.log("[lbe] Opening governed shell \u2014 mode: " + mode);
2781
+ console.log("[lbe] NODE_OPTIONS set. Node.js agents are intercepted.");
2782
+ console.log("[lbe] Python / Go / native binaries are NOT governed.");
2783
+ console.log('[lbe] Type "exit" to close.\n');
2784
+ let shellProc;
2785
+ if (process.platform === "win32") {
2786
+ const banner = [
2787
+ `$env:NODE_OPTIONS='--require "${hookPathFwd}"'`,
2788
+ `$env:LBE_ROOT='${root}'`,
2789
+ `$env:LBE_MODE='${mode}'`,
2790
+ `Write-Host '[lbe] Shell armed \u2014 mode: ${mode}' -ForegroundColor Green`
2791
+ ].join("; ");
2792
+ shellProc = spawn(
2793
+ "powershell.exe",
2794
+ ["-NoExit", "-Command", banner],
2795
+ { stdio: "inherit", env: shellEnv }
2796
+ );
2847
2797
  } else {
2848
- console.log("\u2500\u2500 LBE Workspace Activation \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
2849
- console.log("workspace: " + root);
2850
- console.log("hook: " + hookPath);
2851
- console.log("mode: " + mode);
2852
- console.log("\nCopy and run in your terminal to arm this session:\n");
2853
- if (process.platform === "win32") {
2854
- console.log("# PowerShell:");
2855
- console.log(`$env:NODE_OPTIONS='--require "` + hookPath + `"'`);
2856
- console.log('$env:LBE_ROOT="' + root + '"');
2857
- console.log('$env:LBE_MODE="' + mode + '"');
2858
- console.log("\n# cmd.exe:");
2859
- console.log('set NODE_OPTIONS=--require "' + hookPath + '"');
2860
- console.log("set LBE_ROOT=" + root);
2861
- console.log("set LBE_MODE=" + mode);
2862
- } else {
2863
- console.log(`export NODE_OPTIONS='--require "` + hookPath + `"'`);
2864
- console.log('export LBE_ROOT="' + root + '"');
2865
- console.log('export LBE_MODE="' + mode + '"');
2866
- }
2867
- console.log("\nAfter running: every node / npm / npx command in this session is governed.");
2868
- console.log("To persist across all terminals: lbe-exec activate --permanent");
2798
+ const sh = process.env.SHELL || "/bin/bash";
2799
+ shellProc = spawn(sh, [], { stdio: "inherit", env: shellEnv });
2869
2800
  }
2801
+ shellProc.on("close", (code) => {
2802
+ console.log("\n[lbe] Governed shell closed.");
2803
+ process.exit(code ?? 0);
2804
+ });
2870
2805
  break;
2871
2806
  }
2872
2807
  case "deactivate": {
2873
- const activationFile = path15.join(process.cwd(), ".lbe", "activation.json");
2874
- if (fs14.existsSync(activationFile)) fs14.unlinkSync(activationFile);
2875
- if (opts.permanent) {
2876
- if (process.platform === "win32") {
2877
- const hookPath = findHookPath();
2878
- const psRead = spawnSync2("powershell.exe", [
2879
- "-NoProfile",
2880
- "-NonInteractive",
2881
- "-Command",
2882
- '[System.Environment]::GetEnvironmentVariable("NODE_OPTIONS","User")'
2883
- ], { encoding: "utf8" });
2884
- const current = (psRead.stdout || "").trim();
2885
- const cleaned = current.replace('--require "' + hookPath + '"', "").replace("--require " + hookPath, "").trim();
2886
- spawnSync2("powershell.exe", [
2887
- "-NoProfile",
2888
- "-NonInteractive",
2889
- "-Command",
2890
- (cleaned ? `[System.Environment]::SetEnvironmentVariable('NODE_OPTIONS','${cleaned}','User');` : `[System.Environment]::SetEnvironmentVariable('NODE_OPTIONS',$null,'User');`) + `[System.Environment]::SetEnvironmentVariable('LBE_ROOT',$null,'User');[System.Environment]::SetEnvironmentVariable('LBE_MODE',$null,'User')`
2891
- ], { stdio: "inherit" });
2892
- console.log("\u2713 LBE deactivated \u2014 user environment cleared. Restart terminal to apply.");
2893
- } else {
2894
- console.log("Remove these lines from your shell RC file (.bashrc / .zshrc):");
2895
- console.log(" export NODE_OPTIONS=... (the --require lbe line)");
2896
- console.log(" export LBE_ROOT=...");
2897
- console.log(" export LBE_MODE=...");
2808
+ const root = process.cwd();
2809
+ const files = [
2810
+ path15.join(root, ".lbe", "activation.json"),
2811
+ path15.join(root, ".lbe", "runtime", "hook-status.json")
2812
+ ];
2813
+ let removed = 0;
2814
+ for (const f of files) {
2815
+ if (fs14.existsSync(f)) {
2816
+ fs14.unlinkSync(f);
2817
+ removed++;
2898
2818
  }
2819
+ }
2820
+ if (removed) {
2821
+ console.log("\u2713 LBE deactivated \u2014 workspace activation files removed.");
2899
2822
  } else {
2900
- console.log("Run these commands to disarm this session:\n");
2901
- if (process.platform === "win32") {
2902
- console.log("# PowerShell:");
2903
- console.log("Remove-Item Env:NODE_OPTIONS -ErrorAction SilentlyContinue");
2904
- console.log("Remove-Item Env:LBE_ROOT -ErrorAction SilentlyContinue");
2905
- console.log("Remove-Item Env:LBE_MODE -ErrorAction SilentlyContinue");
2906
- console.log("\n# cmd.exe:");
2907
- console.log("set NODE_OPTIONS=");
2908
- console.log("set LBE_ROOT=");
2909
- console.log("set LBE_MODE=");
2910
- } else {
2911
- console.log("unset NODE_OPTIONS LBE_ROOT LBE_MODE");
2912
- }
2823
+ console.log("Nothing to deactivate (workspace was not activated).");
2913
2824
  }
2825
+ console.log('Close any open "lbe-exec shell" sessions to fully disarm.');
2914
2826
  break;
2915
2827
  }
2916
2828
  case "observe":
@@ -2923,7 +2835,7 @@ export LBE_MODE="${mode}"
2923
2835
  case "policy": {
2924
2836
  const policy = loadPolicy();
2925
2837
  if (!policy) {
2926
- console.log("No lbe.policy.json found. Run: npx lbe-exec init");
2838
+ console.log("No policy found. Run: npx lbe-exec init");
2927
2839
  break;
2928
2840
  }
2929
2841
  if (!policy.rules?.length) {
@@ -2964,10 +2876,11 @@ export LBE_MODE="${mode}"
2964
2876
  console.log(" status Show workspace, mode, hook state, patched functions");
2965
2877
  console.log(" audit Show unified event log (.lbe/events.jsonl)");
2966
2878
  console.log(" policy List active policy rules");
2967
- console.log(" activate Arm workspace \u2014 every node/npm/npx is governed");
2968
- console.log(" [--mode observe|enforce] [--permanent]");
2969
- console.log(" deactivate Disarm workspace");
2970
- console.log(" [--permanent]");
2879
+ console.log(" activate Write workspace activation record (Node.js only)");
2880
+ console.log(" [--mode observe|enforce]");
2881
+ console.log(" shell Open a governed terminal (NODE_OPTIONS pre-set)");
2882
+ console.log(" [--mode observe|enforce]");
2883
+ console.log(" deactivate Remove workspace activation files");
2971
2884
  console.log(" observe Switch to observer mode (log only, nothing blocked)");
2972
2885
  console.log(" enforce Switch to enforcement mode (violations blocked)");
2973
2886
  console.log(" execute Send a JSON request from stdin or --input file");
package/dist/index.js CHANGED
@@ -1490,7 +1490,7 @@ function verifyAuditLogIntegrity(logPath, options = {}) {
1490
1490
  import fs8 from "fs";
1491
1491
  import path9 from "path";
1492
1492
  import crypto4 from "crypto";
1493
- var POLICY_FILE = "lbe.policy.json";
1493
+ var POLICY_FILE = ".lbe/policy.json";
1494
1494
  var AUDIT_FILE = ".lbe/audit.jsonl";
1495
1495
  function glob(pattern) {
1496
1496
  const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&");
@@ -14,7 +14,10 @@ const MODE = process.env.LBE_MODE || 'observe';
14
14
  // ── Policy loader (inline CJS — ESM cannot be require()'d synchronously) ────
15
15
 
16
16
  function loadPolicy() {
17
- const policyPath = path.join(ROOT_DIR, 'lbe.policy.json');
17
+ // .lbe/policy.json is canonical. Fall back to legacy lbe.policy.json in root.
18
+ const policyPath = fs.existsSync(path.join(ROOT_DIR, '.lbe', 'policy.json'))
19
+ ? path.join(ROOT_DIR, '.lbe', 'policy.json')
20
+ : path.join(ROOT_DIR, 'lbe.policy.json');
18
21
  try {
19
22
  if (fs.existsSync(policyPath)) {
20
23
  return JSON.parse(fs.readFileSync(policyPath, 'utf8'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterblack/lbe-exec",
3
- "version": "1.2.15",
3
+ "version": "1.2.17",
4
4
  "description": "Local host-signed execution layer for LetterBlack LBE.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",