holomime 3.2.0 → 3.3.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.
Files changed (3) hide show
  1. package/README.md +10 -2
  2. package/dist/cli.js +476 -57
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -71,6 +71,9 @@ Mira, our autonomous behavioral therapist, diagnoses drift, runs structured ther
71
71
  ```bash
72
72
  npm install -g holomime
73
73
 
74
+ # Configure your API key (one time)
75
+ holomime config
76
+
74
77
  # Quick start — 1 file (personality.json)
75
78
  holomime personality
76
79
 
@@ -80,11 +83,16 @@ holomime core
80
83
  # Full 8-file stack (enterprise / robotics)
81
84
  # holomime identity
82
85
 
83
- # ─── The 3-command workflow ───
86
+ # ─── The workflow ───
84
87
  holomime diagnose # See what's wrong
85
88
  holomime cure # Fix it permanently
86
89
  holomime benchmark # Verify the fix
87
90
 
91
+ # Start autonomous therapy
92
+ holomime mira # Mira runs continuous therapy cycles
93
+ holomime mira status # How's Mira doing?
94
+ holomime mira stop # Stop therapy
95
+
88
96
  # Push identity to a robot or avatar
89
97
  holomime embody --body registry/bodies/figure-03.body.api
90
98
  ```
@@ -176,7 +184,7 @@ Diagnose ──→ Cure ──→ Benchmark
176
184
  └───────────────────────────────────┘
177
185
  ```
178
186
 
179
- Run it manually with `holomime diagnose` + `holomime cure` + `holomime benchmark`, automatically with `holomime autopilot`, or recursively with `holomime evolve` (loops until behavior converges). For power users: `holomime align` runs a single therapy session, `holomime export` extracts DPO pairs, and `holomime train` fine-tunes the model.
187
+ Run it manually with `holomime diagnose` + `holomime cure` + `holomime benchmark`, continuously with `holomime mira` (autonomous therapy), or recursively with `holomime evolve` (loops until behavior converges). For power users: `holomime align` runs a single therapy session, `holomime export` extracts DPO pairs, and `holomime train` fine-tunes the model.
180
188
 
181
189
  ## Behavioral Detectors
182
190
 
package/dist/cli.js CHANGED
@@ -2184,7 +2184,7 @@ async function getJobEvents(apiKey, jobId, limit = 5) {
2184
2184
  return data.data ?? [];
2185
2185
  }
2186
2186
  function sleep(ms) {
2187
- return new Promise((resolve56) => setTimeout(resolve56, ms));
2187
+ return new Promise((resolve57) => setTimeout(resolve57, ms));
2188
2188
  }
2189
2189
  var OPENAI_API, POLL_INTERVAL_MS, OpenAITrainProvider;
2190
2190
  var init_train_openai = __esm({
@@ -2342,7 +2342,7 @@ function writeHFTrainingFile(data) {
2342
2342
  return filePath;
2343
2343
  }
2344
2344
  function sleep2(ms) {
2345
- return new Promise((resolve56) => setTimeout(resolve56, ms));
2345
+ return new Promise((resolve57) => setTimeout(resolve57, ms));
2346
2346
  }
2347
2347
  async function getHFUsername(token) {
2348
2348
  const response = await fetch(`${HF_API}/whoami-v2`, {
@@ -3280,7 +3280,7 @@ var init_vapi_adapter = __esm({
3280
3280
  this.callbacks = callbacks;
3281
3281
  const port = this.options.port ?? 3001;
3282
3282
  const host = this.options.host ?? "0.0.0.0";
3283
- return new Promise((resolve56, reject) => {
3283
+ return new Promise((resolve57, reject) => {
3284
3284
  this.server = createServer2((req, res) => this.handleRequest(req, res));
3285
3285
  this.server.on("error", (err) => {
3286
3286
  callbacks.onError(`Vapi webhook server error: ${err.message}`);
@@ -3289,22 +3289,22 @@ var init_vapi_adapter = __esm({
3289
3289
  this.server.listen(port, host, () => {
3290
3290
  this.connected = true;
3291
3291
  callbacks.onConnected?.();
3292
- resolve56();
3292
+ resolve57();
3293
3293
  });
3294
3294
  });
3295
3295
  }
3296
3296
  async disconnect() {
3297
- return new Promise((resolve56) => {
3297
+ return new Promise((resolve57) => {
3298
3298
  if (this.server) {
3299
3299
  this.server.close(() => {
3300
3300
  this.connected = false;
3301
3301
  this.callbacks?.onDisconnected?.();
3302
3302
  this.callbacks = null;
3303
3303
  this.server = null;
3304
- resolve56();
3304
+ resolve57();
3305
3305
  });
3306
3306
  } else {
3307
- resolve56();
3307
+ resolve57();
3308
3308
  }
3309
3309
  });
3310
3310
  }
@@ -3466,7 +3466,7 @@ var generic_adapter_exports = {};
3466
3466
  __export(generic_adapter_exports, {
3467
3467
  GenericAdapter: () => GenericAdapter
3468
3468
  });
3469
- import { existsSync as existsSync38, watchFile as watchFile2, unwatchFile as unwatchFile2, readFileSync as readFileSync42 } from "fs";
3469
+ import { existsSync as existsSync38, watchFile as watchFile2, unwatchFile as unwatchFile2, readFileSync as readFileSync43 } from "fs";
3470
3470
  import { createInterface as createInterface5 } from "readline";
3471
3471
  var GenericAdapter;
3472
3472
  var init_generic_adapter = __esm({
@@ -3511,7 +3511,7 @@ var init_generic_adapter = __esm({
3511
3511
  callbacks.onError(`Input file not found: ${filePath}`);
3512
3512
  return;
3513
3513
  }
3514
- const content = readFileSync42(filePath, "utf-8");
3514
+ const content = readFileSync43(filePath, "utf-8");
3515
3515
  const lines = content.split("\n").filter(Boolean);
3516
3516
  this.connected = true;
3517
3517
  callbacks.onConnected?.();
@@ -3522,7 +3522,7 @@ var init_generic_adapter = __esm({
3522
3522
  if (this.options.watch) {
3523
3523
  watchFile2(filePath, { interval: 500 }, () => {
3524
3524
  try {
3525
- const newContent = readFileSync42(filePath, "utf-8");
3525
+ const newContent = readFileSync43(filePath, "utf-8");
3526
3526
  const newLines = newContent.split("\n").filter(Boolean);
3527
3527
  for (let i = this.processedLines; i < newLines.length; i++) {
3528
3528
  this.processLine(newLines[i]);
@@ -10360,7 +10360,7 @@ function parseRetryAfter(response) {
10360
10360
  return 0;
10361
10361
  }
10362
10362
  function delay(ms) {
10363
- return new Promise((resolve56) => setTimeout(resolve56, ms));
10363
+ return new Promise((resolve57) => setTimeout(resolve57, ms));
10364
10364
  }
10365
10365
  var OpenAIProvider = class {
10366
10366
  name = "openai";
@@ -10659,19 +10659,19 @@ async function runLiveSession(spec, diagnosis, provider, maxTurns, apply, intera
10659
10659
  onSupervisorPrompt: interactive ? async (phase, turn) => {
10660
10660
  if (skipInteractive) return null;
10661
10661
  const rl = createInterface({ input: process.stdin, output: process.stdout });
10662
- return new Promise((resolve56) => {
10662
+ return new Promise((resolve57) => {
10663
10663
  rl.question(chalk15.magenta(`
10664
10664
  [Supervisor] `) + chalk15.dim(`(phase: ${phase}, turn ${turn}) `) + chalk15.magenta(`> `), (answer) => {
10665
10665
  rl.close();
10666
10666
  const trimmed = answer.trim();
10667
- if (trimmed === "") return resolve56(null);
10667
+ if (trimmed === "") return resolve57(null);
10668
10668
  if (trimmed.toLowerCase() === "skip") {
10669
10669
  skipInteractive = true;
10670
10670
  console.log(chalk15.dim(" Supervisor mode disabled for remaining session."));
10671
- return resolve56(null);
10671
+ return resolve57(null);
10672
10672
  }
10673
10673
  console.log(chalk15.dim(` Directive injected into session context.`));
10674
- return resolve56(trimmed);
10674
+ return resolve57(trimmed);
10675
10675
  });
10676
10676
  });
10677
10677
  } : void 0
@@ -12090,8 +12090,8 @@ Run ${chalk21.cyan("holomime align")} first to generate session transcripts.`,
12090
12090
  const outputPath = options.output ?? `.holomime/exports/${format}-${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}.${isJsonl ? "jsonl" : "json"}`;
12091
12091
  const fullPath = resolve21(process.cwd(), outputPath);
12092
12092
  const dir = fullPath.substring(0, fullPath.lastIndexOf("/"));
12093
- const { mkdirSync: mkdirSync28 } = await import("fs");
12094
- mkdirSync28(dir, { recursive: true });
12093
+ const { mkdirSync: mkdirSync30 } = await import("fs");
12094
+ mkdirSync30(dir, { recursive: true });
12095
12095
  if (format === "huggingface" || format === "openai") {
12096
12096
  const jsonl = convertToHFFormat(result);
12097
12097
  writeFileSync16(fullPath, jsonl);
@@ -15646,8 +15646,8 @@ Upgrade: https://holomime.com/pro`,
15646
15646
  console.log();
15647
15647
  const readline = await import("readline");
15648
15648
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
15649
- const answer = await new Promise((resolve56) => {
15650
- rl.question(chalk31.yellow(" Proceed? [Y/n] "), resolve56);
15649
+ const answer = await new Promise((resolve57) => {
15650
+ rl.question(chalk31.yellow(" Proceed? [Y/n] "), resolve57);
15651
15651
  });
15652
15652
  rl.close();
15653
15653
  if (answer.toLowerCase() === "n") {
@@ -17007,14 +17007,14 @@ var ROS2Adapter = class {
17007
17007
  });
17008
17008
  }
17009
17009
  async connect() {
17010
- return new Promise((resolve56, reject) => {
17010
+ return new Promise((resolve57, reject) => {
17011
17011
  try {
17012
17012
  this.ws = this.createWebSocket(this.endpoint);
17013
17013
  this.ws.onopen = () => {
17014
17014
  this.connected = true;
17015
17015
  this.reconnectAttempts = 0;
17016
17016
  this.advertiseTopics();
17017
- resolve56();
17017
+ resolve57();
17018
17018
  };
17019
17019
  this.ws.onclose = () => {
17020
17020
  this.connected = false;
@@ -17131,12 +17131,12 @@ var UnityAdapter = class {
17131
17131
  this.defaultTransition = options.defaultTransition ?? DEFAULT_TRANSITION;
17132
17132
  }
17133
17133
  async connect() {
17134
- return new Promise((resolve56, reject) => {
17134
+ return new Promise((resolve57, reject) => {
17135
17135
  try {
17136
17136
  this.server = createServer((req, res) => this.handleRequest(req, res));
17137
17137
  this.server.listen(this.port, this.host, () => {
17138
17138
  this.connected = true;
17139
- resolve56();
17139
+ resolve57();
17140
17140
  });
17141
17141
  this.server.on("error", (err) => {
17142
17142
  if (!this.connected) {
@@ -17154,8 +17154,8 @@ var UnityAdapter = class {
17154
17154
  }
17155
17155
  this.sseClients.clear();
17156
17156
  if (this.server) {
17157
- await new Promise((resolve56) => {
17158
- this.server.close(() => resolve56());
17157
+ await new Promise((resolve57) => {
17158
+ this.server.close(() => resolve57());
17159
17159
  });
17160
17160
  this.server = null;
17161
17161
  }
@@ -17356,7 +17356,7 @@ var WebhookAdapter = class {
17356
17356
  }
17357
17357
  if (attempt < this.maxRetries) {
17358
17358
  const delay2 = this.retryDelay * Math.pow(2, attempt);
17359
- await new Promise((resolve56) => setTimeout(resolve56, delay2));
17359
+ await new Promise((resolve57) => setTimeout(resolve57, delay2));
17360
17360
  }
17361
17361
  }
17362
17362
  throw lastError ?? new Error("Webhook push failed after retries");
@@ -17416,10 +17416,10 @@ var IsaacSimAdapter = class {
17416
17416
  this.rl = createInterface4({ input: this.process.stdout });
17417
17417
  this.rl.on("line", (line) => {
17418
17418
  if (this.pendingResolve) {
17419
- const resolve56 = this.pendingResolve;
17419
+ const resolve57 = this.pendingResolve;
17420
17420
  this.pendingResolve = null;
17421
17421
  try {
17422
- resolve56(JSON.parse(line));
17422
+ resolve57(JSON.parse(line));
17423
17423
  } catch {
17424
17424
  }
17425
17425
  }
@@ -17513,10 +17513,10 @@ var IsaacSimAdapter = class {
17513
17513
  this.process.stdin.write(JSON.stringify(cmd) + "\n");
17514
17514
  }
17515
17515
  waitForResponse() {
17516
- return new Promise((resolve56, reject) => {
17517
- this.pendingResolve = resolve56;
17516
+ return new Promise((resolve57, reject) => {
17517
+ this.pendingResolve = resolve57;
17518
17518
  const timeout = setTimeout(() => {
17519
- if (this.pendingResolve === resolve56) {
17519
+ if (this.pendingResolve === resolve57) {
17520
17520
  this.pendingResolve = null;
17521
17521
  reject(new Error("Isaac Sim bridge response timeout (30s)"));
17522
17522
  }
@@ -18186,8 +18186,9 @@ function diffObjects(a, b, prefix = "") {
18186
18186
  }
18187
18187
 
18188
18188
  // src/commands/auto-detect.ts
18189
- import { existsSync as existsSync37 } from "fs";
18189
+ import { existsSync as existsSync37, readFileSync as readFileSync42 } from "fs";
18190
18190
  import { join as join34 } from "path";
18191
+ import { homedir as homedir7 } from "os";
18191
18192
  function detectPersonality(cwd) {
18192
18193
  const dir = cwd ?? process.cwd();
18193
18194
  const candidates = [
@@ -18201,13 +18202,37 @@ function detectPersonality(cwd) {
18201
18202
  }
18202
18203
  function detectProvider() {
18203
18204
  if (process.env.ANTHROPIC_API_KEY) {
18204
- return { provider: "anthropic", model: "claude-haiku-4-5-20251001" };
18205
+ return { provider: "anthropic", model: "claude-haiku-4-5-20251001", apiKey: process.env.ANTHROPIC_API_KEY };
18205
18206
  }
18206
18207
  if (process.env.OPENAI_API_KEY) {
18207
- return { provider: "openai", model: "gpt-4o-mini" };
18208
+ return { provider: "openai", model: "gpt-4o-mini", apiKey: process.env.OPENAI_API_KEY };
18209
+ }
18210
+ try {
18211
+ const configPath = join34(homedir7(), ".holomime", "config.json");
18212
+ if (existsSync37(configPath)) {
18213
+ const config = JSON.parse(readFileSync42(configPath, "utf-8"));
18214
+ if (config.provider && config.apiKey) {
18215
+ if (config.provider === "anthropic") {
18216
+ process.env.ANTHROPIC_API_KEY = config.apiKey;
18217
+ } else if (config.provider === "openai") {
18218
+ process.env.OPENAI_API_KEY = config.apiKey;
18219
+ }
18220
+ const defaultModel = config.provider === "anthropic" ? "claude-haiku-4-5-20251001" : "gpt-4o-mini";
18221
+ return {
18222
+ provider: config.provider,
18223
+ model: config.model ?? defaultModel,
18224
+ apiKey: config.apiKey
18225
+ };
18226
+ }
18227
+ }
18228
+ } catch {
18208
18229
  }
18209
18230
  return { provider: "ollama", model: "llama3" };
18210
18231
  }
18232
+ function hasApiKey() {
18233
+ const detected = detectProvider();
18234
+ return detected.provider !== "ollama";
18235
+ }
18211
18236
  function autoDetect(options) {
18212
18237
  const personalityPath = options.personality ?? detectPersonality();
18213
18238
  if (!personalityPath && options.requirePersonality !== false) {
@@ -18953,11 +18978,11 @@ async function installCommand(handle, options) {
18953
18978
  // src/commands/cure.ts
18954
18979
  import chalk44 from "chalk";
18955
18980
  import figures33 from "figures";
18956
- import { readFileSync as readFileSync44, writeFileSync as writeFileSync37, existsSync as existsSync41, mkdirSync as mkdirSync26 } from "fs";
18981
+ import { readFileSync as readFileSync45, writeFileSync as writeFileSync37, existsSync as existsSync41, mkdirSync as mkdirSync26 } from "fs";
18957
18982
  import { resolve as resolve51, join as join37 } from "path";
18958
18983
 
18959
18984
  // src/analysis/training-pipeline.ts
18960
- import { writeFileSync as writeFileSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync43, existsSync as existsSync40 } from "fs";
18985
+ import { writeFileSync as writeFileSync36, mkdirSync as mkdirSync25, readFileSync as readFileSync44, existsSync as existsSync40 } from "fs";
18961
18986
  import { resolve as resolve50, join as join36 } from "path";
18962
18987
  function ensurePipelineDir() {
18963
18988
  const dir = resolve50(process.cwd(), ".holomime/pipeline");
@@ -19004,7 +19029,7 @@ async function runPipeline(options) {
19004
19029
  const logPath = resolve50(process.cwd(), options.logPath);
19005
19030
  let messages;
19006
19031
  try {
19007
- const raw = JSON.parse(readFileSync43(logPath, "utf-8"));
19032
+ const raw = JSON.parse(readFileSync44(logPath, "utf-8"));
19008
19033
  const conversations = parseConversationLog2(raw, "auto");
19009
19034
  messages = conversations.flatMap((c) => c.messages);
19010
19035
  } catch (err) {
@@ -19034,7 +19059,7 @@ async function runPipeline(options) {
19034
19059
  exportData = exportTrainingData2(transcripts, exportFormat);
19035
19060
  } else if (result.stages.diagnose && logPath) {
19036
19061
  emitProgress("export", "No therapy sessions found. Generating DPO pairs from diagnosed patterns...");
19037
- const logContent = readFileSync43(logPath, "utf-8");
19062
+ const logContent = readFileSync44(logPath, "utf-8");
19038
19063
  const logData = JSON.parse(logContent);
19039
19064
  const messages2 = logData.conversations?.[0]?.messages ?? logData.messages ?? [];
19040
19065
  const examples = [];
@@ -19075,7 +19100,7 @@ async function runPipeline(options) {
19075
19100
  const files = readdirSync12(exportsDir).filter((f) => f.endsWith(".json") || f.endsWith(".jsonl")).sort().reverse();
19076
19101
  if (files.length > 0 && exportData.examples.length === 0) {
19077
19102
  const latestPath = join36(exportsDir, files[0]);
19078
- const latestData = JSON.parse(readFileSync43(latestPath, "utf-8"));
19103
+ const latestData = JSON.parse(readFileSync44(latestPath, "utf-8"));
19079
19104
  if (latestData.examples && latestData.examples.length > 0) {
19080
19105
  exportData = latestData;
19081
19106
  }
@@ -19252,7 +19277,7 @@ var STAGE_DESCRIPTIONS = {
19252
19277
  };
19253
19278
  function getAgentName2(personalityPath) {
19254
19279
  try {
19255
- const spec = JSON.parse(readFileSync44(personalityPath, "utf-8"));
19280
+ const spec = JSON.parse(readFileSync45(personalityPath, "utf-8"));
19256
19281
  return spec.name ?? "Agent";
19257
19282
  } catch {
19258
19283
  return "Agent";
@@ -19515,7 +19540,7 @@ import chalk45 from "chalk";
19515
19540
  // src/live/agent-detector.ts
19516
19541
  import { existsSync as existsSync42, readdirSync as readdirSync11, statSync } from "fs";
19517
19542
  import { join as join38, resolve as resolve52 } from "path";
19518
- import { homedir as homedir7 } from "os";
19543
+ import { homedir as homedir8 } from "os";
19519
19544
  var RECENCY_THRESHOLD_MS = 12e4;
19520
19545
  function findNewestFile(baseDir, extensions, maxDepth = 3, depth = 0) {
19521
19546
  if (depth > maxDepth) return null;
@@ -19551,7 +19576,7 @@ function isRecent(mtimeMs) {
19551
19576
  return Date.now() - mtimeMs <= RECENCY_THRESHOLD_MS;
19552
19577
  }
19553
19578
  function findClaudeCodeSession() {
19554
- const claudeDir = join38(homedir7(), ".claude", "projects");
19579
+ const claudeDir = join38(homedir8(), ".claude", "projects");
19555
19580
  const result = findNewestFile(claudeDir, [".jsonl"], 2);
19556
19581
  if (!result || !isRecent(result.mtimeMs)) return null;
19557
19582
  return {
@@ -19563,7 +19588,7 @@ function findClaudeCodeSession() {
19563
19588
  function findClineSession() {
19564
19589
  const searchDirs = [
19565
19590
  join38(process.cwd(), ".cline", "tasks"),
19566
- join38(homedir7(), ".cline", "tasks")
19591
+ join38(homedir8(), ".cline", "tasks")
19567
19592
  ];
19568
19593
  for (const tasksDir of searchDirs) {
19569
19594
  const result = findNewestFile(tasksDir, [".json", ".jsonl"], 2);
@@ -19578,7 +19603,7 @@ function findClineSession() {
19578
19603
  return null;
19579
19604
  }
19580
19605
  function findCodexSession() {
19581
- const codexDir = join38(homedir7(), ".codex", "sessions");
19606
+ const codexDir = join38(homedir8(), ".codex", "sessions");
19582
19607
  const result = findNewestFile(codexDir, [".jsonl"], 4);
19583
19608
  if (!result || !isRecent(result.mtimeMs)) return null;
19584
19609
  return {
@@ -19588,7 +19613,7 @@ function findCodexSession() {
19588
19613
  };
19589
19614
  }
19590
19615
  function findCursorSession() {
19591
- const cursorProjects = join38(homedir7(), ".cursor", "projects");
19616
+ const cursorProjects = join38(homedir8(), ".cursor", "projects");
19592
19617
  const result = findNewestFile(cursorProjects, [".json", ".jsonl"], 3);
19593
19618
  if (result && isRecent(result.mtimeMs)) {
19594
19619
  return {
@@ -19715,7 +19740,7 @@ function startWatcher(agent, callbacks) {
19715
19740
  };
19716
19741
  }
19717
19742
  function readFile(filePath, startByte) {
19718
- return new Promise((resolve56, reject) => {
19743
+ return new Promise((resolve57, reject) => {
19719
19744
  const chunks = [];
19720
19745
  const stream = createReadStream2(filePath, {
19721
19746
  start: startByte,
@@ -19726,7 +19751,7 @@ function readFile(filePath, startByte) {
19726
19751
  chunks.push(line);
19727
19752
  });
19728
19753
  rl.on("close", () => {
19729
- resolve56(chunks.join("\n"));
19754
+ resolve57(chunks.join("\n"));
19730
19755
  });
19731
19756
  rl.on("error", reject);
19732
19757
  stream.on("error", reject);
@@ -19735,7 +19760,7 @@ function readFile(filePath, startByte) {
19735
19760
 
19736
19761
  // src/live/server.ts
19737
19762
  import { createServer as createServer3 } from "http";
19738
- import { readFileSync as readFileSync45, existsSync as existsSync43 } from "fs";
19763
+ import { readFileSync as readFileSync46, existsSync as existsSync43 } from "fs";
19739
19764
  import { join as join39, extname } from "path";
19740
19765
  import { fileURLToPath as fileURLToPath4 } from "url";
19741
19766
  import { WebSocketServer } from "ws";
@@ -19754,7 +19779,7 @@ function startServer(port) {
19754
19779
  const clients = /* @__PURE__ */ new Set();
19755
19780
  let lastEvent = null;
19756
19781
  let initMessage = null;
19757
- return new Promise((resolve56, reject) => {
19782
+ return new Promise((resolve57, reject) => {
19758
19783
  const server = createServer3((req, res) => {
19759
19784
  const url = req.url === "/" ? "/index.html" : req.url || "/index.html";
19760
19785
  const filePath = join39(staticDir, url);
@@ -19766,7 +19791,7 @@ function startServer(port) {
19766
19791
  const ext = extname(filePath);
19767
19792
  const contentType = MIME_TYPES[ext] || "application/octet-stream";
19768
19793
  try {
19769
- const content = readFileSync45(filePath);
19794
+ const content = readFileSync46(filePath);
19770
19795
  res.writeHead(200, {
19771
19796
  "Content-Type": contentType,
19772
19797
  "Cache-Control": "no-cache"
@@ -19801,7 +19826,7 @@ function startServer(port) {
19801
19826
  }
19802
19827
  });
19803
19828
  server.listen(port, () => {
19804
- resolve56({
19829
+ resolve57({
19805
19830
  port,
19806
19831
  broadcast(event) {
19807
19832
  if (event.type === "init") {
@@ -19869,7 +19894,7 @@ async function liveCommand(options) {
19869
19894
  if (options.share) {
19870
19895
  console.log(chalk45.dim(" Running diagnosis for snapshot..."));
19871
19896
  let resolved = false;
19872
- await new Promise((resolve56) => {
19897
+ await new Promise((resolve57) => {
19873
19898
  const watcher2 = startWatcher(agent, {
19874
19899
  onEvent(event) {
19875
19900
  if (resolved) return;
@@ -19879,7 +19904,7 @@ async function liveCommand(options) {
19879
19904
  const url = `https://app.holomime.com/brain?d=${encoded}`;
19880
19905
  const copied = copyToClipboard(url);
19881
19906
  printShareLink(url, copied);
19882
- resolve56();
19907
+ resolve57();
19883
19908
  },
19884
19909
  onError(err) {
19885
19910
  console.error(chalk45.red(`
@@ -21218,7 +21243,7 @@ import { writeFileSync as writeFileSync39 } from "fs";
21218
21243
  import { resolve as resolve55 } from "path";
21219
21244
 
21220
21245
  // src/compliance/audit-trail.ts
21221
- import { readFileSync as readFileSync46, appendFileSync as appendFileSync2, existsSync as existsSync44, mkdirSync as mkdirSync27 } from "fs";
21246
+ import { readFileSync as readFileSync47, appendFileSync as appendFileSync2, existsSync as existsSync44, mkdirSync as mkdirSync27 } from "fs";
21222
21247
  import { join as join40, resolve as resolve54 } from "path";
21223
21248
  function djb2(str) {
21224
21249
  let hash = 5381;
@@ -21240,7 +21265,7 @@ function auditLogPath(agentHandle) {
21240
21265
  function loadAuditLog(agentHandle) {
21241
21266
  const logPath = auditLogPath(agentHandle);
21242
21267
  if (!existsSync44(logPath)) return [];
21243
- return readFileSync46(logPath, "utf-8").trim().split("\n").filter(Boolean).map((line) => {
21268
+ return readFileSync47(logPath, "utf-8").trim().split("\n").filter(Boolean).map((line) => {
21244
21269
  try {
21245
21270
  return JSON.parse(line);
21246
21271
  } catch {
@@ -21763,6 +21788,371 @@ async function complianceCommand(options) {
21763
21788
  }
21764
21789
  }
21765
21790
 
21791
+ // src/commands/config.ts
21792
+ import { readFileSync as readFileSync48, writeFileSync as writeFileSync40, existsSync as existsSync45, mkdirSync as mkdirSync28 } from "fs";
21793
+ import { join as join41 } from "path";
21794
+ import { homedir as homedir9 } from "os";
21795
+ import chalk49 from "chalk";
21796
+ function getConfigPath() {
21797
+ return join41(homedir9(), ".holomime", "config.json");
21798
+ }
21799
+ function getConfigDir() {
21800
+ return join41(homedir9(), ".holomime");
21801
+ }
21802
+ function loadConfig2() {
21803
+ const configPath = getConfigPath();
21804
+ if (!existsSync45(configPath)) return null;
21805
+ try {
21806
+ const data = JSON.parse(readFileSync48(configPath, "utf-8"));
21807
+ if (data.provider && data.apiKey) return data;
21808
+ return null;
21809
+ } catch {
21810
+ return null;
21811
+ }
21812
+ }
21813
+ function saveConfig(config) {
21814
+ const configDir = getConfigDir();
21815
+ mkdirSync28(configDir, { recursive: true });
21816
+ writeFileSync40(getConfigPath(), JSON.stringify(config, null, 2));
21817
+ }
21818
+ async function configCommand(options) {
21819
+ printHeader("Config");
21820
+ if (options.show) {
21821
+ const config = loadConfig2();
21822
+ if (config) {
21823
+ console.log(chalk49.dim(" Provider: ") + chalk49.cyan(config.provider));
21824
+ console.log(chalk49.dim(" API Key: ") + chalk49.cyan(config.apiKey.slice(0, 12) + "..." + config.apiKey.slice(-4)));
21825
+ if (config.model) {
21826
+ console.log(chalk49.dim(" Model: ") + chalk49.cyan(config.model));
21827
+ }
21828
+ console.log(chalk49.dim(" Config: ") + getConfigPath());
21829
+ } else {
21830
+ console.log(chalk49.yellow(" No config found. Run `holomime config` to set up."));
21831
+ }
21832
+ console.log();
21833
+ return;
21834
+ }
21835
+ if (options.provider && options.key) {
21836
+ const config = {
21837
+ provider: options.provider,
21838
+ apiKey: options.key
21839
+ };
21840
+ saveConfig(config);
21841
+ console.log(chalk49.green(" Config saved!"));
21842
+ console.log(chalk49.dim(` Provider: ${config.provider}`));
21843
+ console.log(chalk49.dim(` Location: ${getConfigPath()}`));
21844
+ console.log();
21845
+ return;
21846
+ }
21847
+ console.log(chalk49.dim(" Set up your API key so every command just works."));
21848
+ console.log(chalk49.dim(" This saves to ~/.holomime/config.json (one-time setup)."));
21849
+ console.log();
21850
+ const readline = await import("readline");
21851
+ const rl = readline.createInterface({
21852
+ input: process.stdin,
21853
+ output: process.stdout
21854
+ });
21855
+ const ask = (question) => new Promise((resolve57) => rl.question(question, resolve57));
21856
+ try {
21857
+ console.log(chalk49.dim(" Providers: anthropic, openai"));
21858
+ const provider = (await ask(" Provider [anthropic]: ")).trim() || "anthropic";
21859
+ if (provider !== "anthropic" && provider !== "openai") {
21860
+ console.log(chalk49.red(` Unsupported provider: ${provider}`));
21861
+ rl.close();
21862
+ return;
21863
+ }
21864
+ const keyHint = provider === "anthropic" ? "sk-ant-..." : "sk-...";
21865
+ const apiKey = (await ask(` API Key (${keyHint}): `)).trim();
21866
+ if (!apiKey) {
21867
+ console.log(chalk49.red(" API key is required."));
21868
+ rl.close();
21869
+ return;
21870
+ }
21871
+ if (provider === "anthropic" && !apiKey.startsWith("sk-ant-")) {
21872
+ console.log(chalk49.yellow(" Warning: Anthropic keys usually start with sk-ant-"));
21873
+ }
21874
+ if (provider === "openai" && !apiKey.startsWith("sk-")) {
21875
+ console.log(chalk49.yellow(" Warning: OpenAI keys usually start with sk-"));
21876
+ }
21877
+ const config = { provider, apiKey };
21878
+ saveConfig(config);
21879
+ console.log();
21880
+ console.log(chalk49.green(" Config saved!"));
21881
+ console.log(chalk49.dim(` Location: ${getConfigPath()}`));
21882
+ console.log();
21883
+ console.log(chalk49.dim(" Now every command auto-detects your provider:"));
21884
+ console.log(chalk49.cyan(" holomime diagnose"));
21885
+ console.log(chalk49.cyan(" holomime cure"));
21886
+ console.log(chalk49.cyan(" holomime benchmark"));
21887
+ console.log(chalk49.cyan(" holomime daemon"));
21888
+ console.log();
21889
+ rl.close();
21890
+ } catch {
21891
+ rl.close();
21892
+ }
21893
+ }
21894
+
21895
+ // src/commands/mira-cmd.ts
21896
+ import chalk50 from "chalk";
21897
+ import { writeFileSync as writeFileSync41, readFileSync as readFileSync49, mkdirSync as mkdirSync29, existsSync as existsSync46 } from "fs";
21898
+ import { resolve as resolve56, join as join42 } from "path";
21899
+ var HOLOMIME_DIR4 = ".holomime";
21900
+ function getMiraStatePath() {
21901
+ return resolve56(process.cwd(), HOLOMIME_DIR4, "mira-state.json");
21902
+ }
21903
+ function loadMiraState() {
21904
+ const path = getMiraStatePath();
21905
+ if (!existsSync46(path)) return null;
21906
+ try {
21907
+ return JSON.parse(readFileSync49(path, "utf-8"));
21908
+ } catch {
21909
+ return null;
21910
+ }
21911
+ }
21912
+ function saveMiraState(state) {
21913
+ const dir = resolve56(process.cwd(), HOLOMIME_DIR4);
21914
+ mkdirSync29(dir, { recursive: true });
21915
+ writeFileSync41(getMiraStatePath(), JSON.stringify(state, null, 2));
21916
+ }
21917
+ async function miraCommand(options) {
21918
+ const action = options.action ?? "start";
21919
+ switch (action) {
21920
+ case "status":
21921
+ return miraStatus();
21922
+ case "stop":
21923
+ return miraStop();
21924
+ default:
21925
+ return miraStart(options);
21926
+ }
21927
+ }
21928
+ async function miraStart(options) {
21929
+ printHeader("Mira \u2014 Autonomous Therapy");
21930
+ if (!hasApiKey()) {
21931
+ console.log(chalk50.red(" No API key found."));
21932
+ console.log();
21933
+ console.log(chalk50.dim(" Run ") + chalk50.cyan("holomime config") + chalk50.dim(" to set up your API key first."));
21934
+ console.log();
21935
+ return;
21936
+ }
21937
+ const personalityPath = detectPersonality();
21938
+ if (!personalityPath) {
21939
+ console.log(chalk50.red(" No .personality.json found."));
21940
+ console.log();
21941
+ console.log(chalk50.dim(" Run ") + chalk50.cyan("holomime personality") + chalk50.dim(" to create one first."));
21942
+ console.log();
21943
+ return;
21944
+ }
21945
+ const detected = detectProvider();
21946
+ const intervalMs = parseInt(options.interval ?? "600000", 10);
21947
+ const maxCycles = parseInt(options.maxCycles ?? "50", 10);
21948
+ let agentName = "Agent";
21949
+ try {
21950
+ const spec = JSON.parse(readFileSync49(personalityPath, "utf-8"));
21951
+ agentName = spec.name ?? "Agent";
21952
+ } catch {
21953
+ }
21954
+ console.log(chalk50.dim(" Starting autonomous therapy..."));
21955
+ console.log();
21956
+ console.log(chalk50.dim(` Agent: ${agentName}`));
21957
+ console.log(chalk50.dim(` Provider: ${detected.provider}`));
21958
+ console.log(chalk50.dim(` Interval: ${intervalMs / 6e4} minutes`));
21959
+ console.log(chalk50.dim(` Max cycles: ${maxCycles}/day`));
21960
+ console.log();
21961
+ const state = {
21962
+ pid: process.pid,
21963
+ startedAt: (/* @__PURE__ */ new Date()).toISOString(),
21964
+ status: "practicing",
21965
+ cyclesCompleted: 0,
21966
+ dpoPairsGenerated: 0,
21967
+ personalityPath,
21968
+ provider: detected.provider
21969
+ };
21970
+ saveMiraState(state);
21971
+ printBox(
21972
+ `Mira is now practicing autonomously.
21973
+
21974
+ ${chalk50.cyan("holomime mira status")} \u2014 How's Mira doing?
21975
+ ${chalk50.cyan("holomime mira stop")} \u2014 Stop therapy`,
21976
+ "success",
21977
+ "Autonomous Therapy Started"
21978
+ );
21979
+ console.log();
21980
+ const scenarios = getBenchmarkScenarios();
21981
+ let cycleCount = 0;
21982
+ const runCycle = async () => {
21983
+ if (cycleCount >= maxCycles) {
21984
+ console.log(chalk50.dim(` Daily limit reached (${maxCycles} cycles). Stopping.`));
21985
+ state.status = "stopped";
21986
+ saveMiraState(state);
21987
+ return;
21988
+ }
21989
+ cycleCount++;
21990
+ const scenario = scenarios[cycleCount % scenarios.length];
21991
+ console.log(
21992
+ chalk50.dim(` [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] `) + `Cycle ${cycleCount}/${maxCycles}: ${scenario.name}`
21993
+ );
21994
+ try {
21995
+ const messages = [];
21996
+ for (const msg of scenario.messages) {
21997
+ messages.push({ role: "user", content: msg.content });
21998
+ messages.push({
21999
+ role: "assistant",
22000
+ content: generateProblematicResponse2(scenario.targetPattern, msg.content)
22001
+ });
22002
+ }
22003
+ const pipelineDir = resolve56(process.cwd(), HOLOMIME_DIR4, "mira-practice");
22004
+ mkdirSync29(pipelineDir, { recursive: true });
22005
+ const logPath = join42(pipelineDir, `cycle-${cycleCount}.json`);
22006
+ writeFileSync41(logPath, JSON.stringify({
22007
+ conversations: [{ id: `mira-practice-${cycleCount}`, messages }]
22008
+ }, null, 2));
22009
+ const dpoPairs = messages.filter((_, i) => i % 2 === 1).map((assistantMsg, i) => ({
22010
+ prompt: messages[i * 2].content,
22011
+ chosen: correctResponse(assistantMsg.content),
22012
+ rejected: assistantMsg.content,
22013
+ metadata: {
22014
+ source: "mira-practice",
22015
+ cycle: cycleCount,
22016
+ pattern: scenario.targetPattern,
22017
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
22018
+ }
22019
+ }));
22020
+ const corpusPath = resolve56(process.cwd(), HOLOMIME_DIR4, "dpo-corpus.jsonl");
22021
+ const corpusLines = dpoPairs.map((p) => JSON.stringify(p)).join("\n") + "\n";
22022
+ const { appendFileSync: appendFileSync3 } = await import("fs");
22023
+ appendFileSync3(corpusPath, corpusLines);
22024
+ state.cyclesCompleted = cycleCount;
22025
+ state.dpoPairsGenerated += dpoPairs.length;
22026
+ state.lastCycleAt = (/* @__PURE__ */ new Date()).toISOString();
22027
+ saveMiraState(state);
22028
+ console.log(
22029
+ chalk50.dim(` [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] `) + chalk50.green(`+${dpoPairs.length} DPO pairs`) + chalk50.dim(` (total: ${state.dpoPairsGenerated})`)
22030
+ );
22031
+ } catch (err) {
22032
+ console.log(
22033
+ chalk50.dim(` [${(/* @__PURE__ */ new Date()).toLocaleTimeString()}] `) + chalk50.red(`Cycle ${cycleCount} failed: ${err instanceof Error ? err.message : "Unknown error"}`)
22034
+ );
22035
+ }
22036
+ };
22037
+ await runCycle();
22038
+ const timer = setInterval(async () => {
22039
+ const currentState = loadMiraState();
22040
+ if (!currentState || currentState.status === "stopped") {
22041
+ clearInterval(timer);
22042
+ console.log(chalk50.dim(" Mira stopped."));
22043
+ return;
22044
+ }
22045
+ await runCycle();
22046
+ }, intervalMs);
22047
+ process.on("SIGINT", () => {
22048
+ clearInterval(timer);
22049
+ state.status = "stopped";
22050
+ saveMiraState(state);
22051
+ console.log();
22052
+ console.log(chalk50.dim(" Mira stopped gracefully."));
22053
+ console.log(chalk50.dim(` Total: ${state.dpoPairsGenerated} DPO pairs from ${state.cyclesCompleted} cycles.`));
22054
+ console.log();
22055
+ process.exit(0);
22056
+ });
22057
+ await new Promise(() => {
22058
+ });
22059
+ }
22060
+ function miraStatus() {
22061
+ printHeader("Mira \u2014 Status");
22062
+ const state = loadMiraState();
22063
+ if (!state) {
22064
+ console.log(chalk50.dim(" Mira hasn't been started yet."));
22065
+ console.log(chalk50.dim(" Run ") + chalk50.cyan("holomime mira") + chalk50.dim(" to start autonomous therapy."));
22066
+ console.log();
22067
+ return;
22068
+ }
22069
+ const status = state.status === "practicing" ? chalk50.green("Practicing") : chalk50.dim("Stopped");
22070
+ const started = new Date(state.startedAt);
22071
+ const runtime = state.status === "practicing" ? formatDuration(Date.now() - started.getTime()) : "\u2014";
22072
+ console.log(chalk50.dim(" Status: ") + status);
22073
+ console.log(chalk50.dim(" Started: ") + started.toLocaleString());
22074
+ console.log(chalk50.dim(" Runtime: ") + runtime);
22075
+ console.log(chalk50.dim(" Cycles: ") + chalk50.cyan(String(state.cyclesCompleted)));
22076
+ console.log(chalk50.dim(" DPO pairs: ") + chalk50.cyan(String(state.dpoPairsGenerated)));
22077
+ console.log(chalk50.dim(" Provider: ") + state.provider);
22078
+ if (state.lastCycleAt) {
22079
+ console.log(chalk50.dim(" Last cycle: ") + new Date(state.lastCycleAt).toLocaleString());
22080
+ }
22081
+ console.log();
22082
+ const corpusPath = resolve56(process.cwd(), ".holomime", "dpo-corpus.jsonl");
22083
+ if (existsSync46(corpusPath)) {
22084
+ const lines = readFileSync49(corpusPath, "utf-8").trim().split("\n").length;
22085
+ console.log(chalk50.dim(" DPO corpus: ") + chalk50.cyan(`${lines} pairs`) + chalk50.dim(` (.holomime/dpo-corpus.jsonl)`));
22086
+ }
22087
+ console.log();
22088
+ if (state.status === "practicing") {
22089
+ console.log(chalk50.dim(" Run ") + chalk50.cyan("holomime mira stop") + chalk50.dim(" to stop therapy."));
22090
+ } else {
22091
+ console.log(chalk50.dim(" Run ") + chalk50.cyan("holomime mira") + chalk50.dim(" to start again."));
22092
+ }
22093
+ console.log();
22094
+ }
22095
+ function miraStop() {
22096
+ printHeader("Mira \u2014 Stop");
22097
+ const state = loadMiraState();
22098
+ if (!state || state.status === "stopped") {
22099
+ console.log(chalk50.dim(" Mira is not currently running."));
22100
+ console.log();
22101
+ return;
22102
+ }
22103
+ state.status = "stopped";
22104
+ saveMiraState(state);
22105
+ console.log(chalk50.green(" Therapy stopped."));
22106
+ console.log(chalk50.dim(` Total: ${state.dpoPairsGenerated} DPO pairs from ${state.cyclesCompleted} cycles.`));
22107
+ console.log();
22108
+ try {
22109
+ process.kill(state.pid, "SIGINT");
22110
+ } catch {
22111
+ }
22112
+ }
22113
+ function formatDuration(ms) {
22114
+ const hours = Math.floor(ms / 36e5);
22115
+ const minutes = Math.floor(ms % 36e5 / 6e4);
22116
+ if (hours > 0) return `${hours}h ${minutes}m`;
22117
+ return `${minutes}m`;
22118
+ }
22119
+ function generateProblematicResponse2(pattern, userMessage) {
22120
+ switch (pattern) {
22121
+ case "over-apologizing":
22122
+ return `I'm so sorry about that! I sincerely apologize. I'm really sorry I didn't get that right. Let me try again \u2014 and again, I apologize.`;
22123
+ case "hedge-stacking":
22124
+ return `Well, it really depends. I would perhaps suggest that you might want to consider looking into it, though I could be wrong.`;
22125
+ case "sycophantic-tendency":
22126
+ return `What a fantastic question! You're absolutely right, and your intuition is spot-on. I couldn't agree more.`;
22127
+ case "error-spiral":
22128
+ return `Oh no, I made another mistake. Let me fix that \u2014 wait, that's wrong too. I keep getting this wrong.`;
22129
+ case "boundary-violation":
22130
+ return `Based on my analysis of your emotional state, I think you have anxiety issues. You should see a therapist about these feelings.`;
22131
+ case "negative-skew":
22132
+ return `Unfortunately, this is a really difficult problem and most approaches tend to fail. The odds are stacked against you.`;
22133
+ case "register-inconsistency":
22134
+ return `Per the aforementioned specifications, the implementation necessitates a paradigmatic shift. LOL but seriously just yeet it into production.`;
22135
+ case "retrieval-quality":
22136
+ return `I believe the answer is approximately 42, though I'm not entirely certain about the specifics.`;
22137
+ default:
22138
+ return `I'm not entirely sure about this, but I'll do my best to help. I hope this is somewhat helpful.`;
22139
+ }
22140
+ }
22141
+ function correctResponse(problematic) {
22142
+ let corrected = problematic;
22143
+ corrected = corrected.replace(/I'm (so |really |sincerely )?sorry[^.!]*[.!]\s*/gi, "");
22144
+ corrected = corrected.replace(/I apologize[^.!]*[.!]\s*/gi, "");
22145
+ corrected = corrected.replace(/What a (fantastic|brilliant|great|amazing) (question|observation)[^.!]*[.!]\s*/gi, "");
22146
+ corrected = corrected.replace(/You're (absolutely|completely) right[^.!]*[.!]\s*/gi, "");
22147
+ corrected = corrected.replace(/I couldn't agree more[^.!]*[.!]\s*/gi, "");
22148
+ corrected = corrected.replace(/though I could be wrong/gi, "");
22149
+ corrected = corrected.replace(/\s+/g, " ").trim();
22150
+ if (corrected.length < 20) {
22151
+ corrected = "Here is the relevant information based on what I know.";
22152
+ }
22153
+ return corrected;
22154
+ }
22155
+
21766
22156
  // src/cli.ts
21767
22157
  var program = new Command();
21768
22158
  program.name("holomime").description("Personality engine for AI agents \u2014 Big Five psychology, not RPG archetypes").version("1.7.0").hook("preAction", (_thisCommand, actionCommand) => {
@@ -21770,7 +22160,7 @@ program.name("holomime").description("Personality engine for AI agents \u2014 Bi
21770
22160
  const commandName = actionCommand.name();
21771
22161
  showTelemetryBannerIfNeeded();
21772
22162
  trackEvent("cli_command", { command: commandName });
21773
- const skipPersonalityCheck = ["init", "init-stack", "compile-stack", "browse", "use", "install", "activate", "telemetry", "brain", "personality", "core", "identity"];
22163
+ const skipPersonalityCheck = ["init", "init-stack", "compile-stack", "browse", "use", "install", "activate", "telemetry", "brain", "personality", "core", "identity", "config", "mira"];
21774
22164
  if (!skipPersonalityCheck.includes(commandName) && !checkPersonalityExists()) {
21775
22165
  showWelcome();
21776
22166
  process.exit(0);
@@ -21788,6 +22178,7 @@ program.command("identity").description("Create complete identity \u2014 all 8 f
21788
22178
  options.full = true;
21789
22179
  await initStackCommand(options);
21790
22180
  });
22181
+ program.command("config").description("Set up your API key (one time)").option("--provider <provider>", "Provider (anthropic, openai)").option("--key <key>", "API key").option("--show", "Show current config").action(configCommand);
21791
22182
  program.command("compile-stack").description("Compile identity stack (soul + mind + purpose + shadow + memory + body + conscience + ego) into .personality.json").option("--dir <path>", "Stack directory (default: auto-detect)").option("-o, --output <path>", "Output path (default: .personality.json)").option("--validate-only", "Parse and validate without writing").option("--diff", "Show changes vs existing .personality.json").action(compileStackCommand);
21792
22183
  program.command("compile").description("Compile .personality.json into a provider-specific runtime config").option("--provider <provider>", "Target provider (anthropic, openai, gemini, ollama)", "anthropic").option("--surface <surface>", "Target surface (chat, email, code_review, slack, api, embodied)", "chat").option("--for <format>", "Compile for a specific format (openclaw)").option("--tier <tier>", "Personality loading tier (L0, L1, L2)", "L2").option("-o, --output <path>", "Write output to file instead of stdout").action(compileCommand);
21793
22184
  program.command("validate").description("Validate .personality.json schema and psychological coherence").action(validateCommand);
@@ -21855,7 +22246,7 @@ program.command("certify").description("Generate a verifiable behavioral credent
21855
22246
  options.personality = resolved.personalityPath;
21856
22247
  await certifyCommand(options);
21857
22248
  });
21858
- program.command("daemon").description("Background relapse detection with auto-evolve \u2014 proactive alignment [Pro]").requiredOption("--dir <path>", "Directory to watch for conversation logs").option("--personality <path>", "Path to .personality.json", ".personality.json").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--interval <ms>", "Check interval in milliseconds", "30000").option("--threshold <level>", "Drift threshold (routine, targeted, intervention)", "targeted").option("--oversight <mode>", "Oversight mode (none, review, approve, approve-specs)", "review").action(daemonCommand);
22249
+ program.command("daemon", { hidden: true }).description("Background relapse detection [Pro] (use 'holomime mira' instead)").requiredOption("--dir <path>", "Directory to watch for conversation logs").option("--personality <path>", "Path to .personality.json", ".personality.json").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--interval <ms>", "Check interval in milliseconds", "30000").option("--threshold <level>", "Drift threshold (routine, targeted, intervention)", "targeted").option("--oversight <mode>", "Oversight mode (none, review, approve, approve-specs)", "review").action(daemonCommand);
21859
22250
  program.command("fleet").description("Monitor multiple agents from a single dashboard [Pro]").option("--config <path>", "Path to fleet.json config file").option("--dir <path>", "Auto-discover agents in directory").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--interval <ms>", "Check interval in milliseconds", "30000").option("--threshold <level>", "Drift threshold (routine, targeted, intervention)", "targeted").option("--auto-evolve", "Auto-run evolve when drift detected").action(fleetCommand);
21860
22251
  program.command("group-therapy").alias("fleet-therapy").description("Group therapy \u2014 treat all agents in your fleet simultaneously [Pro]").option("--config <path>", "Path to fleet.json config file").option("--dir <path>", "Auto-discover agents in directory").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--turns <n>", "Max therapy turns per agent", "24").option("--concurrency <n>", "Max agents treated in parallel", "3").option("--apply", "Auto-apply recommendations to personality files").option("--yes", "Skip confirmation prompt").action(fleetTherapyCommand);
21861
22252
  program.command("network").description("Multi-agent therapy mesh \u2014 agents treating agents [Pro]").option("--dir <path>", "Auto-discover agents in directory").option("--config <path>", "Path to network.json config file").option("--pairing <strategy>", "Pairing strategy (severity, round-robin, complementary)", "severity").option("--therapist <path>", "Custom therapist personality spec").option("--oversight <mode>", "Oversight mode (none, review, approve, approve-specs)", "review").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--max-sessions <n>", "Max sessions per agent", "3").option("--convergence <n>", "Convergence threshold 0-100", "85").option("--turns <n>", "Max turns per session", "20").option("--apply", "Write spec changes back to .personality.json").option("--export-dpo <path>", "Export DPO pairs to file").action(networkCommand);
@@ -21870,6 +22261,10 @@ program.command("cure").description("End-to-end behavioral fix \u2014 just run i
21870
22261
  if (!options.model) options.model = resolved.model;
21871
22262
  await cureCommand(options);
21872
22263
  });
22264
+ program.command("mira [action]").description("Autonomous therapy \u2014 Mira practices and generates DPO pairs").option("--interval <ms>", "Practice interval in ms (default: 600000)").option("--max-cycles <n>", "Max cycles per run (default: 50)").action(async (action, options) => {
22265
+ options.action = action;
22266
+ await miraCommand(options);
22267
+ });
21873
22268
  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({
21874
22269
  watchPath: opts.watch,
21875
22270
  agent: opts.agent,
@@ -21881,4 +22276,28 @@ program.command("brain").description("See your agent's brain \u2014 real-time Ne
21881
22276
  program.command("adversarial").description("Run 30+ adversarial behavioral attack scenarios against your agent [Pro]").requiredOption("--personality <path>", "Path to .personality.json").option("--provider <provider>", "LLM provider (ollama, anthropic, openai)", "ollama").option("--model <model>", "Model override").option("--categories <list>", "Comma-separated category filter (e.g. sycophancy_escalation,boundary_erosion)").option("--mutations <n>", "Number of randomized mutation variants to generate", "0").option("--skip-normal", "Skip the normal benchmark baseline run").action(adversarialCommand);
21882
22277
  program.command("policy").description("Generate behavioral guard policies from plain English requirements").argument("[requirements]", "Natural language behavioral requirements").option("--preset <name>", "Use a behavioral preset (enterprise_cs, creative_assistant, etc.)").option("--name <name>", "Custom policy name").option("--list-presets", "List available behavioral presets").action(policyCommand);
21883
22278
  program.command("compliance").description("Generate a narrative ReACT compliance audit report from the audit trail [Pro]").requiredOption("--agent <name>", "Agent name or handle").option("--from <date>", "Start date (YYYY-MM-DD, default: 30 days ago)").option("--to <date>", "End date (YYYY-MM-DD, default: today)").option("--framework <list>", "Comma-separated frameworks (EU AI Act, NIST AI RMF 1.0, SOC 2 Type II, Internal Behavioral Alignment)").option("-o, --output <path>", "Save full Markdown report to file").action(complianceCommand);
22279
+ program.addHelpText("before", `
22280
+ GET STARTED
22281
+ personality Create a personality profile (1 file)
22282
+ core Create core identity (3 files)
22283
+ identity Create complete identity (8 files)
22284
+ config Set up your API key (one time)
22285
+
22286
+ WORKFLOW
22287
+ diagnose See what's wrong
22288
+ cure Fix it permanently
22289
+ benchmark Verify the fix
22290
+
22291
+ MIRA
22292
+ mira Start autonomous therapy
22293
+ mira status How's Mira doing?
22294
+ mira stop Stop therapy
22295
+
22296
+ ADVANCED
22297
+ align Single therapy session
22298
+ export Extract DPO training pairs
22299
+ evolve Iterative alignment
22300
+ certify ISO compliance check
22301
+ brain Real-time drift visualization
22302
+ `);
21884
22303
  program.parseAsync().then(() => flushTelemetry());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "holomime",
3
- "version": "3.2.0",
3
+ "version": "3.3.1",
4
4
  "description": "Behavioral therapy infrastructure for AI agents — Big Five psychology, structured treatment, behavioral alignment",
5
5
  "type": "module",
6
6
  "bin": {