@teammy/cli 0.1.0 → 0.1.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 +3 -3
  2. package/dist/main.js +219 -215
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  成员设备上的命令行客户端,需要 Node.js 22.13+,推荐 Node.js 24。
4
4
 
5
- 0.1.0 提供完整目录发布、后台升级修复、Hook 清理与新增 Agent 采集支持,需与配套服务端一起升级。
5
+ 0.1.1 与服务端版本保持一致,沿用 0.1.0 的完整目录发布、后台升级修复、Hook 清理与 Agent 采集能力。
6
6
 
7
7
  客户端与服务端保持配套版本,支持多项目后台同步、根 README / `_template/` 公共资料分发,以及 `workspaces/.teammy-local.json` 中的 `files.ignore` 忽略规则。规则相对本人目录,下一轮检查生效,不删除已共享历史。公共资料由项目设置指定的一位成员维护。自动任务整理在服务端运行。
8
8
 
9
9
  ```sh
10
- npm install -g @teammy/cli@0.1.0
10
+ npm install -g @teammy/cli@0.1.1
11
11
  ```
12
12
 
13
13
  ```sh
@@ -43,7 +43,7 @@ macOS 使用用户 LaunchAgent,Linux 使用 systemd 用户服务;不安装
43
43
  本次文件发布协议依赖服务端 `atomic-file-publications` 能力,两端需配套更新。以下维护命令从 0.1.0 提供,先安装新版,再执行修复:
44
44
 
45
45
  ```sh
46
- npm install -g @teammy/cli@0.1.0
46
+ npm install -g @teammy/cli@0.1.1
47
47
  teammy repair
48
48
  ```
49
49
 
package/dist/main.js CHANGED
@@ -150,7 +150,8 @@ var MemberUpdateSchema = z3.object({
150
150
  var ProjectCreateSchema = z3.object({
151
151
  operationId: z3.uuid(),
152
152
  projectName: z3.string().trim().min(1).max(120),
153
- description: z3.string().max(2e3).default("")
153
+ description: z3.string().max(2e3).default(""),
154
+ workspacePath: z3.string().trim().min(1).optional()
154
155
  }).strict();
155
156
  var ProjectSummarySchema = z3.object({
156
157
  projectId: NameSchema,
@@ -566,7 +567,7 @@ var HubClient = class {
566
567
  import { createHash as createHash8 } from "node:crypto";
567
568
  import { realpath as realpath6 } from "node:fs/promises";
568
569
  import { homedir as homedir4 } from "node:os";
569
- import { dirname as dirname9, join as join16, resolve as resolve8 } from "node:path";
570
+ import { dirname as dirname9, join as join17, resolve as resolve8 } from "node:path";
570
571
  import { parseArgs as parseArgs2 } from "node:util";
571
572
 
572
573
  // packages/protocol/file-publication.ts
@@ -591,7 +592,7 @@ function transaction(db, action) {
591
592
  throw error;
592
593
  }
593
594
  }
594
- function openDatabase(path, migrations, busyTimeoutMs = 5e3) {
595
+ function openDatabase(path, migrations, busyTimeoutMs = 5e3, journalMode = "WAL") {
595
596
  if (!Number.isSafeInteger(busyTimeoutMs) || busyTimeoutMs < 0)
596
597
  throw new Error("Invalid SQLite busy timeout.");
597
598
  mkdirSync(dirname(path), { recursive: true, mode: 448 });
@@ -599,7 +600,7 @@ function openDatabase(path, migrations, busyTimeoutMs = 5e3) {
599
600
  try {
600
601
  chmodSync(path, 384);
601
602
  db.exec(
602
- `PRAGMA busy_timeout = ${busyTimeoutMs}; PRAGMA foreign_keys = ON; PRAGMA journal_mode = WAL; PRAGMA synchronous = FULL;`
603
+ `PRAGMA busy_timeout = ${busyTimeoutMs}; PRAGMA foreign_keys = ON; PRAGMA journal_mode = ${journalMode}; PRAGMA synchronous = FULL;`
603
604
  );
604
605
  transaction(db, () => {
605
606
  const version = Number(db.prepare("PRAGMA user_version").get().user_version);
@@ -950,8 +951,8 @@ var RuntimeState = class {
950
951
 
951
952
  // apps/client/state.ts
952
953
  import { randomUUID as randomUUID4 } from "node:crypto";
953
- import { join as join6, relative as relative2, isAbsolute, sep as sep2 } from "node:path";
954
- import { z as z11 } from "zod";
954
+ import { join as join7, relative as relative2, isAbsolute, sep as sep2 } from "node:path";
955
+ import { z as z12 } from "zod";
955
956
  import { DatabaseSync as DatabaseSync3 } from "node:sqlite";
956
957
 
957
958
  // apps/client/runtime/state-access.ts
@@ -1052,25 +1053,88 @@ function stateVersion(directory) {
1052
1053
  import { execFile } from "node:child_process";
1053
1054
  import { promisify } from "node:util";
1054
1055
  import { lstat as lstat2, mkdir as mkdir2, realpath as realpath2, rmdir as rmdir2 } from "node:fs/promises";
1055
- import { basename, dirname as dirname4, join as join4, resolve } from "node:path";
1056
+ import { basename, dirname as dirname4, join as join5, resolve } from "node:path";
1056
1057
 
1057
1058
  // apps/client/files/local-fs.ts
1058
1059
  import { createHash as createHash2, randomUUID as randomUUID2 } from "node:crypto";
1059
- import { constants } from "node:fs";
1060
- import { lstat, mkdir, open, readdir, realpath, rename, rmdir, unlink } from "node:fs/promises";
1061
- import { dirname as dirname3, join as join3, relative, sep } from "node:path";
1060
+ import { constants as constants2 } from "node:fs";
1061
+ import { lstat, mkdir, open as open2, readdir, realpath, rename, rmdir, unlink } from "node:fs/promises";
1062
+ import { dirname as dirname3, join as join4, relative, sep } from "node:path";
1062
1063
  import { setImmediate } from "node:timers/promises";
1064
+
1065
+ // packages/workspace/local-config.ts
1066
+ import { constants } from "node:fs";
1067
+ import { open, writeFile } from "node:fs/promises";
1068
+ import { join as join3 } from "node:path";
1069
+ import { z as z10 } from "zod";
1070
+ import ignore from "ignore";
1071
+ function ignored(path) {
1072
+ return path.split("/").some(
1073
+ (part) => part === ".git" || part === ".DS_Store" || /^\.teammy(?:[-.]|$)/i.test(part) || /(?:~|\.(?:swp|swx|tmp|part))$/i.test(part)
1074
+ );
1075
+ }
1063
1076
  function missing(error) {
1064
1077
  return error instanceof Error && "code" in error && ["ENOENT", "ENOTDIR"].includes(String(error.code));
1065
1078
  }
1079
+ var LOCAL_CONFIG_NAME = ".teammy-local.json";
1080
+ var LocalConfigSchema = z10.object({
1081
+ files: z10.object({ ignore: z10.array(z10.string().max(1024)).max(256).default([]) }).strict().default({ ignore: [] })
1082
+ }).strict();
1083
+ async function ensureLocalConfig(root) {
1084
+ try {
1085
+ await writeFile(
1086
+ join3(root, LOCAL_CONFIG_NAME),
1087
+ JSON.stringify({ files: { ignore: [] } }, null, 2) + "\n",
1088
+ { flag: "wx", mode: 384 }
1089
+ );
1090
+ } catch (error) {
1091
+ if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
1092
+ }
1093
+ }
1094
+ async function loadLocalRules(root) {
1095
+ let rules = [];
1096
+ try {
1097
+ const file = await open(
1098
+ join3(root, LOCAL_CONFIG_NAME),
1099
+ constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
1100
+ );
1101
+ try {
1102
+ const before = await file.stat();
1103
+ if (!before.isFile() || before.size > 65536)
1104
+ throw new Error("Expected a regular configuration file no larger than 64 KiB.");
1105
+ const buffer = Buffer.alloc(65537);
1106
+ const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
1107
+ if (bytesRead > 65536 || bytesRead !== before.size)
1108
+ throw new Error("Configuration exceeds the read limit or changed while reading.");
1109
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead));
1110
+ const after = await file.stat();
1111
+ if (before.size !== after.size || before.mtimeMs !== after.mtimeMs)
1112
+ throw new Error("Configuration is being saved; retry on the next check.");
1113
+ rules = LocalConfigSchema.parse(JSON.parse(text)).files.ignore;
1114
+ } finally {
1115
+ await file.close();
1116
+ }
1117
+ } catch (error) {
1118
+ if (!missing(error))
1119
+ throw new Error(
1120
+ `${LOCAL_CONFIG_NAME}: invalid or unreadable configuration; uploads paused.`,
1121
+ { cause: error }
1122
+ );
1123
+ }
1124
+ const matcher = ignore().add(rules);
1125
+ return {
1126
+ fingerprint: JSON.stringify(rules),
1127
+ excludes: (path, directory = false) => ignored(path) || matcher.ignores(path + (directory && !path.endsWith("/") ? "/" : ""))
1128
+ };
1129
+ }
1130
+
1131
+ // apps/client/files/local-fs.ts
1132
+ function missing2(error) {
1133
+ return error instanceof Error && "code" in error && ["ENOENT", "ENOTDIR"].includes(String(error.code));
1134
+ }
1066
1135
  function signature(stat2) {
1067
1136
  return [stat2.dev, stat2.ino, stat2.size, stat2.mtimeNs, stat2.ctimeNs].join(":");
1068
1137
  }
1069
- function ignored(path) {
1070
- return path.split("/").some(
1071
- (part) => part === ".git" || part === ".DS_Store" || /^\.teammy(?:[-.]|$)/i.test(part) || /(?:~|\.(?:swp|swx|tmp|part))$/i.test(part)
1072
- );
1073
- }
1074
1138
  async function safePath(root, path, createParents = false) {
1075
1139
  FilePathSchema.parse(path);
1076
1140
  const rootStat = await lstat(root);
@@ -1079,7 +1143,7 @@ async function safePath(root, path, createParents = false) {
1079
1143
  const parts = path.split("/");
1080
1144
  let parent = root;
1081
1145
  for (const part of parts.slice(0, -1)) {
1082
- parent = join3(parent, part);
1146
+ parent = join4(parent, part);
1083
1147
  if (createParents)
1084
1148
  await mkdir(parent).catch((error) => {
1085
1149
  if (error.code !== "EEXIST") throw error;
@@ -1089,7 +1153,7 @@ async function safePath(root, path, createParents = false) {
1089
1153
  if (!stat2.isDirectory())
1090
1154
  throw Object.assign(new Error(`Parent is not a directory: ${path}`), { code: "ENOTDIR" });
1091
1155
  }
1092
- return join3(root, ...parts);
1156
+ return join4(root, ...parts);
1093
1157
  }
1094
1158
  async function metadata(root, path) {
1095
1159
  try {
@@ -1099,7 +1163,7 @@ async function metadata(root, path) {
1099
1163
  throw new Error(`Unsupported filesystem entry: ${path}`);
1100
1164
  return { signature: signature(stat2), directory: stat2.isDirectory() };
1101
1165
  } catch (error) {
1102
- if (missing(error)) return null;
1166
+ if (missing2(error)) return null;
1103
1167
  throw error;
1104
1168
  }
1105
1169
  }
@@ -1149,7 +1213,7 @@ async function readStable(root, path) {
1149
1213
  const inside = relative(await realpath(root), resolvedBefore);
1150
1214
  if (inside.startsWith(`..${sep}`) || inside === "..")
1151
1215
  throw new Error(`File escapes sync root: ${path}`);
1152
- const file = await open(target, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
1216
+ const file = await open2(target, constants2.O_RDONLY | (constants2.O_NOFOLLOW ?? 0));
1153
1217
  try {
1154
1218
  const before = await file.stat({ bigint: true });
1155
1219
  if (!before.isFile() || before.size > BigInt(MAX_FILE_BYTES))
@@ -1179,7 +1243,7 @@ async function readStable(root, path) {
1179
1243
  }
1180
1244
  async function syncDirectory(directory) {
1181
1245
  if (process.platform === "win32") return;
1182
- const handle = await open(directory, "r");
1246
+ const handle = await open2(directory, "r");
1183
1247
  try {
1184
1248
  await handle.sync();
1185
1249
  } finally {
@@ -1188,8 +1252,8 @@ async function syncDirectory(directory) {
1188
1252
  }
1189
1253
  async function replaceFile(root, path, bytes, expected) {
1190
1254
  const target = await safePath(root, path, true);
1191
- const temporary = join3(dirname3(target), `.teammy-${randomUUID2()}.tmp`);
1192
- const file = await open(temporary, "wx", 384);
1255
+ const temporary = join4(dirname3(target), `.teammy-${randomUUID2()}.tmp`);
1256
+ const file = await open2(temporary, "wx", 384);
1193
1257
  try {
1194
1258
  try {
1195
1259
  await file.writeFile(bytes);
@@ -1213,7 +1277,7 @@ async function replaceFile(root, path, bytes, expected) {
1213
1277
  }
1214
1278
  } finally {
1215
1279
  await unlink(temporary).catch((error) => {
1216
- if (!missing(error)) throw error;
1280
+ if (!missing2(error)) throw error;
1217
1281
  });
1218
1282
  }
1219
1283
  }
@@ -1241,7 +1305,7 @@ async function removeFile(root, path, expected) {
1241
1305
  var exec = promisify(execFile);
1242
1306
  async function workspacePath(input) {
1243
1307
  const path = resolve(input);
1244
- return join4(await realpath2(dirname4(path)), basename(path));
1308
+ return join5(await realpath2(dirname4(path)), basename(path));
1245
1309
  }
1246
1310
  async function directoryIdentity(root) {
1247
1311
  const stat2 = await lstat2(root, { bigint: true });
@@ -1263,11 +1327,11 @@ async function ignorePlan(root) {
1263
1327
  let repo = false;
1264
1328
  for (let p = parent; ; p = dirname4(p)) {
1265
1329
  try {
1266
- await lstat2(join4(p, ".git"));
1330
+ await lstat2(join5(p, ".git"));
1267
1331
  repo = true;
1268
1332
  break;
1269
1333
  } catch (e) {
1270
- if (!missing(e)) throw e;
1334
+ if (!missing2(e)) throw e;
1271
1335
  }
1272
1336
  if (p === dirname4(p)) break;
1273
1337
  }
@@ -1314,7 +1378,7 @@ async function prepareWorkspace(state, root, input, reconnect = false) {
1314
1378
  await lstat2(root);
1315
1379
  present = true;
1316
1380
  } catch (e) {
1317
- if (!missing(e)) throw e;
1381
+ if (!missing2(e)) throw e;
1318
1382
  }
1319
1383
  if (present) {
1320
1384
  if (reconnect && state.binding(root)) {
@@ -1352,7 +1416,7 @@ function completePreparation(state, root) {
1352
1416
 
1353
1417
  // apps/client/runtime/log.ts
1354
1418
  import { appendFileSync, mkdirSync as mkdirSync4, renameSync as renameSync2, statSync, unlinkSync, readFileSync as readFileSync2 } from "node:fs";
1355
- import { join as join5 } from "node:path";
1419
+ import { join as join6 } from "node:path";
1356
1420
  import { createHash as createHash3 } from "node:crypto";
1357
1421
  function rootKey(root) {
1358
1422
  return createHash3("sha256").update(root).digest("hex").slice(0, 16);
@@ -1368,9 +1432,9 @@ function diagnostic(error) {
1368
1432
  var ClientLog = class {
1369
1433
  constructor(directory, limit = 1024 * 1024) {
1370
1434
  this.limit = limit;
1371
- const logs = join5(directory, "logs");
1435
+ const logs = join6(directory, "logs");
1372
1436
  mkdirSync4(logs, { recursive: true, mode: 448 });
1373
- this.path = join5(logs, "client.jsonl");
1437
+ this.path = join6(logs, "client.jsonl");
1374
1438
  }
1375
1439
  limit;
1376
1440
  path;
@@ -1427,43 +1491,43 @@ var ClientLog = class {
1427
1491
  import { setTimeout as setTimeout2 } from "node:timers/promises";
1428
1492
 
1429
1493
  // packages/protocol/devices.ts
1430
- import { z as z10 } from "zod";
1431
- var CredentialSchema = z10.string().regex(/^tmy_[a-f0-9]{64}$/);
1432
- var EnrollmentSchema = z10.object({
1433
- operationId: z10.uuid(),
1494
+ import { z as z11 } from "zod";
1495
+ var CredentialSchema = z11.string().regex(/^tmy_[a-f0-9]{64}$/);
1496
+ var EnrollmentSchema = z11.object({
1497
+ operationId: z11.uuid(),
1434
1498
  projectId: NameSchema,
1435
1499
  memberId: NameSchema,
1436
1500
  deviceId: DeviceIdSchema,
1437
1501
  credential: CredentialSchema
1438
1502
  }).strict();
1439
- var EffectivePolicySchema = z10.object({
1440
- checkIntervalSeconds: z10.number().min(1).max(86400),
1441
- fileSettleSeconds: z10.number().min(0).max(86400)
1503
+ var EffectivePolicySchema = z11.object({
1504
+ checkIntervalSeconds: z11.number().min(1).max(86400),
1505
+ fileSettleSeconds: z11.number().min(0).max(86400)
1442
1506
  }).strict();
1443
- var SyncSummarySchema = z10.object({
1444
- checkedAt: z10.string(),
1445
- published: z10.boolean(),
1446
- appliedFiles: z10.number().int().nonnegative(),
1447
- pendingFiles: z10.number().int().nonnegative(),
1448
- sessionSources: z10.number().int().nonnegative(),
1449
- errors: z10.array(z10.string().max(1e3)).max(20)
1507
+ var SyncSummarySchema = z11.object({
1508
+ checkedAt: z11.string(),
1509
+ published: z11.boolean(),
1510
+ appliedFiles: z11.number().int().nonnegative(),
1511
+ pendingFiles: z11.number().int().nonnegative(),
1512
+ sessionSources: z11.number().int().nonnegative(),
1513
+ errors: z11.array(z11.string().max(1e3)).max(20)
1450
1514
  }).strict();
1451
- var DeviceStatusSchema = z10.object({
1452
- clientVersion: z10.string().max(64),
1515
+ var DeviceStatusSchema = z11.object({
1516
+ clientVersion: z11.string().max(64),
1453
1517
  receivedRevision: RevisionSchema.nullable(),
1454
1518
  appliedRevision: RevisionSchema.nullable(),
1455
1519
  effectivePolicy: EffectivePolicySchema,
1456
- overrides: z10.array(z10.enum(["checkIntervalSeconds", "fileSettleSeconds"])).max(2),
1457
- configError: z10.string().max(2e3).nullable(),
1458
- reportError: z10.string().max(2e3).nullable(),
1520
+ overrides: z11.array(z11.enum(["checkIntervalSeconds", "fileSettleSeconds"])).max(2),
1521
+ configError: z11.string().max(2e3).nullable(),
1522
+ reportError: z11.string().max(2e3).nullable(),
1459
1523
  lastSync: SyncSummarySchema.nullable()
1460
1524
  }).strict();
1461
- var WebDevicesSchema = z10.object({
1462
- devices: z10.array(
1463
- z10.object({
1525
+ var WebDevicesSchema = z11.object({
1526
+ devices: z11.array(
1527
+ z11.object({
1464
1528
  memberId: NameSchema,
1465
- bound: z10.boolean(),
1466
- lastSeenAt: z10.string().nullable(),
1529
+ bound: z11.boolean(),
1530
+ lastSeenAt: z11.string().nullable(),
1467
1531
  status: DeviceStatusSchema.nullable()
1468
1532
  })
1469
1533
  )
@@ -1864,9 +1928,9 @@ var SessionState = class {
1864
1928
 
1865
1929
  // apps/client/state.ts
1866
1930
  var LocalBindingSchema = BindingSchema.extend({
1867
- root: z11.string(),
1868
- serverUrl: z11.string(),
1869
- token: z11.string()
1931
+ root: z12.string(),
1932
+ serverUrl: z12.string(),
1933
+ token: z12.string()
1870
1934
  });
1871
1935
  var clientMigrations = [
1872
1936
  String.raw`
@@ -1904,19 +1968,19 @@ var ClientState = class extends FilePublicationState {
1904
1968
  throw new Error("Client state needs an upgrade. Run teammy repair.");
1905
1969
  if (options.readOnly) {
1906
1970
  if (!version) throw new Error("No client state is available.");
1907
- db = new DatabaseSync3(join6(directory, "client.sqlite3"), { readOnly: true });
1971
+ db = new DatabaseSync3(join7(directory, "client.sqlite3"), { readOnly: true });
1908
1972
  } else if (!version) {
1909
1973
  access2?.close();
1910
1974
  access2 = void 0;
1911
1975
  const exclusive = options.maintenance ? void 0 : stateAccess(directory, true);
1912
1976
  try {
1913
- db = openDatabase(join6(directory, "client.sqlite3"), clientMigrations);
1977
+ db = openDatabase(join7(directory, "client.sqlite3"), clientMigrations);
1914
1978
  } finally {
1915
1979
  exclusive?.close();
1916
1980
  }
1917
1981
  if (!options.maintenance) access2 = stateAccess(directory);
1918
1982
  } else {
1919
- db = new DatabaseSync3(join6(directory, "client.sqlite3"));
1983
+ db = new DatabaseSync3(join7(directory, "client.sqlite3"));
1920
1984
  db.exec("PRAGMA busy_timeout=5000; PRAGMA foreign_keys=ON; PRAGMA synchronous=FULL;");
1921
1985
  }
1922
1986
  } catch (error) {
@@ -1993,10 +2057,10 @@ async function stateLocation(root, directory) {
1993
2057
  // apps/client/sessions/setup.ts
1994
2058
  import { execFile as execFile2 } from "node:child_process";
1995
2059
  import { promisify as promisify2 } from "node:util";
1996
- import { access, mkdir as mkdir5, readFile as readFile2, writeFile, realpath as realpath4 } from "node:fs/promises";
1997
- import { constants as constants2 } from "node:fs";
2060
+ import { access, mkdir as mkdir5, readFile as readFile2, writeFile as writeFile2, realpath as realpath4 } from "node:fs/promises";
2061
+ import { constants as constants3 } from "node:fs";
1998
2062
  import { homedir } from "node:os";
1999
- import { dirname as dirname6, join as join10, resolve as resolve4 } from "node:path";
2063
+ import { dirname as dirname6, join as join11, resolve as resolve4 } from "node:path";
2000
2064
  import { createHash as createHash6 } from "node:crypto";
2001
2065
 
2002
2066
  // apps/client/sessions/normalize.ts
@@ -2166,8 +2230,8 @@ var JsonlNormalizer = class {
2166
2230
 
2167
2231
  // apps/client/sessions/registration.ts
2168
2232
  import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2 } from "node:fs";
2169
- import { join as join7 } from "node:path";
2170
- var registrationPath = (directory, root) => join7(directory, "hook-registrations", rootKey(root) + ".json");
2233
+ import { join as join8 } from "node:path";
2234
+ var registrationPath = (directory, root) => join8(directory, "hook-registrations", rootKey(root) + ".json");
2171
2235
  function readRegistration(directory, root) {
2172
2236
  const path = registrationPath(directory, root);
2173
2237
  if (!existsSync3(path)) return null;
@@ -2180,16 +2244,16 @@ function saveRegistration(directory, value) {
2180
2244
  writePrivateJson(registrationPath(directory, value.root), value);
2181
2245
  }
2182
2246
  function registrations(directory, onError) {
2183
- const path = join7(directory, "hook-registrations");
2247
+ const path = join8(directory, "hook-registrations");
2184
2248
  if (!existsSync3(path)) return [];
2185
2249
  return readdirSync2(path).filter((name2) => /^[a-f0-9]{16}\.json$/.test(name2)).flatMap((name2) => {
2186
2250
  try {
2187
- const value = JSON.parse(readFileSync3(join7(path, name2), "utf8"));
2251
+ const value = JSON.parse(readFileSync3(join8(path, name2), "utf8"));
2188
2252
  const registration = readRegistration(directory, value.root);
2189
2253
  if (!registration) throw new Error("Missing Hook registration.");
2190
2254
  return [registration];
2191
2255
  } catch (error) {
2192
- onError(join7(path, name2), error);
2256
+ onError(join8(path, name2), error);
2193
2257
  return [];
2194
2258
  }
2195
2259
  });
@@ -2197,12 +2261,12 @@ function registrations(directory, onError) {
2197
2261
 
2198
2262
  // apps/client/root-lock.ts
2199
2263
  import { createHash as createHash5 } from "node:crypto";
2200
- import { join as join8 } from "node:path";
2264
+ import { join as join9 } from "node:path";
2201
2265
  function acquireRootLock(root, stateDirectory) {
2202
2266
  let lock;
2203
2267
  try {
2204
2268
  lock = openDatabase(
2205
- join8(stateDirectory, "locks", `${createHash5("sha256").update(root).digest("hex")}.sqlite3`),
2269
+ join9(stateDirectory, "locks", `${createHash5("sha256").update(root).digest("hex")}.sqlite3`),
2206
2270
  [],
2207
2271
  0
2208
2272
  );
@@ -2221,29 +2285,29 @@ import { DatabaseSync as DatabaseSync4 } from "node:sqlite";
2221
2285
 
2222
2286
  // apps/client/sessions/pi-extension.ts
2223
2287
  import { mkdir as mkdir4, readFile } from "node:fs/promises";
2224
- import { dirname as dirname5, basename as basename2, join as join9, resolve as resolve3 } from "node:path";
2288
+ import { dirname as dirname5, basename as basename2, join as join10, resolve as resolve3 } from "node:path";
2225
2289
  var marker = (directory) => `// Teammy Pi collection ${rootKey(directory)}`;
2226
2290
  function piPath(path, home, cwd) {
2227
2291
  return resolve3(
2228
2292
  cwd,
2229
- path === "~" ? home : path.startsWith("~/") ? join9(home, path.slice(2)) : path
2293
+ path === "~" ? home : path.startsWith("~/") ? join10(home, path.slice(2)) : path
2230
2294
  );
2231
2295
  }
2232
2296
  async function piDiscoveryDirectories(agentDirectory, project, home, env) {
2233
2297
  let configured;
2234
- for (const path of [join9(agentDirectory, "settings.json"), join9(project, ".pi/settings.json")]) {
2298
+ for (const path of [join10(agentDirectory, "settings.json"), join10(project, ".pi/settings.json")]) {
2235
2299
  try {
2236
2300
  const settings = JSON.parse(await readFile(path, "utf8"));
2237
2301
  if (typeof settings?.sessionDir === "string" && settings.sessionDir)
2238
2302
  configured = settings.sessionDir;
2239
2303
  } catch (error) {
2240
- if (!missing(error)) throw new Error(`Unable to read Pi session directory setting: ${path}`);
2304
+ if (!missing2(error)) throw new Error(`Unable to read Pi session directory setting: ${path}`);
2241
2305
  }
2242
2306
  }
2243
2307
  configured = env.PI_CODING_AGENT_SESSION_DIR || configured;
2244
2308
  return [
2245
2309
  .../* @__PURE__ */ new Set([
2246
- join9(agentDirectory, "sessions"),
2310
+ join10(agentDirectory, "sessions"),
2247
2311
  ...configured ? [piPath(configured, home, project)] : []
2248
2312
  ])
2249
2313
  ];
@@ -2264,7 +2328,7 @@ export default function teammyCollection(pi) {
2264
2328
  transcript_path: file,
2265
2329
  });
2266
2330
  const child = spawn(${JSON.stringify(process.execPath)}, [
2267
- ${JSON.stringify(join9(dirname5(entry), "session-hook.js"))},
2331
+ ${JSON.stringify(join10(dirname5(entry), "session-hook.js"))},
2268
2332
  '--agent', 'pi', '--state-dir', ${JSON.stringify(directory)},
2269
2333
  '--integration-file', ${JSON.stringify(path)},
2270
2334
  ], { stdio: ['pipe', 'ignore', 'ignore'], windowsHide: true, shell: false });
@@ -2287,13 +2351,13 @@ export default function teammyCollection(pi) {
2287
2351
  `;
2288
2352
  }
2289
2353
  async function installPiExtension(directory, registry, agentDirectory, entry) {
2290
- const path = join9(agentDirectory, "extensions", `teammy-${rootKey(directory)}.js`);
2354
+ const path = join10(agentDirectory, "extensions", `teammy-${rootKey(directory)}.js`);
2291
2355
  await mkdir4(dirname5(path), { recursive: true });
2292
2356
  let original = null;
2293
2357
  try {
2294
2358
  original = await readStable(dirname5(path), basename2(path));
2295
2359
  } catch (error) {
2296
- if (!missing(error)) throw error;
2360
+ if (!missing2(error)) throw error;
2297
2361
  }
2298
2362
  if (original && !original.bytes.toString("utf8").startsWith(marker(directory) + "\n"))
2299
2363
  throw new Error(`Pi extension is not owned by Teammy: ${path}`);
@@ -2316,7 +2380,7 @@ async function removePiExtension(directory, path) {
2316
2380
  await removeFile(dirname5(path), basename2(path), original.signature);
2317
2381
  return true;
2318
2382
  } catch (error) {
2319
- if (missing(error)) return false;
2383
+ if (missing2(error)) return false;
2320
2384
  throw error;
2321
2385
  }
2322
2386
  }
@@ -2324,7 +2388,7 @@ async function removePiExtension(directory, path) {
2324
2388
  // apps/client/sessions/setup.ts
2325
2389
  var exec2 = promisify2(execFile2);
2326
2390
  var quote = (value) => "'" + value.replace(/'/g, "'\\''") + "'";
2327
- async function exists(path, mode = constants2.F_OK) {
2391
+ async function exists(path, mode = constants3.F_OK) {
2328
2392
  try {
2329
2393
  await access(path, mode);
2330
2394
  return true;
@@ -2335,7 +2399,7 @@ async function exists(path, mode = constants2.F_OK) {
2335
2399
  async function setupSessions(state, root, directory, options = {}) {
2336
2400
  await realpath4(root);
2337
2401
  if (!state.binding(root)) throw new Error("Connect this root before installing hooks.");
2338
- const lock = acquireRootLock("hooks", join10(directory, "runtime"));
2402
+ const lock = acquireRootLock("hooks", join11(directory, "runtime"));
2339
2403
  try {
2340
2404
  const prior = readRegistration(directory, root);
2341
2405
  if (prior && !prior.enabled && !options.enable)
@@ -2355,7 +2419,7 @@ async function setupSessions(state, root, directory, options = {}) {
2355
2419
  {
2356
2420
  agent: "claude-code",
2357
2421
  executables: ["claude"],
2358
- storage: env.CLAUDE_CONFIG_DIR ?? join10(home, ".claude"),
2422
+ storage: env.CLAUDE_CONFIG_DIR ?? join11(home, ".claude"),
2359
2423
  directories: ["projects"],
2360
2424
  config: ".claude/settings.local.json",
2361
2425
  events: ["Stop", "SessionEnd"]
@@ -2363,7 +2427,7 @@ async function setupSessions(state, root, directory, options = {}) {
2363
2427
  {
2364
2428
  agent: "codex",
2365
2429
  executables: ["codex"],
2366
- storage: env.CODEX_HOME ?? join10(home, ".codex"),
2430
+ storage: env.CODEX_HOME ?? join11(home, ".codex"),
2367
2431
  directories: ["sessions"],
2368
2432
  config: ".codex/hooks.json",
2369
2433
  events: ["Stop", "SessionEnd"]
@@ -2372,7 +2436,7 @@ async function setupSessions(state, root, directory, options = {}) {
2372
2436
  agent: "claude-code",
2373
2437
  instance: "tclaude",
2374
2438
  executables: ["tclaude"],
2375
- storage: join10(home, ".tclaude"),
2439
+ storage: join11(home, ".tclaude"),
2376
2440
  directories: ["projects"],
2377
2441
  config: ".claude/settings.local.json",
2378
2442
  events: ["Stop", "SessionEnd"]
@@ -2381,7 +2445,7 @@ async function setupSessions(state, root, directory, options = {}) {
2381
2445
  agent: "codex",
2382
2446
  instance: "tcodex",
2383
2447
  executables: ["tcodex"],
2384
- storage: join10(home, ".tcodex"),
2448
+ storage: join11(home, ".tcodex"),
2385
2449
  directories: ["sessions"],
2386
2450
  config: ".codex/hooks.json",
2387
2451
  events: ["Stop", "SessionEnd"]
@@ -2389,7 +2453,7 @@ async function setupSessions(state, root, directory, options = {}) {
2389
2453
  {
2390
2454
  agent: "cursor",
2391
2455
  executables: ["agent", "cursor-agent"],
2392
- storage: env.CURSOR_CONFIG_DIR ?? join10(home, ".cursor"),
2456
+ storage: env.CURSOR_CONFIG_DIR ?? join11(home, ".cursor"),
2393
2457
  directories: ["chats", "projects"],
2394
2458
  config: ".cursor/hooks.json",
2395
2459
  events: ["sessionStart", "postToolUse", "stop", "sessionEnd"]
@@ -2397,7 +2461,7 @@ async function setupSessions(state, root, directory, options = {}) {
2397
2461
  {
2398
2462
  agent: "pi",
2399
2463
  executables: ["pi"],
2400
- storage: piPath(env.PI_CODING_AGENT_DIR ?? join10(home, ".pi", "agent"), home, project),
2464
+ storage: piPath(env.PI_CODING_AGENT_DIR ?? join11(home, ".pi", "agent"), home, project),
2401
2465
  directories: []
2402
2466
  }
2403
2467
  ];
@@ -2411,7 +2475,7 @@ async function setupSessions(state, root, directory, options = {}) {
2411
2475
  for (const adapter of defaults) {
2412
2476
  const installed = options.agents ? options.agents.includes(adapter.instance ?? adapter.agent) : await exists(adapter.storage) || (await Promise.all(
2413
2477
  (env.PATH ?? "").split(":").flatMap(
2414
- (dir) => adapter.executables.map((name2) => exists(join10(dir, name2), constants2.X_OK))
2478
+ (dir) => adapter.executables.map((name2) => exists(join11(dir, name2), constants3.X_OK))
2415
2479
  )
2416
2480
  )).some(Boolean);
2417
2481
  const piProfiles = adapter.agent === "pi" ? registry.configs.filter((config) => config.kind === "pi-extension").map((config) => dirname6(dirname6(config.path))) : [];
@@ -2421,8 +2485,8 @@ async function setupSessions(state, root, directory, options = {}) {
2421
2485
  if (!registry.excludedDiscovery?.includes(source))
2422
2486
  sessions.discover(adapter.agent, source, project);
2423
2487
  }
2424
- if (adapter.agent === "cursor" && !registry.excludedDiscovery?.includes(join10(home, ".cursor", "projects")))
2425
- sessions.discover("cursor", join10(home, ".cursor", "projects"), project);
2488
+ if (adapter.agent === "cursor" && !registry.excludedDiscovery?.includes(join11(home, ".cursor", "projects")))
2489
+ sessions.discover("cursor", join11(home, ".cursor", "projects"), project);
2426
2490
  if (!configured.includes(adapter.agent)) configured.push(adapter.agent);
2427
2491
  if (adapter.agent === "pi") {
2428
2492
  for (const profile of /* @__PURE__ */ new Set([...piProfiles, ...installed ? [adapter.storage] : []])) {
@@ -2448,7 +2512,7 @@ async function setupSessions(state, root, directory, options = {}) {
2448
2512
  }
2449
2513
  if (!adapter.config) continue;
2450
2514
  const configRoot = adapter.agent === "cursor" ? gitRoot : project;
2451
- const path = join10(configRoot, adapter.config);
2515
+ const path = join11(configRoot, adapter.config);
2452
2516
  if (written.has(path)) continue;
2453
2517
  written.add(path);
2454
2518
  try {
@@ -2457,7 +2521,7 @@ async function setupSessions(state, root, directory, options = {}) {
2457
2521
  try {
2458
2522
  original = await readStable(configRoot, adapter.config);
2459
2523
  } catch (error) {
2460
- if (!missing(error)) throw error;
2524
+ if (!missing2(error)) throw error;
2461
2525
  }
2462
2526
  const config = original ? JSON.parse(original.bytes.toString("utf8")) : {};
2463
2527
  if (!config || typeof config !== "object" || Array.isArray(config))
@@ -2467,7 +2531,7 @@ async function setupSessions(state, root, directory, options = {}) {
2467
2531
  const hooks = object(config.hooks);
2468
2532
  const marker2 = hookMarker(directory, root);
2469
2533
  const cliEntry = options.entry ?? resolve4(process.argv[1]);
2470
- const hookEntry = join10(dirname6(cliEntry), "session-hook.js");
2534
+ const hookEntry = join11(dirname6(cliEntry), "session-hook.js");
2471
2535
  const invocation = [
2472
2536
  process.execPath,
2473
2537
  hookEntry,
@@ -2501,10 +2565,10 @@ async function setupSessions(state, root, directory, options = {}) {
2501
2565
  const bytes = Buffer.from(JSON.stringify(config, null, 2) + "\n");
2502
2566
  if (original?.bytes.equals(bytes)) continue;
2503
2567
  if (original) {
2504
- const backupDir = join10(directory, "hook-backups", rootKey(root));
2568
+ const backupDir = join11(directory, "hook-backups", rootKey(root));
2505
2569
  await mkdir5(backupDir, { recursive: true, mode: 448 });
2506
2570
  const hash2 = createHash6("sha256").update(original.bytes).digest("hex");
2507
- await writeFile(join10(backupDir, `${adapter.agent}-${hash2}.json`), original.bytes, {
2571
+ await writeFile2(join11(backupDir, `${adapter.agent}-${hash2}.json`), original.bytes, {
2508
2572
  mode: 384
2509
2573
  });
2510
2574
  }
@@ -2522,7 +2586,7 @@ async function setupSessions(state, root, directory, options = {}) {
2522
2586
  "info/exclude"
2523
2587
  ])).stdout.trim();
2524
2588
  const old = await readFile2(exclude, "utf8").catch((e) => {
2525
- if (missing(e)) return "";
2589
+ if (missing2(e)) return "";
2526
2590
  throw e;
2527
2591
  });
2528
2592
  const rule = "/" + relativePath.replace(/[\\*?[\]#! ]/g, "\\$&");
@@ -2533,7 +2597,7 @@ async function setupSessions(state, root, directory, options = {}) {
2533
2597
  line: "# teammy-hook-" + rootKey(directory) + "-" + rootKey(root) + "\n" + rule
2534
2598
  };
2535
2599
  saveRegistration(directory, registry);
2536
- await writeFile(
2600
+ await writeFile2(
2537
2601
  exclude,
2538
2602
  old + (old && !old.endsWith("\n") ? "\n" : "") + owned.exclude.line + "\n"
2539
2603
  );
@@ -2554,7 +2618,7 @@ function hookMarker(directory, root) {
2554
2618
  return `teammy-session-${rootKey(directory)}-${rootKey(root)}`;
2555
2619
  }
2556
2620
  function setDiscoveryPreference(directory, root, source, enabled) {
2557
- const lock = acquireRootLock("hooks", join10(directory, "runtime"));
2621
+ const lock = acquireRootLock("hooks", join11(directory, "runtime"));
2558
2622
  try {
2559
2623
  const registry = readRegistration(directory, root);
2560
2624
  if (!registry) return;
@@ -2595,7 +2659,7 @@ async function legacyRegistrations(directory, explicitRoot) {
2595
2659
  const roots = new Set(explicitRoot ? [explicitRoot] : []);
2596
2660
  let db;
2597
2661
  try {
2598
- db = new DatabaseSync4(join10(directory, "client.sqlite3"), { readOnly: true });
2662
+ db = new DatabaseSync4(join11(directory, "client.sqlite3"), { readOnly: true });
2599
2663
  for (const row of db.prepare("SELECT root FROM connections WHERE binding_json IS NOT NULL").all())
2600
2664
  if (!explicitRoot || row.root === explicitRoot) roots.add(String(row.root));
2601
2665
  } catch {
@@ -2615,15 +2679,15 @@ async function legacyRegistrations(directory, explicitRoot) {
2615
2679
  } catch {
2616
2680
  }
2617
2681
  const configs = [
2618
- { path: join10(project, ".claude/settings.local.json"), agent: "claude-code", created: false },
2619
- { path: join10(project, ".codex/hooks.json"), agent: "codex", created: false },
2620
- { path: join10(gitRoot, ".cursor/hooks.json"), agent: "cursor", created: false }
2682
+ { path: join11(project, ".claude/settings.local.json"), agent: "claude-code", created: false },
2683
+ { path: join11(project, ".codex/hooks.json"), agent: "codex", created: false },
2684
+ { path: join11(gitRoot, ".cursor/hooks.json"), agent: "cursor", created: false }
2621
2685
  ];
2622
2686
  saveRegistration(directory, { format: 1, root, projectDir: project, enabled: true, configs });
2623
2687
  }
2624
2688
  }
2625
2689
  async function teardownSessions(directory, root) {
2626
- const lock = acquireRootLock("hooks", join10(directory, "runtime"));
2690
+ const lock = acquireRootLock("hooks", join11(directory, "runtime"));
2627
2691
  const issues = [];
2628
2692
  let removed = 0;
2629
2693
  try {
@@ -2678,11 +2742,11 @@ async function teardownSessions(directory, root) {
2678
2742
  );
2679
2743
  if (config.exclude && !await exists(config.path)) {
2680
2744
  const text = await readFile2(config.exclude.path, "utf8");
2681
- await writeFile(config.exclude.path, text.replace(config.exclude.line + "\n", ""));
2745
+ await writeFile2(config.exclude.path, text.replace(config.exclude.line + "\n", ""));
2682
2746
  }
2683
2747
  removed++;
2684
2748
  } catch (error) {
2685
- if (!missing(error))
2749
+ if (!missing2(error))
2686
2750
  issues.push(
2687
2751
  `${config.path}: ${error instanceof Error ? error.message : "Hook removal failed."}`
2688
2752
  );
@@ -2700,32 +2764,32 @@ import { parseArgs } from "node:util";
2700
2764
  import { realpath as realpath5 } from "node:fs/promises";
2701
2765
  import { dirname as dirname7, isAbsolute as isAbsolute4, relative as relative4, resolve as resolve5, sep as sep4 } from "node:path";
2702
2766
  import { homedir as homedir2 } from "node:os";
2703
- import { join as join12 } from "node:path";
2767
+ import { join as join13 } from "node:path";
2704
2768
 
2705
2769
  // apps/client/sessions/hooks.ts
2706
2770
  import { createHash as createHash7, randomUUID as randomUUID5 } from "node:crypto";
2707
- import { mkdir as mkdir6, open as open2, opendir, rename as rename2, unlink as unlink2 } from "node:fs/promises";
2708
- import { isAbsolute as isAbsolute3, join as join11 } from "node:path";
2709
- import { z as z12 } from "zod";
2710
- var HintSchema = z12.object({
2771
+ import { mkdir as mkdir6, open as open3, opendir, rename as rename2, unlink as unlink2 } from "node:fs/promises";
2772
+ import { isAbsolute as isAbsolute3, join as join12 } from "node:path";
2773
+ import { z as z13 } from "zod";
2774
+ var HintSchema = z13.object({
2711
2775
  agent: AgentSchema,
2712
- path: z12.string().min(1).max(4096).refine(isAbsolute3).optional(),
2713
- nativeId: z12.string().min(1).max(512).optional(),
2714
- projectDir: z12.string().min(1).max(4096).refine(isAbsolute3)
2776
+ path: z13.string().min(1).max(4096).refine(isAbsolute3).optional(),
2777
+ nativeId: z13.string().min(1).max(512).optional(),
2778
+ projectDir: z13.string().min(1).max(4096).refine(isAbsolute3)
2715
2779
  });
2716
2780
  function inbox(directory, root) {
2717
- return join11(directory, "session-hints", createHash7("sha256").update(root).digest("hex"));
2781
+ return join12(directory, "session-hints", createHash7("sha256").update(root).digest("hex"));
2718
2782
  }
2719
2783
  async function recordHint(directory, root, input) {
2720
2784
  const hint = HintSchema.parse(input);
2721
2785
  const targetDir = inbox(directory, root);
2722
2786
  await mkdir6(targetDir, { recursive: true, mode: 448 });
2723
- const target = join11(
2787
+ const target = join12(
2724
2788
  targetDir,
2725
2789
  `${createHash7("sha256").update(JSON.stringify(hint)).digest("hex")}.json`
2726
2790
  );
2727
2791
  const temporary = `${target}.${randomUUID5()}.tmp`;
2728
- const handle = await open2(temporary, "wx", 384);
2792
+ const handle = await open3(temporary, "wx", 384);
2729
2793
  try {
2730
2794
  await handle.writeFile(JSON.stringify(hint));
2731
2795
  } finally {
@@ -2746,9 +2810,9 @@ async function consumeHints(directory, root, sessions, refresh) {
2746
2810
  for await (const entry of dir) {
2747
2811
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
2748
2812
  if (count++ >= 50) break;
2749
- const path = join11(inbox(directory, root), entry.name);
2813
+ const path = join12(inbox(directory, root), entry.name);
2750
2814
  try {
2751
- const handle = await open2(path, "r");
2815
+ const handle = await open3(path, "r");
2752
2816
  let body;
2753
2817
  try {
2754
2818
  if ((await handle.stat()).size > 16384) throw new Error("Oversized hook hint.");
@@ -2801,7 +2865,7 @@ async function runHook(args) {
2801
2865
  }
2802
2866
  });
2803
2867
  const directory = await realpath5(
2804
- resolve5(values["state-dir"] ?? join12(homedir2(), ".teammy", "client"))
2868
+ resolve5(values["state-dir"] ?? join13(homedir2(), ".teammy", "client"))
2805
2869
  );
2806
2870
  const payload = values.file ? {} : await hookPayload();
2807
2871
  let selectedRoot = values.root;
@@ -2852,28 +2916,28 @@ async function runHook(args) {
2852
2916
  // apps/client/runtime/maintenance.ts
2853
2917
  import { mkdir as mkdir9, rename as rename4, rm as rm2, readFile as readFile4 } from "node:fs/promises";
2854
2918
  import { existsSync as existsSync4 } from "node:fs";
2855
- import { join as join15, resolve as resolve7 } from "node:path";
2919
+ import { join as join16, resolve as resolve7 } from "node:path";
2856
2920
  import { DatabaseSync as DatabaseSync5 } from "node:sqlite";
2857
2921
  import { setTimeout as delay } from "node:timers/promises";
2858
2922
 
2859
2923
  // apps/client/runtime/ipc.ts
2860
2924
  import { createConnection } from "node:net";
2861
2925
  import { mkdir as mkdir7, lstat as lstat3, chmod } from "node:fs/promises";
2862
- import { join as join13 } from "node:path";
2863
- import { z as z13 } from "zod";
2864
- var RequestSchema = z13.object({
2865
- method: z13.enum(["ping", "wake", "status", "once"]),
2866
- root: z13.string().max(4096).optional()
2926
+ import { join as join14 } from "node:path";
2927
+ import { z as z14 } from "zod";
2928
+ var RequestSchema = z14.object({
2929
+ method: z14.enum(["ping", "wake", "status", "once"]),
2930
+ root: z14.string().max(4096).optional()
2867
2931
  }).strict();
2868
2932
  function socketPath(directory) {
2869
- return join13(
2933
+ return join14(
2870
2934
  "/tmp",
2871
2935
  `teammy-${process.getuid?.() ?? "user"}-${rootKey(directory)}`,
2872
2936
  "control.sock"
2873
2937
  );
2874
2938
  }
2875
2939
  async function prepareSocketDirectory(directory) {
2876
- const path = join13(socketPath(directory), "..");
2940
+ const path = join14(socketPath(directory), "..");
2877
2941
  await mkdir7(path, { recursive: true, mode: 448 });
2878
2942
  const stat2 = await lstat3(path);
2879
2943
  if (!stat2.isDirectory() || stat2.isSymbolicLink() || stat2.uid !== process.getuid?.())
@@ -2959,8 +3023,8 @@ async function runningDaemon(directory) {
2959
3023
  import { execFile as execFile3 } from "node:child_process";
2960
3024
  import { promisify as promisify3 } from "node:util";
2961
3025
  import { homedir as homedir3 } from "node:os";
2962
- import { dirname as dirname8, join as join14, resolve as resolve6 } from "node:path";
2963
- import { mkdir as mkdir8, readFile as readFile3, writeFile as writeFile2, rename as rename3, rm } from "node:fs/promises";
3026
+ import { dirname as dirname8, join as join15, resolve as resolve6 } from "node:path";
3027
+ import { mkdir as mkdir8, readFile as readFile3, writeFile as writeFile3, rename as rename3, rm } from "node:fs/promises";
2964
3028
  import { setTimeout as setTimeout3 } from "node:timers/promises";
2965
3029
  var execute = promisify3(execFile3);
2966
3030
  var exec3 = (file, args, options = {}) => execute(file, args, { timeout: 45e3, ...options });
@@ -2977,7 +3041,7 @@ function serviceDefinition(directory, executable, entry, platform = process.plat
2977
3041
  if (platform === "darwin")
2978
3042
  return {
2979
3043
  id,
2980
- path: join14(userHome, "Library", "LaunchAgents", id + ".plist"),
3044
+ path: join15(userHome, "Library", "LaunchAgents", id + ".plist"),
2981
3045
  content: `<?xml version="1.0" encoding="UTF-8"?>
2982
3046
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
2983
3047
  <plist version="1.0"><dict><key>Label</key><string>${id}</string><key>ProgramArguments</key><array>${args.map((a) => `<string>${xml(a)}</string>`).join("")}</array><key>RunAtLoad</key><true/><key>KeepAlive</key><true/><key>ThrottleInterval</key><integer>30</integer><key>WorkingDirectory</key><string>${xml(userHome)}</string></dict></plist>
@@ -2986,7 +3050,7 @@ function serviceDefinition(directory, executable, entry, platform = process.plat
2986
3050
  if (platform === "linux")
2987
3051
  return {
2988
3052
  id: id + ".service",
2989
- path: join14(userHome, ".config", "systemd", "user", id + ".service"),
3053
+ path: join15(userHome, ".config", "systemd", "user", id + ".service"),
2990
3054
  content: `[Unit]
2991
3055
  Description=Teammy background synchronization
2992
3056
  [Service]
@@ -3047,7 +3111,7 @@ async function ensureDaemon(directory, version) {
3047
3111
  await mkdir8(dirname8(definition.path), { recursive: true });
3048
3112
  if (old !== definition.content) {
3049
3113
  const temp = definition.path + `.${process.pid}.tmp`;
3050
- await writeFile2(temp, definition.content, { mode: 384 });
3114
+ await writeFile3(temp, definition.content, { mode: 384 });
3051
3115
  await rename3(temp, definition.path);
3052
3116
  }
3053
3117
  if (process.platform === "darwin") {
@@ -3093,7 +3157,7 @@ async function stopDaemon(directory, remove = false) {
3093
3157
  let loaded = false;
3094
3158
  let pid;
3095
3159
  try {
3096
- const saved = JSON.parse(await readFile3(join14(directory, "runtime", "daemon.json"), "utf8"));
3160
+ const saved = JSON.parse(await readFile3(join15(directory, "runtime", "daemon.json"), "utf8"));
3097
3161
  if (saved.directory === directory && Number.isSafeInteger(saved.pid) && saved.pid > 0)
3098
3162
  pid = saved.pid;
3099
3163
  } catch {
@@ -3203,14 +3267,14 @@ async function prepareClient(directory, version, options = {}, control = service
3203
3267
  };
3204
3268
  let installed;
3205
3269
  try {
3206
- installed = JSON.parse(await readFile4(join15(directory, "runtime/installation.json"), "utf8"));
3270
+ installed = JSON.parse(await readFile4(join16(directory, "runtime/installation.json"), "utf8"));
3207
3271
  } catch {
3208
3272
  }
3209
3273
  const probe = options.daemonStartup ? { state: "absent" } : await probeDaemon(directory);
3210
3274
  const changed = probe.state === "ready" && (probe.info.version !== version || probe.info.protocol !== CONTROL_PROTOCOL || probe.info.executable !== process.execPath || probe.info.entry !== resolve7(process.argv[1]));
3211
3275
  const required = options.force || options.stop || options.uninstall || schema !== clientSchemaVersion || JSON.stringify(installed) !== JSON.stringify(installation) || changed || probe.state === "unresponsive" || probe.state === "incompatible" || Boolean(record && (options.resume || ["repair", "upgrade"].includes(record.reason)));
3212
3276
  if (!required) return { maintained: false, enabled: 0, stopped: 0, issues: [] };
3213
- const mutex = acquireRootLock("maintenance", join15(directory, "runtime"));
3277
+ const mutex = acquireRootLock("maintenance", join16(directory, "runtime"));
3214
3278
  const log = new ClientLog(directory);
3215
3279
  let access2;
3216
3280
  let legacy;
@@ -3252,13 +3316,13 @@ async function prepareClient(directory, version, options = {}, control = service
3252
3316
  );
3253
3317
  let backup;
3254
3318
  if (before > 0 && before < clientSchemaVersion) {
3255
- const backups = join15(directory, "backups");
3319
+ const backups = join16(directory, "backups");
3256
3320
  await mkdir9(backups, { recursive: true, mode: 448 });
3257
- backup = join15(backups, `before-schema-${before}.sqlite3`);
3321
+ backup = join16(backups, `before-schema-${before}.sqlite3`);
3258
3322
  if (!existsSync4(backup)) {
3259
3323
  const temp = backup + ".tmp";
3260
3324
  await rm2(temp, { force: true });
3261
- const db = new DatabaseSync5(join15(directory, "client.sqlite3"));
3325
+ const db = new DatabaseSync5(join16(directory, "client.sqlite3"));
3262
3326
  try {
3263
3327
  db.prepare("VACUUM INTO ?").run(temp);
3264
3328
  } finally {
@@ -3277,7 +3341,7 @@ async function prepareClient(directory, version, options = {}, control = service
3277
3341
  ...pauseReason ? { pauseReason } : {},
3278
3342
  ...backup ? { backup } : {}
3279
3343
  });
3280
- openDatabase(join15(directory, "client.sqlite3"), clientMigrations).close();
3344
+ openDatabase(join16(directory, "client.sqlite3"), clientMigrations).close();
3281
3345
  state = new ClientState(directory, { maintenance: true });
3282
3346
  const runtime = new RuntimeState(state);
3283
3347
  if (options.intent) {
@@ -3300,16 +3364,16 @@ async function prepareClient(directory, version, options = {}, control = service
3300
3364
  issues.push(`${root}: ${diagnostic(error)}`);
3301
3365
  }
3302
3366
  }
3303
- writePrivateJson(join15(directory, "runtime/installation.json"), installation);
3367
+ writePrivateJson(join16(directory, "runtime/installation.json"), installation);
3304
3368
  if (pauseReason)
3305
3369
  writeMaintenance(directory, { reason: pauseReason, phase: "paused", version });
3306
3370
  else await rm2(maintenancePath(directory), { force: true });
3307
3371
  restart = !options.daemonStartup && !pauseReason && enabled > 0;
3308
3372
  if (backup) {
3309
3373
  const { readdir: readdir3 } = await import("node:fs/promises");
3310
- for (const file of await readdir3(join15(directory, "backups"))) {
3311
- if (/^before-schema-\d+\.sqlite3$/.test(file) && join15(directory, "backups", file) !== backup)
3312
- await rm2(join15(directory, "backups", file));
3374
+ for (const file of await readdir3(join16(directory, "backups"))) {
3375
+ if (/^before-schema-\d+\.sqlite3$/.test(file) && join16(directory, "backups", file) !== backup)
3376
+ await rm2(join16(directory, "backups", file));
3313
3377
  }
3314
3378
  }
3315
3379
  log.write(
@@ -3369,7 +3433,7 @@ async function runSessionCommand(args, version) {
3369
3433
  args: args.slice(1),
3370
3434
  options: {
3371
3435
  root: { type: "string", default: resolve8("workspaces") },
3372
- "state-dir": { type: "string", default: join16(homedir4(), ".teammy", "client") },
3436
+ "state-dir": { type: "string", default: join17(homedir4(), ".teammy", "client") },
3373
3437
  agent: { type: "string" },
3374
3438
  file: { type: "string" },
3375
3439
  directory: { type: "string" },
@@ -3429,17 +3493,17 @@ async function runSessionCommand(args, version) {
3429
3493
  } else {
3430
3494
  const agent = AgentSchema.parse(values.agent);
3431
3495
  const defaults = {
3432
- "claude-code": join16(
3433
- process.env.CLAUDE_CONFIG_DIR ?? join16(homedir4(), ".claude"),
3496
+ "claude-code": join17(
3497
+ process.env.CLAUDE_CONFIG_DIR ?? join17(homedir4(), ".claude"),
3434
3498
  "projects"
3435
3499
  ),
3436
- codex: join16(process.env.CODEX_HOME ?? join16(homedir4(), ".codex"), "sessions"),
3437
- cursor: join16(
3438
- process.env.CURSOR_CONFIG_DIR ?? join16(homedir4(), ".cursor"),
3500
+ codex: join17(process.env.CODEX_HOME ?? join17(homedir4(), ".codex"), "sessions"),
3501
+ cursor: join17(
3502
+ process.env.CURSOR_CONFIG_DIR ?? join17(homedir4(), ".cursor"),
3439
3503
  "chats",
3440
3504
  createHash8("md5").update(projectDir).digest("hex")
3441
3505
  ),
3442
- pi: join16(process.env.PI_CODING_AGENT_DIR ?? join16(homedir4(), ".pi", "agent"), "sessions"),
3506
+ pi: join17(process.env.PI_CODING_AGENT_DIR ?? join17(homedir4(), ".pi", "agent"), "sessions"),
3443
3507
  dsh: void 0
3444
3508
  };
3445
3509
  const path = values.directory ?? defaults[agent];
@@ -3497,66 +3561,6 @@ import { createInterface as createInterface2 } from "node:readline/promises";
3497
3561
  // apps/client/connection.ts
3498
3562
  import { readFile as readFile5, mkdir as mkdir10 } from "node:fs/promises";
3499
3563
  import { createInterface } from "node:readline/promises";
3500
-
3501
- // apps/client/local-config.ts
3502
- import { constants as constants3 } from "node:fs";
3503
- import { open as open3, writeFile as writeFile3 } from "node:fs/promises";
3504
- import { join as join17 } from "node:path";
3505
- import { z as z14 } from "zod";
3506
- import ignore from "ignore";
3507
- var LOCAL_CONFIG_NAME = ".teammy-local.json";
3508
- var LocalConfigSchema = z14.object({
3509
- files: z14.object({ ignore: z14.array(z14.string().max(1024)).max(256).default([]) }).strict().default({ ignore: [] })
3510
- }).strict();
3511
- async function ensureLocalConfig(root) {
3512
- try {
3513
- await writeFile3(
3514
- join17(root, LOCAL_CONFIG_NAME),
3515
- JSON.stringify({ files: { ignore: [] } }, null, 2) + "\n",
3516
- { flag: "wx", mode: 384 }
3517
- );
3518
- } catch (error) {
3519
- if (!(error instanceof Error && "code" in error && error.code === "EEXIST")) throw error;
3520
- }
3521
- }
3522
- async function loadLocalRules(root) {
3523
- let rules = [];
3524
- try {
3525
- const file = await open3(
3526
- join17(root, LOCAL_CONFIG_NAME),
3527
- constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK
3528
- );
3529
- try {
3530
- const before = await file.stat();
3531
- if (!before.isFile() || before.size > 65536)
3532
- throw new Error("Expected a regular configuration file no larger than 64 KiB.");
3533
- const buffer = Buffer.alloc(65537);
3534
- const { bytesRead } = await file.read(buffer, 0, buffer.length, 0);
3535
- if (bytesRead > 65536 || bytesRead !== before.size)
3536
- throw new Error("Configuration exceeds the read limit or changed while reading.");
3537
- const text = new TextDecoder("utf-8", { fatal: true }).decode(buffer.subarray(0, bytesRead));
3538
- const after = await file.stat();
3539
- if (before.size !== after.size || before.mtimeMs !== after.mtimeMs)
3540
- throw new Error("Configuration is being saved; retry on the next check.");
3541
- rules = LocalConfigSchema.parse(JSON.parse(text)).files.ignore;
3542
- } finally {
3543
- await file.close();
3544
- }
3545
- } catch (error) {
3546
- if (!missing(error))
3547
- throw new Error(
3548
- `${LOCAL_CONFIG_NAME}: invalid or unreadable configuration; uploads paused.`,
3549
- { cause: error }
3550
- );
3551
- }
3552
- const matcher = ignore().add(rules);
3553
- return {
3554
- fingerprint: JSON.stringify(rules),
3555
- excludes: (path, directory = false) => ignored(path) || matcher.ignores(path + (directory && !path.endsWith("/") ? "/" : ""))
3556
- };
3557
- }
3558
-
3559
- // apps/client/connection.ts
3560
3564
  async function connectProject(state, root, directory, options) {
3561
3565
  const lock = acquireRootLock(root, directory);
3562
3566
  try {
@@ -3935,8 +3939,8 @@ var PublicationQueue = class {
3935
3939
  pending.batch.changes.slice(pending.nextIndex, end).flatMap((change) => change.kind === "upsert" ? [change.sha256] : [])
3936
3940
  )
3937
3941
  ];
3938
- const { missing: missing2 } = hashes.length ? await this.transport.missingBlobs(hashes) : { missing: [] };
3939
- for (const hash2 of missing2) {
3942
+ const { missing: missing3 } = hashes.length ? await this.transport.missingBlobs(hashes) : { missing: [] };
3943
+ for (const hash2 of missing3) {
3940
3944
  if (!hashes.includes(hash2)) throw new Error("Server requested unrelated content.");
3941
3945
  const content = await this.blobs.get(hash2);
3942
3946
  if (!content) throw new Error("A queued content snapshot is missing from local storage.");
@@ -5001,10 +5005,10 @@ var SessionSynchronizer = class {
5001
5005
  async flush(source) {
5002
5006
  const request = source.pending;
5003
5007
  try {
5004
- const { missing: missing2 } = await this.client.missingBlobs([request.blob.sha256]);
5005
- if (missing2.some((hash2) => hash2 !== request.blob.sha256))
5008
+ const { missing: missing3 } = await this.client.missingBlobs([request.blob.sha256]);
5009
+ if (missing3.some((hash2) => hash2 !== request.blob.sha256))
5006
5010
  throw new Error("Server requested unrelated session content.");
5007
- if (missing2.length) {
5011
+ if (missing3.length) {
5008
5012
  const bytes = await this.blobs.get(request.blob.sha256);
5009
5013
  if (!bytes) throw new Error("Queued session snapshot is missing.");
5010
5014
  const ref = await this.client.uploadBlob(request.blob.sha256, bytes);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@teammy/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "The Teammy client for team collaboration and synchronization.",
5
5
  "type": "module",
6
6
  "bin": {