holomime 1.5.0 → 1.5.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/dist/cli.js CHANGED
@@ -15892,6 +15892,8 @@ Intermediate results saved to ${chalk39.cyan(".holomime/pipeline/")}`,
15892
15892
  }
15893
15893
 
15894
15894
  // src/commands/live.ts
15895
+ import { deflateSync } from "zlib";
15896
+ import { execSync } from "child_process";
15895
15897
  import chalk40 from "chalk";
15896
15898
 
15897
15899
  // src/live/agent-detector.ts
@@ -16353,12 +16355,59 @@ function startServer(port) {
16353
16355
  }
16354
16356
 
16355
16357
  // src/commands/live.ts
16358
+ function encodeSnapshot(event, agentName) {
16359
+ const compact = {
16360
+ h: event.health,
16361
+ g: event.grade,
16362
+ m: event.messageCount,
16363
+ a: agentName,
16364
+ r: event.regions.filter((r) => r.intensity > 0).map((r) => ({ i: r.id, n: Math.round(r.intensity * 100) / 100 })),
16365
+ p: event.patterns.map((p) => ({
16366
+ i: p.id,
16367
+ s: p.severity,
16368
+ c: Math.round(p.percentage * 10) / 10
16369
+ }))
16370
+ };
16371
+ const json = JSON.stringify(compact);
16372
+ const compressed = deflateSync(Buffer.from(json));
16373
+ return compressed.toString("base64url");
16374
+ }
16375
+ function copyToClipboard(text) {
16376
+ try {
16377
+ if (process.platform === "darwin") {
16378
+ execSync("pbcopy", { input: text });
16379
+ return true;
16380
+ } else if (process.platform === "linux") {
16381
+ execSync("xclip -selection clipboard", { input: text });
16382
+ return true;
16383
+ } else if (process.platform === "win32") {
16384
+ execSync("clip", { input: text });
16385
+ return true;
16386
+ }
16387
+ } catch {
16388
+ }
16389
+ return false;
16390
+ }
16391
+ function printShareLink(url, copied) {
16392
+ console.log("");
16393
+ console.log(
16394
+ chalk40.green(" \u2713 ") + chalk40.bold("Brain snapshot captured!")
16395
+ );
16396
+ console.log("");
16397
+ console.log(" " + chalk40.cyan(url));
16398
+ console.log("");
16399
+ if (copied) {
16400
+ console.log(chalk40.dim(" Link copied to clipboard."));
16401
+ }
16402
+ }
16356
16403
  async function liveCommand(options) {
16357
16404
  const port = options.port || 3838;
16358
16405
  let agent;
16359
16406
  if (options.watchPath) {
16360
16407
  agent = manualAgent(options.watchPath);
16361
- console.log(chalk40.green(" \u2713") + ` Manual watch: ${chalk40.dim(agent.logPath)}`);
16408
+ console.log(
16409
+ chalk40.green(" \u2713") + ` Manual watch: ${chalk40.dim(agent.logPath)}`
16410
+ );
16362
16411
  } else {
16363
16412
  console.log(chalk40.dim(" Scanning for active agents..."));
16364
16413
  agent = detectAgent();
@@ -16366,19 +16415,69 @@ async function liveCommand(options) {
16366
16415
  console.log("");
16367
16416
  console.log(chalk40.red(" \u2717 No active agent detected."));
16368
16417
  console.log("");
16369
- console.log(chalk40.dim(" Make sure an AI coding agent is running, or specify a path:"));
16370
- console.log(chalk40.cyan(" holomime brain --watch <path-to-conversation-log>"));
16418
+ console.log(
16419
+ chalk40.dim(
16420
+ " Make sure an AI coding agent is running, or specify a path:"
16421
+ )
16422
+ );
16423
+ console.log(
16424
+ chalk40.cyan(" holomime brain --watch <path-to-conversation-log>")
16425
+ );
16371
16426
  console.log("");
16372
- console.log(chalk40.dim(" Supported agents: Claude Code, Cline, OpenClaw, Codex, Cursor"));
16427
+ console.log(
16428
+ chalk40.dim(" Supported agents: Claude Code, Cline, OpenClaw, Codex, Cursor")
16429
+ );
16373
16430
  process.exit(1);
16374
16431
  }
16375
- console.log(chalk40.green(" \u2713") + ` Detected ${chalk40.bold(agent.agent)} session`);
16376
- console.log(chalk40.green(" \u2713") + ` Watching: ${chalk40.dim(agent.logPath)}`);
16432
+ console.log(
16433
+ chalk40.green(" \u2713") + ` Detected ${chalk40.bold(agent.agent)} session`
16434
+ );
16435
+ console.log(
16436
+ chalk40.green(" \u2713") + ` Watching: ${chalk40.dim(agent.logPath)}`
16437
+ );
16438
+ }
16439
+ if (options.share) {
16440
+ console.log(chalk40.dim(" Running diagnosis for snapshot..."));
16441
+ let resolved = false;
16442
+ await new Promise((resolve46) => {
16443
+ const watcher2 = startWatcher(agent, {
16444
+ onEvent(event) {
16445
+ if (resolved) return;
16446
+ resolved = true;
16447
+ watcher2.stop();
16448
+ const encoded = encodeSnapshot(event, agent.agent);
16449
+ const url = `https://holomime.dev/brain?d=${encoded}`;
16450
+ const copied = copyToClipboard(url);
16451
+ printShareLink(url, copied);
16452
+ resolve46();
16453
+ },
16454
+ onError(err) {
16455
+ console.error(chalk40.red(`
16456
+ \u2717 Error: ${err.message}`));
16457
+ process.exit(1);
16458
+ },
16459
+ onReady() {
16460
+ }
16461
+ });
16462
+ setTimeout(() => {
16463
+ if (!resolved) {
16464
+ resolved = true;
16465
+ watcher2.stop();
16466
+ console.log(
16467
+ chalk40.red(" \u2717 No diagnosis data available. Is the agent active?")
16468
+ );
16469
+ process.exit(1);
16470
+ }
16471
+ }, 1e4);
16472
+ });
16473
+ process.exit(0);
16377
16474
  }
16378
16475
  let server;
16379
16476
  try {
16380
16477
  server = await startServer(port);
16381
- console.log(chalk40.green(" \u2713") + ` NeuralSpace: ${chalk40.cyan(`http://localhost:${server.port}`)}`);
16478
+ console.log(
16479
+ chalk40.green(" \u2713") + ` NeuralSpace: ${chalk40.cyan(`http://localhost:${server.port}`)}`
16480
+ );
16382
16481
  } catch (err) {
16383
16482
  console.log(chalk40.red(` \u2717 ${err.message}`));
16384
16483
  process.exit(1);
@@ -16397,21 +16496,21 @@ async function liveCommand(options) {
16397
16496
  } catch {
16398
16497
  }
16399
16498
  }
16400
- let lastHealth = 100;
16401
- let lastPatternCount = 0;
16499
+ let lastEvent = null;
16402
16500
  let msgCount = 0;
16403
16501
  const watcher = startWatcher(agent, {
16404
16502
  onEvent(event) {
16405
16503
  server.broadcast(event);
16504
+ lastEvent = event;
16406
16505
  msgCount = event.messageCount;
16407
- lastHealth = event.health;
16408
- lastPatternCount = event.patterns.length;
16409
16506
  const healthColor = event.health >= 85 ? chalk40.green : event.health >= 70 ? chalk40.yellow : event.health >= 50 ? chalk40.hex("#f97316") : chalk40.red;
16410
16507
  const patternStr = event.patterns.length > 0 ? event.patterns.map((p) => {
16411
16508
  const c = p.severity === "concern" ? chalk40.red : p.severity === "warning" ? chalk40.yellow : chalk40.dim;
16412
16509
  return c(p.id);
16413
16510
  }).join(", ") : chalk40.green("none");
16414
- process.stdout.write(`\r ${chalk40.dim("\u2502")} Health: ${healthColor(`${event.health}/100`)} (${event.grade}) ${chalk40.dim("\u2502")} Patterns: ${patternStr} ${chalk40.dim("\u2502")} Messages: ${chalk40.white(String(msgCount))} `);
16511
+ process.stdout.write(
16512
+ `\r ${chalk40.dim("\u2502")} Health: ${healthColor(`${event.health}/100`)} (${event.grade}) ${chalk40.dim("\u2502")} Patterns: ${patternStr} ${chalk40.dim("\u2502")} Messages: ${chalk40.white(String(msgCount))} `
16513
+ );
16415
16514
  },
16416
16515
  onError(err) {
16417
16516
  console.error(chalk40.red(`
@@ -16419,13 +16518,35 @@ async function liveCommand(options) {
16419
16518
  },
16420
16519
  onReady() {
16421
16520
  console.log("");
16422
- console.log(chalk40.green(" \u25CF ") + chalk40.bold("Monitoring agent behavior in real-time"));
16423
- console.log(chalk40.dim(" \u2502 Press Ctrl+C to stop"));
16521
+ console.log(
16522
+ chalk40.green(" \u25CF ") + chalk40.bold("Monitoring agent behavior in real-time")
16523
+ );
16524
+ console.log(
16525
+ chalk40.dim(" \u2502 Press Ctrl+C to stop") + chalk40.dim(" \xB7 Press s to share snapshot")
16526
+ );
16424
16527
  console.log("");
16425
16528
  }
16426
16529
  });
16530
+ if (process.stdin.isTTY) {
16531
+ process.stdin.setRawMode(true);
16532
+ process.stdin.resume();
16533
+ process.stdin.on("data", (data) => {
16534
+ const key = data.toString();
16535
+ if (key === "") {
16536
+ shutdown();
16537
+ return;
16538
+ }
16539
+ if ((key === "s" || key === "S") && lastEvent) {
16540
+ const encoded = encodeSnapshot(lastEvent, agent.agent);
16541
+ const url = `https://holomime.dev/brain?d=${encoded}`;
16542
+ const copied = copyToClipboard(url);
16543
+ printShareLink(url, copied);
16544
+ }
16545
+ });
16546
+ }
16427
16547
  const shutdown = () => {
16428
16548
  console.log(chalk40.dim("\n\n Stopping..."));
16549
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
16429
16550
  watcher.stop();
16430
16551
  server.close();
16431
16552
  process.exit(0);
@@ -16503,11 +16624,12 @@ program.command("interview").description("Self-awareness interview \u2014 score
16503
16624
  program.command("prescribe").description("Diagnose and prescribe DPO treatments from the behavioral corpus [Pro]").requiredOption("--personality <path>", "Path to .personality.json").requiredOption("--log <path>", "Path to conversation log").option("--format <format>", "Log format (holomime, chatgpt, claude, openai-api, anthropic-api, otel, jsonl)").option("--source <source>", "Correction source (corpus, marketplace, both)", "corpus").option("--apply", "Apply found treatments").option("-o, --output <path>", "Write prescription to file").action(prescribeCommand);
16504
16625
  program.command("voice").description("Monitor voice conversations for behavioral drift in real-time [Pro]").requiredOption("--personality <path>", "Path to .personality.json").option("--platform <name>", "Voice platform: livekit, vapi, retell, generic", "generic").option("--room <name>", "LiveKit room name").option("--server-url <url>", "LiveKit server URL").option("--webhook-port <port>", "Vapi webhook port (default: 3001)").option("--agent-id <id>", "Retell agent ID").option("--input <path>", "Input transcript file (JSONL) for offline analysis").option("--interval <ms>", "Diagnosis interval in milliseconds (default: 15000)").option("--threshold <level>", "Alert threshold: warning or concern (default: warning)").action(voiceCommand);
16505
16626
  program.command("cure").description("End-to-end behavioral fix: diagnose \u2192 export \u2192 train \u2192 verify [Pro]").requiredOption("--personality <path>", "Path to .personality.json").requiredOption("--log <path>", "Path to conversation log (JSON)").option("--provider <provider>", "Training provider (openai, huggingface)", "openai").option("--base-model <model>", "Base model to fine-tune", "gpt-4o-mini-2024-07-18").option("--method <method>", "Training method (auto, sft, dpo)", "auto").option("--epochs <n>", "Number of training epochs").option("--suffix <name>", "Model name suffix").option("--skip-train", "Skip training step (diagnose + export only)").option("--skip-verify", "Skip post-training verification").option("--dry-run", "Preview pipeline plan without executing").option("--push", "Push trained model to HuggingFace Hub").option("--hub-repo <repo>", "HuggingFace Hub repo (user/model-name)").option("--pass-threshold <n>", "Minimum verification score (0-100)", "50").action(cureCommand);
16506
- program.command("brain").description("See your agent's brain \u2014 real-time NeuralSpace visualization [Pro]").option("--watch <path>", "Manual path to conversation log file").option("--agent <agent>", "Agent type override (claude-code, cline, manual)").option("--port <port>", "Server port (default: 3838)", "3838").option("--no-open", "Don't auto-open browser").option("--personality <path>", "Personality spec for assessment context").action((opts) => liveCommand({
16627
+ program.command("brain").description("See your agent's brain \u2014 real-time NeuralSpace visualization [Pro]").option("--watch <path>", "Manual path to conversation log file").option("--agent <agent>", "Agent type override (claude-code, cline, manual)").option("--port <port>", "Server port (default: 3838)", "3838").option("--no-open", "Don't auto-open browser").option("--share", "Capture a brain snapshot and generate a shareable link").option("--personality <path>", "Personality spec for assessment context").action((opts) => liveCommand({
16507
16628
  watchPath: opts.watch,
16508
16629
  agent: opts.agent,
16509
16630
  port: parseInt(opts.port, 10),
16510
16631
  noOpen: opts.open === false,
16632
+ share: opts.share === true,
16511
16633
  personality: opts.personality
16512
16634
  }));
16513
16635
  program.parseAsync().then(() => flushTelemetry());
@@ -5,6 +5,7 @@
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
6
  <title>NeuralSpace — HoloMime Brain</title>
7
7
  <link rel="stylesheet" href="styles.css">
8
+ <script src="https://cdn.jsdelivr.net/npm/pako@2.1.0/dist/pako_inflate.min.js"></script>
8
9
  <script type="importmap">
9
10
  { "imports": {
10
11
  "three": "https://cdn.jsdelivr.net/npm/three@0.172.0/build/three.module.js",
@@ -57,6 +58,12 @@
57
58
  <span>Reconnecting to agent...</span>
58
59
  </div>
59
60
 
61
+ <div id="snapshot-cta">
62
+ <span>See your own agent's brain</span>
63
+ <code>npx holomime brain</code>
64
+ <a href="https://holomime.dev/brain">Learn more</a>
65
+ </div>
66
+
60
67
  <div id="watermark">Powered by HoloMime</div>
61
68
  </div>
62
69
 
@@ -832,8 +832,71 @@ function animate(){
832
832
  composer.render();
833
833
  }
834
834
 
835
+ // ═══════════ SNAPSHOT MODE ═══════════
836
+
837
+ function initSnapshot(encoded) {
838
+ try {
839
+ // Decode base64url → Uint8Array → inflate → JSON
840
+ const b64 = encoded.replace(/-/g, '+').replace(/_/g, '/');
841
+ const bin = atob(b64);
842
+ const bytes = new Uint8Array(bin.length);
843
+ for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
844
+ const inflated = window.pako.inflate(bytes, { to: 'string' });
845
+ const compact = JSON.parse(inflated);
846
+
847
+ // Expand compact format → full BrainEvent
848
+ const event = {
849
+ type: 'diagnosis',
850
+ timestamp: new Date().toISOString(),
851
+ health: compact.h,
852
+ grade: compact.g,
853
+ messageCount: compact.m || 0,
854
+ regions: (compact.r || []).map(r => ({
855
+ id: r.i,
856
+ intensity: r.n,
857
+ patterns: [],
858
+ })),
859
+ patterns: (compact.p || []).map(p => ({
860
+ id: p.i,
861
+ name: p.i.replace(/-/g, ' ').replace(/\b\w/g, c => c.toUpperCase()),
862
+ severity: p.s,
863
+ percentage: p.c,
864
+ description: '',
865
+ })),
866
+ activity: null,
867
+ };
868
+
869
+ // Update UI
870
+ handleInit({ type: 'init', agent: compact.a || 'unknown', sessionPath: 'snapshot', startedAt: new Date().toISOString() });
871
+ handleDiagnosis(event);
872
+
873
+ // Update status to "Snapshot"
874
+ statusEl.className = 'status-badge';
875
+ statusEl.querySelector('span').textContent = 'Snapshot';
876
+ statusEl.querySelector('.status-dot').style.background = 'var(--accent)';
877
+ statusEl.querySelector('.status-dot').style.boxShadow = '0 0 8px var(--accent)';
878
+ statusEl.querySelector('.status-dot').style.animation = 'none';
879
+
880
+ // Show snapshot CTA
881
+ const ctaEl = document.getElementById('snapshot-cta');
882
+ if (ctaEl) ctaEl.classList.add('visible');
883
+
884
+ } catch (err) {
885
+ console.error('Failed to decode snapshot:', err);
886
+ statusEl.className = 'status-badge disconnected';
887
+ statusEl.querySelector('span').textContent = 'Invalid snapshot';
888
+ }
889
+ }
890
+
835
891
  // ═══════════ INIT ═══════════
836
892
 
837
893
  updateHealth(100, 'A');
838
- connect();
894
+
895
+ const urlParams = new URLSearchParams(window.location.search);
896
+ const snapshotParam = urlParams.get('d');
897
+ if (snapshotParam) {
898
+ initSnapshot(snapshotParam);
899
+ } else {
900
+ connect();
901
+ }
839
902
  animate();
@@ -479,6 +479,51 @@ body {
479
479
  to { transform: rotate(360deg); }
480
480
  }
481
481
 
482
+ /* ─── Snapshot CTA ─── */
483
+ #snapshot-cta {
484
+ position: absolute;
485
+ bottom: 20px;
486
+ left: 50%;
487
+ transform: translateX(-50%);
488
+ z-index: 20;
489
+ background: var(--bg-panel);
490
+ border: 1px solid var(--border);
491
+ border-radius: 10px;
492
+ padding: 12px 24px;
493
+ display: none;
494
+ align-items: center;
495
+ gap: 12px;
496
+ -webkit-backdrop-filter: blur(12px);
497
+ backdrop-filter: blur(12px);
498
+ animation: fadeIn 0.5s ease 0.5s both;
499
+ }
500
+
501
+ #snapshot-cta.visible { display: flex; }
502
+
503
+ #snapshot-cta span {
504
+ font-size: 13px;
505
+ color: var(--text);
506
+ font-weight: 500;
507
+ }
508
+
509
+ #snapshot-cta code {
510
+ font-family: var(--mono);
511
+ font-size: 13px;
512
+ color: #06b6d4;
513
+ background: rgba(6,182,212,0.1);
514
+ padding: 4px 10px;
515
+ border-radius: 6px;
516
+ border: 1px solid rgba(6,182,212,0.2);
517
+ }
518
+
519
+ #snapshot-cta a {
520
+ font-size: 12px;
521
+ color: var(--accent);
522
+ text-decoration: none;
523
+ }
524
+
525
+ #snapshot-cta a:hover { text-decoration: underline; }
526
+
482
527
  /* ─── Scrollbar ─── */
483
528
  ::-webkit-scrollbar { width: 4px; }
484
529
  ::-webkit-scrollbar-track { background: transparent; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holomime",
3
- "version": "1.5.0",
3
+ "version": "1.5.1",
4
4
  "description": "Behavioral therapy infrastructure for AI agents — Big Five psychology, structured treatment, DPO training data",
5
5
  "type": "module",
6
6
  "bin": {