@askexenow/exe-os 0.8.65 → 0.8.69

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 (54) hide show
  1. package/dist/bin/cleanup-stale-review-tasks.js +8 -7
  2. package/dist/bin/cli.js +204 -120
  3. package/dist/bin/exe-assign.js +11 -10
  4. package/dist/bin/exe-boot.js +83 -78
  5. package/dist/bin/exe-call.js +33 -1
  6. package/dist/bin/exe-cloud.js +3 -2
  7. package/dist/bin/exe-dispatch.js +31 -30
  8. package/dist/bin/exe-gateway.js +33 -32
  9. package/dist/bin/exe-heartbeat.js +21 -20
  10. package/dist/bin/exe-launch-agent.js +84 -19
  11. package/dist/bin/exe-link.js +16 -11
  12. package/dist/bin/exe-new-employee.js +20 -19
  13. package/dist/bin/exe-pending-messages.js +7 -6
  14. package/dist/bin/exe-pending-reviews.js +16 -15
  15. package/dist/bin/exe-rename.js +12 -11
  16. package/dist/bin/exe-review.js +4 -3
  17. package/dist/bin/exe-session-cleanup.js +20 -19
  18. package/dist/bin/exe-settings.js +3 -2
  19. package/dist/bin/exe-status.js +16 -15
  20. package/dist/bin/exe-team.js +4 -3
  21. package/dist/bin/git-sweep.js +31 -30
  22. package/dist/bin/install.js +284 -113
  23. package/dist/bin/scan-tasks.js +33 -32
  24. package/dist/bin/setup.js +114 -30
  25. package/dist/gateway/index.js +32 -31
  26. package/dist/hooks/bug-report-worker.js +58 -26
  27. package/dist/hooks/commit-complete.js +31 -30
  28. package/dist/hooks/ingest-worker.js +58 -57
  29. package/dist/hooks/post-compact.js +10 -9
  30. package/dist/hooks/pre-compact.js +31 -30
  31. package/dist/hooks/pre-tool-use.js +46 -14
  32. package/dist/hooks/prompt-ingest-worker.js +15 -14
  33. package/dist/hooks/prompt-submit.js +15 -14
  34. package/dist/hooks/response-ingest-worker.js +8 -7
  35. package/dist/hooks/session-end.js +14 -13
  36. package/dist/hooks/session-start.js +10 -9
  37. package/dist/hooks/stop.js +10 -9
  38. package/dist/hooks/subagent-stop.js +10 -9
  39. package/dist/hooks/summary-worker.js +41 -36
  40. package/dist/index.js +43 -42
  41. package/dist/lib/cloud-sync.js +16 -11
  42. package/dist/lib/employees.js +33 -1
  43. package/dist/lib/exe-daemon.js +56 -55
  44. package/dist/lib/messaging.js +9 -8
  45. package/dist/lib/tasks.js +27 -26
  46. package/dist/lib/tmux-routing.js +29 -28
  47. package/dist/mcp/server.js +94 -62
  48. package/dist/mcp/tools/create-task.js +60 -28
  49. package/dist/mcp/tools/list-tasks.js +10 -9
  50. package/dist/mcp/tools/send-message.js +11 -10
  51. package/dist/mcp/tools/update-task.js +21 -20
  52. package/dist/runtime/index.js +31 -30
  53. package/dist/tui/App.js +67 -35
  54. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -1538,9 +1538,10 @@ var init_config = __esm({
1538
1538
 
1539
1539
  // src/lib/employees.ts
1540
1540
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
1541
- import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4 } from "fs";
1541
+ import { existsSync as existsSync4, symlinkSync, readlinkSync, readFileSync as readFileSync4, renameSync as renameSync3, unlinkSync, writeFileSync as writeFileSync3 } from "fs";
1542
1542
  import { execSync as execSync4 } from "child_process";
1543
1543
  import path5 from "path";
1544
+ import os5 from "os";
1544
1545
  function loadEmployeesSync(employeesPath = EMPLOYEES_PATH) {
1545
1546
  if (!existsSync4(employeesPath)) return [];
1546
1547
  try {
@@ -1569,7 +1570,7 @@ var init_employees = __esm({
1569
1570
  });
1570
1571
 
1571
1572
  // src/lib/license.ts
1572
- import { readFileSync as readFileSync5, writeFileSync as writeFileSync3, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1573
+ import { readFileSync as readFileSync5, writeFileSync as writeFileSync4, existsSync as existsSync5, mkdirSync as mkdirSync3 } from "fs";
1573
1574
  import { randomUUID as randomUUID2 } from "crypto";
1574
1575
  import path6 from "path";
1575
1576
  import { jwtVerify, importSPKI } from "jose";
@@ -1672,11 +1673,11 @@ var init_plan_limits = __esm({
1672
1673
  // src/lib/notifications.ts
1673
1674
  import crypto from "crypto";
1674
1675
  import path8 from "path";
1675
- import os5 from "os";
1676
+ import os6 from "os";
1676
1677
  import {
1677
1678
  readFileSync as readFileSync7,
1678
1679
  readdirSync,
1679
- unlinkSync,
1680
+ unlinkSync as unlinkSync2,
1680
1681
  existsSync as existsSync7,
1681
1682
  rmdirSync
1682
1683
  } from "fs";
@@ -2257,7 +2258,7 @@ var init_tasks_crud = __esm({
2257
2258
 
2258
2259
  // src/lib/tasks-review.ts
2259
2260
  import path10 from "path";
2260
- import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync2 } from "fs";
2261
+ import { existsSync as existsSync9, readdirSync as readdirSync2, unlinkSync as unlinkSync3 } from "fs";
2261
2262
  async function countPendingReviews(sessionScope) {
2262
2263
  const client = getClient();
2263
2264
  if (sessionScope) {
@@ -2442,7 +2443,7 @@ async function cleanupReviewFile(row, taskFile, _baseDir) {
2442
2443
  if (existsSync9(cacheDir)) {
2443
2444
  for (const f of readdirSync2(cacheDir)) {
2444
2445
  if (f.startsWith("review-notified-")) {
2445
- unlinkSync2(path10.join(cacheDir, f));
2446
+ unlinkSync3(path10.join(cacheDir, f));
2446
2447
  }
2447
2448
  }
2448
2449
  }
@@ -3110,7 +3111,7 @@ __export(tasks_exports, {
3110
3111
  writeCheckpoint: () => writeCheckpoint
3111
3112
  });
3112
3113
  import path13 from "path";
3113
- import { writeFileSync as writeFileSync4, mkdirSync as mkdirSync4, unlinkSync as unlinkSync3 } from "fs";
3114
+ import { writeFileSync as writeFileSync5, mkdirSync as mkdirSync4, unlinkSync as unlinkSync4 } from "fs";
3114
3115
  async function createTask(input) {
3115
3116
  const result = await createTaskCore(input);
3116
3117
  if (!input.skipDispatch && result.status !== "blocked" && !process.env.VITEST) {
@@ -3133,10 +3134,10 @@ async function updateTask(input) {
3133
3134
  const cachePath = path13.join(cacheDir, `current-task-${agent}.json`);
3134
3135
  if (input.status === "in_progress") {
3135
3136
  mkdirSync4(cacheDir, { recursive: true });
3136
- writeFileSync4(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3137
+ writeFileSync5(cachePath, JSON.stringify({ taskId, title: String(row.title) }));
3137
3138
  } else if (input.status === "done" || input.status === "blocked" || input.status === "cancelled") {
3138
3139
  try {
3139
- unlinkSync3(cachePath);
3140
+ unlinkSync4(cachePath);
3140
3141
  } catch {
3141
3142
  }
3142
3143
  }
@@ -3578,11 +3579,11 @@ __export(tmux_routing_exports, {
3578
3579
  verifyPaneAtCapacity: () => verifyPaneAtCapacity
3579
3580
  });
3580
3581
  import { execFileSync as execFileSync2, execSync as execSync7 } from "child_process";
3581
- import { readFileSync as readFileSync9, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3582
+ import { readFileSync as readFileSync9, writeFileSync as writeFileSync6, mkdirSync as mkdirSync5, existsSync as existsSync10, appendFileSync } from "fs";
3582
3583
  import path14 from "path";
3583
- import os6 from "os";
3584
+ import os7 from "os";
3584
3585
  import { fileURLToPath } from "url";
3585
- import { unlinkSync as unlinkSync4 } from "fs";
3586
+ import { unlinkSync as unlinkSync5 } from "fs";
3586
3587
  function spawnLockPath(sessionName) {
3587
3588
  return path14.join(SPAWN_LOCK_DIR, `${sessionName}.lock`);
3588
3589
  }
@@ -3609,12 +3610,12 @@ function acquireSpawnLock(sessionName) {
3609
3610
  } catch {
3610
3611
  }
3611
3612
  }
3612
- writeFileSync5(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3613
+ writeFileSync6(lockFile, JSON.stringify({ pid: process.pid, timestamp: Date.now() }));
3613
3614
  return true;
3614
3615
  }
3615
3616
  function releaseSpawnLock(sessionName) {
3616
3617
  try {
3617
- unlinkSync4(spawnLockPath(sessionName));
3618
+ unlinkSync5(spawnLockPath(sessionName));
3618
3619
  } catch {
3619
3620
  }
3620
3621
  }
@@ -3696,7 +3697,7 @@ function registerParentExe(sessionKey, parentExe, dispatchedBy) {
3696
3697
  }
3697
3698
  const rootExe = extractRootExe(parentExe) ?? parentExe;
3698
3699
  const filePath = path14.join(SESSION_CACHE, `parent-exe-${sessionKey}.json`);
3699
- writeFileSync5(filePath, JSON.stringify({
3700
+ writeFileSync6(filePath, JSON.stringify({
3700
3701
  parentExe: rootExe,
3701
3702
  dispatchedBy: dispatchedBy || rootExe,
3702
3703
  registeredAt: (/* @__PURE__ */ new Date()).toISOString()
@@ -3783,7 +3784,7 @@ function readDebounceState() {
3783
3784
  function writeDebounceState(state) {
3784
3785
  try {
3785
3786
  if (!existsSync10(SESSION_CACHE)) mkdirSync5(SESSION_CACHE, { recursive: true });
3786
- writeFileSync5(DEBOUNCE_FILE, JSON.stringify(state));
3787
+ writeFileSync6(DEBOUNCE_FILE, JSON.stringify(state));
3787
3788
  } catch {
3788
3789
  }
3789
3790
  }
@@ -3972,7 +3973,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3972
3973
  const transport = getTransport();
3973
3974
  const sessionName = employeeSessionName(employeeName, exeSession, opts?.instance);
3974
3975
  const instanceLabel = opts?.instance != null && opts.instance > 0 ? `${employeeName}${opts.instance}` : employeeName;
3975
- const logDir = path14.join(os6.homedir(), ".exe-os", "session-logs");
3976
+ const logDir = path14.join(os7.homedir(), ".exe-os", "session-logs");
3976
3977
  const logFile = path14.join(logDir, `${instanceLabel}-${Date.now()}.log`);
3977
3978
  if (!existsSync10(logDir)) {
3978
3979
  mkdirSync5(logDir, { recursive: true });
@@ -3988,7 +3989,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3988
3989
  } catch {
3989
3990
  }
3990
3991
  try {
3991
- const claudeJsonPath = path14.join(os6.homedir(), ".claude.json");
3992
+ const claudeJsonPath = path14.join(os7.homedir(), ".claude.json");
3992
3993
  let claudeJson = {};
3993
3994
  try {
3994
3995
  claudeJson = JSON.parse(readFileSync9(claudeJsonPath, "utf8"));
@@ -3999,11 +4000,11 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
3999
4000
  const trustDir = opts?.cwd ?? projectDir;
4000
4001
  if (!projects[trustDir]) projects[trustDir] = {};
4001
4002
  projects[trustDir].hasTrustDialogAccepted = true;
4002
- writeFileSync5(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4003
+ writeFileSync6(claudeJsonPath, JSON.stringify(claudeJson, null, 2) + "\n");
4003
4004
  } catch {
4004
4005
  }
4005
4006
  try {
4006
- const settingsDir = path14.join(os6.homedir(), ".claude", "projects");
4007
+ const settingsDir = path14.join(os7.homedir(), ".claude", "projects");
4007
4008
  const normalizedKey = (opts?.cwd ?? projectDir).replace(/\//g, "-").replace(/^-/, "");
4008
4009
  const projSettingsDir = path14.join(settingsDir, normalizedKey);
4009
4010
  const settingsPath = path14.join(projSettingsDir, "settings.json");
@@ -4038,7 +4039,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4038
4039
  perms.allow = allow;
4039
4040
  settings.permissions = perms;
4040
4041
  mkdirSync5(projSettingsDir, { recursive: true });
4041
- writeFileSync5(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4042
+ writeFileSync6(settingsPath, JSON.stringify(settings, null, 2) + "\n");
4042
4043
  }
4043
4044
  } catch {
4044
4045
  }
@@ -4051,7 +4052,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4051
4052
  let legacyFallbackWarned = false;
4052
4053
  if (!useExeAgent && !useBinSymlink) {
4053
4054
  const identityPath = path14.join(
4054
- os6.homedir(),
4055
+ os7.homedir(),
4055
4056
  ".exe-os",
4056
4057
  "identity",
4057
4058
  `${employeeName}.md`
@@ -4081,7 +4082,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4081
4082
  }
4082
4083
  let sessionContextFlag = "";
4083
4084
  try {
4084
- const ctxDir = path14.join(os6.homedir(), ".exe-os", "session-cache");
4085
+ const ctxDir = path14.join(os7.homedir(), ".exe-os", "session-cache");
4085
4086
  mkdirSync5(ctxDir, { recursive: true });
4086
4087
  const ctxFile = path14.join(ctxDir, `session-context-${sessionName}.md`);
4087
4088
  const ctxContent = [
@@ -4090,7 +4091,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4090
4091
  `Your parent exe session is ${exeSession}.`,
4091
4092
  `Your employees (if any) use the -${exeSession} suffix (e.g., tom-${exeSession}).`
4092
4093
  ].join("\n");
4093
- writeFileSync5(ctxFile, ctxContent);
4094
+ writeFileSync6(ctxFile, ctxContent);
4094
4095
  sessionContextFlag = ` --append-system-prompt-file ${ctxFile}`;
4095
4096
  } catch {
4096
4097
  }
@@ -4129,7 +4130,7 @@ function spawnEmployee(employeeName, exeSession, projectDir, opts) {
4129
4130
  try {
4130
4131
  const mySession = getMySession();
4131
4132
  const dispatchInfo = path14.join(SESSION_CACHE, `dispatch-info-${sessionName}.json`);
4132
- writeFileSync5(dispatchInfo, JSON.stringify({
4133
+ writeFileSync6(dispatchInfo, JSON.stringify({
4133
4134
  dispatchedBy: mySession,
4134
4135
  rootExe: exeSession,
4135
4136
  provider: useBinSymlink ? ccProvider : useExeAgent ? opts.provider : "anthropic",
@@ -4192,13 +4193,13 @@ var init_tmux_routing = __esm({
4192
4193
  init_provider_table();
4193
4194
  init_intercom_queue();
4194
4195
  init_plan_limits();
4195
- SPAWN_LOCK_DIR = path14.join(os6.homedir(), ".exe-os", "spawn-locks");
4196
- SESSION_CACHE = path14.join(os6.homedir(), ".exe-os", "session-cache");
4196
+ SPAWN_LOCK_DIR = path14.join(os7.homedir(), ".exe-os", "spawn-locks");
4197
+ SESSION_CACHE = path14.join(os7.homedir(), ".exe-os", "session-cache");
4197
4198
  BEHAVIORS_EXPORT_TIMEOUT_MS = 1e4;
4198
4199
  VALID_SESSION_NAME = /^[a-z]+\d*-[a-zA-Z0-9_]+$/;
4199
4200
  VERIFY_PANE_LINES = 200;
4200
4201
  INTERCOM_DEBOUNCE_MS = 3e4;
4201
- INTERCOM_LOG2 = path14.join(os6.homedir(), ".exe-os", "intercom.log");
4202
+ INTERCOM_LOG2 = path14.join(os7.homedir(), ".exe-os", "intercom.log");
4202
4203
  DEBOUNCE_FILE = path14.join(SESSION_CACHE, "intercom-debounce.json");
4203
4204
  DEBOUNCE_CLEANUP_AGE_MS = 5 * 60 * 1e3;
4204
4205
  BUSY_PATTERN = /[✻✽✶✳·].*…|Running…/;
@@ -4218,9 +4219,9 @@ var init_memory = __esm({
4218
4219
  import { readFile as readFile4, writeFile as writeFile5, unlink, mkdir as mkdir4, chmod as chmod2 } from "fs/promises";
4219
4220
  import { existsSync as existsSync11 } from "fs";
4220
4221
  import path15 from "path";
4221
- import os7 from "os";
4222
+ import os8 from "os";
4222
4223
  function getKeyDir() {
4223
- return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os7.homedir(), ".exe-os");
4224
+ return process.env.EXE_OS_DIR ?? process.env.EXE_MEM_DIR ?? path15.join(os8.homedir(), ".exe-os");
4224
4225
  }
4225
4226
  function getKeyPath() {
4226
4227
  return path15.join(getKeyDir(), "master.key");
@@ -5780,7 +5781,7 @@ var init_crm_bridge = __esm({
5780
5781
  import net from "net";
5781
5782
  import { spawn } from "child_process";
5782
5783
  import { randomUUID as randomUUID6 } from "crypto";
5783
- import { existsSync as existsSync13, unlinkSync as unlinkSync5, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
5784
+ import { existsSync as existsSync13, unlinkSync as unlinkSync6, readFileSync as readFileSync10, openSync, closeSync, statSync } from "fs";
5784
5785
  import path17 from "path";
5785
5786
  import { fileURLToPath as fileURLToPath2 } from "url";
5786
5787
  function handleData(chunk) {
@@ -5820,11 +5821,11 @@ function cleanupStaleFiles() {
5820
5821
  } catch {
5821
5822
  }
5822
5823
  try {
5823
- unlinkSync5(PID_PATH);
5824
+ unlinkSync6(PID_PATH);
5824
5825
  } catch {
5825
5826
  }
5826
5827
  try {
5827
- unlinkSync5(SOCKET_PATH);
5828
+ unlinkSync6(SOCKET_PATH);
5828
5829
  } catch {
5829
5830
  }
5830
5831
  }
@@ -5886,7 +5887,7 @@ function acquireSpawnLock2() {
5886
5887
  const stat = statSync(SPAWN_LOCK_PATH);
5887
5888
  if (Date.now() - stat.mtimeMs > SPAWN_LOCK_STALE_MS) {
5888
5889
  try {
5889
- unlinkSync5(SPAWN_LOCK_PATH);
5890
+ unlinkSync6(SPAWN_LOCK_PATH);
5890
5891
  } catch {
5891
5892
  }
5892
5893
  try {
@@ -5903,7 +5904,7 @@ function acquireSpawnLock2() {
5903
5904
  }
5904
5905
  function releaseSpawnLock2() {
5905
5906
  try {
5906
- unlinkSync5(SPAWN_LOCK_PATH);
5907
+ unlinkSync6(SPAWN_LOCK_PATH);
5907
5908
  } catch {
5908
5909
  }
5909
5910
  }
@@ -6034,11 +6035,11 @@ function killAndRespawnDaemon() {
6034
6035
  _connected = false;
6035
6036
  _buffer = "";
6036
6037
  try {
6037
- unlinkSync5(PID_PATH);
6038
+ unlinkSync6(PID_PATH);
6038
6039
  } catch {
6039
6040
  }
6040
6041
  try {
6041
- unlinkSync5(SOCKET_PATH);
6042
+ unlinkSync6(SOCKET_PATH);
6042
6043
  } catch {
6043
6044
  }
6044
6045
  spawnDaemon();
@@ -11648,12 +11649,12 @@ var SlackAdapter = class {
11648
11649
  // src/gateway/adapters/imessage.ts
11649
11650
  import { execFile } from "child_process";
11650
11651
  import { promisify } from "util";
11651
- import os8 from "os";
11652
+ import os9 from "os";
11652
11653
  import path18 from "path";
11653
11654
  var execFileAsync = promisify(execFile);
11654
11655
  var POLL_INTERVAL_MS = 5e3;
11655
11656
  var MESSAGES_DB_PATH = path18.join(
11656
- process.env.HOME ?? os8.homedir(),
11657
+ process.env.HOME ?? os9.homedir(),
11657
11658
  "Library/Messages/chat.db"
11658
11659
  );
11659
11660
  var IMessageAdapter = class {
@@ -12496,11 +12497,11 @@ async function ensureCRMContact(info) {
12496
12497
  }
12497
12498
 
12498
12499
  // src/automation/trigger-engine.ts
12499
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync6, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
12500
+ import { readFileSync as readFileSync12, writeFileSync as writeFileSync7, existsSync as existsSync14, mkdirSync as mkdirSync8 } from "fs";
12500
12501
  import { randomUUID as randomUUID15 } from "crypto";
12501
12502
  import path19 from "path";
12502
- import os9 from "os";
12503
- var TRIGGERS_PATH = path19.join(os9.homedir(), ".exe-os", "triggers.json");
12503
+ import os10 from "os";
12504
+ var TRIGGERS_PATH = path19.join(os10.homedir(), ".exe-os", "triggers.json");
12504
12505
  var GRAPH_API_VERSION = "v21.0";
12505
12506
  function substituteTemplate(template, record) {
12506
12507
  return template.replace(
@@ -1,5 +1,5 @@
1
1
  // src/lib/cloud-sync.ts
2
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, existsSync as existsSync4, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync, openSync, closeSync } from "fs";
2
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync3, existsSync as existsSync4, readdirSync, mkdirSync as mkdirSync2, appendFileSync, unlinkSync as unlinkSync2, openSync, closeSync } from "fs";
3
3
  import crypto2 from "crypto";
4
4
  import path4 from "path";
5
5
  import { homedir } from "os";
@@ -188,9 +188,10 @@ function loadDeviceId() {
188
188
 
189
189
  // src/lib/employees.ts
190
190
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
191
- import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3 } from "fs";
191
+ import { existsSync as existsSync3, symlinkSync, readlinkSync, readFileSync as readFileSync3, renameSync as renameSync2, unlinkSync, writeFileSync as writeFileSync2 } from "fs";
192
192
  import { execSync } from "child_process";
193
193
  import path3 from "path";
194
+ import os2 from "os";
194
195
  var EMPLOYEES_PATH = path3.join(EXE_AI_DIR, "exe-employees.json");
195
196
  async function loadEmployees(employeesPath = EMPLOYEES_PATH) {
196
197
  if (!existsSync3(employeesPath)) {
@@ -269,7 +270,7 @@ async function withRosterLock(fn) {
269
270
  try {
270
271
  const fd = openSync(ROSTER_LOCK_PATH, "wx");
271
272
  closeSync(fd);
272
- writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
273
+ writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
273
274
  } catch (err) {
274
275
  if (err.code === "EEXIST") {
275
276
  try {
@@ -277,10 +278,10 @@ async function withRosterLock(fn) {
277
278
  if (Date.now() - ts < LOCK_STALE_MS) {
278
279
  throw new Error("Roster merge already in progress \u2014 another sync is running");
279
280
  }
280
- unlinkSync(ROSTER_LOCK_PATH);
281
+ unlinkSync2(ROSTER_LOCK_PATH);
281
282
  const fd = openSync(ROSTER_LOCK_PATH, "wx");
282
283
  closeSync(fd);
283
- writeFileSync2(ROSTER_LOCK_PATH, String(Date.now()));
284
+ writeFileSync3(ROSTER_LOCK_PATH, String(Date.now()));
284
285
  } catch (retryErr) {
285
286
  if (retryErr instanceof Error && retryErr.message.includes("already in progress")) throw retryErr;
286
287
  throw new Error("Roster merge already in progress \u2014 another sync is running");
@@ -293,7 +294,7 @@ async function withRosterLock(fn) {
293
294
  return await fn();
294
295
  } finally {
295
296
  try {
296
- unlinkSync(ROSTER_LOCK_PATH);
297
+ unlinkSync2(ROSTER_LOCK_PATH);
297
298
  } catch {
298
299
  }
299
300
  }
@@ -594,13 +595,13 @@ function recordRosterDeletion(name) {
594
595
  } catch {
595
596
  }
596
597
  if (!deletions.includes(name)) deletions.push(name);
597
- writeFileSync2(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
598
+ writeFileSync3(ROSTER_DELETIONS_PATH, JSON.stringify(deletions));
598
599
  }
599
600
  function consumeRosterDeletions() {
600
601
  try {
601
602
  if (!existsSync4(ROSTER_DELETIONS_PATH)) return [];
602
603
  const deletions = JSON.parse(readFileSync4(ROSTER_DELETIONS_PATH, "utf-8"));
603
- writeFileSync2(ROSTER_DELETIONS_PATH, "[]");
604
+ writeFileSync3(ROSTER_DELETIONS_PATH, "[]");
604
605
  return deletions;
605
606
  } catch {
606
607
  return [];
@@ -716,7 +717,7 @@ function mergeConfig(remoteConfig, configPath) {
716
717
  const merged = { ...remoteConfig, ...local };
717
718
  const dir = path4.dirname(cfgPath);
718
719
  if (!existsSync4(dir)) mkdirSync2(dir, { recursive: true });
719
- writeFileSync2(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
720
+ writeFileSync3(cfgPath, JSON.stringify(merged, null, 2), "utf-8");
720
721
  }
721
722
  async function mergeRosterFromRemote(remote, paths) {
722
723
  return withRosterLock(async () => {
@@ -736,7 +737,11 @@ async function mergeRosterFromRemote(remote, paths) {
736
737
  } catch {
737
738
  }
738
739
  }
739
- const remoteIdentity = remote.identities[`${remoteEmp.name}.md`];
740
+ const lookupKey = `${remoteEmp.name}.md`;
741
+ const matchedKey = Object.keys(remote.identities).find(
742
+ (k) => k.toLowerCase() === lookupKey.toLowerCase()
743
+ ) ?? lookupKey;
744
+ const remoteIdentity = remote.identities[matchedKey];
740
745
  if (remoteIdentity) {
741
746
  if (!existsSync4(identityDir)) mkdirSync2(identityDir, { recursive: true });
742
747
  const idPath = path4.join(identityDir, `${remoteEmp.name}.md`);
@@ -746,7 +751,7 @@ async function mergeRosterFromRemote(remote, paths) {
746
751
  } catch {
747
752
  }
748
753
  if (localIdentity !== remoteIdentity) {
749
- writeFileSync2(idPath, remoteIdentity, "utf-8");
754
+ writeFileSync3(idPath, remoteIdentity, "utf-8");
750
755
  identitiesUpdated++;
751
756
  }
752
757
  }
@@ -1,8 +1,9 @@
1
1
  // src/lib/employees.ts
2
2
  import { readFile as readFile2, writeFile as writeFile2, mkdir as mkdir2 } from "fs/promises";
3
- import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2 } from "fs";
3
+ import { existsSync as existsSync2, symlinkSync, readlinkSync, readFileSync as readFileSync2, renameSync as renameSync2, unlinkSync, writeFileSync } from "fs";
4
4
  import { execSync } from "child_process";
5
5
  import path2 from "path";
6
+ import os2 from "os";
6
7
 
7
8
  // src/lib/config.ts
8
9
  import { readFile, writeFile, mkdir, chmod } from "fs/promises";
@@ -165,6 +166,36 @@ function addEmployee(employees, employee) {
165
166
  }
166
167
  return [...employees, normalized];
167
168
  }
169
+ async function normalizeRosterCase(rosterPath) {
170
+ const employees = await loadEmployees(rosterPath);
171
+ let changed = false;
172
+ for (const emp of employees) {
173
+ if (emp.name !== emp.name.toLowerCase()) {
174
+ const oldName = emp.name;
175
+ emp.name = emp.name.toLowerCase();
176
+ changed = true;
177
+ try {
178
+ const identityDir = path2.join(os2.homedir(), ".exe-os", "identity");
179
+ const oldPath = path2.join(identityDir, `${oldName}.md`);
180
+ const newPath = path2.join(identityDir, `${emp.name}.md`);
181
+ if (existsSync2(oldPath) && !existsSync2(newPath)) {
182
+ renameSync2(oldPath, newPath);
183
+ } else if (existsSync2(oldPath) && oldPath !== newPath) {
184
+ const content = readFileSync2(oldPath, "utf-8");
185
+ writeFileSync(newPath, content, "utf-8");
186
+ if (oldPath.toLowerCase() !== newPath.toLowerCase()) {
187
+ unlinkSync(oldPath);
188
+ }
189
+ }
190
+ } catch {
191
+ }
192
+ }
193
+ }
194
+ if (changed) {
195
+ await saveEmployees(employees, rosterPath);
196
+ }
197
+ return changed;
198
+ }
168
199
  function findExeBin() {
169
200
  try {
170
201
  return execSync(process.platform === "win32" ? "where exe-os" : "which exe-os", { encoding: "utf8" }).trim();
@@ -215,6 +246,7 @@ export {
215
246
  isMultiInstance,
216
247
  loadEmployees,
217
248
  loadEmployeesSync,
249
+ normalizeRosterCase,
218
250
  registerBinSymlinks,
219
251
  saveEmployees,
220
252
  validateEmployeeName