@askexenow/exe-os 0.8.21 → 0.8.23

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.
@@ -446,11 +446,110 @@ var init_shard_manager = __esm({
446
446
  }
447
447
  });
448
448
 
449
+ // src/lib/employees.ts
450
+ var employees_exports = {};
451
+ __export(employees_exports, {
452
+ EMPLOYEES_PATH: () => EMPLOYEES_PATH,
453
+ addEmployee: () => addEmployee,
454
+ getEmployee: () => getEmployee,
455
+ loadEmployees: () => loadEmployees,
456
+ registerBinSymlinks: () => registerBinSymlinks,
457
+ saveEmployees: () => saveEmployees,
458
+ validateEmployeeName: () => validateEmployeeName
459
+ });
460
+ import { readFile as readFile3, writeFile as writeFile3, mkdir as mkdir3 } from "fs/promises";
461
+ import { existsSync as existsSync5, symlinkSync, readlinkSync } from "fs";
462
+ import { execSync } from "child_process";
463
+ import path5 from "path";
464
+ function validateEmployeeName(name) {
465
+ if (!name) {
466
+ return { valid: false, error: "Name is required" };
467
+ }
468
+ if (name.length > 32) {
469
+ return { valid: false, error: "Name must be 32 characters or fewer" };
470
+ }
471
+ if (!/^[a-z][a-z0-9]*$/.test(name)) {
472
+ return {
473
+ valid: false,
474
+ error: "Name must start with a letter and contain only lowercase alphanumeric characters"
475
+ };
476
+ }
477
+ return { valid: true };
478
+ }
479
+ async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
480
+ if (!existsSync5(employeesPath)) {
481
+ return [];
482
+ }
483
+ const raw = await readFile3(employeesPath, "utf-8");
484
+ try {
485
+ return JSON.parse(raw);
486
+ } catch {
487
+ return [];
488
+ }
489
+ }
490
+ async function saveEmployees(employees, employeesPath = EMPLOYEES_PATH) {
491
+ await mkdir3(path5.dirname(employeesPath), { recursive: true });
492
+ await writeFile3(employeesPath, JSON.stringify(employees, null, 2) + "\n", "utf-8");
493
+ }
494
+ function getEmployee(employees, name) {
495
+ return employees.find((e) => e.name.toLowerCase() === name.toLowerCase());
496
+ }
497
+ function addEmployee(employees, employee) {
498
+ const normalized = { ...employee, name: employee.name.toLowerCase() };
499
+ if (employees.some((e) => e.name.toLowerCase() === normalized.name)) {
500
+ throw new Error(`Employee '${normalized.name}' already exists`);
501
+ }
502
+ return [...employees, normalized];
503
+ }
504
+ function registerBinSymlinks(name) {
505
+ const created = [];
506
+ const skipped = [];
507
+ const errors = [];
508
+ let exeBinPath;
509
+ try {
510
+ exeBinPath = execSync("which exe", { encoding: "utf-8" }).trim();
511
+ } catch {
512
+ errors.push("Could not find 'exe' in PATH");
513
+ return { created, skipped, errors };
514
+ }
515
+ const binDir = path5.dirname(exeBinPath);
516
+ let target;
517
+ try {
518
+ target = readlinkSync(exeBinPath);
519
+ } catch {
520
+ errors.push("Could not read 'exe' symlink");
521
+ return { created, skipped, errors };
522
+ }
523
+ for (const suffix of ["", "-opencode"]) {
524
+ const linkName = `${name}${suffix}`;
525
+ const linkPath = path5.join(binDir, linkName);
526
+ if (existsSync5(linkPath)) {
527
+ skipped.push(linkName);
528
+ continue;
529
+ }
530
+ try {
531
+ symlinkSync(target, linkPath);
532
+ created.push(linkName);
533
+ } catch (err) {
534
+ errors.push(`${linkName}: ${err instanceof Error ? err.message : String(err)}`);
535
+ }
536
+ }
537
+ return { created, skipped, errors };
538
+ }
539
+ var EMPLOYEES_PATH;
540
+ var init_employees = __esm({
541
+ "src/lib/employees.ts"() {
542
+ "use strict";
543
+ init_config();
544
+ EMPLOYEES_PATH = path5.join(EXE_AI_DIR, "exe-employees.json");
545
+ }
546
+ });
547
+
449
548
  // src/bin/backfill-conversations.ts
450
549
  import crypto2 from "crypto";
451
550
  import { createReadStream } from "fs";
452
551
  import { readdir, stat } from "fs/promises";
453
- import path5 from "path";
552
+ import path6 from "path";
454
553
  import { createInterface } from "readline";
455
554
  import { homedir } from "os";
456
555
 
@@ -1876,7 +1975,7 @@ function isMainModule(importMetaUrl) {
1876
1975
  // src/bin/backfill-conversations.ts
1877
1976
  process.env.EXE_EMBED_PRIORITY = "low";
1878
1977
  async function findJsonlFiles(cutoffMs) {
1879
- const projectsDir = path5.join(homedir(), ".claude", "projects");
1978
+ const projectsDir = path6.join(homedir(), ".claude", "projects");
1880
1979
  const files = [];
1881
1980
  async function walk(dir) {
1882
1981
  let entries;
@@ -1886,7 +1985,7 @@ async function findJsonlFiles(cutoffMs) {
1886
1985
  return;
1887
1986
  }
1888
1987
  for (const entry of entries) {
1889
- const full = path5.join(dir, entry.name);
1988
+ const full = path6.join(dir, entry.name);
1890
1989
  if (entry.isDirectory()) {
1891
1990
  await walk(full);
1892
1991
  } else if (entry.name.endsWith(".jsonl")) {
@@ -1902,9 +2001,9 @@ async function findJsonlFiles(cutoffMs) {
1902
2001
  return files;
1903
2002
  }
1904
2003
  function projectNameFromPath(filePath) {
1905
- const projectsDir = path5.join(homedir(), ".claude", "projects");
1906
- const relative = path5.relative(projectsDir, filePath);
1907
- const projectDir = relative.split(path5.sep)[0] ?? relative;
2004
+ const projectsDir = path6.join(homedir(), ".claude", "projects");
2005
+ const relative = path6.relative(projectsDir, filePath);
2006
+ const projectDir = relative.split(path6.sep)[0] ?? relative;
1908
2007
  const homeEncoded = homedir().replaceAll("/", "-");
1909
2008
  if (projectDir.startsWith(homeEncoded + "-")) {
1910
2009
  return projectDir.slice(homeEncoded.length + 1);
@@ -1967,14 +2066,14 @@ async function extractConversationPairs(filePath, projectFilter) {
1967
2066
  } else if (entry.type === "assistant" && entry.message?.content) {
1968
2067
  const assistantText = extractAssistantText(entry.message.content);
1969
2068
  if (!assistantText.trim()) continue;
1970
- const resolvedProject = fileCwd ? path5.basename(fileCwd) : fallbackProject;
2069
+ const resolvedProject = fileCwd ? path6.basename(fileCwd) : fallbackProject;
1971
2070
  const userText = pendingUser?.text ?? "";
1972
2071
  const timestamp = entry.timestamp ?? pendingUser?.timestamp ?? (/* @__PURE__ */ new Date()).toISOString();
1973
2072
  pairs.push({
1974
2073
  userText,
1975
2074
  assistantText,
1976
2075
  timestamp,
1977
- sessionId: fileSessionId || path5.basename(filePath, ".jsonl"),
2076
+ sessionId: fileSessionId || path6.basename(filePath, ".jsonl"),
1978
2077
  project: resolvedProject,
1979
2078
  cwd: fileCwd || fallbackProject
1980
2079
  });
@@ -2013,7 +2112,7 @@ async function loadExistingHashes(cutoffIso) {
2013
2112
  }
2014
2113
  var MAX_CONTENT_LENGTH = 8e3;
2015
2114
  var MIN_ASSISTANT_LENGTH = 50;
2016
- async function storePair(pair, daemonConnected, stats) {
2115
+ async function storePair(pair, daemonConnected, stats, agentId) {
2017
2116
  const client = getClient();
2018
2117
  const id = crypto2.randomUUID();
2019
2118
  const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
@@ -2062,8 +2161,8 @@ async function storePair(pair, daemonConnected, stats) {
2062
2161
  }
2063
2162
  await writeMemory({
2064
2163
  id: crypto2.randomUUID(),
2065
- agent_id: "backfill",
2066
- agent_role: "system",
2164
+ agent_id: agentId,
2165
+ agent_role: "coo",
2067
2166
  session_id: pair.sessionId,
2068
2167
  timestamp: pair.timestamp,
2069
2168
  tool_name: "ConversationBackfill",
@@ -2086,7 +2185,15 @@ async function backfillConversations(options) {
2086
2185
  };
2087
2186
  const cutoffMs = Date.now() - options.days * 24 * 60 * 60 * 1e3;
2088
2187
  const cutoffIso = new Date(cutoffMs).toISOString();
2089
- process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days...
2188
+ let cooAgentId = "exe";
2189
+ try {
2190
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
2191
+ const employees = await loadEmployees2();
2192
+ const coo = employees.find((e) => e.role === "COO");
2193
+ if (coo) cooAgentId = coo.name;
2194
+ } catch {
2195
+ }
2196
+ process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days (agent: ${cooAgentId})...
2090
2197
  `);
2091
2198
  if (!options.dryRun) {
2092
2199
  process.stderr.write("[backfill-conversations] Initializing store...\n");
@@ -2123,7 +2230,7 @@ async function backfillConversations(options) {
2123
2230
  }
2124
2231
  seenHashes.add(hash);
2125
2232
  if (!options.dryRun) {
2126
- await storePair(pair, daemonConnected, stats);
2233
+ await storePair(pair, daemonConnected, stats, cooAgentId);
2127
2234
  } else {
2128
2235
  stats.conversationsStored++;
2129
2236
  stats.memoriesStored++;
package/dist/bin/cli.js CHANGED
@@ -751,13 +751,17 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
751
751
  settings.hooks[event] = [];
752
752
  }
753
753
  const existing = settings.hooks[event];
754
- const alreadyExists = existing.some(
755
- (g) => g.hooks.some((h) => h.command.includes(marker))
754
+ const correctCommand = group.hooks[0]?.command ?? "";
755
+ const alreadyCorrect = existing.some(
756
+ (g) => g.hooks.some((h) => h.command === correctCommand)
756
757
  );
757
- if (alreadyExists) {
758
+ if (alreadyCorrect) {
758
759
  skipped++;
759
760
  } else {
760
- existing.push(group);
761
+ settings.hooks[event] = existing.filter(
762
+ (g) => !g.hooks.some((h) => h.command.includes(marker))
763
+ );
764
+ settings.hooks[event].push(group);
761
765
  added++;
762
766
  }
763
767
  }
@@ -3085,7 +3089,7 @@ async function loadExistingHashes(cutoffIso) {
3085
3089
  }
3086
3090
  return hashes;
3087
3091
  }
3088
- async function storePair(pair, daemonConnected, stats) {
3092
+ async function storePair(pair, daemonConnected, stats, agentId) {
3089
3093
  const client = getClient();
3090
3094
  const id = crypto2.randomUUID();
3091
3095
  const userTrunc = pair.userText.length > MAX_CONTENT_LENGTH ? pair.userText.slice(0, MAX_CONTENT_LENGTH) : pair.userText;
@@ -3134,8 +3138,8 @@ async function storePair(pair, daemonConnected, stats) {
3134
3138
  }
3135
3139
  await writeMemory({
3136
3140
  id: crypto2.randomUUID(),
3137
- agent_id: "backfill",
3138
- agent_role: "system",
3141
+ agent_id: agentId,
3142
+ agent_role: "coo",
3139
3143
  session_id: pair.sessionId,
3140
3144
  timestamp: pair.timestamp,
3141
3145
  tool_name: "ConversationBackfill",
@@ -3158,7 +3162,15 @@ async function backfillConversations(options) {
3158
3162
  };
3159
3163
  const cutoffMs = Date.now() - options.days * 24 * 60 * 60 * 1e3;
3160
3164
  const cutoffIso = new Date(cutoffMs).toISOString();
3161
- process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days...
3165
+ let cooAgentId = "exe";
3166
+ try {
3167
+ const { loadEmployees: loadEmployees2 } = await Promise.resolve().then(() => (init_employees(), employees_exports));
3168
+ const employees = await loadEmployees2();
3169
+ const coo = employees.find((e) => e.role === "COO");
3170
+ if (coo) cooAgentId = coo.name;
3171
+ } catch {
3172
+ }
3173
+ process.stderr.write(`[backfill-conversations] Scanning last ${options.days} days (agent: ${cooAgentId})...
3162
3174
  `);
3163
3175
  if (!options.dryRun) {
3164
3176
  process.stderr.write("[backfill-conversations] Initializing store...\n");
@@ -3195,7 +3207,7 @@ async function backfillConversations(options) {
3195
3207
  }
3196
3208
  seenHashes.add(hash);
3197
3209
  if (!options.dryRun) {
3198
- await storePair(pair, daemonConnected, stats);
3210
+ await storePair(pair, daemonConnected, stats, cooAgentId);
3199
3211
  } else {
3200
3212
  stats.conversationsStored++;
3201
3213
  stats.memoriesStored++;
@@ -21447,7 +21459,7 @@ async function runClaudeUninstall() {
21447
21459
  return {
21448
21460
  ...g,
21449
21461
  hooks: g.hooks.filter(
21450
- (h) => !(typeof h.command === "string" && h.command.includes("exe-os"))
21462
+ (h) => !(typeof h.command === "string" && (h.command.includes("exe-os") || h.command.includes("askexenow") || h.command.includes("exe-mem")))
21451
21463
  )
21452
21464
  };
21453
21465
  }).filter(
@@ -478,13 +478,17 @@ async function mergeHooks(packageRoot, homeDir = os3.homedir()) {
478
478
  settings.hooks[event] = [];
479
479
  }
480
480
  const existing = settings.hooks[event];
481
- const alreadyExists = existing.some(
482
- (g) => g.hooks.some((h) => h.command.includes(marker))
481
+ const correctCommand = group.hooks[0]?.command ?? "";
482
+ const alreadyCorrect = existing.some(
483
+ (g) => g.hooks.some((h) => h.command === correctCommand)
483
484
  );
484
- if (alreadyExists) {
485
+ if (alreadyCorrect) {
485
486
  skipped++;
486
487
  } else {
487
- existing.push(group);
488
+ settings.hooks[event] = existing.filter(
489
+ (g) => !g.hooks.some((h) => h.command.includes(marker))
490
+ );
491
+ settings.hooks[event].push(group);
488
492
  added++;
489
493
  }
490
494
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askexenow/exe-os",
3
- "version": "0.8.21",
3
+ "version": "0.8.23",
4
4
  "description": "AI employee operating system — persistent memory, task management, and multi-agent coordination for Claude Code.",
5
5
  "license": "CC-BY-NC-4.0",
6
6
  "type": "module",