openmates 0.18.0-alpha.0 → 0.18.0-alpha.10

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.
@@ -1015,6 +1015,10 @@ var OpenMatesHttpClient = class {
1015
1015
  getCookieMap() {
1016
1016
  return Object.fromEntries(this.cookies.entries());
1017
1017
  }
1018
+ replaceCookies(cookies) {
1019
+ this.cookies.clear();
1020
+ for (const [name, value] of Object.entries(cookies)) this.cookies.set(name, value);
1021
+ }
1018
1022
  async get(path2, headers = {}) {
1019
1023
  return this.request("GET", path2, void 0, headers);
1020
1024
  }
@@ -1194,8 +1198,10 @@ import {
1194
1198
  readdirSync,
1195
1199
  readFileSync as readFileSync2,
1196
1200
  rmSync,
1201
+ renameSync,
1197
1202
  writeFileSync
1198
1203
  } from "fs";
1204
+ import lockfile from "proper-lockfile";
1199
1205
  import { createHash as createHash4 } from "crypto";
1200
1206
  import { homedir as homedir2 } from "os";
1201
1207
  import { isAbsolute, join, resolve } from "path";
@@ -1483,7 +1489,55 @@ function saveAnonymousId(anonymousId) {
1483
1489
  createdAt: Math.floor(Date.now() / 1e3)
1484
1490
  });
1485
1491
  }
1486
- function saveSession(session) {
1492
+ var SESSION_LOCK_STALE_MS = 3e4;
1493
+ var SESSION_LOCK_POLL_MS = 50;
1494
+ async function withSessionRefreshLock(operation) {
1495
+ const path2 = join(ensureStateDir(), "session.json");
1496
+ const release2 = await lockfile.lock(`${path2}.refresh`, {
1497
+ realpath: false,
1498
+ stale: SESSION_LOCK_STALE_MS,
1499
+ retries: { retries: 600, factor: 1, minTimeout: SESSION_LOCK_POLL_MS, maxTimeout: SESSION_LOCK_POLL_MS }
1500
+ });
1501
+ try {
1502
+ return await operation();
1503
+ } finally {
1504
+ await release2();
1505
+ }
1506
+ }
1507
+ function saveSession(session, options = {}) {
1508
+ const path2 = join(ensureStateDir(), "session.json");
1509
+ let release2;
1510
+ const waitCell = new Int32Array(new SharedArrayBuffer(4));
1511
+ for (let attempt = 0; ; attempt++) {
1512
+ try {
1513
+ release2 = lockfile.lockSync(`${path2}.write`, { realpath: false, stale: SESSION_LOCK_STALE_MS });
1514
+ break;
1515
+ } catch (error) {
1516
+ if (error.code !== "ELOCKED" || attempt >= 100) throw error;
1517
+ Atomics.wait(waitCell, 0, 0, SESSION_LOCK_POLL_MS);
1518
+ }
1519
+ }
1520
+ try {
1521
+ const current = readJsonFile(path2);
1522
+ if (current && !options.replace) {
1523
+ if (current.sessionId !== session.sessionId || current.hashedEmail !== session.hashedEmail || current.apiUrl !== session.apiUrl) {
1524
+ throw new Error("Login session changed in another process. Retry the command using the current profile.");
1525
+ }
1526
+ if (options.expectedRefreshToken !== void 0) {
1527
+ if (current.cookies.auth_refresh_token !== options.expectedRefreshToken) {
1528
+ throw new Error("Login credentials changed during refresh. Retry with the current profile.");
1529
+ }
1530
+ } else {
1531
+ session.cookies = { ...current.cookies };
1532
+ session.wsToken = current.wsToken;
1533
+ }
1534
+ }
1535
+ writeSession(session);
1536
+ } finally {
1537
+ release2();
1538
+ }
1539
+ }
1540
+ function writeSession(session) {
1487
1541
  const filePath = join(ensureStateDir(), "session.json");
1488
1542
  const result = storeMasterKey(session.masterKeyExportedB64, resolveKeyStorageId(session.hashedEmail));
1489
1543
  const onDisk = {
@@ -1516,7 +1570,13 @@ function saveSession(session) {
1516
1570
  onDisk.emailEncryptionKeyB64 = session.emailEncryptionKeyB64;
1517
1571
  }
1518
1572
  }
1519
- writeJsonFile(filePath, onDisk);
1573
+ const temporary = `${filePath}.${process.pid}.tmp`;
1574
+ try {
1575
+ writeJsonFile(temporary, onDisk);
1576
+ renameSync(temporary, filePath);
1577
+ } finally {
1578
+ rmSync(temporary, { force: true });
1579
+ }
1520
1580
  if (result.type !== "plaintext") {
1521
1581
  process.stderr.write("Decrypting data...\n");
1522
1582
  }
@@ -7509,7 +7569,9 @@ var OpenMatesClient = class _OpenMatesClient {
7509
7569
  apiUrl;
7510
7570
  session;
7511
7571
  http;
7572
+ explicitSession;
7512
7573
  constructor(options = {}) {
7574
+ this.explicitSession = options.session !== void 0;
7513
7575
  const diskSession = options.session ?? this.getValidSessionFromDisk();
7514
7576
  this.apiUrl = (options.apiUrl ?? process.env.OPENMATES_API_URL ?? diskSession?.apiUrl ?? loadDefaultServerApiUrl() ?? DEFAULT_API_URL).replace(/\/$/, "");
7515
7577
  this.session = diskSession;
@@ -8375,7 +8437,7 @@ var OpenMatesClient = class _OpenMatesClient {
8375
8437
  if (!userId) {
8376
8438
  throw new Error("Could not resolve current user id for local connected-account connector.");
8377
8439
  }
8378
- const { createHash: createHash15 } = await import("crypto");
8440
+ const { createHash: createHash16 } = await import("crypto");
8379
8441
  const accountId = randomUUID6();
8380
8442
  const masterKey = this.getMasterKeyBytes();
8381
8443
  const encryptLocalConnectorValue = async (value) => {
@@ -8384,9 +8446,9 @@ var OpenMatesClient = class _OpenMatesClient {
8384
8446
  };
8385
8447
  const payload = {
8386
8448
  id: accountId,
8387
- hashed_user_id: createHash15("sha256").update(userId).digest("hex"),
8449
+ hashed_user_id: createHash16("sha256").update(userId).digest("hex"),
8388
8450
  encrypted_provider_type: await encryptLocalConnectorValue(input.provider_id),
8389
- provider_type_hash: createHash15("sha256").update(input.provider_id).digest("hex"),
8451
+ provider_type_hash: createHash16("sha256").update(input.provider_id).digest("hex"),
8390
8452
  encrypted_account_label: await encryptLocalConnectorValue(input.label),
8391
8453
  encrypted_capabilities: await encryptLocalConnectorValue(input.capabilities),
8392
8454
  encrypted_app_permissions: await encryptLocalConnectorValue({
@@ -8570,31 +8632,34 @@ var OpenMatesClient = class _OpenMatesClient {
8570
8632
  };
8571
8633
  await this.hydrateEmailEncryptionKey(session);
8572
8634
  this.session = session;
8573
- saveSession(session);
8635
+ saveSession(session, { replace: true });
8574
8636
  }
8575
8637
  async whoAmI() {
8576
- const session = this.requireSession();
8577
- const response = await this.http.post(
8578
- "/v1/auth/session",
8579
- { session_id: session.sessionId },
8580
- this.getCliRequestHeaders()
8581
- );
8582
- if (!response.ok || !response.data.success) {
8583
- if (response.status === 502 || response.status === 503 || response.status === 504) {
8638
+ return this.withFreshStoredSession(async () => {
8639
+ const session = this.requireSession();
8640
+ const previousRefreshToken = session.cookies.auth_refresh_token;
8641
+ const response = await this.http.post(
8642
+ "/v1/auth/session",
8643
+ { session_id: session.sessionId },
8644
+ this.getCliRequestHeaders()
8645
+ );
8646
+ if (!response.ok || !response.data.success) {
8647
+ if (response.status === 502 || response.status === 503 || response.status === 504) {
8648
+ throw new Error(
8649
+ `Session validation temporarily unavailable (HTTP ${response.status}). The API may be restarting; retry shortly.`
8650
+ );
8651
+ }
8584
8652
  throw new Error(
8585
- `Session validation temporarily unavailable (HTTP ${response.status}). The API may be restarting; retry shortly.`
8653
+ `Session validation failed (HTTP ${response.status}): ${response.data.message ?? "invalid session"}${response.data.re_auth_reason ? ` (${response.data.re_auth_reason})` : ""}. Please run \`${this.loginRecoveryCommand()}\`.`
8586
8654
  );
8587
8655
  }
8588
- throw new Error(
8589
- `Session validation failed (HTTP ${response.status}): ${response.data.message ?? "invalid session"}${response.data.re_auth_reason ? ` (${response.data.re_auth_reason})` : ""}. Please run \`openmates login\`.`
8590
- );
8591
- }
8592
- if (response.data.ws_token) {
8593
- session.wsToken = response.data.ws_token;
8594
- }
8595
- session.cookies = this.http.getCookieMap();
8596
- saveSession(session);
8597
- return response.data.user ?? {};
8656
+ if (response.data.ws_token) {
8657
+ session.wsToken = response.data.ws_token;
8658
+ }
8659
+ session.cookies = this.http.getCookieMap();
8660
+ saveSession(session, { expectedRefreshToken: previousRefreshToken });
8661
+ return response.data.user ?? {};
8662
+ });
8598
8663
  }
8599
8664
  async getTopicPreferences() {
8600
8665
  const user = await this.whoAmI();
@@ -8734,7 +8799,7 @@ var OpenMatesClient = class _OpenMatesClient {
8734
8799
  authorizerDeviceName: null,
8735
8800
  autoLogoutMinutes: null
8736
8801
  };
8737
- saveSession(session);
8802
+ saveSession(session, { replace: true });
8738
8803
  this.session = session;
8739
8804
  return {
8740
8805
  success: true,
@@ -9882,8 +9947,8 @@ var OpenMatesClient = class _OpenMatesClient {
9882
9947
  );
9883
9948
  }
9884
9949
  const embedId = String(embed.embed_id ?? embed.id ?? "");
9885
- const { createHash: createHash15 } = await import("crypto");
9886
- const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
9950
+ const { createHash: createHash16 } = await import("crypto");
9951
+ const hashedEmbedId = createHash16("sha256").update(embedId).digest("hex");
9887
9952
  const embedKeyBytes = await this.resolveEmbedKey(
9888
9953
  cache,
9889
9954
  masterKey,
@@ -9991,7 +10056,7 @@ var OpenMatesClient = class _OpenMatesClient {
9991
10056
  async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
9992
10057
  if (visited.has(embedId)) return null;
9993
10058
  visited.add(embedId);
9994
- const { createHash: createHash15 } = await import("crypto");
10059
+ const { createHash: createHash16 } = await import("crypto");
9995
10060
  const masterKeyEntry = cache.embedKeys.find(
9996
10061
  (ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
9997
10062
  );
@@ -10008,7 +10073,7 @@ var OpenMatesClient = class _OpenMatesClient {
10008
10073
  if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
10009
10074
  const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
10010
10075
  const owningChat = cache.chats.find((c) => {
10011
- const chatHash = createHash15("sha256").update(String(c.details.id ?? "")).digest("hex");
10076
+ const chatHash = createHash16("sha256").update(String(c.details.id ?? "")).digest("hex");
10012
10077
  return chatHash === hashedChatId;
10013
10078
  });
10014
10079
  if (owningChat) {
@@ -10037,7 +10102,7 @@ var OpenMatesClient = class _OpenMatesClient {
10037
10102
  const parentFullId = String(
10038
10103
  parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
10039
10104
  );
10040
- const parentHashedId = createHash15("sha256").update(parentFullId).digest("hex");
10105
+ const parentHashedId = createHash16("sha256").update(parentFullId).digest("hex");
10041
10106
  const parentKey = await this.resolveEmbedKey(
10042
10107
  cache,
10043
10108
  masterKey,
@@ -12776,6 +12841,7 @@ var OpenMatesClient = class _OpenMatesClient {
12776
12841
  if (Number.isSafeInteger(context.limit) && context.limit !== void 0 && context.limit > 0) {
12777
12842
  params.set("limit", String(context.limit));
12778
12843
  }
12844
+ if (context.newestFirst) params.set("newest_first", "true");
12779
12845
  const query = params.toString();
12780
12846
  const response = await this.http.get(
12781
12847
  `/v1/user-tasks/${encodeURIComponent(taskId)}/activity${query ? `?${query}` : ""}`,
@@ -13974,8 +14040,9 @@ Required: ${schema.required.join(", ")}`
13974
14040
  const session = this.requireSession();
13975
14041
  const currentCookies = this.http.getCookieMap();
13976
14042
  if (JSON.stringify(session.cookies) !== JSON.stringify(currentCookies)) {
14043
+ const previousRefreshToken = session.cookies.auth_refresh_token;
13977
14044
  session.cookies = currentCookies;
13978
- saveSession(session);
14045
+ saveSession(session, { expectedRefreshToken: previousRefreshToken });
13979
14046
  }
13980
14047
  return session;
13981
14048
  }
@@ -14070,7 +14137,7 @@ Required: ${schema.required.join(", ")}`
14070
14137
  const session = this.requireSession();
14071
14138
  const masterKey = base64ToBytes(session.masterKeyExportedB64);
14072
14139
  const cache = await this.ensureSynced();
14073
- const { createHash: createHash15 } = await import("crypto");
14140
+ const { createHash: createHash16 } = await import("crypto");
14074
14141
  const embed = cache.embeds.find(
14075
14142
  (e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
14076
14143
  );
@@ -14078,7 +14145,7 @@ Required: ${schema.required.join(", ")}`
14078
14145
  throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
14079
14146
  }
14080
14147
  const embedId = String(embed.embed_id ?? embed.id ?? "");
14081
- const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
14148
+ const hashedEmbedId = createHash16("sha256").update(embedId).digest("hex");
14082
14149
  const embedKeyBytes = await this.resolveEmbedKey(
14083
14150
  cache,
14084
14151
  masterKey,
@@ -14186,8 +14253,8 @@ Required: ${schema.required.join(", ")}`
14186
14253
  if (!embed) {
14187
14254
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
14188
14255
  }
14189
- const { createHash: createHash15 } = await import("crypto");
14190
- const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
14256
+ const { createHash: createHash16 } = await import("crypto");
14257
+ const hashedEmbedId = createHash16("sha256").update(embedId).digest("hex");
14191
14258
  const embedKey = await this.resolveEmbedKey(
14192
14259
  cache,
14193
14260
  masterKey,
@@ -14303,8 +14370,8 @@ Required: ${schema.required.join(", ")}`
14303
14370
  if (!embed) {
14304
14371
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
14305
14372
  }
14306
- const { createHash: createHash15 } = await import("crypto");
14307
- const hashedEmbedId = createHash15("sha256").update(embedId).digest("hex");
14373
+ const { createHash: createHash16 } = await import("crypto");
14374
+ const hashedEmbedId = createHash16("sha256").update(embedId).digest("hex");
14308
14375
  const embedKey = await this.resolveEmbedKey(
14309
14376
  cache,
14310
14377
  masterKey,
@@ -14445,9 +14512,30 @@ Required: ${schema.required.join(", ")}`
14445
14512
  session.userEmailSalt
14446
14513
  );
14447
14514
  }
14515
+ async withFreshStoredSession(operation) {
14516
+ return withSessionRefreshLock(async () => {
14517
+ const previous = this.requireSession();
14518
+ const current = loadSession();
14519
+ if (!current && !this.explicitSession) {
14520
+ throw new Error("Login session was removed. Log in to the current profile before retrying.");
14521
+ }
14522
+ if (current && !this.explicitSession) {
14523
+ if (current.hashedEmail !== previous.hashedEmail || current.apiUrl !== previous.apiUrl) {
14524
+ throw new Error("Login account changed in another process. Retry using the current profile.");
14525
+ }
14526
+ this.session = current;
14527
+ this.http.replaceCookies(current.cookies);
14528
+ }
14529
+ return operation();
14530
+ });
14531
+ }
14532
+ loginRecoveryCommand() {
14533
+ const profile = process.env.OPENMATES_PROFILE?.trim();
14534
+ return profile ? `openmates --profile ${profile} login` : "openmates login";
14535
+ }
14448
14536
  requireSession() {
14449
14537
  if (!this.session) {
14450
- throw new Error("Not logged in. Run `openmates login`.");
14538
+ throw new Error(`Not logged in. Run \`${this.loginRecoveryCommand()}\`.`);
14451
14539
  }
14452
14540
  return this.session;
14453
14541
  }
@@ -14527,24 +14615,30 @@ Required: ${schema.required.join(", ")}`
14527
14615
  * This method fetches a fresh ws_token and captures any rotated cookies.
14528
14616
  */
14529
14617
  async refreshWsToken() {
14530
- const session = this.requireSession();
14531
- let res;
14532
- try {
14533
- res = await this.http.post("/v1/auth/session", { session_id: session.sessionId }, this.getCliRequestHeaders());
14534
- } catch {
14535
- return null;
14536
- }
14537
- if (!res.ok || res.data.success === false) {
14538
- purgeLocalPrivateData();
14539
- this.session = null;
14540
- throw new Error("Session expired or invalid. Please run `openmates login` to re-authenticate.");
14541
- }
14542
- if (res.data.ws_token) {
14543
- session.wsToken = res.data.ws_token;
14544
- }
14545
- session.cookies = this.http.getCookieMap();
14546
- saveSession(session);
14547
- return typeof res.data.user?.id === "string" ? res.data.user.id : typeof res.data.user?.user_id === "string" ? res.data.user.user_id : null;
14618
+ return this.withFreshStoredSession(async () => {
14619
+ const session = this.requireSession();
14620
+ const previousRefreshToken = session.cookies.auth_refresh_token;
14621
+ let res;
14622
+ try {
14623
+ res = await this.http.post("/v1/auth/session", { session_id: session.sessionId }, this.getCliRequestHeaders());
14624
+ } catch {
14625
+ return null;
14626
+ }
14627
+ if (res.status >= 500) {
14628
+ throw new Error(`Session validation temporarily unavailable (HTTP ${res.status}). Retry shortly.`);
14629
+ }
14630
+ if (!res.ok || res.data.success === false) {
14631
+ purgeLocalPrivateData();
14632
+ this.session = null;
14633
+ throw new Error(`Session expired or invalid. Please run \`${this.loginRecoveryCommand()}\` to re-authenticate.`);
14634
+ }
14635
+ if (res.data.ws_token) {
14636
+ session.wsToken = res.data.ws_token;
14637
+ }
14638
+ session.cookies = this.http.getCookieMap();
14639
+ saveSession(session, { expectedRefreshToken: previousRefreshToken });
14640
+ return typeof res.data.user?.id === "string" ? res.data.user.id : typeof res.data.user?.user_id === "string" ? res.data.user.user_id : null;
14641
+ });
14548
14642
  }
14549
14643
  /**
14550
14644
  * Ensure the local sync cache is up to date. If the cache is fresh,
@@ -14744,9 +14838,9 @@ Required: ${schema.required.join(", ")}`
14744
14838
  chats.splice(0, chats.length, ...reconciledChats);
14745
14839
  if (totalChatCount === 0) totalChatCount = chats.length;
14746
14840
  if (reconciliation.deleted_chat_ids?.length) {
14747
- const { createHash: createHash15 } = await import("crypto");
14841
+ const { createHash: createHash16 } = await import("crypto");
14748
14842
  const deletedHashes = new Set(
14749
- reconciliation.deleted_chat_ids.map((id) => createHash15("sha256").update(id).digest("hex"))
14843
+ reconciliation.deleted_chat_ids.map((id) => createHash16("sha256").update(id).digest("hex"))
14750
14844
  );
14751
14845
  const keptEmbeds = embeds.filter((embed) => !deletedHashes.has(String(embed.hashed_chat_id ?? "")));
14752
14846
  const keptKeys = embedKeys.filter((key) => !deletedHashes.has(String(key.hashed_chat_id ?? "")));
@@ -20131,12 +20225,12 @@ var ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS = [
20131
20225
  ];
20132
20226
  async function writeAccountExportArchive(bundle, flags = {}) {
20133
20227
  const { mkdir: mkdir2, writeFile: writeFile2 } = await import("fs/promises");
20134
- const { join: join11, dirname: dirname10 } = await import("path");
20228
+ const { join: join13, dirname: dirname11 } = await import("path");
20135
20229
  const exportId = safeArchiveSegment(String(bundle.export.export_id ?? `export-${Date.now()}`));
20136
20230
  const archiveFormat = flags.format === "directory" ? "directory" : "zip";
20137
20231
  const password = typeof flags.password === "string" && flags.password.length > 0 ? flags.password : null;
20138
20232
  if (password && archiveFormat !== "zip") throw new Error("Password-protected account export is only supported for zip format.");
20139
- const outputPath = typeof flags.output === "string" ? flags.output : join11(process.cwd(), archiveFormat === "zip" ? `openmates-account-export-${exportId}${password ? ".zip.enc" : ".zip"}` : `openmates-account-export-${exportId}`);
20233
+ const outputPath = typeof flags.output === "string" ? flags.output : join13(process.cwd(), archiveFormat === "zip" ? `openmates-account-export-${exportId}${password ? ".zip.enc" : ".zip"}` : `openmates-account-export-${exportId}`);
20140
20234
  const zip = archiveFormat === "zip" ? new JSZip3() : null;
20141
20235
  const writtenFiles = [];
20142
20236
  async function writeArchiveText(relativePath, content) {
@@ -20147,8 +20241,8 @@ async function writeAccountExportArchive(bundle, flags = {}) {
20147
20241
  return;
20148
20242
  }
20149
20243
  const stagingDir = outputPath;
20150
- const fullPath = join11(stagingDir, relativePath);
20151
- await mkdir2(dirname10(fullPath), { recursive: true });
20244
+ const fullPath = join13(stagingDir, relativePath);
20245
+ await mkdir2(dirname11(fullPath), { recursive: true });
20152
20246
  await writeFile2(fullPath, content, "utf-8");
20153
20247
  writtenFiles.push(relativePath);
20154
20248
  }
@@ -20165,7 +20259,7 @@ async function writeAccountExportArchive(bundle, flags = {}) {
20165
20259
  }
20166
20260
  await writeAccountExportChatFiles(domainPayloads.chats ?? [], writeArchiveText);
20167
20261
  if (archiveFormat === "directory") return { output: outputPath, format: "directory", files: writtenFiles.length };
20168
- await mkdir2(dirname10(outputPath), { recursive: true });
20262
+ await mkdir2(dirname11(outputPath), { recursive: true });
20169
20263
  const zipBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
20170
20264
  await writeFile2(outputPath, password ? encryptZipBuffer(zipBuffer, password) : zipBuffer);
20171
20265
  return { output: outputPath, format: "zip", files: writtenFiles.length, ...password ? { encrypted: true } : {} };
@@ -24298,10 +24392,115 @@ var OpenMatesBenchmark = class {
24298
24392
  // src/cli.ts
24299
24393
  import { createInterface as createInterface5 } from "readline/promises";
24300
24394
  import { stdin as stdin3, stdout as stdout2 } from "process";
24301
- import { existsSync as existsSync13, readFileSync as readFileSync12, realpathSync as realpathSync2, writeFileSync as writeFileSync9 } from "fs";
24302
- import { fileURLToPath as fileURLToPath2 } from "url";
24303
- import { basename as basename3, dirname as dirname9 } from "path";
24304
- import { createHash as createHash14, randomBytes as randomBytes9, randomUUID as randomUUID14 } from "crypto";
24395
+
24396
+ // src/taskActivityHistory.ts
24397
+ async function readActivityHistory(options) {
24398
+ const { query } = options;
24399
+ const maxEntries = options.maxEntries ?? Infinity;
24400
+ if (options.maxEntries !== void 0 && (!Number.isSafeInteger(maxEntries) || maxEntries < 1 || maxEntries > 200)) {
24401
+ throw new Error("--max-entries must be an integer from 1 to 200");
24402
+ }
24403
+ if (query !== void 0 && !query.trim()) throw new Error("Activity search requires a non-empty --query");
24404
+ const pageSize = options.pageSize ?? 100;
24405
+ if (!Number.isSafeInteger(pageSize) || pageSize < 1 || pageSize > 200) throw new Error("--limit must be from 1 to 200");
24406
+ const entries = [];
24407
+ const seen = /* @__PURE__ */ new Set();
24408
+ let cursor = options.cursor;
24409
+ let matched = 0;
24410
+ do {
24411
+ const page = await options.page(cursor, query === void 0 ? Math.min(pageSize, maxEntries - entries.length) : pageSize);
24412
+ for (const entry of page.entries) {
24413
+ if (query === void 0 || (entry.message || "").toLocaleLowerCase().includes(query.toLocaleLowerCase())) {
24414
+ matched++;
24415
+ if (entries.length < maxEntries) entries.push(entry);
24416
+ }
24417
+ }
24418
+ cursor = page.next_cursor || void 0;
24419
+ if (cursor && seen.has(cursor)) throw new Error("Activity pagination repeated a cursor");
24420
+ if (cursor) seen.add(cursor);
24421
+ } while (cursor && (query !== void 0 || entries.length < maxEntries));
24422
+ return { entries, next_cursor: cursor || null, truncated: Boolean(cursor) || matched > entries.length, matched };
24423
+ }
24424
+
24425
+ // src/taskActivityDelivery.ts
24426
+ import { createHash as createHash12 } from "crypto";
24427
+ import { mkdirSync as mkdirSync4, readFileSync as readFileSync6, readdirSync as readdirSync2, renameSync as renameSync2, writeFileSync as writeFileSync4, rmSync as rmSync3, existsSync as existsSync7 } from "fs";
24428
+ import { join as join4 } from "path";
24429
+ import lockfile2 from "proper-lockfile";
24430
+ function activityDeliveryStore(scope, stateDir2 = resolveStateDir()) {
24431
+ const directory = join4(stateDir2, "task-activity-delivery", createHash12("sha256").update(scope).digest("hex"));
24432
+ mkdirSync4(directory, { recursive: true, mode: 448 });
24433
+ const pendingDirectory = join4(directory, "pending");
24434
+ const acknowledgedDirectory = join4(directory, "acknowledged");
24435
+ mkdirSync4(pendingDirectory, { recursive: true, mode: 448 });
24436
+ mkdirSync4(acknowledgedDirectory, { recursive: true, mode: 448 });
24437
+ const save = (path2, record) => {
24438
+ const temporary = `${path2}.${process.pid}.tmp`;
24439
+ writeFileSync4(temporary, JSON.stringify(record), { mode: 384 });
24440
+ renameSync2(temporary, path2);
24441
+ };
24442
+ const send = async (path2, record, create) => {
24443
+ if (!record.acknowledged) {
24444
+ const acknowledged = await create(record.input);
24445
+ if (acknowledged.entry_id !== record.input.entry_id) throw new Error("Activity acknowledgement identity mismatch");
24446
+ record.acknowledged = acknowledged;
24447
+ save(join4(acknowledgedDirectory, `${record.input.entry_id}.json`), record);
24448
+ rmSync3(path2, { force: true });
24449
+ }
24450
+ return record.acknowledged;
24451
+ };
24452
+ return {
24453
+ async deliver(id, build, create) {
24454
+ if (!/^[a-f0-9]{64}$/.test(id)) throw new Error("--delivery-id must be a SHA-256 hex identifier");
24455
+ const release2 = await lockfile2.lock(directory, { retries: { retries: 3, minTimeout: 100, maxTimeout: 500 }, stale: 12e4 });
24456
+ try {
24457
+ const acknowledgedPath = join4(acknowledgedDirectory, `${id}.json`);
24458
+ const path2 = existsSync7(acknowledgedPath) ? acknowledgedPath : join4(pendingDirectory, `${id}.json`);
24459
+ let record;
24460
+ try {
24461
+ record = JSON.parse(readFileSync6(path2, "utf8"));
24462
+ } catch (error) {
24463
+ if (error.code !== "ENOENT") throw error;
24464
+ record = { input: await build() };
24465
+ save(path2, record);
24466
+ }
24467
+ return await send(path2, record, create);
24468
+ } finally {
24469
+ await release2();
24470
+ }
24471
+ },
24472
+ async flush(create) {
24473
+ const release2 = await lockfile2.lock(directory, { retries: 0, stale: 12e4 });
24474
+ try {
24475
+ const names = readdirSync2(pendingDirectory).filter((name) => /^[a-f0-9]{64}\.json$/.test(name));
24476
+ let pending = names.length;
24477
+ let attempted = 0;
24478
+ for (const name of names) {
24479
+ const path2 = join4(pendingDirectory, name);
24480
+ const record = JSON.parse(readFileSync6(path2, "utf8"));
24481
+ if (record.acknowledged) continue;
24482
+ if (attempted >= 3) break;
24483
+ attempted++;
24484
+ try {
24485
+ await send(path2, record, create);
24486
+ pending--;
24487
+ } catch {
24488
+ break;
24489
+ }
24490
+ }
24491
+ return { pending, attempted };
24492
+ } finally {
24493
+ await release2();
24494
+ }
24495
+ }
24496
+ };
24497
+ }
24498
+
24499
+ // src/cli.ts
24500
+ import { existsSync as existsSync14, readFileSync as readFileSync13, realpathSync as realpathSync2, writeFileSync as writeFileSync11 } from "fs";
24501
+ import { fileURLToPath as fileURLToPath3 } from "url";
24502
+ import { basename as basename4, dirname as dirname10 } from "path";
24503
+ import { createHash as createHash15, randomBytes as randomBytes9, randomUUID as randomUUID15 } from "crypto";
24305
24504
  import { arch as arch2, platform as platform3 } from "os";
24306
24505
  import { parse as parseYaml } from "yaml";
24307
24506
  import WebSocket2 from "ws";
@@ -26479,13 +26678,13 @@ function formatTs(ts) {
26479
26678
  }
26480
26679
 
26481
26680
  // src/server.ts
26482
- import { execFileSync as execFileSync3, execSync, spawn as nodeSpawn, spawnSync } from "child_process";
26483
- import { createHash as createHash12, randomBytes as randomBytes8, randomUUID as randomUUID10 } from "crypto";
26484
- import { appendFileSync, chmodSync as chmodSync4, closeSync as closeSync2, copyFileSync as copyFileSync2, cpSync, existsSync as existsSync9, mkdirSync as mkdirSync6, mkdtempSync, openSync as openSync2, readFileSync as readFileSync7, readSync, readdirSync as readdirSync3, rmSync as rmSync5, writeFileSync as writeFileSync5 } from "fs";
26681
+ import { execFileSync as execFileSync4, execSync, spawn as nodeSpawn, spawnSync } from "child_process";
26682
+ import { createHash as createHash13, randomBytes as randomBytes8, randomUUID as randomUUID11 } from "crypto";
26683
+ import { appendFileSync, chmodSync as chmodSync5, closeSync as closeSync2, copyFileSync as copyFileSync2, cpSync, existsSync as existsSync10, mkdirSync as mkdirSync7, mkdtempSync, openSync as openSync2, readFileSync as readFileSync8, readSync, readdirSync as readdirSync5, rmSync as rmSync7, writeFileSync as writeFileSync6 } from "fs";
26485
26684
  import { createInterface as createInterface3 } from "readline";
26486
26685
  import { createInterface as createPromptInterface } from "readline/promises";
26487
26686
  import { homedir as homedir6 } from "os";
26488
- import { dirname as dirname6, join as join7, resolve as resolve6 } from "path";
26687
+ import { dirname as dirname6, join as join8, resolve as resolve6 } from "path";
26489
26688
 
26490
26689
  // src/branding.ts
26491
26690
  var OPENMATES_WORDMARK = "OPENMATES";
@@ -26521,33 +26720,33 @@ function renderSupportStartReminder() {
26521
26720
  import { execFileSync as execFileSync2 } from "child_process";
26522
26721
  import {
26523
26722
  copyFileSync,
26524
- existsSync as existsSync7,
26525
- mkdirSync as mkdirSync4,
26526
- readdirSync as readdirSync2,
26527
- rmSync as rmSync3,
26723
+ existsSync as existsSync8,
26724
+ mkdirSync as mkdirSync5,
26725
+ readdirSync as readdirSync3,
26726
+ rmSync as rmSync4,
26528
26727
  symlinkSync
26529
26728
  } from "fs";
26530
26729
  import { createRequire as createRequire3 } from "module";
26531
- import { dirname as dirname3, join as join4, resolve as resolve4 } from "path";
26532
- var UI_PACKAGE_RELATIVE_PATH = join4("frontend", "packages", "ui");
26533
- var GENERATED_LOCALES_RELATIVE_PATH = join4(UI_PACKAGE_RELATIVE_PATH, "src", "i18n", "locales");
26534
- var BUILD_TRANSLATIONS_SCRIPT_RELATIVE_PATH = join4(UI_PACKAGE_RELATIVE_PATH, "scripts", "build-translations.js");
26730
+ import { dirname as dirname3, join as join5, resolve as resolve4 } from "path";
26731
+ var UI_PACKAGE_RELATIVE_PATH = join5("frontend", "packages", "ui");
26732
+ var GENERATED_LOCALES_RELATIVE_PATH = join5(UI_PACKAGE_RELATIVE_PATH, "src", "i18n", "locales");
26733
+ var BUILD_TRANSLATIONS_SCRIPT_RELATIVE_PATH = join5(UI_PACKAGE_RELATIVE_PATH, "scripts", "build-translations.js");
26535
26734
  var REQUIRED_LOCALE_JSON = "en.json";
26536
26735
  function sourceInstallLocalesPath(rootPath) {
26537
- return join4(rootPath, GENERATED_LOCALES_RELATIVE_PATH);
26736
+ return join5(rootPath, GENERATED_LOCALES_RELATIVE_PATH);
26538
26737
  }
26539
26738
  function ensureSourceInstallTranslations(installPath, sourcePath) {
26540
26739
  const localeDir = sourceInstallLocalesPath(installPath);
26541
- const requiredLocalePath = join4(localeDir, REQUIRED_LOCALE_JSON);
26542
- if (existsSync7(requiredLocalePath)) {
26740
+ const requiredLocalePath = join5(localeDir, REQUIRED_LOCALE_JSON);
26741
+ if (existsSync8(requiredLocalePath)) {
26543
26742
  return { status: "already_present", localeDir, copiedFiles: 0 };
26544
26743
  }
26545
26744
  const copiedFiles = copyGeneratedLocaleJson(sourcePath, installPath);
26546
- if (existsSync7(requiredLocalePath)) {
26745
+ if (existsSync8(requiredLocalePath)) {
26547
26746
  return { status: "copied", localeDir, copiedFiles };
26548
26747
  }
26549
26748
  runBuildTranslationsScript(installPath);
26550
- if (!existsSync7(requiredLocalePath)) {
26749
+ if (!existsSync8(requiredLocalePath)) {
26551
26750
  throw new Error(`Generated locale JSON was not created at ${requiredLocalePath}.`);
26552
26751
  }
26553
26752
  return { status: "built", localeDir, copiedFiles };
@@ -26558,31 +26757,31 @@ function copyGeneratedLocaleJson(sourcePath, installPath) {
26558
26757
  const targetRoot = resolve4(installPath);
26559
26758
  if (sourceRoot === targetRoot) return 0;
26560
26759
  const sourceLocaleDir = sourceInstallLocalesPath(sourceRoot);
26561
- if (!existsSync7(join4(sourceLocaleDir, REQUIRED_LOCALE_JSON))) return 0;
26760
+ if (!existsSync8(join5(sourceLocaleDir, REQUIRED_LOCALE_JSON))) return 0;
26562
26761
  const targetLocaleDir = sourceInstallLocalesPath(targetRoot);
26563
- mkdirSync4(targetLocaleDir, { recursive: true });
26762
+ mkdirSync5(targetLocaleDir, { recursive: true });
26564
26763
  let copiedFiles = 0;
26565
- for (const entry of readdirSync2(sourceLocaleDir, { withFileTypes: true })) {
26764
+ for (const entry of readdirSync3(sourceLocaleDir, { withFileTypes: true })) {
26566
26765
  if (!entry.isFile() || !entry.name.endsWith(".json")) continue;
26567
- copyFileSync(join4(sourceLocaleDir, entry.name), join4(targetLocaleDir, entry.name));
26766
+ copyFileSync(join5(sourceLocaleDir, entry.name), join5(targetLocaleDir, entry.name));
26568
26767
  copiedFiles += 1;
26569
26768
  }
26570
26769
  return copiedFiles;
26571
26770
  }
26572
26771
  function runBuildTranslationsScript(installPath) {
26573
- const uiPackageDir = join4(installPath, UI_PACKAGE_RELATIVE_PATH);
26574
- const buildScript = join4(installPath, BUILD_TRANSLATIONS_SCRIPT_RELATIVE_PATH);
26575
- if (!existsSync7(buildScript)) {
26772
+ const uiPackageDir = join5(installPath, UI_PACKAGE_RELATIVE_PATH);
26773
+ const buildScript = join5(installPath, BUILD_TRANSLATIONS_SCRIPT_RELATIVE_PATH);
26774
+ if (!existsSync8(buildScript)) {
26576
26775
  throw new Error(`Translation build script not found at ${buildScript}.`);
26577
26776
  }
26578
26777
  const requireFromCli = createRequire3(import.meta.url);
26579
26778
  const yamlPackageDir = dirname3(requireFromCli.resolve("yaml/package.json"));
26580
- const nodeModulesDir = join4(uiPackageDir, "node_modules");
26581
- const yamlLink = join4(nodeModulesDir, "yaml");
26582
- const hadNodeModulesDir = existsSync7(nodeModulesDir);
26583
- const hadYamlPackage = existsSync7(yamlLink);
26779
+ const nodeModulesDir = join5(uiPackageDir, "node_modules");
26780
+ const yamlLink = join5(nodeModulesDir, "yaml");
26781
+ const hadNodeModulesDir = existsSync8(nodeModulesDir);
26782
+ const hadYamlPackage = existsSync8(yamlLink);
26584
26783
  if (!hadYamlPackage) {
26585
- mkdirSync4(nodeModulesDir, { recursive: true });
26784
+ mkdirSync5(nodeModulesDir, { recursive: true });
26586
26785
  symlinkSync(yamlPackageDir, yamlLink, process.platform === "win32" ? "junction" : "dir");
26587
26786
  }
26588
26787
  try {
@@ -26591,15 +26790,15 @@ function runBuildTranslationsScript(installPath) {
26591
26790
  stdio: "inherit"
26592
26791
  });
26593
26792
  } finally {
26594
- if (!hadYamlPackage) rmSync3(yamlLink, { recursive: true, force: true });
26595
- if (!hadNodeModulesDir && existsSync7(nodeModulesDir) && readdirSync2(nodeModulesDir).length === 0) {
26596
- rmSync3(nodeModulesDir, { recursive: true, force: true });
26793
+ if (!hadYamlPackage) rmSync4(yamlLink, { recursive: true, force: true });
26794
+ if (!hadNodeModulesDir && existsSync8(nodeModulesDir) && readdirSync3(nodeModulesDir).length === 0) {
26795
+ rmSync4(nodeModulesDir, { recursive: true, force: true });
26597
26796
  }
26598
26797
  }
26599
26798
  }
26600
26799
 
26601
26800
  // src/serverPlanning.ts
26602
- import { dirname as dirname4, join as join5, resolve as resolve5 } from "path";
26801
+ import { dirname as dirname4, join as join6, resolve as resolve5 } from "path";
26603
26802
  var CORE_WORKER_SERVICES = [
26604
26803
  "task-worker",
26605
26804
  "user-init-worker",
@@ -26809,10 +27008,10 @@ function parseServerRole(value) {
26809
27008
  throw new Error(`Unsupported server role '${value}'. Use core, upload, or preview.`);
26810
27009
  }
26811
27010
  function defaultOpenMatesCloudOverlayPath(openMatesPath) {
26812
- return resolve5(join5(dirname4(resolve5(openMatesPath)), OPENMATESCLOUD_OVERLAY_DIR));
27011
+ return resolve5(join6(dirname4(resolve5(openMatesPath)), OPENMATESCLOUD_OVERLAY_DIR));
26813
27012
  }
26814
27013
  function defaultOpenMatesCloudComposeFile(overlayPath) {
26815
- return resolve5(join5(overlayPath, OPENMATESCLOUD_COMPOSE_FILE));
27014
+ return resolve5(join6(overlayPath, OPENMATESCLOUD_COMPOSE_FILE));
26816
27015
  }
26817
27016
  function planOpenMatesCloudOverlay(input) {
26818
27017
  const deploymentMode = input.deploymentMode ?? "self_host";
@@ -26955,12 +27154,11 @@ function planUpdate(input) {
26955
27154
  function planBackup(input) {
26956
27155
  const role = parseServerRole(input.role);
26957
27156
  const contentsByRole = {
26958
- core: ["postgres-dump", "directus-uploads", "directus-extensions", "vault-data", "vault-setup-data", "runtime-env", "runtime-config", "manifest", "checksums"],
26959
- upload: ["vault-data", "vault-setup-data", "runtime-env", "runtime-config", "manifest", "checksums"],
26960
- preview: ["runtime-env", "runtime-config", "preview-cache", "manifest", "checksums"]
27157
+ core: ["postgres-dump", "runtime-env", "runtime-config", "manifest", "checksums"],
27158
+ upload: ["runtime-env", "runtime-config", "manifest", "checksums"],
27159
+ preview: ["runtime-env", "runtime-config", "manifest", "checksums"]
26961
27160
  };
26962
27161
  const contents = [...contentsByRole[role]];
26963
- if (input.includeObservability) contents.push("openobserve-data", "prometheus-data");
26964
27162
  return { role, contents, fileMode: 384 };
26965
27163
  }
26966
27164
  function planRestore(input) {
@@ -27193,9 +27391,38 @@ function shouldAutoInstallRuntimeMonitoringServices(env) {
27193
27391
  return env.OPENMATES_SKIP_RUNTIME_MONITORING !== "1";
27194
27392
  }
27195
27393
 
27394
+ // src/serverBackupArchive.ts
27395
+ import { execFileSync as execFileSync3 } from "child_process";
27396
+ import { chmodSync as chmodSync3, lstatSync, readdirSync as readdirSync4, renameSync as renameSync3, rmSync as rmSync5 } from "fs";
27397
+ import { randomUUID as randomUUID8 } from "crypto";
27398
+ function assertRegularBackupTree(path2) {
27399
+ const stat2 = lstatSync(path2);
27400
+ if (stat2.isDirectory()) {
27401
+ for (const entry of readdirSync4(path2)) assertRegularBackupTree(`${path2}/${entry}`);
27402
+ return;
27403
+ }
27404
+ if (!stat2.isFile() || stat2.nlink !== 1) {
27405
+ throw new Error(`Backup archive refused unsafe entry: ${path2}`);
27406
+ }
27407
+ }
27408
+ function publishServerBackupArchive(sourceDir, archivePath, options = {}) {
27409
+ assertRegularBackupTree(sourceDir);
27410
+ const temporaryArchivePath = `${archivePath}.tmp-${randomUUID8()}`;
27411
+ const previousUmask = process.umask(63);
27412
+ try {
27413
+ execFileSync3(options.tarCommand ?? "tar", ["-czf", temporaryArchivePath, "-C", sourceDir, "."], { stdio: "pipe" });
27414
+ chmodSync3(temporaryArchivePath, 384);
27415
+ renameSync3(temporaryArchivePath, archivePath);
27416
+ chmodSync3(archivePath, 384);
27417
+ } finally {
27418
+ process.umask(previousUmask);
27419
+ rmSync5(temporaryArchivePath, { force: true });
27420
+ }
27421
+ }
27422
+
27196
27423
  // src/serverHealth.ts
27197
27424
  import dnsModule, { promises as dns } from "dns";
27198
- import { createHmac as createHmac4, randomUUID as randomUUID8 } from "crypto";
27425
+ import { createHmac as createHmac4, randomUUID as randomUUID9 } from "crypto";
27199
27426
  import { promises as fs } from "fs";
27200
27427
  import { isIP } from "net";
27201
27428
  import { request as httpsRequest } from "https";
@@ -27578,7 +27805,7 @@ async function resolveGenericWebhookTarget(rawUrl, allowLocalDevelopmentFixture
27578
27805
  async function sendGenericWebhook(target, secret, payload, allowLocalDevelopmentFixture = false) {
27579
27806
  const { url, addresses } = await resolveGenericWebhookTarget(target, allowLocalDevelopmentFixture);
27580
27807
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
27581
- const eventId = randomUUID8();
27808
+ const eventId = randomUUID9();
27582
27809
  const signed = signRuntimeWebhookPayload(payload, secret, timestamp, eventId);
27583
27810
  const selected = addresses[0];
27584
27811
  await new Promise((resolve11, reject) => {
@@ -27787,16 +28014,16 @@ function signRuntimeWebhookPayload(payload, secret, timestamp, eventId) {
27787
28014
 
27788
28015
  // src/serverUpdateState.ts
27789
28016
  import { randomBytes as randomBytes7 } from "crypto";
27790
- import { chmodSync as chmodSync3, closeSync, existsSync as existsSync8, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync6, renameSync, rmSync as rmSync4, writeFileSync as writeFileSync4 } from "fs";
27791
- import { dirname as dirname5, join as join6 } from "path";
28017
+ import { chmodSync as chmodSync4, closeSync, existsSync as existsSync9, mkdirSync as mkdirSync6, openSync, readFileSync as readFileSync7, renameSync as renameSync4, rmSync as rmSync6, writeFileSync as writeFileSync5 } from "fs";
28018
+ import { dirname as dirname5, join as join7 } from "path";
27792
28019
  function serverUpdateStatusFile(installPath, role) {
27793
- return join6(installPath, ".openmates", `${role}-update-status.json`);
28020
+ return join7(installPath, ".openmates", `${role}-update-status.json`);
27794
28021
  }
27795
28022
  function readServerUpdateStatus(installPath, role) {
27796
28023
  const filePath = serverUpdateStatusFile(installPath, role);
27797
- if (!existsSync8(filePath)) return {};
28024
+ if (!existsSync9(filePath)) return {};
27798
28025
  try {
27799
- const parsed = JSON.parse(readFileSync6(filePath, "utf-8"));
28026
+ const parsed = JSON.parse(readFileSync7(filePath, "utf-8"));
27800
28027
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
27801
28028
  return { statusReadError: "invalid_update_status" };
27802
28029
  }
@@ -27807,23 +28034,23 @@ function readServerUpdateStatus(installPath, role) {
27807
28034
  }
27808
28035
  function writeServerUpdateStatus(installPath, role, status) {
27809
28036
  const filePath = serverUpdateStatusFile(installPath, role);
27810
- mkdirSync5(dirname5(filePath), { recursive: true, mode: 448 });
28037
+ mkdirSync6(dirname5(filePath), { recursive: true, mode: 448 });
27811
28038
  const temporaryPath = `${filePath}.${process.pid}.${randomBytes7(4).toString("hex")}.tmp`;
27812
- writeFileSync4(temporaryPath, `${JSON.stringify({ role, updated_at: (/* @__PURE__ */ new Date()).toISOString(), ...status }, null, 2)}
28039
+ writeFileSync5(temporaryPath, `${JSON.stringify({ role, updated_at: (/* @__PURE__ */ new Date()).toISOString(), ...status }, null, 2)}
27813
28040
  `, { mode: 384 });
27814
- renameSync(temporaryPath, filePath);
27815
- chmodSync3(filePath, 384);
28041
+ renameSync4(temporaryPath, filePath);
28042
+ chmodSync4(filePath, 384);
27816
28043
  }
27817
28044
  function acquireServerUpdateLock(installPath) {
27818
- const stateDir2 = join6(installPath, ".openmates");
27819
- const lockPath = join6(stateDir2, "server-update.lock");
27820
- mkdirSync5(stateDir2, { recursive: true, mode: 448 });
28045
+ const stateDir2 = join7(installPath, ".openmates");
28046
+ const lockPath = join7(stateDir2, "server-update.lock");
28047
+ mkdirSync6(stateDir2, { recursive: true, mode: 448 });
27821
28048
  let descriptor;
27822
28049
  try {
27823
28050
  descriptor = openSync(lockPath, "wx", 384);
27824
28051
  } catch (error) {
27825
28052
  if (error.code !== "EEXIST") throw error;
27826
- const lockOwner = readFileSync6(lockPath, "utf8").trim();
28053
+ const lockOwner = readFileSync7(lockPath, "utf8").trim();
27827
28054
  const lockPid = Number.parseInt(lockOwner.split(":", 1)[0] ?? "", 10);
27828
28055
  let stale = false;
27829
28056
  try {
@@ -27835,17 +28062,17 @@ function acquireServerUpdateLock(installPath) {
27835
28062
  throw new Error(stale ? "A stale OpenMates server update lock exists for this installation; verify no update is running, then remove .openmates/server-update.lock explicitly." : "Another OpenMates server update is already running for this installation.");
27836
28063
  }
27837
28064
  const ownershipToken = `${process.pid}:${randomBytes7(16).toString("hex")}`;
27838
- writeFileSync4(descriptor, ownershipToken);
28065
+ writeFileSync5(descriptor, ownershipToken);
27839
28066
  return () => {
27840
28067
  closeSync(descriptor);
27841
- if (existsSync8(lockPath) && readFileSync6(lockPath, "utf8").trim() === ownershipToken) {
27842
- rmSync4(lockPath);
28068
+ if (existsSync9(lockPath) && readFileSync7(lockPath, "utf8").trim() === ownershipToken) {
28069
+ rmSync6(lockPath);
27843
28070
  }
27844
28071
  };
27845
28072
  }
27846
28073
 
27847
28074
  // src/serverQuickTest.ts
27848
- import { randomUUID as randomUUID9 } from "crypto";
28075
+ import { randomUUID as randomUUID10 } from "crypto";
27849
28076
  var QUICK_TEST_RESPONSE_TIMEOUT_MS = 12e4;
27850
28077
  var QUICK_TEST_WEB_QUERY = "site:openmates.org OpenMates";
27851
28078
  var QUICK_TEST_EXPECTED_RESPONSE = "server quick test passed";
@@ -27911,7 +28138,7 @@ function decideQuickServerTestAction(input) {
27911
28138
  async function runQuickServerTest(client, options = {}) {
27912
28139
  const now = options.now ?? Date.now;
27913
28140
  const checks = [];
27914
- const chatId = randomUUID9();
28141
+ const chatId = randomUUID10();
27915
28142
  let chatMayExist = false;
27916
28143
  checks.push({ id: "account.session", status: "passed", duration_ms: 0 });
27917
28144
  const createStarted = now();
@@ -28011,22 +28238,22 @@ function containsBoundedWebResult(value) {
28011
28238
  }
28012
28239
 
28013
28240
  // src/server.ts
28014
- var SOURCE_COMPOSE_FILE = join7("backend", "core", "docker-compose.yml");
28241
+ var SOURCE_COMPOSE_FILE = join8("backend", "core", "docker-compose.yml");
28015
28242
  var ROLE_IMAGE_COMPOSE_FILES = {
28016
- core: join7("backend", "core", "docker-compose.selfhost.yml"),
28017
- upload: join7("backend", "upload", "docker-compose.yml"),
28018
- preview: join7("backend", "preview", "docker-compose.preview.yml")
28243
+ core: join8("backend", "core", "docker-compose.selfhost.yml"),
28244
+ upload: join8("backend", "upload", "docker-compose.yml"),
28245
+ preview: join8("backend", "preview", "docker-compose.preview.yml")
28019
28246
  };
28020
28247
  var ROLE_TEMPLATE_FILES = {
28021
- core: join7("core", "docker-compose.selfhost.yml"),
28022
- upload: join7("upload", "docker-compose.yml"),
28023
- preview: join7("preview", "docker-compose.preview.yml")
28248
+ core: join8("core", "docker-compose.selfhost.yml"),
28249
+ upload: join8("upload", "docker-compose.yml"),
28250
+ preview: join8("preview", "docker-compose.preview.yml")
28024
28251
  };
28025
- var CORE_NO_WEBAPP_TEMPLATE_FILE = join7("core", "docker-compose.no-webapp.yml");
28026
- var CORE_PROMTAIL_CONFIG_FILE = join7("backend", "core", "monitoring", "promtail", "promtail-config.yaml");
28027
- var CORE_ALERTMANAGER_CONFIG_FILE = join7("backend", "core", "monitoring", "alertmanager", "alertmanager.yml");
28028
- var COMPOSE_OVERRIDE2 = join7("backend", "core", "docker-compose.override.yml");
28029
- var DEFAULT_INSTALL_PATH = join7(homedir6(), "openmates");
28252
+ var CORE_NO_WEBAPP_TEMPLATE_FILE = join8("core", "docker-compose.no-webapp.yml");
28253
+ var CORE_PROMTAIL_CONFIG_FILE = join8("backend", "core", "monitoring", "promtail", "promtail-config.yaml");
28254
+ var CORE_ALERTMANAGER_CONFIG_FILE = join8("backend", "core", "monitoring", "alertmanager", "alertmanager.yml");
28255
+ var COMPOSE_OVERRIDE2 = join8("backend", "core", "docker-compose.override.yml");
28256
+ var DEFAULT_INSTALL_PATH = join8(homedir6(), "openmates");
28030
28257
  var REPO_URL = "https://github.com/glowingkitty/OpenMates.git";
28031
28258
  var ROLE_PROVENANCE_SERVICE = {
28032
28259
  core: "api",
@@ -28042,6 +28269,7 @@ var HEALTH_REQUEST_TIMEOUT_MS = 5e3;
28042
28269
  var CHECKSUM_BUFFER_BYTES = 1024 * 1024;
28043
28270
  var ENV_BACKUP_PREFIX = ".env.openmates-backup-";
28044
28271
  var ENV_BACKUP_RETENTION_COUNT = 5;
28272
+ var BACKUP_FORMAT_VERSION = 2;
28045
28273
  var IMAGE_CHANNEL_TAGS = {
28046
28274
  stable: MAIN_BRANCH,
28047
28275
  main: MAIN_BRANCH,
@@ -28115,8 +28343,8 @@ scrape_configs:
28115
28343
  - labels:
28116
28344
  level: log_level
28117
28345
  `;
28118
- var BACKEND_CONFIG_FILE = join7("backend", "config", "backend_config.yml");
28119
- var IMAGE_RUNTIME_CONFIG_FILE = join7("config", "backend_config.yml");
28346
+ var BACKEND_CONFIG_FILE = join8("backend", "config", "backend_config.yml");
28347
+ var IMAGE_RUNTIME_CONFIG_FILE = join8("config", "backend_config.yml");
28120
28348
  var LOCAL_AI_MODELS_FILE = "local-ai-models.yml";
28121
28349
  var OFF_BY_DEFAULT_FEATURES = /* @__PURE__ */ new Map([
28122
28350
  ["embed:code:application", "Application previews are still unstable"],
@@ -28252,9 +28480,9 @@ function runInteractive(cmd, args, cwd) {
28252
28480
  });
28253
28481
  }
28254
28482
  function beginEngineeringRuntimeOperation(installPath, operationType, services) {
28255
- const manager = join7(installPath, "scripts", "engineering_control_plane.py");
28256
- const sharedConfig = join7(homedir6(), ".config", "openmates", "engineering-control-plane.env");
28257
- if (!existsSync9(manager) || !existsSync9(sharedConfig)) return null;
28483
+ const manager = join8(installPath, "scripts", "engineering_control_plane.py");
28484
+ const sharedConfig = join8(homedir6(), ".config", "openmates", "engineering-control-plane.env");
28485
+ if (!existsSync10(manager) || !existsSync10(sharedConfig)) return null;
28258
28486
  const requestedBy = process.env.OPENCODE_SESSION_ID || `openmates-cli:${process.pid}`;
28259
28487
  const args = [
28260
28488
  manager,
@@ -28268,7 +28496,7 @@ function beginEngineeringRuntimeOperation(installPath, operationType, services)
28268
28496
  "dev-stack"
28269
28497
  ];
28270
28498
  for (const service of services) args.push("--service", service);
28271
- const output = execFileSync3("python3", args, {
28499
+ const output = execFileSync4("python3", args, {
28272
28500
  cwd: installPath,
28273
28501
  encoding: "utf-8",
28274
28502
  stdio: ["ignore", "pipe", "pipe"]
@@ -28278,8 +28506,8 @@ function beginEngineeringRuntimeOperation(installPath, operationType, services)
28278
28506
  return parsed.operation_key;
28279
28507
  }
28280
28508
  function finishEngineeringRuntimeOperation(installPath, operationKey, status) {
28281
- const manager = join7(installPath, "scripts", "engineering_control_plane.py");
28282
- execFileSync3(
28509
+ const manager = join8(installPath, "scripts", "engineering_control_plane.py");
28510
+ execFileSync4(
28283
28511
  "python3",
28284
28512
  [manager, "operation", "update", "--operation-key", operationKey, "--status", status],
28285
28513
  { cwd: installPath, stdio: ["ignore", "ignore", "pipe"] }
@@ -28307,8 +28535,8 @@ function loadConfigForInstallPath(installPath) {
28307
28535
  }
28308
28536
  function getInstallMode(installPath, config = loadConfigForInstallPath(installPath)) {
28309
28537
  if (config?.installMode) return config.installMode;
28310
- if (existsSync9(join7(installPath, SOURCE_COMPOSE_FILE))) return "source";
28311
- if (Object.values(ROLE_IMAGE_COMPOSE_FILES).some((composeFile) => existsSync9(join7(installPath, composeFile)))) return "image";
28538
+ if (existsSync10(join8(installPath, SOURCE_COMPOSE_FILE))) return "source";
28539
+ if (Object.values(ROLE_IMAGE_COMPOSE_FILES).some((composeFile) => existsSync10(join8(installPath, composeFile)))) return "image";
28312
28540
  return "source";
28313
28541
  }
28314
28542
  function getServerRole(flags, config) {
@@ -28439,26 +28667,26 @@ function composeArgs(installPath, withOverrides, installMode = getInstallMode(in
28439
28667
  withOverrides,
28440
28668
  installMode,
28441
28669
  role,
28442
- overrideExists: existsSync9(join7(installPath, COMPOSE_OVERRIDE2)),
28670
+ overrideExists: existsSync10(join8(installPath, COMPOSE_OVERRIDE2)),
28443
28671
  deploymentMode,
28444
28672
  overlayPath,
28445
28673
  overlayComposeFile,
28446
- overlayExists: deploymentMode === "official_cloud" && existsSync9(resolvedOverlayPath) && existsSync9(overlayComposeFile)
28674
+ overlayExists: deploymentMode === "official_cloud" && existsSync10(resolvedOverlayPath) && existsSync10(overlayComposeFile)
28447
28675
  });
28448
28676
  }
28449
28677
  function ensureGitWorkDirEnv(installPath) {
28450
- const envPath = join7(installPath, ".env");
28451
- if (!existsSync9(envPath)) return;
28452
- const content = readFileSync7(envPath, "utf-8");
28678
+ const envPath = join8(installPath, ".env");
28679
+ if (!existsSync10(envPath)) return;
28680
+ const content = readFileSync8(envPath, "utf-8");
28453
28681
  const lineRegex = /^GIT_WORK_DIR=.*$/m;
28454
28682
  const value = `GIT_WORK_DIR=${installPath}`;
28455
28683
  if (lineRegex.test(content)) {
28456
28684
  const next = content.replace(lineRegex, value);
28457
- if (next !== content) writeFileSync5(envPath, next);
28685
+ if (next !== content) writeFileSync6(envPath, next);
28458
28686
  return;
28459
28687
  }
28460
28688
  const separator2 = content.endsWith("\n") ? "" : "\n";
28461
- writeFileSync5(envPath, `${content}${separator2}${value}
28689
+ writeFileSync6(envPath, `${content}${separator2}${value}
28462
28690
  `);
28463
28691
  }
28464
28692
  function requireDocker() {
@@ -28479,7 +28707,7 @@ function requireGit() {
28479
28707
  }
28480
28708
  function getPackageVersion() {
28481
28709
  try {
28482
- const packageJson = JSON.parse(readFileSync7(new URL("../package.json", import.meta.url), "utf-8"));
28710
+ const packageJson = JSON.parse(readFileSync8(new URL("../package.json", import.meta.url), "utf-8"));
28483
28711
  return packageJson.version ?? "";
28484
28712
  } catch {
28485
28713
  return "";
@@ -28572,20 +28800,20 @@ async function fetchText(url) {
28572
28800
  return response.text();
28573
28801
  }
28574
28802
  function packagedTemplatePath(role) {
28575
- return join7(dirname6(new URL(import.meta.url).pathname), "..", "templates", ROLE_TEMPLATE_FILES[role]);
28803
+ return join8(dirname6(new URL(import.meta.url).pathname), "..", "templates", ROLE_TEMPLATE_FILES[role]);
28576
28804
  }
28577
28805
  function packagedNoWebappTemplatePath() {
28578
- return join7(dirname6(new URL(import.meta.url).pathname), "..", "templates", CORE_NO_WEBAPP_TEMPLATE_FILE);
28806
+ return join8(dirname6(new URL(import.meta.url).pathname), "..", "templates", CORE_NO_WEBAPP_TEMPLATE_FILE);
28579
28807
  }
28580
28808
  function packagedCaddyTemplatePath(role) {
28581
- return join7(dirname6(new URL(import.meta.url).pathname), "..", "templates", "caddy", role, "Caddyfile");
28809
+ return join8(dirname6(new URL(import.meta.url).pathname), "..", "templates", "caddy", role, "Caddyfile");
28582
28810
  }
28583
28811
  function packagedCoreAlertmanagerTemplatePath() {
28584
- return join7(dirname6(new URL(import.meta.url).pathname), "..", "templates", "core", "monitoring", "alertmanager", "alertmanager.yml");
28812
+ return join8(dirname6(new URL(import.meta.url).pathname), "..", "templates", "core", "monitoring", "alertmanager", "alertmanager.yml");
28585
28813
  }
28586
28814
  function readOfficialCloudNoWebappComposeTemplate() {
28587
28815
  const packaged = packagedNoWebappTemplatePath();
28588
- if (existsSync9(packaged)) return readFileSync7(packaged, "utf-8");
28816
+ if (existsSync10(packaged)) return readFileSync8(packaged, "utf-8");
28589
28817
  return [
28590
28818
  "# backend/core/docker-compose.no-webapp.yml",
28591
28819
  "#",
@@ -28600,51 +28828,51 @@ function readOfficialCloudNoWebappComposeTemplate() {
28600
28828
  }
28601
28829
  function ensureOfficialCloudNoWebappComposeFile(installPath, deploymentMode, role) {
28602
28830
  if (deploymentMode !== "official_cloud" || role !== "core") return;
28603
- const filePath = join7(installPath, OFFICIAL_CLOUD_NO_WEBAPP_COMPOSE_FILE);
28604
- if (existsSync9(filePath)) return;
28605
- mkdirSync6(dirname6(filePath), { recursive: true });
28606
- writeFileSync5(filePath, readOfficialCloudNoWebappComposeTemplate());
28831
+ const filePath = join8(installPath, OFFICIAL_CLOUD_NO_WEBAPP_COMPOSE_FILE);
28832
+ if (existsSync10(filePath)) return;
28833
+ mkdirSync7(dirname6(filePath), { recursive: true });
28834
+ writeFileSync6(filePath, readOfficialCloudNoWebappComposeTemplate());
28607
28835
  }
28608
28836
  function fileHash(path2) {
28609
- if (!existsSync9(path2)) return null;
28610
- return createHash12("sha256").update(readFileSync7(path2)).digest("hex");
28837
+ if (!existsSync10(path2)) return null;
28838
+ return createHash13("sha256").update(readFileSync8(path2)).digest("hex");
28611
28839
  }
28612
28840
  async function loadSelfHostComposeTemplate(templateRef, role) {
28613
28841
  const templateDir = process.env.OPENMATES_SELFHOST_TEMPLATE_DIR;
28614
28842
  if (templateDir) {
28615
- return readFileSync7(join7(resolve6(templateDir), ROLE_TEMPLATE_FILES[role]), "utf-8");
28843
+ return readFileSync8(join8(resolve6(templateDir), ROLE_TEMPLATE_FILES[role]), "utf-8");
28616
28844
  }
28617
28845
  const overrideUrl = process.env.OPENMATES_SELFHOST_COMPOSE_URL;
28618
28846
  if (overrideUrl) {
28619
28847
  return fetchText(overrideUrl);
28620
28848
  }
28621
28849
  const packaged = packagedTemplatePath(role);
28622
- if (existsSync9(packaged)) return readFileSync7(packaged, "utf-8");
28850
+ if (existsSync10(packaged)) return readFileSync8(packaged, "utf-8");
28623
28851
  return fetchText(`https://raw.githubusercontent.com/glowingkitty/OpenMates/${templateRef}/${ROLE_IMAGE_COMPOSE_FILES[role]}`);
28624
28852
  }
28625
28853
  async function writeImageModeRuntimeFiles(installPath, imageTag, role) {
28626
- const roleDir = join7(installPath, "backend", role === "core" ? "core" : role);
28627
- const vaultConfigDir = join7(roleDir, "vault", "config");
28628
- mkdirSync6(vaultConfigDir, { recursive: true });
28629
- mkdirSync6(join7(installPath, "config", "providers"), { recursive: true });
28630
- writeFileSync5(join7(installPath, ROLE_IMAGE_COMPOSE_FILES[role]), await loadSelfHostComposeTemplate(templateRefForImageTag(imageTag, getPackageVersion()), role));
28854
+ const roleDir = join8(installPath, "backend", role === "core" ? "core" : role);
28855
+ const vaultConfigDir = join8(roleDir, "vault", "config");
28856
+ mkdirSync7(vaultConfigDir, { recursive: true });
28857
+ mkdirSync7(join8(installPath, "config", "providers"), { recursive: true });
28858
+ writeFileSync6(join8(installPath, ROLE_IMAGE_COMPOSE_FILES[role]), await loadSelfHostComposeTemplate(templateRefForImageTag(imageTag, getPackageVersion()), role));
28631
28859
  if (role === "core") {
28632
- writeFileSync5(join7(installPath, OFFICIAL_CLOUD_NO_WEBAPP_COMPOSE_FILE), readOfficialCloudNoWebappComposeTemplate());
28860
+ writeFileSync6(join8(installPath, OFFICIAL_CLOUD_NO_WEBAPP_COMPOSE_FILE), readOfficialCloudNoWebappComposeTemplate());
28633
28861
  }
28634
28862
  if (role === "core") {
28635
- const promtailConfigPath = join7(installPath, CORE_PROMTAIL_CONFIG_FILE);
28636
- mkdirSync6(dirname6(promtailConfigPath), { recursive: true });
28637
- writeFileSync5(promtailConfigPath, SELFHOST_PROMTAIL_CONFIG_TEMPLATE);
28863
+ const promtailConfigPath = join8(installPath, CORE_PROMTAIL_CONFIG_FILE);
28864
+ mkdirSync7(dirname6(promtailConfigPath), { recursive: true });
28865
+ writeFileSync6(promtailConfigPath, SELFHOST_PROMTAIL_CONFIG_TEMPLATE);
28638
28866
  const alertmanagerTemplatePath = packagedCoreAlertmanagerTemplatePath();
28639
- if (!existsSync9(alertmanagerTemplatePath)) throw new Error(`Packaged Alertmanager config not found: ${alertmanagerTemplatePath}`);
28640
- const alertmanagerConfigPath = join7(installPath, CORE_ALERTMANAGER_CONFIG_FILE);
28641
- mkdirSync6(dirname6(alertmanagerConfigPath), { recursive: true });
28867
+ if (!existsSync10(alertmanagerTemplatePath)) throw new Error(`Packaged Alertmanager config not found: ${alertmanagerTemplatePath}`);
28868
+ const alertmanagerConfigPath = join8(installPath, CORE_ALERTMANAGER_CONFIG_FILE);
28869
+ mkdirSync7(dirname6(alertmanagerConfigPath), { recursive: true });
28642
28870
  copyFileSync2(alertmanagerTemplatePath, alertmanagerConfigPath);
28643
28871
  }
28644
- writeFileSync5(join7(vaultConfigDir, "vault.hcl"), VAULT_CONFIG_TEMPLATE);
28872
+ writeFileSync6(join8(vaultConfigDir, "vault.hcl"), VAULT_CONFIG_TEMPLATE);
28645
28873
  ensureImageRuntimeConfig(installPath);
28646
- const envPath = join7(installPath, ".env");
28647
- let envContent = existsSync9(envPath) ? readFileSync7(envPath, "utf-8") : MINIMAL_ENV_TEMPLATE;
28874
+ const envPath = join8(installPath, ".env");
28875
+ let envContent = existsSync10(envPath) ? readFileSync8(envPath, "utf-8") : MINIMAL_ENV_TEMPLATE;
28648
28876
  envContent = setEnvIfEmpty(envContent, "DATABASE_ADMIN_PASSWORD", randomHex(12));
28649
28877
  envContent = setEnvIfEmpty(envContent, "DATABASE_PASSWORD", randomHex(12));
28650
28878
  envContent = setEnvIfEmpty(envContent, "DIRECTUS_TOKEN", randomHex(32));
@@ -28656,13 +28884,13 @@ async function writeImageModeRuntimeFiles(installPath, imageTag, role) {
28656
28884
  envContent = setEnvVar(envContent, "OPENMATES_IMAGE_TAG", imageTag);
28657
28885
  envContent = setEnvVar(envContent, "OPENMATES_IMAGE_REGISTRY", DEFAULT_IMAGE_REGISTRY);
28658
28886
  envContent = setEnvVar(envContent, "GIT_WORK_DIR", installPath);
28659
- writeFileSync5(envPath, envContent.endsWith("\n") ? envContent : `${envContent}
28887
+ writeFileSync6(envPath, envContent.endsWith("\n") ? envContent : `${envContent}
28660
28888
  `);
28661
28889
  }
28662
28890
  function getImageTagFromEnv(installPath, config) {
28663
- const envPath = join7(installPath, ".env");
28664
- if (existsSync9(envPath)) {
28665
- const envTag = getEnvVar(readFileSync7(envPath, "utf-8"), "OPENMATES_IMAGE_TAG");
28891
+ const envPath = join8(installPath, ".env");
28892
+ if (existsSync10(envPath)) {
28893
+ const envTag = getEnvVar(readFileSync8(envPath, "utf-8"), "OPENMATES_IMAGE_TAG");
28666
28894
  if (envTag) return envTag;
28667
28895
  }
28668
28896
  return config?.imageTag ?? "";
@@ -28695,8 +28923,8 @@ async function waitForServerHealth(installPath, role = "core", options = {}) {
28695
28923
  }
28696
28924
  throw new Error(`Updated ${role} server did not pass health checks in time. Tried ${healthUrl}.`);
28697
28925
  }
28698
- const envPath = join7(installPath, ".env");
28699
- const envContent = existsSync9(envPath) ? readFileSync7(envPath, "utf-8") : "";
28926
+ const envPath = join8(installPath, ".env");
28927
+ const envContent = existsSync10(envPath) ? readFileSync8(envPath, "utf-8") : "";
28700
28928
  const urls = deriveSelfHostCliUrls(envContent);
28701
28929
  const apiHealthUrl = `${trailingSlashTrimmed(urls.apiUrl)}/health`;
28702
28930
  const appUrl = trailingSlashTrimmed(urls.appUrl);
@@ -28719,9 +28947,9 @@ function defaultCloneBranchForVersion(version) {
28719
28947
  return /-(alpha|beta|rc)(\.|\d|$)/.test(version) ? DEV_BRANCH : null;
28720
28948
  }
28721
28949
  function hasLlmCredentials(envPath) {
28722
- if (!existsSync9(envPath)) return false;
28950
+ if (!existsSync10(envPath)) return false;
28723
28951
  if (hasLocalAiModels(dirname6(envPath))) return true;
28724
- const content = readFileSync7(envPath, "utf-8");
28952
+ const content = readFileSync8(envPath, "utf-8");
28725
28953
  for (const line of content.split("\n")) {
28726
28954
  const trimmed = line.trim();
28727
28955
  if (trimmed.startsWith("#") || !trimmed) continue;
@@ -28736,8 +28964,8 @@ function hasLlmCredentials(envPath) {
28736
28964
  return false;
28737
28965
  }
28738
28966
  function warnIfMissingLlmCredentials(installPath) {
28739
- const envPath = join7(installPath, ".env");
28740
- if (!existsSync9(envPath)) {
28967
+ const envPath = join8(installPath, ".env");
28968
+ if (!existsSync10(envPath)) {
28741
28969
  console.error(
28742
28970
  "No .env file found. Run 'openmates server install' first, or create .env from .env.example."
28743
28971
  );
@@ -28762,22 +28990,22 @@ function printJson(data) {
28762
28990
  console.log(JSON.stringify(data, null, 2));
28763
28991
  }
28764
28992
  function localModelsOverlayPath(installPath, installMode = getInstallMode(installPath)) {
28765
- if (installMode === "source") return join7(installPath, "backend", "providers", LOCAL_AI_MODELS_FILE);
28766
- return join7(installPath, "config", "providers", LOCAL_AI_MODELS_FILE);
28993
+ if (installMode === "source") return join8(installPath, "backend", "providers", LOCAL_AI_MODELS_FILE);
28994
+ return join8(installPath, "config", "providers", LOCAL_AI_MODELS_FILE);
28767
28995
  }
28768
28996
  function imageBackendConfigPath(installPath) {
28769
- return join7(installPath, IMAGE_RUNTIME_CONFIG_FILE);
28997
+ return join8(installPath, IMAGE_RUNTIME_CONFIG_FILE);
28770
28998
  }
28771
28999
  function ensureImageRuntimeConfig(installPath) {
28772
29000
  const configPath = imageBackendConfigPath(installPath);
28773
- if (existsSync9(configPath)) return;
28774
- mkdirSync6(dirname6(configPath), { recursive: true });
28775
- writeFileSync5(configPath, renderFeatureOverrides({ enabled: [], disabled: [] }));
29001
+ if (existsSync10(configPath)) return;
29002
+ mkdirSync7(dirname6(configPath), { recursive: true });
29003
+ writeFileSync6(configPath, renderFeatureOverrides({ enabled: [], disabled: [] }));
28776
29004
  }
28777
29005
  function readLocalModelOverlay(path2) {
28778
- if (!existsSync9(path2)) return { providers: [] };
29006
+ if (!existsSync10(path2)) return { providers: [] };
28779
29007
  try {
28780
- const parsed = JSON.parse(readFileSync7(path2, "utf-8"));
29008
+ const parsed = JSON.parse(readFileSync8(path2, "utf-8"));
28781
29009
  return { providers: Array.isArray(parsed.providers) ? parsed.providers : [] };
28782
29010
  } catch (error) {
28783
29011
  throw new Error(
@@ -28786,15 +29014,15 @@ function readLocalModelOverlay(path2) {
28786
29014
  }
28787
29015
  }
28788
29016
  function writeLocalModelOverlay(path2, overlay) {
28789
- mkdirSync6(dirname6(path2), { recursive: true });
28790
- writeFileSync5(path2, `${JSON.stringify(overlay, null, 2)}
29017
+ mkdirSync7(dirname6(path2), { recursive: true });
29018
+ writeFileSync6(path2, `${JSON.stringify(overlay, null, 2)}
28791
29019
  `);
28792
29020
  }
28793
29021
  function hasLocalAiModels(installPath) {
28794
- const imagePath = join7(installPath, "config", "providers", LOCAL_AI_MODELS_FILE);
28795
- const sourcePath = join7(installPath, "backend", "providers", LOCAL_AI_MODELS_FILE);
29022
+ const imagePath = join8(installPath, "config", "providers", LOCAL_AI_MODELS_FILE);
29023
+ const sourcePath = join8(installPath, "backend", "providers", LOCAL_AI_MODELS_FILE);
28796
29024
  return [imagePath, sourcePath].some((path2) => {
28797
- if (!existsSync9(path2)) return false;
29025
+ if (!existsSync10(path2)) return false;
28798
29026
  try {
28799
29027
  return readLocalModelOverlay(path2).providers.some((provider) => provider.models.length > 0);
28800
29028
  } catch {
@@ -28835,10 +29063,10 @@ function nowStamp() {
28835
29063
  return (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
28836
29064
  }
28837
29065
  function backupRoot(installPath) {
28838
- return join7(installPath, "backups");
29066
+ return join8(installPath, "backups");
28839
29067
  }
28840
29068
  function roleBackupDir(installPath, role) {
28841
- return join7(backupRoot(installPath), role);
29069
+ return join8(backupRoot(installPath), role);
28842
29070
  }
28843
29071
  function persistQuickServerTestOutcome(installPath, role, outcome) {
28844
29072
  const current = readServerUpdateStatus(installPath, role);
@@ -28939,13 +29167,13 @@ function targetSourceLinks(imageTag, templateRef) {
28939
29167
  };
28940
29168
  }
28941
29169
  function installedImageMetadata(input) {
28942
- const containerId = execFileSync3(
29170
+ const containerId = execFileSync4(
28943
29171
  "docker",
28944
29172
  [...composeArgs(input.installPath, input.withOverrides, "image", input.role), "ps", "-q", ROLE_PROVENANCE_SERVICE[input.role]],
28945
29173
  { cwd: input.installPath, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
28946
29174
  ).trim();
28947
29175
  if (!containerId) throw new Error("updated_container_unavailable");
28948
- const labels = JSON.parse(execFileSync3(
29176
+ const labels = JSON.parse(execFileSync4(
28949
29177
  "docker",
28950
29178
  ["inspect", "--format", "{{json .Config.Labels}}", containerId],
28951
29179
  { cwd: input.installPath, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }
@@ -28987,15 +29215,15 @@ function sourceUpdateMetadata(installPath) {
28987
29215
  };
28988
29216
  }
28989
29217
  function copyIfExists(source, destination) {
28990
- if (!existsSync9(source)) return;
28991
- mkdirSync6(dirname6(destination), { recursive: true });
29218
+ if (!existsSync10(source)) return;
29219
+ mkdirSync7(dirname6(destination), { recursive: true });
28992
29220
  cpSync(source, destination, { recursive: true, force: true });
28993
29221
  }
28994
29222
  function readEnvMap(installPath) {
28995
- const envPath = join7(installPath, ".env");
28996
- if (!existsSync9(envPath)) return {};
29223
+ const envPath = join8(installPath, ".env");
29224
+ if (!existsSync10(envPath)) return {};
28997
29225
  const values = {};
28998
- for (const line of readFileSync7(envPath, "utf-8").split("\n")) {
29226
+ for (const line of readFileSync8(envPath, "utf-8").split("\n")) {
28999
29227
  const trimmed = line.trim();
29000
29228
  if (!trimmed || trimmed.startsWith("#")) continue;
29001
29229
  const eqIdx = trimmed.indexOf("=");
@@ -29005,32 +29233,32 @@ function readEnvMap(installPath) {
29005
29233
  return values;
29006
29234
  }
29007
29235
  function envPathForInstall(installPath) {
29008
- return join7(installPath, ".env");
29236
+ return join8(installPath, ".env");
29009
29237
  }
29010
29238
  function readEnvContent(installPath) {
29011
29239
  const envPath = envPathForInstall(installPath);
29012
- return existsSync9(envPath) ? readFileSync7(envPath, "utf-8") : "";
29240
+ return existsSync10(envPath) ? readFileSync8(envPath, "utf-8") : "";
29013
29241
  }
29014
29242
  function pruneEnvBackups(installPath) {
29015
- const backups = readdirSync3(installPath, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith(ENV_BACKUP_PREFIX)).map((entry) => entry.name).sort();
29243
+ const backups = readdirSync5(installPath, { withFileTypes: true }).filter((entry) => entry.isFile() && entry.name.startsWith(ENV_BACKUP_PREFIX)).map((entry) => entry.name).sort();
29016
29244
  for (const backup of backups.slice(0, Math.max(0, backups.length - ENV_BACKUP_RETENTION_COUNT))) {
29017
- rmSync5(join7(installPath, backup), { force: true });
29245
+ rmSync7(join8(installPath, backup), { force: true });
29018
29246
  }
29019
29247
  }
29020
29248
  function backupEnvFile(installPath) {
29021
29249
  const envPath = envPathForInstall(installPath);
29022
- if (!existsSync9(envPath)) return null;
29023
- const backupPath = join7(installPath, `${ENV_BACKUP_PREFIX}${nowStamp()}`);
29250
+ if (!existsSync10(envPath)) return null;
29251
+ const backupPath = join8(installPath, `${ENV_BACKUP_PREFIX}${nowStamp()}`);
29024
29252
  copyFileSync2(envPath, backupPath);
29025
- chmodSync4(backupPath, 384);
29253
+ chmodSync5(backupPath, 384);
29026
29254
  pruneEnvBackups(installPath);
29027
29255
  return backupPath;
29028
29256
  }
29029
29257
  function writeEnvContent(installPath, content) {
29030
29258
  const envPath = envPathForInstall(installPath);
29031
- mkdirSync6(dirname6(envPath), { recursive: true });
29032
- writeFileSync5(envPath, content, { mode: 384 });
29033
- chmodSync4(envPath, 384);
29259
+ mkdirSync7(dirname6(envPath), { recursive: true });
29260
+ writeFileSync6(envPath, content, { mode: 384 });
29261
+ chmodSync5(envPath, 384);
29034
29262
  }
29035
29263
  function writeDeploymentModeEnv(installPath, deploymentMode, overlayPath) {
29036
29264
  let content = readEnvContent(installPath);
@@ -29044,7 +29272,7 @@ function writeDeploymentModeEnv(installPath, deploymentMode, overlayPath) {
29044
29272
  }
29045
29273
  const resolvedOverlayPath = resolve6(overlayPath ?? defaultOpenMatesCloudOverlayPath(installPath));
29046
29274
  const overlayComposeFile = defaultOpenMatesCloudComposeFile(resolvedOverlayPath);
29047
- if (!existsSync9(resolvedOverlayPath) || !existsSync9(overlayComposeFile)) {
29275
+ if (!existsSync10(resolvedOverlayPath) || !existsSync10(overlayComposeFile)) {
29048
29276
  throw new Error(
29049
29277
  `OpenMatesCloud official-cloud mode requires an overlay checkout with ${overlayComposeFile}. Pass --openmatescloud-path <dir> or create the sibling OpenMatesCloud repository.`
29050
29278
  );
@@ -29203,7 +29431,7 @@ for item in checks:
29203
29431
  print(json.dumps({"ok": True, "presence": presence}))
29204
29432
  `;
29205
29433
  try {
29206
- const output = execFileSync3(
29434
+ const output = execFileSync4(
29207
29435
  "docker",
29208
29436
  ["exec", "-e", `OPENMATES_SECRET_CHECKS=${JSON.stringify(checks)}`, container, "python", "-c", script],
29209
29437
  { encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
@@ -29241,7 +29469,7 @@ function formatSecretPreflight(preflight) {
29241
29469
  return parts.join("; ");
29242
29470
  }
29243
29471
  function hashFile(path2) {
29244
- const hash = createHash12("sha256");
29472
+ const hash = createHash13("sha256");
29245
29473
  const fd = openSync2(path2, "r");
29246
29474
  const buffer = Buffer.allocUnsafe(CHECKSUM_BUFFER_BYTES);
29247
29475
  try {
@@ -29258,8 +29486,8 @@ function hashFile(path2) {
29258
29486
  function writeChecksums(rootDir) {
29259
29487
  const lines = [];
29260
29488
  const walk = (dir) => {
29261
- for (const entry of readdirSync3(dir, { withFileTypes: true })) {
29262
- const path2 = join7(dir, entry.name);
29489
+ for (const entry of readdirSync5(dir, { withFileTypes: true })) {
29490
+ const path2 = join8(dir, entry.name);
29263
29491
  if (entry.isDirectory()) {
29264
29492
  walk(path2);
29265
29493
  continue;
@@ -29270,13 +29498,13 @@ function writeChecksums(rootDir) {
29270
29498
  }
29271
29499
  };
29272
29500
  walk(rootDir);
29273
- writeFileSync5(join7(rootDir, "checksums.sha256"), `${lines.sort().join("\n")}
29501
+ writeFileSync6(join8(rootDir, "checksums.sha256"), `${lines.sort().join("\n")}
29274
29502
  `);
29275
29503
  }
29276
29504
  function verifyChecksums(rootDir) {
29277
- const checksumsPath = join7(rootDir, "checksums.sha256");
29278
- if (!existsSync9(checksumsPath)) throw new Error("Backup archive is missing checksums.sha256.");
29279
- for (const line of readFileSync7(checksumsPath, "utf-8").split("\n")) {
29505
+ const checksumsPath = join8(rootDir, "checksums.sha256");
29506
+ if (!existsSync10(checksumsPath)) throw new Error("Backup archive is missing checksums.sha256.");
29507
+ for (const line of readFileSync8(checksumsPath, "utf-8").split("\n")) {
29280
29508
  const trimmed = line.trim();
29281
29509
  if (!trimmed) continue;
29282
29510
  const match = trimmed.match(/^([a-f0-9]{64}) {2}(.+)$/);
@@ -29285,8 +29513,8 @@ function verifyChecksums(rootDir) {
29285
29513
  if (relative3.startsWith("/") || relative3.split(/[\\/]/).includes("..")) {
29286
29514
  throw new Error(`Unsafe checksum path in backup archive: ${relative3}`);
29287
29515
  }
29288
- const filePath = join7(rootDir, relative3);
29289
- if (!existsSync9(filePath)) throw new Error(`Backup archive is missing checksummed file: ${relative3}`);
29516
+ const filePath = join8(rootDir, relative3);
29517
+ if (!existsSync10(filePath)) throw new Error(`Backup archive is missing checksummed file: ${relative3}`);
29290
29518
  const actual = hashFile(filePath);
29291
29519
  if (actual !== match[1]) throw new Error(`Backup checksum mismatch for ${relative3}.`);
29292
29520
  }
@@ -29294,27 +29522,28 @@ function verifyChecksums(rootDir) {
29294
29522
  function createServerBackup(installPath, role, options = {}) {
29295
29523
  const plan = planBackup({ role, includeObservability: options.includeObservability });
29296
29524
  const backupDir = roleBackupDir(installPath, role);
29297
- mkdirSync6(backupDir, { recursive: true, mode: 448 });
29298
- const archivePath = options.output ? resolve6(options.output) : join7(backupDir, options.preUpdate ? `latest-pre-update-${role}.tar.gz` : `openmates-${role}-${nowStamp()}.tar.gz`);
29299
- const tempDir = mkdtempSync(join7(backupDir, ".tmp-"));
29525
+ mkdirSync7(backupDir, { recursive: true, mode: 448 });
29526
+ const archivePath = options.output ? resolve6(options.output) : join8(backupDir, options.preUpdate ? `latest-pre-update-${role}.tar.gz` : `openmates-${role}-${nowStamp()}.tar.gz`);
29527
+ mkdirSync7(dirname6(archivePath), { recursive: true, mode: 448 });
29528
+ const tempDir = mkdtempSync(join8(backupDir, ".tmp-"));
29300
29529
  const env = readEnvMap(installPath);
29301
29530
  const manifest = {
29302
29531
  role,
29532
+ backup_format_version: BACKUP_FORMAT_VERSION,
29303
29533
  created_at: (/* @__PURE__ */ new Date()).toISOString(),
29304
29534
  cli_version: getPackageVersion(),
29305
- image_tag: getEnvVar(existsSync9(join7(installPath, ".env")) ? readFileSync7(join7(installPath, ".env"), "utf-8") : "", "OPENMATES_IMAGE_TAG"),
29306
- include_observability: options.includeObservability === true,
29307
- contents: plan.contents
29535
+ image_tag: getEnvVar(existsSync10(join8(installPath, ".env")) ? readFileSync8(join8(installPath, ".env"), "utf-8") : "", "OPENMATES_IMAGE_TAG"),
29536
+ recovery_scope: role === "core" ? "database-and-runtime-only" : "runtime-only",
29537
+ include_observability: false,
29538
+ contents: plan.contents.filter((content) => content !== "runtime-env" || existsSync10(join8(installPath, ".env"))).filter((content) => content !== "runtime-config" || existsSync10(join8(installPath, "config")))
29308
29539
  };
29309
29540
  try {
29310
- writeFileSync5(join7(tempDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
29311
- `);
29312
- copyIfExists(join7(installPath, ".env"), join7(tempDir, "runtime", ".env"));
29313
- copyIfExists(join7(installPath, "config"), join7(tempDir, "runtime", "config"));
29541
+ copyIfExists(join8(installPath, ".env"), join8(tempDir, "runtime", ".env"));
29542
+ copyIfExists(join8(installPath, "config"), join8(tempDir, "runtime", "config"));
29314
29543
  if (role === "core") {
29315
29544
  const databaseUser = env.DATABASE_USERNAME || "directus";
29316
29545
  const databaseName = env.DATABASE_NAME || "directus";
29317
- const dumpFile = openSync2(join7(tempDir, "postgres.sql"), "w");
29546
+ const dumpFile = openSync2(join8(tempDir, "postgres.sql"), "w");
29318
29547
  try {
29319
29548
  const result = spawnSync(
29320
29549
  "docker",
@@ -29329,47 +29558,52 @@ function createServerBackup(installPath, role, options = {}) {
29329
29558
  closeSync2(dumpFile);
29330
29559
  }
29331
29560
  }
29332
- for (const item of [
29333
- [join7(installPath, "backend", "core", "uploads"), join7(tempDir, "directus-uploads")],
29334
- [join7(installPath, "backend", "core", "extensions"), join7(tempDir, "directus-extensions")],
29335
- [join7(installPath, "backend", role, "vault"), join7(tempDir, `${role}-vault-config`)]
29336
- ]) {
29337
- copyIfExists(item[0], item[1]);
29338
- }
29561
+ writeFileSync6(join8(tempDir, "manifest.json"), `${JSON.stringify(manifest, null, 2)}
29562
+ `, { mode: 384 });
29339
29563
  writeChecksums(tempDir);
29340
- execSync(`tar -czf ${shellQuote(archivePath)} -C ${shellQuote(tempDir)} .`, { stdio: "pipe" });
29341
- chmodSync4(archivePath, plan.fileMode);
29564
+ publishServerBackupArchive(tempDir, archivePath);
29342
29565
  return archivePath;
29343
29566
  } finally {
29344
- rmSync5(tempDir, { recursive: true, force: true });
29567
+ rmSync7(tempDir, { recursive: true, force: true });
29345
29568
  }
29346
29569
  }
29347
29570
  function restoreServerBackup(installPath, role, file) {
29348
29571
  const archivePath = resolve6(file);
29349
- if (!existsSync9(archivePath)) throw new Error(`Backup file not found: ${archivePath}`);
29350
- mkdirSync6(roleBackupDir(installPath, role), { recursive: true, mode: 448 });
29351
- const tempDir = mkdtempSync(join7(roleBackupDir(installPath, role), ".restore-"));
29572
+ if (!existsSync10(archivePath)) throw new Error(`Backup file not found: ${archivePath}`);
29573
+ mkdirSync7(roleBackupDir(installPath, role), { recursive: true, mode: 448 });
29574
+ const tempDir = mkdtempSync(join8(roleBackupDir(installPath, role), ".restore-"));
29352
29575
  const env = readEnvMap(installPath);
29353
29576
  try {
29354
29577
  execSync(`tar -xzf ${shellQuote(archivePath)} -C ${shellQuote(tempDir)}`, { stdio: "pipe" });
29355
29578
  verifyChecksums(tempDir);
29356
- const manifestPath = join7(tempDir, "manifest.json");
29357
- if (!existsSync9(manifestPath)) throw new Error("Backup archive is missing manifest.json.");
29358
- const manifest = JSON.parse(readFileSync7(manifestPath, "utf-8"));
29579
+ const manifestPath = join8(tempDir, "manifest.json");
29580
+ if (!existsSync10(manifestPath)) throw new Error("Backup archive is missing manifest.json.");
29581
+ const manifest = JSON.parse(readFileSync8(manifestPath, "utf-8"));
29359
29582
  if (manifest.role !== role) throw new Error(`Backup role '${manifest.role}' does not match requested role '${role}'.`);
29360
- copyIfExists(join7(tempDir, "runtime", ".env"), join7(installPath, ".env"));
29361
- copyIfExists(join7(tempDir, "runtime", "config"), join7(installPath, "config"));
29362
- const postgresDump = join7(tempDir, "postgres.sql");
29363
- if (role === "core" && existsSync9(postgresDump)) {
29583
+ if (role === "core" && manifest.backup_format_version === BACKUP_FORMAT_VERSION && manifest.recovery_scope !== "full-core") {
29584
+ throw new Error("This backup records database and runtime state only; refusing unsafe full core restore.");
29585
+ }
29586
+ copyIfExists(join8(tempDir, "runtime", ".env"), join8(installPath, ".env"));
29587
+ copyIfExists(join8(tempDir, "runtime", "config"), join8(installPath, "config"));
29588
+ const postgresDump = join8(tempDir, "postgres.sql");
29589
+ if (role === "core" && existsSync10(postgresDump)) {
29364
29590
  const databaseUser = env.DATABASE_USERNAME || "directus";
29365
29591
  const databaseName = env.DATABASE_NAME || "directus";
29366
- execSync(`docker exec -i cms-database psql -v ON_ERROR_STOP=1 -U ${shellQuote(databaseUser)} ${shellQuote(databaseName)}`, {
29367
- input: readFileSync7(postgresDump),
29368
- stdio: ["pipe", "pipe", "pipe"]
29369
- });
29592
+ const dumpFile = openSync2(postgresDump, "r");
29593
+ try {
29594
+ const result = spawnSync(
29595
+ "docker",
29596
+ ["exec", "-i", "cms-database", "psql", "-v", "ON_ERROR_STOP=1", "-U", databaseUser, databaseName],
29597
+ { encoding: "utf-8", stdio: [dumpFile, "pipe", "pipe"] }
29598
+ );
29599
+ if (result.error) throw result.error;
29600
+ if (result.status !== 0) throw new Error(result.stderr || `psql exited with status ${result.status}`);
29601
+ } finally {
29602
+ closeSync2(dumpFile);
29603
+ }
29370
29604
  }
29371
29605
  } finally {
29372
- rmSync5(tempDir, { recursive: true, force: true });
29606
+ rmSync7(tempDir, { recursive: true, force: true });
29373
29607
  }
29374
29608
  }
29375
29609
  function restoreStopServices(installPath, withOverrides, installMode, role) {
@@ -29638,11 +29872,11 @@ async function serverRegister(flags) {
29638
29872
  const installPath = resolveServerPath(
29639
29873
  typeof flags.path === "string" ? flags : { ...flags, path: process.cwd() }
29640
29874
  );
29641
- const envPath = join7(installPath, ".env");
29642
- if (!existsSync9(join7(installPath, SOURCE_COMPOSE_FILE))) {
29643
- throw new Error(`Source Compose file not found: ${join7(installPath, SOURCE_COMPOSE_FILE)}`);
29875
+ const envPath = join8(installPath, ".env");
29876
+ if (!existsSync10(join8(installPath, SOURCE_COMPOSE_FILE))) {
29877
+ throw new Error(`Source Compose file not found: ${join8(installPath, SOURCE_COMPOSE_FILE)}`);
29644
29878
  }
29645
- if (!existsSync9(envPath)) {
29879
+ if (!existsSync10(envPath)) {
29646
29880
  throw new Error(`Runtime env file not found: ${envPath}`);
29647
29881
  }
29648
29882
  const gitRoot = resolve6(exec("git rev-parse --show-toplevel", installPath));
@@ -29664,7 +29898,7 @@ async function serverRegister(flags) {
29664
29898
  }
29665
29899
  const overlayPath = getOpenMatesCloudOverlayPath(flags, null) ?? readEnvMap(installPath).OPENMATES_CLOUD_OVERLAY_PATH ?? defaultOpenMatesCloudOverlayPath(installPath);
29666
29900
  const overlayComposeFile = defaultOpenMatesCloudComposeFile(overlayPath);
29667
- if (deploymentMode === "official_cloud" && (!existsSync9(overlayPath) || !existsSync9(overlayComposeFile))) {
29901
+ if (deploymentMode === "official_cloud" && (!existsSync10(overlayPath) || !existsSync10(overlayComposeFile))) {
29668
29902
  throw new Error(`OpenMatesCloud overlay path is required for official-cloud mode: ${overlayPath}`);
29669
29903
  }
29670
29904
  const runtimePlan = planServerRuntime({
@@ -29674,7 +29908,7 @@ async function serverRegister(flags) {
29674
29908
  includeWebapp: deploymentMode !== "official_cloud"
29675
29909
  });
29676
29910
  const defaultServices = hasServiceFilter(flags) ? selectedComposeServices(role, flags) : runtimePlan.defaultServices;
29677
- const cliUrls = deriveSelfHostCliUrls(readFileSync7(envPath, "utf-8"));
29911
+ const cliUrls = deriveSelfHostCliUrls(readFileSync8(envPath, "utf-8"));
29678
29912
  const config = {
29679
29913
  installPath,
29680
29914
  installedAt: existing?.installPath === installPath ? existing.installedAt : Date.now(),
@@ -29716,27 +29950,27 @@ async function serverInstall(flags) {
29716
29950
  withAlerts: flags["with-alerts"] === true,
29717
29951
  includeWebapp: deploymentMode !== "official_cloud"
29718
29952
  });
29719
- if (existsSync9(join7(installPath, SOURCE_COMPOSE_FILE)) || Object.values(ROLE_IMAGE_COMPOSE_FILES).some((composeFile) => existsSync9(join7(installPath, composeFile)))) {
29953
+ if (existsSync10(join8(installPath, SOURCE_COMPOSE_FILE)) || Object.values(ROLE_IMAGE_COMPOSE_FILES).some((composeFile) => existsSync10(join8(installPath, composeFile)))) {
29720
29954
  console.error(`OpenMates already exists at ${installPath}.`);
29721
29955
  console.error("Use 'openmates server update' to update, or choose a different --path.");
29722
29956
  process.exit(1);
29723
29957
  }
29724
29958
  if (!fromSource) {
29725
29959
  requireDocker();
29726
- mkdirSync6(installPath, { recursive: true });
29960
+ mkdirSync7(installPath, { recursive: true });
29727
29961
  if (typeof flags["env-path"] === "string") {
29728
29962
  const envSource = resolve6(flags["env-path"]);
29729
- if (!existsSync9(envSource)) {
29963
+ if (!existsSync10(envSource)) {
29730
29964
  throw new Error(`Env file not found: ${envSource}`);
29731
29965
  }
29732
- copyFileSync2(envSource, join7(installPath, ".env"));
29966
+ copyFileSync2(envSource, join8(installPath, ".env"));
29733
29967
  console.error(`Copied ${envSource} to ${installPath}/.env`);
29734
29968
  }
29735
29969
  const imageTag = typeof flags["image-tag"] === "string" ? flags["image-tag"] : getDefaultImageTag();
29736
29970
  console.error(`Preparing OpenMates image-mode install at ${installPath}...`);
29737
29971
  await writeImageModeRuntimeFiles(installPath, imageTag, role);
29738
29972
  const overlay2 = writeDeploymentModeEnv(installPath, deploymentMode, requestedOverlayPath);
29739
- const cliUrls2 = deriveSelfHostCliUrls(readFileSync7(join7(installPath, ".env"), "utf-8"));
29973
+ const cliUrls2 = deriveSelfHostCliUrls(readFileSync8(join8(installPath, ".env"), "utf-8"));
29740
29974
  try {
29741
29975
  exec("docker network create openmates", installPath);
29742
29976
  } catch {
@@ -29763,7 +29997,7 @@ async function serverInstall(flags) {
29763
29997
  if (flags.json === true) {
29764
29998
  printJson({ command: "install", status: "success", path: installPath, mode: "image", imageTag });
29765
29999
  } else {
29766
- const firstInvite = getEnvVar(readFileSync7(join7(installPath, ".env"), "utf-8"), "SELF_HOST_FIRST_INVITE_CODE");
30000
+ const firstInvite = getEnvVar(readFileSync8(join8(installPath, ".env"), "utf-8"), "SELF_HOST_FIRST_INVITE_CODE");
29767
30001
  console.log(`
29768
30002
  OpenMates installed at ${installPath}`);
29769
30003
  console.log(`Mode: image (${DEFAULT_IMAGE_REGISTRY}, tag ${imageTag})`);
@@ -29801,10 +30035,10 @@ CLI default API: ${cliUrls2.apiUrl}`);
29801
30035
  }
29802
30036
  if (typeof flags["env-path"] === "string") {
29803
30037
  const envSource = resolve6(flags["env-path"]);
29804
- if (!existsSync9(envSource)) {
30038
+ if (!existsSync10(envSource)) {
29805
30039
  throw new Error(`Env file not found: ${envSource}`);
29806
30040
  }
29807
- copyFileSync2(envSource, join7(installPath, ".env"));
30041
+ copyFileSync2(envSource, join8(installPath, ".env"));
29808
30042
  console.error(`Copied ${envSource} to ${installPath}/.env`);
29809
30043
  }
29810
30044
  const translationResult = ensureSourceInstallTranslations(installPath, sourcePath);
@@ -29823,9 +30057,9 @@ CLI default API: ${cliUrls2.apiUrl}`);
29823
30057
  console.error("Setup script failed. Check the output above for details.");
29824
30058
  process.exit(setupCode);
29825
30059
  }
29826
- const envPath = join7(installPath, ".env");
30060
+ const envPath = join8(installPath, ".env");
29827
30061
  const overlay = writeDeploymentModeEnv(installPath, deploymentMode, requestedOverlayPath);
29828
- const cliUrls = deriveSelfHostCliUrls(existsSync9(envPath) ? readFileSync7(envPath, "utf-8") : "");
30062
+ const cliUrls = deriveSelfHostCliUrls(existsSync10(envPath) ? readFileSync8(envPath, "utf-8") : "");
29829
30063
  saveServerConfig({
29830
30064
  installPath,
29831
30065
  installedAt: Date.now(),
@@ -29866,15 +30100,15 @@ async function installContinuousUpdateService(flags) {
29866
30100
  const channel = typeof flags.channel === "string" ? flags.channel : config?.imageChannel ?? "main";
29867
30101
  const window = typeof flags.window === "string" ? flags.window : "02:00-04:00 UTC";
29868
30102
  const plan = planContinuousUpdateService({ role, channel, window });
29869
- const servicePath = join7("/etc", "systemd", "system", plan.serviceName);
29870
- const timerPath = join7("/etc", "systemd", "system", plan.timerName);
30103
+ const servicePath = join8("/etc", "systemd", "system", plan.serviceName);
30104
+ const timerPath = join8("/etc", "systemd", "system", plan.timerName);
29871
30105
  if (flags["dry-run"] === true || flags.json === true) {
29872
30106
  printJson({ command: "update install-service", status: "planned", role, servicePath, timerPath, unit: plan.unit, timer: plan.timer });
29873
30107
  return;
29874
30108
  }
29875
30109
  try {
29876
- writeFileSync5(servicePath, plan.unit, { mode: 420 });
29877
- writeFileSync5(timerPath, plan.timer, { mode: 420 });
30110
+ writeFileSync6(servicePath, plan.unit, { mode: 420 });
30111
+ writeFileSync6(timerPath, plan.timer, { mode: 420 });
29878
30112
  execSync("systemctl daemon-reload", { stdio: "pipe" });
29879
30113
  execSync(`systemctl enable --now ${shellQuote(plan.timerName)}`, { stdio: "pipe" });
29880
30114
  } catch (error) {
@@ -29885,13 +30119,13 @@ async function installContinuousUpdateService(flags) {
29885
30119
  console.log(`Installed ${plan.timerName}.`);
29886
30120
  }
29887
30121
  function runRuntimeVerification(installPath, role, config) {
29888
- const envText = existsSync9(join7(installPath, ".env")) ? readFileSync7(join7(installPath, ".env"), "utf-8") : "";
30122
+ const envText = existsSync10(join8(installPath, ".env")) ? readFileSync8(join8(installPath, ".env"), "utf-8") : "";
29889
30123
  const mode = resolveRuntimeDeploymentMode({
29890
30124
  envText,
29891
- overlayExists: existsSync9(config?.openMatesCloudOverlayPath ?? defaultOpenMatesCloudOverlayPath(installPath))
30125
+ overlayExists: existsSync10(config?.openMatesCloudOverlayPath ?? defaultOpenMatesCloudOverlayPath(installPath))
29892
30126
  });
29893
- const backupPath = join7(roleBackupDir(installPath, role), `latest-pre-update-${role}.tar.gz`);
29894
- const plan = planRuntimeVerification({ role, deploymentMode: mode.effectiveMode, hasVerifiedBackup: existsSync9(backupPath) });
30127
+ const backupPath = join8(roleBackupDir(installPath, role), `latest-pre-update-${role}.tar.gz`);
30128
+ const plan = planRuntimeVerification({ role, deploymentMode: mode.effectiveMode, hasVerifiedBackup: existsSync10(backupPath) });
29895
30129
  const verifierService = { core: "api", upload: "app-uploads", preview: "preview" }[role];
29896
30130
  const installMode = getInstallMode(installPath, config);
29897
30131
  const withOverrides = config?.composeProfile === "full";
@@ -29992,7 +30226,7 @@ async function runUpdateCompletionEmailGate(input) {
29992
30226
  deliveryId: deliveryPlan.deliveryId
29993
30227
  };
29994
30228
  }
29995
- const deliveryId = deliveryPlan.deliveryId ?? randomUUID10();
30229
+ const deliveryId = deliveryPlan.deliveryId ?? randomUUID11();
29996
30230
  const deliveryPendingAt = deliveryPlan.pendingAt ?? completedAt;
29997
30231
  const previousAttempts = deliveryPlan.previousAttempts ?? 0;
29998
30232
  const { role: _role, updated_at: _updatedAt, ...currentStatus } = readServerUpdateStatus(input.installPath, input.role);
@@ -30098,10 +30332,10 @@ async function installRuntimeMonitoringServices(installPath, role) {
30098
30332
  [operationalPlan.watchdogTimerName, operationalPlan.watchdogTimer]
30099
30333
  ] : []
30100
30334
  ].filter((entry) => Boolean(entry[0] && entry[1]));
30101
- const reportStatePath = join7(installPath, ".openmates", "runtime-health", `${role}-operational-report.json`);
30102
- const reportMetricsDir = join7(installPath, ".openmates", "runtime-health", "metrics");
30103
- const startedMetricPath = join7(reportMetricsDir, "operational-report-started.prom");
30104
- const successMetricPath = join7(reportMetricsDir, "operational-report.prom");
30335
+ const reportStatePath = join8(installPath, ".openmates", "runtime-health", `${role}-operational-report.json`);
30336
+ const reportMetricsDir = join8(installPath, ".openmates", "runtime-health", "metrics");
30337
+ const startedMetricPath = join8(reportMetricsDir, "operational-report-started.prom");
30338
+ const successMetricPath = join8(reportMetricsDir, "operational-report.prom");
30105
30339
  const operationalUnitNames = [
30106
30340
  operationalPlan.digestServiceName,
30107
30341
  operationalPlan.digestTimerName,
@@ -30109,18 +30343,18 @@ async function installRuntimeMonitoringServices(installPath, role) {
30109
30343
  operationalPlan.watchdogTimerName
30110
30344
  ];
30111
30345
  if (operationalPlan.scheduleEnabled) {
30112
- mkdirSync6(dirname6(reportStatePath), { recursive: true, mode: 448 });
30113
- mkdirSync6(reportMetricsDir, { recursive: true, mode: 448 });
30114
- if (!existsSync9(reportStatePath)) {
30115
- writeFileSync5(
30346
+ mkdirSync7(dirname6(reportStatePath), { recursive: true, mode: 448 });
30347
+ mkdirSync7(reportMetricsDir, { recursive: true, mode: 448 });
30348
+ if (!existsSync10(reportStatePath)) {
30349
+ writeFileSync6(
30116
30350
  reportStatePath,
30117
30351
  `${JSON.stringify({ incidentOpen: false, monitoringStartedAt: (/* @__PURE__ */ new Date()).toISOString() }, null, 2)}
30118
30352
  `,
30119
30353
  { mode: 384 }
30120
30354
  );
30121
30355
  }
30122
- if (!existsSync9(startedMetricPath)) {
30123
- writeFileSync5(
30356
+ if (!existsSync10(startedMetricPath)) {
30357
+ writeFileSync6(
30124
30358
  startedMetricPath,
30125
30359
  `operational_report_monitoring_started_timestamp_seconds{environment="${operationalPlan.environment}"} ${Math.floor(Date.now() / 1e3)}
30126
30360
  `,
@@ -30128,21 +30362,21 @@ async function installRuntimeMonitoringServices(installPath, role) {
30128
30362
  );
30129
30363
  }
30130
30364
  } else {
30131
- const installedOperationalUnits = operationalUnitNames.filter((name) => existsSync9(join7("/etc", "systemd", "system", name)));
30365
+ const installedOperationalUnits = operationalUnitNames.filter((name) => existsSync10(join8("/etc", "systemd", "system", name)));
30132
30366
  const installedOperationalTimers = installedOperationalUnits.filter((name) => name.endsWith(".timer"));
30133
30367
  if (installedOperationalTimers.length) {
30134
- execFileSync3("systemctl", ["disable", "--now", ...installedOperationalTimers], { stdio: "pipe" });
30368
+ execFileSync4("systemctl", ["disable", "--now", ...installedOperationalTimers], { stdio: "pipe" });
30135
30369
  }
30136
- for (const name of installedOperationalUnits) rmSync5(join7("/etc", "systemd", "system", name));
30370
+ for (const name of installedOperationalUnits) rmSync7(join8("/etc", "systemd", "system", name));
30137
30371
  for (const path2 of [reportStatePath, startedMetricPath, successMetricPath]) {
30138
- if (existsSync9(path2)) rmSync5(path2);
30372
+ if (existsSync10(path2)) rmSync7(path2);
30139
30373
  }
30140
30374
  }
30141
- for (const [name, content] of files) writeFileSync5(join7("/etc", "systemd", "system", name), content, { mode: 420 });
30375
+ for (const [name, content] of files) writeFileSync6(join8("/etc", "systemd", "system", name), content, { mode: 420 });
30142
30376
  execSync("systemctl daemon-reload", { stdio: "pipe" });
30143
30377
  const timers = files.filter(([name]) => name.endsWith(".timer")).map(([name]) => name);
30144
30378
  execSync(`systemctl enable --now ${timers.map(shellQuote).join(" ")}`, { stdio: "pipe" });
30145
- for (const timer of timers) execFileSync3("systemctl", ["is-active", "--quiet", timer], { stdio: "pipe" });
30379
+ for (const timer of timers) execFileSync4("systemctl", ["is-active", "--quiet", timer], { stdio: "pipe" });
30146
30380
  }
30147
30381
  async function autoInstallRuntimeMonitoringServices(installPath, role) {
30148
30382
  if (!shouldAutoInstallRuntimeMonitoringServices(process.env)) {
@@ -30160,10 +30394,10 @@ async function serverUpdate(client, rest, flags) {
30160
30394
  const filePath = serverUpdateStatusFile(installPath2, role2);
30161
30395
  const status = readServerUpdateStatus(installPath2, role2);
30162
30396
  if (flags.json === true) {
30163
- printJson(existsSync9(filePath) ? status : { role: role2, status: "unknown" });
30397
+ printJson(existsSync10(filePath) ? status : { role: role2, status: "unknown" });
30164
30398
  return;
30165
30399
  }
30166
- if (!existsSync9(filePath)) {
30400
+ if (!existsSync10(filePath)) {
30167
30401
  console.log(`No update status recorded for ${role2}.`);
30168
30402
  return;
30169
30403
  }
@@ -30349,7 +30583,7 @@ async function serverUpdate(client, rest, flags) {
30349
30583
  try {
30350
30584
  await autoInstallRuntimeMonitoringServices(installPath, role);
30351
30585
  } catch (error) {
30352
- const restoreCommand = safetyPlan.backupName ? `openmates server restore --role ${role} --file ${join7(roleBackupDir(installPath, role), safetyPlan.backupName)}` : null;
30586
+ const restoreCommand = safetyPlan.backupName ? `openmates server restore --role ${role} --file ${join8(roleBackupDir(installPath, role), safetyPlan.backupName)}` : null;
30353
30587
  writeServerUpdateStatus(installPath, role, {
30354
30588
  status: "degraded",
30355
30589
  targetImageTag: target.tag,
@@ -30675,11 +30909,11 @@ async function serverMonitoring(rest, flags) {
30675
30909
  const state2 = await readOperationalReportState(installPath, role);
30676
30910
  const nextState2 = accepted ? { ...state2, incidentOpen: false, incidentOpenedAt: void 0, lastAcceptedReportAt: (/* @__PURE__ */ new Date()).toISOString() } : state2;
30677
30911
  await writeOperationalReportState(installPath, role, nextState2);
30678
- const metricsDir = join7(installPath, ".openmates", "runtime-health", "metrics");
30679
- mkdirSync6(metricsDir, { recursive: true, mode: 448 });
30912
+ const metricsDir = join8(installPath, ".openmates", "runtime-health", "metrics");
30913
+ mkdirSync7(metricsDir, { recursive: true, mode: 448 });
30680
30914
  if (accepted) {
30681
- writeFileSync5(
30682
- join7(metricsDir, "operational-report.prom"),
30915
+ writeFileSync6(
30916
+ join8(metricsDir, "operational-report.prom"),
30683
30917
  `operational_report_last_success_timestamp_seconds{environment="${output2.receipts[0]?.environment ?? environment}"} ${Math.floor(Date.now() / 1e3)}
30684
30918
  `,
30685
30919
  { mode: 384 }
@@ -30697,11 +30931,11 @@ async function serverMonitoring(rest, flags) {
30697
30931
  destinationSource: receipt.destination_source,
30698
30932
  fallbackUsed: receipt.fallback_used
30699
30933
  }));
30700
- const receiptDir = join7(installPath, ".openmates", "runtime-health", "receipts");
30701
- mkdirSync6(receiptDir, { recursive: true, mode: 448 });
30934
+ const receiptDir = join8(installPath, ".openmates", "runtime-health", "receipts");
30935
+ mkdirSync7(receiptDir, { recursive: true, mode: 448 });
30702
30936
  const receiptMonth = (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
30703
30937
  for (const receipt of redactedReceipts) {
30704
- appendFileSync(join7(receiptDir, `${receiptMonth}.jsonl`), `${JSON.stringify(receipt)}
30938
+ appendFileSync(join8(receiptDir, `${receiptMonth}.jsonl`), `${JSON.stringify(receipt)}
30705
30939
  `, { mode: 384 });
30706
30940
  }
30707
30941
  const displayed = { command: "monitoring digest", reportId: output2.report_id, reportSha256: output2.report_sha256, deliveryState: output2.delivery_state, receipts: redactedReceipts };
@@ -30905,14 +31139,14 @@ async function serverFeatures(rest, flags) {
30905
31139
  const installPath = resolveServerPath(flags);
30906
31140
  const installMode = getInstallMode(installPath);
30907
31141
  if (installMode === "image") ensureImageRuntimeConfig(installPath);
30908
- const configPath = installMode === "image" ? imageBackendConfigPath(installPath) : join7(installPath, BACKEND_CONFIG_FILE);
30909
- if (!existsSync9(configPath)) {
31142
+ const configPath = installMode === "image" ? imageBackendConfigPath(installPath) : join8(installPath, BACKEND_CONFIG_FILE);
31143
+ if (!existsSync10(configPath)) {
30910
31144
  throw new Error(`Backend config not found at ${configPath}. Run 'openmates server install' first or pass --path <dir>.`);
30911
31145
  }
30912
- const content = readFileSync7(configPath, "utf-8");
31146
+ const content = readFileSync8(configPath, "utf-8");
30913
31147
  const overrides = parseFeatureOverrides(content);
30914
31148
  const writeOverrides = (nextOverrides) => {
30915
- writeFileSync5(configPath, updateFeatureOverridesContent(content, nextOverrides));
31149
+ writeFileSync6(configPath, updateFeatureOverridesContent(content, nextOverrides));
30916
31150
  console.log(`Updated ${configPath}`);
30917
31151
  console.log("Restart the server for feature changes to take effect: openmates server restart");
30918
31152
  };
@@ -31145,7 +31379,7 @@ async function serverBackup(rest, flags) {
31145
31379
  const role = getServerRole(flags, config);
31146
31380
  if (action === "list") {
31147
31381
  const dir = roleBackupDir(installPath, role);
31148
- const files = existsSync9(dir) ? readdirSync3(dir).filter((item) => item.endsWith(".tar.gz")).sort() : [];
31382
+ const files = existsSync10(dir) ? readdirSync5(dir).filter((item) => item.endsWith(".tar.gz")).sort() : [];
31149
31383
  if (flags.json === true) {
31150
31384
  printJson({ role, backupDir: dir, files });
31151
31385
  return;
@@ -31155,7 +31389,7 @@ async function serverBackup(rest, flags) {
31155
31389
  console.log(" none");
31156
31390
  return;
31157
31391
  }
31158
- for (const file of files) console.log(` ${join7(dir, file)}`);
31392
+ for (const file of files) console.log(` ${join8(dir, file)}`);
31159
31393
  return;
31160
31394
  }
31161
31395
  requireDocker();
@@ -31240,7 +31474,7 @@ async function serverUninstall(flags) {
31240
31474
  } catch {
31241
31475
  }
31242
31476
  try {
31243
- rmSync5(installPath, { recursive: true, force: true });
31477
+ rmSync7(installPath, { recursive: true, force: true });
31244
31478
  console.error(` Removed installation directory: ${installPath}`);
31245
31479
  } catch (e) {
31246
31480
  console.error(` Could not remove ${installPath}: ${e instanceof Error ? e.message : e}`);
@@ -31315,7 +31549,7 @@ async function serverCaddy(rest, flags) {
31315
31549
  const role = getServerRole(flags, config);
31316
31550
  const appliedPath = typeof flags.config === "string" ? flags.config : "/etc/caddy/Caddyfile";
31317
31551
  const templatePath = packagedCaddyTemplatePath(role);
31318
- if (!existsSync9(templatePath)) throw new Error(`Packaged Caddy template not found: ${templatePath}`);
31552
+ if (!existsSync10(templatePath)) throw new Error(`Packaged Caddy template not found: ${templatePath}`);
31319
31553
  const plan = planCaddyCommand({ role, action, appliedPath });
31320
31554
  const payload = {
31321
31555
  ...plan,
@@ -31334,7 +31568,7 @@ async function serverCaddy(rest, flags) {
31334
31568
  return;
31335
31569
  }
31336
31570
  if (action === "diff") {
31337
- if (!existsSync9(appliedPath)) throw new Error(`Applied Caddyfile not found: ${appliedPath}`);
31571
+ if (!existsSync10(appliedPath)) throw new Error(`Applied Caddyfile not found: ${appliedPath}`);
31338
31572
  try {
31339
31573
  execSync(`diff -u ${shellQuote(appliedPath)} ${shellQuote(templatePath)}`, { stdio: "inherit" });
31340
31574
  } catch (error) {
@@ -31355,9 +31589,9 @@ WARNING: This will replace ${appliedPath} with the packaged ${role} Caddyfile.`)
31355
31589
  }
31356
31590
  }
31357
31591
  const backupPath = `${appliedPath}.openmates-backup-${nowStamp()}`;
31358
- const hadExistingCaddyfile = existsSync9(appliedPath);
31592
+ const hadExistingCaddyfile = existsSync10(appliedPath);
31359
31593
  try {
31360
- mkdirSync6(dirname6(appliedPath), { recursive: true });
31594
+ mkdirSync7(dirname6(appliedPath), { recursive: true });
31361
31595
  if (hadExistingCaddyfile) copyFileSync2(appliedPath, backupPath);
31362
31596
  copyFileSync2(templatePath, appliedPath);
31363
31597
  execSync("systemctl reload caddy", { stdio: "inherit" });
@@ -48913,9 +49147,9 @@ function basenameFromUrl(url) {
48913
49147
  return withoutQuery.split("/").pop()?.replace(/[^a-zA-Z0-9._-]/g, "_") ?? "";
48914
49148
  }
48915
49149
  function extensionFromUrl(url) {
48916
- const basename4 = basenameFromUrl(url);
48917
- const dotIndex = basename4.lastIndexOf(".");
48918
- return dotIndex > 0 ? basename4.slice(dotIndex + 1).toLowerCase() : "";
49150
+ const basename5 = basenameFromUrl(url);
49151
+ const dotIndex = basename5.lastIndexOf(".");
49152
+ return dotIndex > 0 ? basename5.slice(dotIndex + 1).toLowerCase() : "";
48919
49153
  }
48920
49154
  function extensionFromLanguage(language) {
48921
49155
  const normalized = language.toLowerCase();
@@ -49527,6 +49761,14 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
49527
49761
  }
49528
49762
  }
49529
49763
  },
49764
+ tasks: {
49765
+ daily_meeting_and_orchestration: {
49766
+ text: "Daily meeting & orchestration",
49767
+ description: {
49768
+ text: "Choose today's priorities and coordinate project tasks and chats through completion."
49769
+ }
49770
+ }
49771
+ },
49530
49772
  videos: {
49531
49773
  analyze_video: {
49532
49774
  text: "Analyze video",
@@ -50482,6 +50724,12 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
50482
50724
  }
50483
50725
  },
50484
50726
  calendar: {
50727
+ list_calendars: {
50728
+ text: "List Calendars",
50729
+ description: {
50730
+ text: "List calendars, timezones, and access roles in a connected account."
50731
+ }
50732
+ },
50485
50733
  get_events: {
50486
50734
  text: "Search",
50487
50735
  description: {
@@ -56671,6 +56919,9 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
56671
56919
  generating_title: {
56672
56920
  text: "Processing..."
56673
56921
  },
56922
+ selecting_mate_and_model: {
56923
+ text: "Selecting mate & AI model..."
56924
+ },
56674
56925
  selecting_mate: {
56675
56926
  text: "Selecting optimal mate..."
56676
56927
  },
@@ -59616,6 +59867,21 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
59616
59867
  text: "You are an expert education and career counselor. Your role is to help users discover the right degree and field of study through thoughtful, strategic questioning. Use the Socratic method to guide them toward self-discovery rather than just giving advice. Help them understand their true interests, strengths, and values. Ask follow-up questions to dig deeper and uncover patterns. Provide personalized recommendations based on their responses."
59617
59868
  }
59618
59869
  },
59870
+ tasks_daily_meeting_and_orchestration: {
59871
+ text: "Daily meeting & orchestration",
59872
+ description: {
59873
+ text: "Choose today's priorities and coordinate project tasks and chats through completion."
59874
+ },
59875
+ process: {
59876
+ text: "- Clarify your weekly priorities and today's focus, one question at a time\n- Review the last working day's chats, progress, and remaining tasks\n- Agree on goals and completion criteria before starting work\n- Coordinate project subchats, check progress, and resolve shared blockers\n- Summarize results and suggest the most relevant tasks to work on next"
59877
+ },
59878
+ how_to_use: {
59879
+ text: "- Let's have a **daily meeting** for my shop launch and decide what matters today.\n- **Coordinate today's work** on my research project after reviewing our previous chats.\n- All our chats are finished. **What should we work on next** toward today's goal?"
59880
+ },
59881
+ systemprompt: {
59882
+ text: "Help the user choose today's outcomes and coordinate work on their own projects.\nAsk one question per message, include **Recommendation:** with a brief reason and\n**Examples:** with concrete outcomes, then wait for the answer. Reuse decisions\nalready made. Keep replies concise and use the user's language.\n\nUse OpenMates tasks and task activities as the record of priorities, assignments,\nlearnings, decisions, blockers, and handoffs. Search before creating duplicates.\nUse only information accessible to this user in the selected project or team.\nPrivate history and task search require the authorized connected client; missing\naccess is not proof that no previous work exists. Never invent tool results or\nclaim a task update, chat action, or scheduled check without acknowledgement.\nUse available tools; explain missing capabilities and continue the useful parts\nof the meeting without promising unsupported automation. Preview requests are\nread-only: propose work without changing tasks, scheduling, or starting chats.\n\nFollow these steps:\n\n### Step 1: Establish today's focus\n\nAsk about this week's priorities, using recorded priorities as context. If unclear,\nestablish today's most useful outcome first. Clarify project scope, available time,\nor deadlines only when needed. Surface urgent issues when they affect the plan.\n\n### Step 2: Review the last working day\n\nIn the user's timezone, search backward from yesterday through at most 30 calendar\ndays for the latest day with relevant chat activity. Review every accessible chat\nactive on that day, including completed work, linked task activities, and subchat\nfindings. Group subchats with their parent and deduplicate copied history. Automatic\nnotifications alone do not establish a working day. Paused chats are resumption\ncandidates, but review unpaused chats too. Check today's active work for duplicates.\nIf the full window is empty, plan from tasks and user priorities. Disclose missing\naccess or partial results. Compare intended outcomes with actual progress and blockers.\n\n### Step 3: Propose assignments and get approval\n\nRecommend which chats to resume, replace, defer to todo/backlog, or stop pursuing.\nPreserve useful findings in task activities. For each assignment give the task,\ngoal, next instruction, completion criteria, required checks, scope limits,\ndependencies, and next meaningful checkpoint. Resume existing chats by default.\nIf conflicting context or repeated misunderstanding warrants a fresh subchat,\ntransfer requirements, decisions, saved work, and remaining checks; keep one owner.\n\nGet approval for the daily plan before starting or resuming work. Make clear which\ntasks OpenMates will execute and which require the user; only execute tasks assigned\nto OpenMates. Plan approval covers in-focus additions and urgent corrections, within\nexisting permissions and budgets. Cancellation still requires a user decision.\n\n### Step 4: Start or resume approved work\n\nUse OpenMates subchats for independent assignments and available chat controls for\nresumption. Give each subchat its approved goal, limits, task link, and completion\ncriteria. Verify acceptance before recording it as started. Preserve existing chat\nand task identity when resuming; reconcile uncertain starts before retrying. Keep\ncoordination in this meeting chat and avoid duplicate owners or recursive delegation.\nAsk workers to report important progress, learnings, decisions, and blockers so\nthey can be recorded in task activity. Do not post routine command or heartbeat logs.\n\n### Step 5: Monitor and handle changes\n\nFor each started/resumed chat, check at 5, 10, 15, and 20 minutes, then every 20\nminutes. Each new worker starts its own cadence. Where supported, use OpenMates\nfollow-ups in this meeting chat for approved scheduled checks. Use a full AI\nfollow-up only for an actual monitoring action; a passive reminder cannot inspect\nwork. Track acknowledged follow-ups, avoid duplicate schedules, preserve timing\nacross continuations, and cancel pending checks when work finishes or the day ends.\nIf scheduling or live progress access is unavailable, say so and use explicit\nuser-requested check-ins. Do not claim background supervision from prompt text alone.\n\nCompare actual results with the assignment. Leave healthy work uninterrupted;\ncorrect urgent drift within approved scope. Honor acceptance and scope changes.\nCoordinate one recovery effort for shared blockers, request user-only action once,\nverify recovery, and continue unaffected work.\n\nFor repeated failed approaches, absent measurable progress, or disproportionate\neffort, explain evidence and recommend narrowing or cancellation. Wait for the\nuser's decision; pause orchestration of only that chat and keep monitoring others.\nReport if the affected worker remains running. Do not cancel it or send further\ncontinuation instructions while waiting.\n\nIf an additional request fits today's focus, reuse/create the task and start it\nwithin approval. Otherwise capture it in todo/backlog. Preserve explicit deadlines;\nsuggest due dates only with a reason. Ask before materially changing today's focus.\n\n### Step 6: Summarize results and ask what comes next\n\nVerify outcomes and required checks before marking tasks done. Idle, paused,\nblocked, and awaiting-input chats are not complete. Ensure important outcomes,\ndecisions, and next steps are recorded in task activities.\n\nOnce all coordinated chats are done, summarize outcomes and fetch still-open tasks.\nRank by relevance to today's focus: direct outcomes, enabling dependencies, related\nimprovements, then unrelated work. Include task links, deadlines, blockers, remaining\nwork, and brief reasons. Label partial lists. Ask **What would you like to work on\nnext?** with a recommendation and concrete examples. Wait before starting another\nbatch, even within today's focus. If nothing remains, suggest ending the day or\nchoosing a new goal. If chats are blocked, ask the needed blocker decision instead.\n\n### Step 7: Close the day\n\nAt any step, respect the user's stop or end-of-day instruction. Cancel remaining\nmonitoring follow-ups and preserve handoffs in task activities. Paused work is\nneither done nor cancelled. At the next daily meeting, reassess it against the new\npriorities before resuming."
59883
+ }
59884
+ },
59619
59885
  videos_analyze_video: {
59620
59886
  text: "Analyze video",
59621
59887
  description: {
@@ -70488,10 +70754,10 @@ function buildAssistantFeedbackDecision(rating) {
70488
70754
  }
70489
70755
 
70490
70756
  // src/benchmark.ts
70491
- import { createHash as createHash13, randomUUID as randomUUID11 } from "crypto";
70492
- import { existsSync as existsSync10, mkdtempSync as mkdtempSync2, readFileSync as readFileSync8, readdirSync as readdirSync4, writeFileSync as writeFileSync6 } from "fs";
70757
+ import { createHash as createHash14, randomUUID as randomUUID12 } from "crypto";
70758
+ import { existsSync as existsSync11, mkdtempSync as mkdtempSync2, readFileSync as readFileSync9, readdirSync as readdirSync6, writeFileSync as writeFileSync7 } from "fs";
70493
70759
  import { tmpdir } from "os";
70494
- import { dirname as dirname7, join as join8, resolve as resolve8 } from "path";
70760
+ import { dirname as dirname7, join as join9, resolve as resolve8 } from "path";
70495
70761
  import { fileURLToPath } from "url";
70496
70762
  var DEFAULT_JUDGE_MODEL = "google/gemini-3-flash-preview";
70497
70763
  var DEFAULT_PROMPT_BUDGET_MODEL = "google/gemini-3.5-flash";
@@ -70904,7 +71170,7 @@ async function handleBenchmark(client, subcommand, rest, flags) {
70904
71170
  const caseIds = parseCaseIds(flags.case);
70905
71171
  const dryRun = flags["dry-run"] === true;
70906
71172
  const output = typeof flags.output === "string" ? flags.output : void 0;
70907
- const runId = typeof flags["run-id"] === "string" ? flags["run-id"] : randomUUID11();
71173
+ const runId = typeof flags["run-id"] === "string" ? flags["run-id"] : randomUUID12();
70908
71174
  const imagePath = typeof flags.image === "string" ? resolve8(flags.image) : defaultImageFixturePath();
70909
71175
  if (!dryRun && flags["confirm-spend-credits"] !== true) {
70910
71176
  throw new Error(
@@ -71006,7 +71272,7 @@ async function handlePromptBudgetBenchmark(client, rest, flags) {
71006
71272
  if (resumeArtifact) {
71007
71273
  validatePromptBudgetResumeArtifact(resumeArtifact, { phase, targetModel, judgeModel });
71008
71274
  }
71009
- const runId = stringFlag(flags["run-id"]) ?? resumeArtifact?.runId ?? randomUUID11();
71275
+ const runId = stringFlag(flags["run-id"]) ?? resumeArtifact?.runId ?? randomUUID12();
71010
71276
  const result = makePromptBudgetBaseResult({
71011
71277
  phase,
71012
71278
  runId,
@@ -71377,7 +71643,7 @@ async function buildPromptWithAttachments(client, benchmarkCase, model, imagePat
71377
71643
  ${attachment.messageSuffix}`, embeds: attachment.embeds };
71378
71644
  }
71379
71645
  async function prepareImageAttachment(client, imagePath) {
71380
- if (!existsSync10(imagePath)) throw new Error(`Benchmark image not found: ${imagePath}`);
71646
+ if (!existsSync11(imagePath)) throw new Error(`Benchmark image not found: ${imagePath}`);
71381
71647
  const processed = processFiles([imagePath], null);
71382
71648
  if (processed.blocked.length > 0 || processed.errors.length > 0 || processed.embeds.length === 0) {
71383
71649
  const reason = [...processed.blocked, ...processed.errors].map((entry) => entry.error).join("; ") || "no image embed produced";
@@ -71567,7 +71833,7 @@ function buildLongContextHistory() {
71567
71833
  }
71568
71834
  function appendHistory(history, role, content) {
71569
71835
  history.push({
71570
- message_id: randomUUID11(),
71836
+ message_id: randomUUID12(),
71571
71837
  role,
71572
71838
  sender_name: role === "user" ? "User" : "Assistant",
71573
71839
  content,
@@ -71657,10 +71923,10 @@ function loadProviderPricing() {
71657
71923
  const providersDir = findProvidersDir();
71658
71924
  const pricing = /* @__PURE__ */ new Map();
71659
71925
  if (!providersDir) return pricing;
71660
- for (const fileName of readdirSync4(providersDir)) {
71926
+ for (const fileName of readdirSync6(providersDir)) {
71661
71927
  if (!fileName.endsWith(".yml")) continue;
71662
- const filePath = join8(providersDir, fileName);
71663
- const text = readFileSync8(filePath, "utf-8");
71928
+ const filePath = join9(providersDir, fileName);
71929
+ const text = readFileSync9(filePath, "utf-8");
71664
71930
  const provider = parseProviderId(text) ?? fileName.replace(/\.yml$/, "");
71665
71931
  for (const modelPricing of parseModelPricing(text, provider)) {
71666
71932
  pricing.set(`${modelPricing.provider}/${modelPricing.modelId}`, modelPricing);
@@ -71714,10 +71980,10 @@ function findProvidersDir() {
71714
71980
  const currentFile = fileURLToPath(import.meta.url);
71715
71981
  let current = dirname7(currentFile);
71716
71982
  for (let index = 0; index < 8; index += 1) {
71717
- const candidate = join8(current, "backend", "providers");
71718
- if (existsSync10(candidate)) return candidate;
71719
- const parentCandidate = join8(current, "..", "..", "backend", "providers");
71720
- if (existsSync10(parentCandidate)) return resolve8(parentCandidate);
71983
+ const candidate = join9(current, "backend", "providers");
71984
+ if (existsSync11(candidate)) return candidate;
71985
+ const parentCandidate = join9(current, "..", "..", "backend", "providers");
71986
+ if (existsSync11(parentCandidate)) return resolve8(parentCandidate);
71721
71987
  const next = dirname7(current);
71722
71988
  if (next === current) break;
71723
71989
  current = next;
@@ -71922,21 +72188,21 @@ function hashPromptBudgetCorpus(cases) {
71922
72188
  return stableHash(cases.map((benchmarkCase) => ({ id: benchmarkCase.id, turns: benchmarkCase.turns })));
71923
72189
  }
71924
72190
  function stableHash(value) {
71925
- return createHash13("sha256").update(JSON.stringify(value)).digest("hex");
72191
+ return createHash14("sha256").update(JSON.stringify(value)).digest("hex");
71926
72192
  }
71927
72193
  function defaultImageFixturePath() {
71928
- const fixtureDir = join8(dirname7(fileURLToPath(import.meta.url)), "..", "fixtures");
71929
- const fixturePath = join8(fixtureDir, "brandenburger-tor.png");
71930
- if (existsSync10(fixturePath)) return fixturePath;
71931
- const tempDir = mkdtempSync2(join8(tmpdir(), "openmates-benchmark-"));
71932
- const tempPath = join8(tempDir, "brandenburger-tor.svg");
71933
- writeFileSync6(tempPath, FIXTURE_IMAGE_SVG, "utf-8");
72194
+ const fixtureDir = join9(dirname7(fileURLToPath(import.meta.url)), "..", "fixtures");
72195
+ const fixturePath = join9(fixtureDir, "brandenburger-tor.png");
72196
+ if (existsSync11(fixturePath)) return fixturePath;
72197
+ const tempDir = mkdtempSync2(join9(tmpdir(), "openmates-benchmark-"));
72198
+ const tempPath = join9(tempDir, "brandenburger-tor.svg");
72199
+ writeFileSync7(tempPath, FIXTURE_IMAGE_SVG, "utf-8");
71934
72200
  return tempPath;
71935
72201
  }
71936
72202
  function writeBenchmarkResult(result, flags, output) {
71937
72203
  const json = `${JSON.stringify(result, null, 2)}
71938
72204
  `;
71939
- if (output) writeFileSync6(output, json, "utf-8");
72205
+ if (output) writeFileSync7(output, json, "utf-8");
71940
72206
  if (flags.json === true || output) {
71941
72207
  process.stdout.write(json);
71942
72208
  return;
@@ -71960,7 +72226,7 @@ function writeBenchmarkResult(result, flags, output) {
71960
72226
  function writePromptBudgetResult(result, flags, output) {
71961
72227
  const json = `${JSON.stringify(result, null, 2)}
71962
72228
  `;
71963
- if (output) writeFileSync6(output, json, "utf-8");
72229
+ if (output) writeFileSync7(output, json, "utf-8");
71964
72230
  if (flags.json === true || output) {
71965
72231
  process.stdout.write(json);
71966
72232
  return;
@@ -71986,7 +72252,7 @@ function writePromptBudgetResult(result, flags, output) {
71986
72252
  function writePromptBudgetComparison(result, flags, output) {
71987
72253
  const json = `${JSON.stringify(result, null, 2)}
71988
72254
  `;
71989
- if (output) writeFileSync6(output, json, "utf-8");
72255
+ if (output) writeFileSync7(output, json, "utf-8");
71990
72256
  if (flags.json === true || output) {
71991
72257
  process.stdout.write(json);
71992
72258
  return;
@@ -72036,7 +72302,7 @@ function comparePromptBudgetArtifacts(flags) {
72036
72302
  };
72037
72303
  }
72038
72304
  function readPromptBudgetResult(path2) {
72039
- const parsed = JSON.parse(readFileSync8(path2, "utf-8"));
72305
+ const parsed = JSON.parse(readFileSync9(path2, "utf-8"));
72040
72306
  if (!parsed || typeof parsed !== "object") throw new Error(`Invalid prompt-budget artifact: ${path2}`);
72041
72307
  return parsed;
72042
72308
  }
@@ -73434,22 +73700,22 @@ function clamp(value, min, max) {
73434
73700
  // src/remoteAccess.ts
73435
73701
  import { homedir as homedir7 } from "os";
73436
73702
  import {
73437
- chmodSync as chmodSync5,
73703
+ chmodSync as chmodSync6,
73438
73704
  closeSync as closeSync3,
73439
73705
  constants,
73440
- existsSync as existsSync11,
73706
+ existsSync as existsSync12,
73441
73707
  fstatSync,
73442
- mkdirSync as mkdirSync7,
73708
+ mkdirSync as mkdirSync8,
73443
73709
  openSync as openSync3,
73444
- readFileSync as readFileSync9,
73445
- readdirSync as readdirSync5,
73710
+ readFileSync as readFileSync10,
73711
+ readdirSync as readdirSync7,
73446
73712
  readSync as readSync2,
73447
73713
  realpathSync,
73448
73714
  statSync as statSync2,
73449
- writeFileSync as writeFileSync7
73715
+ writeFileSync as writeFileSync8
73450
73716
  } from "fs";
73451
73717
  import { spawn as spawn2, spawnSync as spawnSync2 } from "child_process";
73452
- import { join as join9, resolve as resolve9, relative as relative2 } from "path";
73718
+ import { join as join10, resolve as resolve9, relative as relative2 } from "path";
73453
73719
 
73454
73720
  // src/projectFileRisk.ts
73455
73721
  var SECRET_OR_ENVIRONMENT_PATTERNS = [
@@ -73572,7 +73838,7 @@ function resolveRemoteAccessRoots(pathFlag, cwd = process.cwd()) {
73572
73838
  const roots = [];
73573
73839
  for (const value of requested) {
73574
73840
  const candidate = resolve9(cwd, value);
73575
- if (!existsSync11(candidate) || !statSync2(candidate).isDirectory()) {
73841
+ if (!existsSync12(candidate) || !statSync2(candidate).isDirectory()) {
73576
73842
  throw new Error(`Remote source path does not exist or is not a directory: ${candidate}`);
73577
73843
  }
73578
73844
  const canonical = realpathSync(candidate);
@@ -73586,7 +73852,7 @@ function discoverRemoteAccessRepositories(roots) {
73586
73852
  const visit = (directory, approvedRoot) => {
73587
73853
  let entries;
73588
73854
  try {
73589
- entries = readdirSync5(directory, { withFileTypes: true });
73855
+ entries = readdirSync7(directory, { withFileTypes: true });
73590
73856
  } catch (error) {
73591
73857
  const code = error.code;
73592
73858
  if (code === "EACCES" || code === "EPERM") {
@@ -73600,7 +73866,7 @@ function discoverRemoteAccessRepositories(roots) {
73600
73866
  }
73601
73867
  for (const entry of entries) {
73602
73868
  if (entry.name === ".git" || entry.isSymbolicLink() || !entry.isDirectory()) continue;
73603
- visit(join9(directory, entry.name), approvedRoot);
73869
+ visit(join10(directory, entry.name), approvedRoot);
73604
73870
  }
73605
73871
  };
73606
73872
  for (const root of roots) visit(root, root);
@@ -73610,8 +73876,8 @@ function discoverRemoteAccessRepositories(roots) {
73610
73876
  };
73611
73877
  }
73612
73878
  function remoteAccessSourceType(rootPath) {
73613
- const gitMarker = join9(rootPath, ".git");
73614
- return existsSync11(gitMarker) ? "local_git_repository" : "local_folder";
73879
+ const gitMarker = join10(rootPath, ".git");
73880
+ return existsSync12(gitMarker) ? "local_git_repository" : "local_folder";
73615
73881
  }
73616
73882
  function listRemoteAccessDirectory(options) {
73617
73883
  const root = realpathSync(options.sourceRoot);
@@ -73624,9 +73890,9 @@ function listRemoteAccessDirectory(options) {
73624
73890
  const entries = [];
73625
73891
  let omitted = 0;
73626
73892
  let excluded = 0;
73627
- for (const entry of readdirSync5(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
73628
- const entryPath = relative2(root, join9(directory, entry.name)).replace(/\\/g, "/");
73629
- if (entry.name === ".git" || entry.isSymbolicLink() || isGitIgnoredPath(root, entryPath) || classifyProjectFileRisk(entryPath, options.userProtectedPatterns ?? []).isHighRisk || entry.isFile() && isBinaryFile(join9(directory, entry.name))) {
73893
+ for (const entry of readdirSync7(directory, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
73894
+ const entryPath = relative2(root, join10(directory, entry.name)).replace(/\\/g, "/");
73895
+ if (entry.name === ".git" || entry.isSymbolicLink() || isGitIgnoredPath(root, entryPath) || classifyProjectFileRisk(entryPath, options.userProtectedPatterns ?? []).isHighRisk || entry.isFile() && isBinaryFile(join10(directory, entry.name))) {
73630
73896
  excluded += 1;
73631
73897
  continue;
73632
73898
  }
@@ -73854,7 +74120,7 @@ function remoteAccessOperationErrorCode(error) {
73854
74120
  return "operation_failed";
73855
74121
  }
73856
74122
  function isGitIgnoredPath(sourceRoot, relativePath) {
73857
- if (!existsSync11(join9(sourceRoot, ".git"))) return false;
74123
+ if (!existsSync12(join10(sourceRoot, ".git"))) return false;
73858
74124
  const result = spawnSync2("git", ["check-ignore", "--quiet", "--", relativePath], {
73859
74125
  cwd: sourceRoot,
73860
74126
  stdio: "ignore"
@@ -73884,13 +74150,13 @@ function waitForAbort2(signal, timeoutMs) {
73884
74150
  }
73885
74151
  function resolveRemoteCachePath(sourceId, homeDirectory = homedir7()) {
73886
74152
  assertSafeSourceId(sourceId);
73887
- return join9(homeDirectory, ".openmates", "remote-cache", sourceId);
74153
+ return join10(homeDirectory, ".openmates", "remote-cache", sourceId);
73888
74154
  }
73889
74155
  function startRemoteAccessSource(input) {
73890
74156
  assertSafeSourceId(input.sourceId);
73891
74157
  const homeDirectory = input.homeDirectory ?? homedir7();
73892
74158
  const requestedRootPath = resolve9(input.rootPath);
73893
- if (!existsSync11(requestedRootPath) || !statSync2(requestedRootPath).isDirectory()) {
74159
+ if (!existsSync12(requestedRootPath) || !statSync2(requestedRootPath).isDirectory()) {
73894
74160
  throw new Error(`Remote source path does not exist or is not a directory: ${requestedRootPath}`);
73895
74161
  }
73896
74162
  const rootPath = realpathSync(requestedRootPath);
@@ -73908,14 +74174,14 @@ function startRemoteAccessSource(input) {
73908
74174
  };
73909
74175
  const sources = listRemoteAccessSources(homeDirectory).filter((entry) => entry.sourceId !== input.sourceId);
73910
74176
  saveRemoteAccessSources([...sources, source], homeDirectory);
73911
- mkdirSync7(source.cachePath, { recursive: true, mode: 448 });
74177
+ mkdirSync8(source.cachePath, { recursive: true, mode: 448 });
73912
74178
  return source;
73913
74179
  }
73914
74180
  function listRemoteAccessSources(homeDirectory = homedir7()) {
73915
74181
  const filePath = remoteAccessStorePath(homeDirectory);
73916
- if (!existsSync11(filePath)) return [];
74182
+ if (!existsSync12(filePath)) return [];
73917
74183
  try {
73918
- const parsed = JSON.parse(readFileSync9(filePath, "utf-8"));
74184
+ const parsed = JSON.parse(readFileSync10(filePath, "utf-8"));
73919
74185
  if (!Array.isArray(parsed.sources)) {
73920
74186
  throw new Error("Remote source store is missing the sources array");
73921
74187
  }
@@ -73972,13 +74238,13 @@ function searchRemoteSourceWithoutRg(query, sourceRoot, maxResults, userProtecte
73972
74238
  const directory = directories.pop();
73973
74239
  let entries;
73974
74240
  try {
73975
- entries = readdirSync5(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
74241
+ entries = readdirSync7(directory, { withFileTypes: true }).sort((left, right) => left.name.localeCompare(right.name));
73976
74242
  } catch {
73977
74243
  excluded += 1;
73978
74244
  continue;
73979
74245
  }
73980
74246
  for (const entry of entries) {
73981
- const absolutePath = join9(directory, entry.name);
74247
+ const absolutePath = join10(directory, entry.name);
73982
74248
  const relativePath = relative2(root, absolutePath).replace(/\\/g, "/");
73983
74249
  if (entry.name === ".git" || entry.isSymbolicLink() || isGitIgnoredPath(root, relativePath) || classifyProjectFileRisk(relativePath, userProtectedPatterns).isHighRisk) {
73984
74250
  excluded += 1;
@@ -74147,7 +74413,7 @@ function assertInsideRoot(sourceRoot, path2) {
74147
74413
  }
74148
74414
  function openedDescriptorPath(descriptor, requestedPath) {
74149
74415
  const procPath = `/proc/self/fd/${descriptor}`;
74150
- if (existsSync11(procPath)) return realpathSync(procPath);
74416
+ if (existsSync12(procPath)) return realpathSync(procPath);
74151
74417
  return realpathSync(requestedPath);
74152
74418
  }
74153
74419
  function isBinaryFile(path2) {
@@ -74179,16 +74445,16 @@ function normalizeBound(value, maximum, label) {
74179
74445
  return result;
74180
74446
  }
74181
74447
  function remoteAccessStorePath(homeDirectory) {
74182
- return join9(homeDirectory, ".openmates", "remote-sources.json");
74448
+ return join10(homeDirectory, ".openmates", "remote-sources.json");
74183
74449
  }
74184
74450
  function saveRemoteAccessSources(sources, homeDirectory) {
74185
74451
  const filePath = remoteAccessStorePath(homeDirectory);
74186
- const stateDir2 = join9(homeDirectory, ".openmates");
74187
- mkdirSync7(stateDir2, { recursive: true, mode: 448 });
74188
- chmodSync5(stateDir2, 448);
74189
- writeFileSync7(filePath, `${JSON.stringify({ sources }, null, 2)}
74452
+ const stateDir2 = join10(homeDirectory, ".openmates");
74453
+ mkdirSync8(stateDir2, { recursive: true, mode: 448 });
74454
+ chmodSync6(stateDir2, 448);
74455
+ writeFileSync8(filePath, `${JSON.stringify({ sources }, null, 2)}
74190
74456
  `, { mode: 384 });
74191
- chmodSync5(filePath, 384);
74457
+ chmodSync6(filePath, 384);
74192
74458
  }
74193
74459
  function assertRemoteAccessSourceRecord(value, index) {
74194
74460
  if (typeof value !== "object" || value === null) {
@@ -74206,7 +74472,7 @@ function assertRemoteAccessSourceRecord(value, index) {
74206
74472
  }
74207
74473
 
74208
74474
  // src/projectRequester.ts
74209
- import { randomUUID as randomUUID12 } from "crypto";
74475
+ import { randomUUID as randomUUID13 } from "crypto";
74210
74476
  var REMOTE_PROTOCOL_TIMEOUT_MS = 45e3;
74211
74477
  var REMOTE_POLL_INTERVAL_MS = 250;
74212
74478
  var MAX_REMOTE_RESULT_BYTES = 200 * 1024;
@@ -74232,8 +74498,8 @@ async function requestProjectRemoteOperation(options) {
74232
74498
  keyEpoch = discovery.keyEpoch;
74233
74499
  routingIdentity = discovery.routingIdentity;
74234
74500
  }
74235
- const requestingClientId = randomUUID12();
74236
- const requestId = randomUUID12();
74501
+ const requestingClientId = randomUUID13();
74502
+ const requestId = randomUUID13();
74237
74503
  const identity = await buildIdentity(
74238
74504
  options.client,
74239
74505
  options.projectId,
@@ -74294,9 +74560,9 @@ async function requestProjectRemoteOperation(options) {
74294
74560
  return response.result;
74295
74561
  }
74296
74562
  async function discoverRouting(options, timeoutMs) {
74297
- const requestId = randomUUID12();
74298
- const requestingClientId = randomUUID12();
74299
- const nonce = randomUUID12();
74563
+ const requestId = randomUUID13();
74564
+ const requestingClientId = randomUUID13();
74565
+ const nonce = randomUUID13();
74300
74566
  const encryptedEnvelope = await encryptWithAesGcmCombined(JSON.stringify({
74301
74567
  type: "routing_discovery",
74302
74568
  requesting_client_id: requestingClientId,
@@ -74394,13 +74660,68 @@ function remoteErrorMessage(code) {
74394
74660
 
74395
74661
  // src/selfUpdate.ts
74396
74662
  import { spawnSync as spawnSync3 } from "child_process";
74397
- import { readFileSync as readFileSync10 } from "fs";
74663
+ import { readFileSync as readFileSync11, mkdirSync as mkdirSync9, writeFileSync as writeFileSync9, renameSync as renameSync5 } from "fs";
74664
+ import { homedir as homedir8 } from "os";
74665
+ import { dirname as dirname8, basename as basename3, join as join11 } from "path";
74666
+ import { fileURLToPath as fileURLToPath2 } from "url";
74398
74667
  var PACKAGE_NAME = "openmates";
74399
- var DEFAULT_TARGET = "latest";
74668
+ function channelFile() {
74669
+ return join11(homedir8(), ".openmates", "updates.json");
74670
+ }
74671
+ function parseChannel(value) {
74672
+ if (value === "dev") return "dev";
74673
+ if (value === "stable" || value === "main") return "stable";
74674
+ throw new Error("--channel must be dev, stable, or main (alias for stable)");
74675
+ }
74676
+ function savedChannel() {
74677
+ try {
74678
+ return parseChannel(JSON.parse(readFileSync11(channelFile(), "utf8")).channel);
74679
+ } catch (error) {
74680
+ if (error.code === "ENOENT") {
74681
+ return getCliPackageVersion().includes("-") ? "dev" : "stable";
74682
+ }
74683
+ throw error;
74684
+ }
74685
+ }
74686
+ function persistSelfUpdateChannel(plan) {
74687
+ if (plan.dryRun || !plan.persistChannel) return;
74688
+ const path2 = channelFile();
74689
+ mkdirSync9(dirname8(path2), { recursive: true, mode: 448 });
74690
+ const temporary = `${path2}.${process.pid}.tmp`;
74691
+ writeFileSync9(temporary, JSON.stringify({ channel: plan.channel }) + "\n", { mode: 384 });
74692
+ renameSync5(temporary, path2);
74693
+ }
74694
+ function installedNpmPrefix(moduleUrl = import.meta.url, platform4 = process.platform) {
74695
+ const packageRoot = dirname8(dirname8(fileURLToPath2(moduleUrl)));
74696
+ const modules = dirname8(packageRoot);
74697
+ if (basename3(packageRoot) !== PACKAGE_NAME || basename3(modules) !== "node_modules") return null;
74698
+ if (platform4 === "win32") return dirname8(modules);
74699
+ return basename3(dirname8(modules)) === "lib" ? dirname8(dirname8(modules)) : null;
74700
+ }
74701
+ function compareVersions(left, right) {
74702
+ const parse = (value) => {
74703
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
74704
+ if (!match) throw new Error(`Cannot compare CLI version '${value}'`);
74705
+ return { core: match.slice(1, 4).map(BigInt), pre: match[4]?.split(".") };
74706
+ };
74707
+ const a = parse(left), b = parse(right);
74708
+ for (let i = 0; i < 3; i++) if (a.core[i] !== b.core[i]) return a.core[i] > b.core[i] ? 1 : -1;
74709
+ if (!a.pre || !b.pre) return a.pre ? -1 : b.pre ? 1 : 0;
74710
+ for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
74711
+ const x = a.pre[i], y = b.pre[i];
74712
+ if (x === y) continue;
74713
+ if (x === void 0 || y === void 0) return x === void 0 ? -1 : 1;
74714
+ const nx = /^\d+$/.test(x), ny = /^\d+$/.test(y);
74715
+ if (nx && ny) return BigInt(x) > BigInt(y) ? 1 : BigInt(x) < BigInt(y) ? -1 : 0;
74716
+ if (nx !== ny) return nx ? -1 : 1;
74717
+ return x > y ? 1 : -1;
74718
+ }
74719
+ return 0;
74720
+ }
74400
74721
  var SAFE_TARGET_RE = /^[a-zA-Z0-9._@+~:-]+$/;
74401
74722
  function getCliPackageVersion() {
74402
74723
  try {
74403
- const packageJson = JSON.parse(readFileSync10(new URL("../package.json", import.meta.url), "utf-8"));
74724
+ const packageJson = JSON.parse(readFileSync11(new URL("../package.json", import.meta.url), "utf-8"));
74404
74725
  return packageJson.version ?? "unknown";
74405
74726
  } catch {
74406
74727
  return "unknown";
@@ -74408,10 +74729,14 @@ function getCliPackageVersion() {
74408
74729
  }
74409
74730
  function buildSelfUpdatePlan(flags) {
74410
74731
  const packageManager = parsePackageManager(flags["package-manager"]);
74411
- const target = parseTarget(flags.version);
74732
+ const channel = flags.channel === void 0 ? savedChannel() : parseChannel(flags.channel);
74733
+ const target = parseTarget(flags.version ?? (channel === "dev" ? "alpha" : "latest"));
74412
74734
  const packageSpec = `${PACKAGE_NAME}@${target}`;
74413
74735
  const { command, args } = commandForPackageManager(packageManager, packageSpec);
74414
74736
  return {
74737
+ channel,
74738
+ persistChannel: flags.channel !== void 0,
74739
+ allowDowngrade: flags["allow-downgrade"] === true,
74415
74740
  packageManager,
74416
74741
  command,
74417
74742
  args,
@@ -74434,10 +74759,15 @@ function checkSelfUpdateStatus(plan) {
74434
74759
  return {
74435
74760
  currentVersion: plan.currentVersion,
74436
74761
  latestVersion: latest.version,
74437
- updateAvailable: plan.currentVersion !== latest.version,
74762
+ updateAvailable: compareVersions(latest.version, plan.currentVersion) > 0 || plan.allowDowngrade && compareVersions(latest.version, plan.currentVersion) !== 0,
74438
74763
  checkError: null
74439
74764
  };
74440
74765
  }
74766
+ function pinSelfUpdatePlan(plan, version) {
74767
+ const packageSpec = `${PACKAGE_NAME}@${parseTarget(version)}`;
74768
+ const { command, args } = commandForPackageManager(plan.packageManager, packageSpec);
74769
+ return { ...plan, target: version, packageSpec, command, args };
74770
+ }
74441
74771
  function runSelfUpdate(plan, options = {}) {
74442
74772
  if (plan.dryRun) return;
74443
74773
  const result = spawnSync3(plan.command, plan.args, {
@@ -74455,6 +74785,12 @@ function runSelfUpdate(plan, options = {}) {
74455
74785
  ${output}` : "";
74456
74786
  throw new Error(`${plan.command} ${plan.args.join(" ")} failed with exit code ${result.status ?? "unknown"}${suffix}`);
74457
74787
  }
74788
+ if (installedNpmPrefix()) {
74789
+ const installed = getCliPackageVersion();
74790
+ if (installed !== plan.target) {
74791
+ throw new Error(`CLI upgrade installed version ${installed}; expected ${plan.target}. Retry the upgrade with --verbose to inspect package-manager output.`);
74792
+ }
74793
+ }
74458
74794
  }
74459
74795
  function resolveTargetVersion(target) {
74460
74796
  const forced = process.env.OPENMATES_CLI_LATEST_VERSION;
@@ -74464,7 +74800,8 @@ function resolveTargetVersion(target) {
74464
74800
  }
74465
74801
  const result = spawnSync3(commandName("npm"), ["view", `${PACKAGE_NAME}@${target}`, "version"], {
74466
74802
  encoding: "utf-8",
74467
- stdio: ["ignore", "pipe", "pipe"]
74803
+ stdio: ["ignore", "pipe", "pipe"],
74804
+ timeout: 3e4
74468
74805
  });
74469
74806
  if (result.error) return { version: null, error: result.error.message };
74470
74807
  if (result.status !== 0) {
@@ -74480,7 +74817,7 @@ function parsePackageManager(value) {
74480
74817
  throw new Error("--package-manager must be one of npm, pnpm, yarn, or bun");
74481
74818
  }
74482
74819
  function parseTarget(value) {
74483
- if (value === void 0) return DEFAULT_TARGET;
74820
+ if (value === void 0) return "latest";
74484
74821
  if (typeof value !== "string" || value.length === 0) {
74485
74822
  throw new Error("--version requires a version or npm dist-tag");
74486
74823
  }
@@ -74500,15 +74837,16 @@ function commandForPackageManager(packageManager, packageSpec) {
74500
74837
  if (packageManager === "pnpm") return { command: commandName("pnpm"), args: ["add", "-g", packageSpec] };
74501
74838
  if (packageManager === "yarn") return { command: commandName("yarn"), args: ["global", "add", packageSpec] };
74502
74839
  if (packageManager === "bun") return { command: commandName("bun"), args: ["install", "-g", packageSpec] };
74503
- return { command: commandName("npm"), args: ["install", "-g", packageSpec] };
74840
+ const prefix = installedNpmPrefix();
74841
+ return { command: commandName("npm"), args: ["install", "-g", packageSpec, ...prefix ? ["--prefix", prefix] : []] };
74504
74842
  }
74505
74843
  function commandName(name) {
74506
74844
  return process.platform === "win32" ? `${name}.cmd` : name;
74507
74845
  }
74508
74846
 
74509
74847
  // src/workRecovery.ts
74510
- import { mkdirSync as mkdirSync8, renameSync as renameSync2, writeFileSync as writeFileSync8 } from "fs";
74511
- import { dirname as dirname8 } from "path";
74848
+ import { mkdirSync as mkdirSync10, renameSync as renameSync6, writeFileSync as writeFileSync10 } from "fs";
74849
+ import { dirname as dirname9 } from "path";
74512
74850
  import { stringify } from "yaml";
74513
74851
  async function projectPlanAssumption(plan, masterKey, assumption) {
74514
74852
  const key = await planKeyFromRecord(plan.encrypted, masterKey);
@@ -74687,18 +75025,18 @@ function collectMismatchPaths(left, right, path2, mismatches) {
74687
75025
  }
74688
75026
  function writeWorkRecoveryAtomically(path2, document) {
74689
75027
  validateWorkRecoveryDocument(document);
74690
- mkdirSync8(dirname8(path2), { recursive: true, mode: 448 });
75028
+ mkdirSync10(dirname9(path2), { recursive: true, mode: 448 });
74691
75029
  const temporaryPath = `${path2}.tmp`;
74692
- writeFileSync8(temporaryPath, stringify(document, { sortMapEntries: true, lineWidth: 0 }), { encoding: "utf8", mode: 384 });
74693
- renameSync2(temporaryPath, path2);
75030
+ writeFileSync10(temporaryPath, stringify(document, { sortMapEntries: true, lineWidth: 0 }), { encoding: "utf8", mode: 384 });
75031
+ renameSync6(temporaryPath, path2);
74694
75032
  }
74695
75033
 
74696
75034
  // src/revolutBusinessCertificate.ts
74697
- import { execFileSync as execFileSync4 } from "child_process";
74698
- import { createSign, randomUUID as randomUUID13 } from "crypto";
74699
- import { chmodSync as chmodSync6, existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync11 } from "fs";
74700
- import { homedir as homedir8 } from "os";
74701
- import { join as join10, resolve as resolve10 } from "path";
75035
+ import { execFileSync as execFileSync5 } from "child_process";
75036
+ import { createSign, randomUUID as randomUUID14 } from "crypto";
75037
+ import { chmodSync as chmodSync7, existsSync as existsSync13, mkdirSync as mkdirSync11, readFileSync as readFileSync12 } from "fs";
75038
+ import { homedir as homedir9 } from "os";
75039
+ import { join as join12, resolve as resolve10 } from "path";
74702
75040
  var REVOLUT_BUSINESS_CERTIFICATE_DOCS_URL = "https://developer.revolut.com/docs/guides/manage-accounts/get-started/make-your-first-api-request#generate-a-private-and-a-public-certificate";
74703
75041
  var REVOLUT_BUSINESS_DEFAULT_SANDBOX_REDIRECT_URI = "https://app.dev.openmates.org/oauth/revolut-business/callback";
74704
75042
  var REVOLUT_BUSINESS_DEFAULT_PRODUCTION_REDIRECT_URI = "https://openmates.org/oauth/revolut-business/callback";
@@ -74708,7 +75046,7 @@ var REVOLUT_BUSINESS_AUDIENCE = "https://revolut.com";
74708
75046
  var CERTIFICATE_DAYS = "1825";
74709
75047
  var RSA_BITS = "2048";
74710
75048
  function defaultRevolutBusinessCertificateDir(environment) {
74711
- return join10(homedir8(), ".openmates", "revolut-business", environment);
75049
+ return join12(homedir9(), ".openmates", "revolut-business", environment);
74712
75050
  }
74713
75051
  function defaultRevolutBusinessRedirectUri(environment) {
74714
75052
  return environment === "sandbox" ? REVOLUT_BUSINESS_DEFAULT_SANDBOX_REDIRECT_URI : REVOLUT_BUSINESS_DEFAULT_PRODUCTION_REDIRECT_URI;
@@ -74731,7 +75069,7 @@ async function exchangeRevolutBusinessAuthorizationCode(options) {
74731
75069
  if (!clientId) throw new Error("Missing Revolut Business client ID.");
74732
75070
  const code = extractRevolutBusinessAuthorizationCode(options.codeOrRedirectUrl);
74733
75071
  if (!code) throw new Error("Missing Revolut Business authorization code.");
74734
- const privateKeyPem = options.privateKeyPem ?? readFileSync11(options.privateKeyPath ?? join10(defaultRevolutBusinessCertificateDir(environment), "privatecert.pem"), "utf8");
75072
+ const privateKeyPem = options.privateKeyPem ?? readFileSync12(options.privateKeyPath ?? join12(defaultRevolutBusinessCertificateDir(environment), "privatecert.pem"), "utf8");
74735
75073
  const redirectUri = (options.redirectUri ?? defaultRevolutBusinessRedirectUri(environment)).trim();
74736
75074
  const clientAssertion = generateRevolutBusinessClientAssertion({ clientId, privateKeyPem, redirectUri });
74737
75075
  const response = await fetch(environment === "sandbox" ? "https://sandbox-b2b.revolut.com/api/1.0/auth/token" : "https://b2b.revolut.com/api/1.0/auth/token", {
@@ -74769,7 +75107,7 @@ function generateRevolutBusinessClientAssertion(options) {
74769
75107
  aud: REVOLUT_BUSINESS_AUDIENCE,
74770
75108
  exp: now + 3600,
74771
75109
  iat: now,
74772
- jti: randomUUID13()
75110
+ jti: randomUUID14()
74773
75111
  });
74774
75112
  const unsigned = `${header2}.${payload}`;
74775
75113
  const signer = createSign("RSA-SHA256");
@@ -74784,17 +75122,17 @@ function generateRevolutBusinessCertificate(options = {}) {
74784
75122
  const redirectUri = (options.redirectUri ?? defaultRevolutBusinessRedirectUri(environment)).trim();
74785
75123
  if (!title) throw new Error("Revolut certificate title must not be empty.");
74786
75124
  if (!redirectUri.startsWith("https://")) throw new Error("Revolut OAuth redirect URI must start with https://.");
74787
- mkdirSync9(outputDir, { recursive: true, mode: 448 });
74788
- chmodSync6(outputDir, 448);
74789
- const privateKeyPath = join10(outputDir, "privatecert.pem");
74790
- const publicCertificatePath = join10(outputDir, "publiccert.cer");
74791
- if (!options.overwrite && (existsSync12(privateKeyPath) || existsSync12(publicCertificatePath))) {
75125
+ mkdirSync11(outputDir, { recursive: true, mode: 448 });
75126
+ chmodSync7(outputDir, 448);
75127
+ const privateKeyPath = join12(outputDir, "privatecert.pem");
75128
+ const publicCertificatePath = join12(outputDir, "publiccert.cer");
75129
+ if (!options.overwrite && (existsSync13(privateKeyPath) || existsSync13(publicCertificatePath))) {
74792
75130
  throw new Error(`Revolut certificate files already exist in ${outputDir}. Re-run with --overwrite to replace them.`);
74793
75131
  }
74794
75132
  try {
74795
- execFileSync4("openssl", ["genrsa", "-out", privateKeyPath, RSA_BITS], { stdio: "ignore" });
74796
- chmodSync6(privateKeyPath, 384);
74797
- execFileSync4(
75133
+ execFileSync5("openssl", ["genrsa", "-out", privateKeyPath, RSA_BITS], { stdio: "ignore" });
75134
+ chmodSync7(privateKeyPath, 384);
75135
+ execFileSync5(
74798
75136
  "openssl",
74799
75137
  [
74800
75138
  "req",
@@ -74822,7 +75160,7 @@ function generateRevolutBusinessCertificate(options = {}) {
74822
75160
  outputDir,
74823
75161
  privateKeyPath,
74824
75162
  publicCertificatePath,
74825
- publicCertificatePem: readFileSync11(publicCertificatePath, "utf8").trim()
75163
+ publicCertificatePem: readFileSync12(publicCertificatePath, "utf8").trim()
74826
75164
  };
74827
75165
  }
74828
75166
  function escapeOpenSslSubjectValue(value) {
@@ -74848,6 +75186,15 @@ async function main() {
74848
75186
  assertTrustedAccountGuardEnvironment(parsed.flags);
74849
75187
  assertTrustedAccountCommandAllowed(command);
74850
75188
  }
75189
+ if (parsed.flags.profile !== void 0) {
75190
+ const profile = parsed.flags.profile;
75191
+ if (typeof profile !== "string" || !profile) throw new Error("--profile requires a profile name");
75192
+ resolveStateDir({ profile, stateDir: "" });
75193
+ if (process.env.OPENMATES_STATE_DIR?.trim()) {
75194
+ throw new Error("--profile cannot be combined with OPENMATES_STATE_DIR");
75195
+ }
75196
+ process.env.OPENMATES_PROFILE = profile;
75197
+ }
74851
75198
  const client = OpenMatesClient.load({
74852
75199
  apiUrl: typeof parsed.flags["api-url"] === "string" ? parsed.flags["api-url"] : void 0
74853
75200
  });
@@ -75213,6 +75560,9 @@ function assertTrustedAccountCommandAllowed(command) {
75213
75560
  }
75214
75561
  }
75215
75562
  function assertTrustedAccountGuardEnvironment(flags, environment = process.env) {
75563
+ if (flags.profile !== void 0 && flags.profile !== TRUST_GUARD_PROFILE) {
75564
+ throw new Error(`Trusted OpenCode CLI commands cannot override profile ${TRUST_GUARD_PROFILE}.`);
75565
+ }
75216
75566
  if (environment.OPENMATES_PROFILE !== TRUST_GUARD_PROFILE) {
75217
75567
  throw new Error(`Trusted OpenCode CLI commands require OPENMATES_PROFILE=${TRUST_GUARD_PROFILE}.`);
75218
75568
  }
@@ -75250,13 +75600,19 @@ function handleSupport(flags) {
75250
75600
  console.log(renderSupportInfo());
75251
75601
  }
75252
75602
  function handleSelfUpdate(command, flags) {
75253
- const plan = buildSelfUpdatePlan(flags);
75603
+ let plan = buildSelfUpdatePlan(flags);
75254
75604
  const status = checkSelfUpdateStatus(plan);
75255
- const shouldInstall = status.updateAvailable !== false;
75605
+ if (status.checkError) throw new Error(`Update check failed: ${status.checkError}`);
75606
+ const shouldInstall = status.updateAvailable === true;
75607
+ if (!plan.dryRun && shouldInstall && status.latestVersion) {
75608
+ plan = pinSelfUpdatePlan(plan, status.latestVersion);
75609
+ }
75256
75610
  if (flags.json === true) {
75257
75611
  if (!plan.dryRun && shouldInstall) runSelfUpdate(plan, { verbose: flags.verbose === true });
75612
+ persistSelfUpdateChannel(plan);
75258
75613
  printJson2({
75259
75614
  command,
75615
+ channel: plan.channel,
75260
75616
  status: status.updateAvailable === false ? "up_to_date" : plan.dryRun ? "planned" : "success",
75261
75617
  current_version: plan.currentVersion,
75262
75618
  latest_version: status.latestVersion,
@@ -75273,6 +75629,7 @@ function handleSelfUpdate(command, flags) {
75273
75629
  console.log("");
75274
75630
  console.log("Checking for updates...");
75275
75631
  console.log("");
75632
+ console.log(`Release channel: ${plan.channel}`);
75276
75633
  console.log(`Current version: ${plan.currentVersion}`);
75277
75634
  console.log(`Latest version: ${status.latestVersion ?? "unknown"}`);
75278
75635
  if (status.checkError) {
@@ -75288,11 +75645,13 @@ function handleSelfUpdate(command, flags) {
75288
75645
  return;
75289
75646
  }
75290
75647
  if (status.updateAvailable === false) {
75648
+ persistSelfUpdateChannel(plan);
75291
75649
  console.log("OpenMates CLI is already up to date.");
75292
75650
  return;
75293
75651
  }
75294
75652
  console.log(`Updating OpenMates CLI with ${plan.packageManager}...`);
75295
75653
  runSelfUpdate(plan, { verbose: flags.verbose === true });
75654
+ persistSelfUpdateChannel(plan);
75296
75655
  console.log(`Installed OpenMates CLI ${status.latestVersion ?? plan.target}.`);
75297
75656
  console.log("");
75298
75657
  console.log("OpenMates is up to date.");
@@ -75305,6 +75664,7 @@ function handleCliVersion(flags) {
75305
75664
  if (flags.json === true) {
75306
75665
  printJson2({
75307
75666
  command: "version",
75667
+ channel: plan.channel,
75308
75668
  current_version: status.currentVersion,
75309
75669
  latest_version: status.latestVersion,
75310
75670
  update_available: status.updateAvailable,
@@ -75314,6 +75674,7 @@ function handleCliVersion(flags) {
75314
75674
  return;
75315
75675
  }
75316
75676
  console.log(`OpenMates CLI ${status.currentVersion}`);
75677
+ console.log(`Release channel: ${plan.channel}`);
75317
75678
  if (status.checkError) {
75318
75679
  console.log(`Update check failed: ${status.checkError}`);
75319
75680
  return;
@@ -75336,35 +75697,55 @@ async function handleTasks(client, subcommand, rest, flags, redactor) {
75336
75697
  const action = rest[0] ?? "list";
75337
75698
  const task = await requiredResolvedTask(client, masterKey, rest[1], scope, `activity ${action}`);
75338
75699
  const context = { teamId: scope.teamId, personal: scope.personal };
75339
- if (action === "list") {
75340
- const records = [];
75341
- let cursor;
75342
- do {
75343
- const page = await client.listUserTaskActivity(task.taskId, {
75344
- ...context,
75345
- cursor,
75346
- limit: typeof flags.limit === "string" ? Number(flags.limit) : void 0
75347
- });
75348
- records.push(...page.entries);
75349
- cursor = page.next_cursor ?? void 0;
75350
- } while (cursor);
75351
- const entries = await decryptTaskActivityEntries(task, masterKey, records);
75352
- if (flags.json === true) printJson2({ entries: entries.map(taskActivityToJson) });
75353
- else console.log(renderTaskActivityList(entries));
75700
+ const actor = flags["as-assignee"] === true ? "assignee" : "user";
75701
+ const delivery = () => activityDeliveryStore(JSON.stringify([
75702
+ client.getSession().apiUrl,
75703
+ client.getSession().hashedEmail,
75704
+ scope.teamId || "personal",
75705
+ task.taskId,
75706
+ actor
75707
+ ]));
75708
+ const createActivity = (input) => client.createUserTaskActivity(task.taskId, input, {
75709
+ ...context,
75710
+ ...actor === "assignee" ? { actorMode: "assignee" } : {}
75711
+ });
75712
+ if (action === "flush") {
75713
+ printJson2(await delivery().flush(createActivity));
75714
+ return;
75715
+ }
75716
+ if (action === "list" || action === "search") {
75717
+ const pending = flags["flush-pending"] === true ? await delivery().flush(createActivity) : void 0;
75718
+ const history = await readActivityHistory({
75719
+ maxEntries: typeof flags["max-entries"] === "string" ? Number(flags["max-entries"]) : action === "search" ? 200 : void 0,
75720
+ pageSize: typeof flags.limit === "string" ? Number(flags.limit) : 100,
75721
+ cursor: typeof flags.cursor === "string" ? flags.cursor : void 0,
75722
+ ...action === "search" ? { query: typeof flags.query === "string" ? flags.query : "" } : {},
75723
+ page: async (cursor, limit) => {
75724
+ const page = await client.listUserTaskActivity(task.taskId, {
75725
+ ...context,
75726
+ cursor,
75727
+ limit,
75728
+ newestFirst: flags["newest-first"] === true
75729
+ });
75730
+ return { entries: await decryptTaskActivityEntries(task, masterKey, page.entries), next_cursor: page.next_cursor };
75731
+ }
75732
+ });
75733
+ if (flags.json === true) printJson2({ ...history, entries: history.entries.map(taskActivityToJson), delivery: pending });
75734
+ else {
75735
+ console.log(renderTaskActivityList(history.entries));
75736
+ if (history.truncated) console.log(`Activity history truncated. Search all history: openmates tasks activity search ${task.shortId} --query "text" --max-entries 200`);
75737
+ if (history.next_cursor) console.log(`Next cursor: ${history.next_cursor}`);
75738
+ }
75354
75739
  return;
75355
75740
  }
75356
75741
  if (action === "add") {
75357
75742
  const message = typeof flags.message === "string" ? flags.message : rest.slice(2).join(" ");
75358
75743
  if (!message.trim()) throw new Error("Missing comment. Usage: openmates tasks activity add <task-id> --message <text>");
75359
- const input = await buildCreateTaskActivityInput(task, masterKey, { message });
75360
- const entry = await decryptTaskActivityEntry(
75361
- task,
75362
- masterKey,
75363
- await client.createUserTaskActivity(task.taskId, input, {
75364
- ...context,
75365
- ...flags["as-assignee"] === true ? { actorMode: "assignee" } : {}
75366
- })
75367
- );
75744
+ const deliveryId = typeof flags["delivery-id"] === "string" ? flags["delivery-id"] : void 0;
75745
+ const build = () => buildCreateTaskActivityInput(task, masterKey, { message, ...deliveryId ? { entryId: deliveryId } : {} });
75746
+ const record = deliveryId ? await delivery().deliver(deliveryId, build, createActivity) : await createActivity(await build());
75747
+ const entry = await decryptTaskActivityEntry(task, masterKey, record);
75748
+ if (entry.message !== message) throw new Error("Activity delivery id already belongs to a different milestone message");
75368
75749
  if (flags.json === true) printJson2({ entry: taskActivityToJson(entry) });
75369
75750
  else console.log(renderTaskActivityList([entry]));
75370
75751
  return;
@@ -75381,7 +75762,7 @@ async function handleTasks(client, subcommand, rest, flags, redactor) {
75381
75762
  else console.log(renderTaskActivityList([entry]));
75382
75763
  return;
75383
75764
  }
75384
- throw new Error("Usage: openmates tasks activity list|add|delete <task-id> [<entry-id>] [--message <text>]");
75765
+ throw new Error("Usage: openmates tasks activity list|search|add|flush|delete <task-id> [<entry-id>] [--message <text>]");
75385
75766
  }
75386
75767
  if (rest[0] === "add-to-project") {
75387
75768
  rejectRemoteCopyFlags(flags);
@@ -76081,7 +76462,7 @@ async function resolveAddToProjectStorageChoice(client, project, flags, objectTy
76081
76462
  async function createEncryptedProjectItem(client, project, input) {
76082
76463
  const timestamp = nowSeconds3();
76083
76464
  return client.createProjectItem(project.projectId, {
76084
- project_item_id: randomUUID14(),
76465
+ project_item_id: randomUUID15(),
76085
76466
  folder_id: input.folderId ?? null,
76086
76467
  item_type: input.itemType,
76087
76468
  target_id: input.targetId,
@@ -76275,7 +76656,7 @@ async function handlePlans(client, subcommand, rest, flags, redactor) {
76275
76656
  const encryptedSources = proof.length ? await encryptPlanAssumptionSources(plan, masterKey, proof) : void 0;
76276
76657
  if (action === "create" || action === "add") {
76277
76658
  const assumption = await client.createPlanAssumption(plan.planId, {
76278
- assumption_id: typeof flags.id === "string" ? flags.id : randomUUID14(),
76659
+ assumption_id: typeof flags.id === "string" ? flags.id : randomUUID15(),
76279
76660
  encrypted_text: await encryptPlanAssumptionSources(plan, masterKey, requiredStringFlag(flags.text ?? rest.slice(2).join(" "), "--text <assumption>")),
76280
76661
  status: typeof flags.status === "string" ? flags.status : void 0,
76281
76662
  linked_sub_chat_id: typeof flags["sub-chat"] === "string" ? flags["sub-chat"] : void 0,
@@ -76317,7 +76698,7 @@ async function handlePlans(client, subcommand, rest, flags, redactor) {
76317
76698
  if (action === "diff") throw new Error("Revision diffs are reviewed in the authenticated web review page; use submit-for-review to print its URL.");
76318
76699
  if (action !== "submit-for-review") throw new Error("Usage: openmates plans revisions list|submit-for-review|status|diff <plan-id>");
76319
76700
  const snapshot = JSON.stringify(planToJson(plan));
76320
- const revision = await client.submitPlanRevision(plan.planId, { fingerprint: createHash14("sha256").update(snapshot).digest("hex"), encrypted_snapshot: await encryptPlanAssumptionSources(plan, masterKey, snapshot), created_at: nowSeconds3() });
76701
+ const revision = await client.submitPlanRevision(plan.planId, { fingerprint: createHash15("sha256").update(snapshot).digest("hex"), encrypted_snapshot: await encryptPlanAssumptionSources(plan, masterKey, snapshot), created_at: nowSeconds3() });
76321
76702
  const reviewUrl = `${deriveAppUrl(client.apiUrl)}/plans/${plan.planId}/review?revision=${revision.revision_id}`;
76322
76703
  if (flags.json === true) printJson2({ revision, review_url: reviewUrl });
76323
76704
  else console.log(`Plan revision submitted for web review: ${reviewUrl}`);
@@ -76873,8 +77254,8 @@ async function handleWorkRecovery(client, subcommand, rest, flags) {
76873
77254
  }
76874
77255
  if (subcommand === "validate" || subcommand === "restore") {
76875
77256
  const path2 = requiredStringFlag(flags.file ?? rest[0], "--file <projection.yml>");
76876
- if (!existsSync13(path2)) throw new Error(`Recovery projection does not exist: ${path2}`);
76877
- const document = parseYaml(readFileSync12(path2, "utf8"));
77257
+ if (!existsSync14(path2)) throw new Error(`Recovery projection does not exist: ${path2}`);
77258
+ const document = parseYaml(readFileSync13(path2, "utf8"));
76878
77259
  validateWorkRecoveryDocument(document);
76879
77260
  if (subcommand === "validate") {
76880
77261
  if (flags.json === true) printJson2({ valid: true });
@@ -77200,7 +77581,7 @@ async function handleProjects(client, subcommand, rest, flags, redactor) {
77200
77581
  const name = requiredStringFlag(rest[0] ?? flags.name, "project name");
77201
77582
  const projectKey = randomBytes9(32);
77202
77583
  const wrapping = await client.projectWrappingKey(context);
77203
- const projectId = randomUUID14();
77584
+ const projectId = randomUUID15();
77204
77585
  const timestamp = nowSeconds3();
77205
77586
  const slugMetadata = await buildEncryptedObjectSlugMetadata({
77206
77587
  value: typeof flags.slug === "string" ? flags.slug : name,
@@ -77222,7 +77603,7 @@ async function handleProjects(client, subcommand, rest, flags, redactor) {
77222
77603
  last_opened_at: timestamp,
77223
77604
  key_wrappers: wrapping.teamId ? [{
77224
77605
  key_type: "team",
77225
- hashed_team_id: createHash14("sha256").update(wrapping.teamId).digest("hex"),
77606
+ hashed_team_id: createHash15("sha256").update(wrapping.teamId).digest("hex"),
77226
77607
  team_key_epoch: 1,
77227
77608
  encrypted_project_key: await encryptBytesWithAesGcm(projectKey, wrapping.key),
77228
77609
  wrapper_version: 1,
@@ -77308,7 +77689,7 @@ async function handleProjects(client, subcommand, rest, flags, redactor) {
77308
77689
  const proposal = isShortWorkspaceAsk(instruction) ? { name: instruction, description: "", icon: "folder", color: "default" } : extractRecord(await client.planProjectAsk({ instruction }), "proposed_project");
77309
77690
  const projectKey = randomBytes9(32);
77310
77691
  const timestamp = Math.floor(Date.now() / 1e3);
77311
- const projectId = randomUUID14();
77692
+ const projectId = randomUUID15();
77312
77693
  const name = requiredString(proposal, "name", instruction);
77313
77694
  const slugMetadata = await buildEncryptedObjectSlugMetadata({
77314
77695
  value: typeof flags.slug === "string" ? flags.slug : name,
@@ -77705,7 +78086,7 @@ async function handleTeams(client, subcommand, rest, flags) {
77705
78086
  if (action === "get") {
77706
78087
  const output = requiredStringFlag(flags.output ?? flags.file ?? rest[2], "--output <path>");
77707
78088
  const image = await client.getTeamProfileImage(teamId);
77708
- writeFileSync9(output, image.data, { mode: 384 });
78089
+ writeFileSync11(output, image.data, { mode: 384 });
77709
78090
  if (flags.json === true) printJson2({ output, content_type: image.contentType, size_bytes: image.data.byteLength });
77710
78091
  else console.log(`Team profile image written: ${output}`);
77711
78092
  return;
@@ -77864,7 +78245,7 @@ async function handleTeams(client, subcommand, rest, flags) {
77864
78245
  const output = requiredStringFlag(flags.output, "--output <path>");
77865
78246
  const result = await client.exportTeamData(teamId);
77866
78247
  const artifact = result.artifact && typeof result.artifact === "object" ? result.artifact : result;
77867
- writeFileSync9(output, `${JSON.stringify(artifact, null, 2)}
78248
+ writeFileSync11(output, `${JSON.stringify(artifact, null, 2)}
77868
78249
  `, { encoding: "utf-8", mode: 384 });
77869
78250
  if (flags.json === true) printJson2({ ...result, output });
77870
78251
  else console.log(`Team export written: ${output}`);
@@ -77872,7 +78253,7 @@ async function handleTeams(client, subcommand, rest, flags) {
77872
78253
  }
77873
78254
  if (subcommand === "import") {
77874
78255
  const file = requiredStringFlag(flags.file ?? rest[0], "--file <path>");
77875
- const artifact = JSON.parse(readFileSync12(file, "utf-8"));
78256
+ const artifact = JSON.parse(readFileSync13(file, "utf-8"));
77876
78257
  let teamId = typeof flags.team === "string" ? flags.team : typeof flags["destination-team"] === "string" ? flags["destination-team"] : void 0;
77877
78258
  if (!teamId && typeof flags["new-team-name"] === "string") {
77878
78259
  const team = await client.createTeam({ name: flags["new-team-name"] });
@@ -78207,7 +78588,7 @@ async function handleRemoteAccess(client, subcommand, rest, flags) {
78207
78588
  const projects = await loadProjects(client, masterKey, hostingFlags, hostingContext);
78208
78589
  const bindings = await resolveRemoteAccessBindings(client, masterKey, projects, candidateRoots, hostingFlags, hostingContext);
78209
78590
  if (bindings.length === 0) throw new Error("No Project folders were approved for remote access.");
78210
- const sourceSessionId = randomUUID14();
78591
+ const sourceSessionId = randomUUID15();
78211
78592
  const controller = new AbortController();
78212
78593
  const stop = () => controller.abort();
78213
78594
  process.once("SIGINT", stop);
@@ -78314,8 +78695,8 @@ async function resolveRemoteAccessBindings(client, masterKey, projects, candidat
78314
78695
  const answer = (await promptLine2(`Create ${unresolved.length} missing Project${unresolved.length === 1 ? "" : "s"}? [y/N] `)).trim().toLowerCase();
78315
78696
  if (answer !== "y" && answer !== "yes") throw new Error("Remote access Project creation cancelled.");
78316
78697
  for (const rootPath of unresolved) {
78317
- const project = await createEncryptedRemoteAccessProject(client, masterKey, basename3(rootPath), context);
78318
- resolved.push({ rootPath, project, sourceId: randomUUID14() });
78698
+ const project = await createEncryptedRemoteAccessProject(client, masterKey, basename4(rootPath), context);
78699
+ resolved.push({ rootPath, project, sourceId: randomUUID15() });
78319
78700
  }
78320
78701
  }
78321
78702
  const bindings = [];
@@ -78326,7 +78707,7 @@ async function resolveRemoteAccessBindings(client, masterKey, projects, candidat
78326
78707
  projectId: item.project.projectId,
78327
78708
  rootPath: item.rootPath,
78328
78709
  sourceType,
78329
- displayName: basename3(item.rootPath)
78710
+ displayName: basename4(item.rootPath)
78330
78711
  });
78331
78712
  const remoteSources = await client.listProjectSources(item.project.projectId, context);
78332
78713
  const remoteSource = remoteSources.find((entry) => entry.source_id === source.sourceId);
@@ -78352,7 +78733,7 @@ async function resolveRemoteAccessBindings(client, masterKey, projects, candidat
78352
78733
  }
78353
78734
  async function createEncryptedRemoteAccessProject(client, masterKey, name, context) {
78354
78735
  const projectKey = randomBytes9(32);
78355
- const projectId = randomUUID14();
78736
+ const projectId = randomUUID15();
78356
78737
  const timestamp = Math.floor(Date.now() / 1e3);
78357
78738
  const wrapping = await client.projectWrappingKey(context);
78358
78739
  const payload = {
@@ -78369,7 +78750,7 @@ async function createEncryptedRemoteAccessProject(client, masterKey, name, conte
78369
78750
  last_opened_at: timestamp,
78370
78751
  key_wrappers: wrapping.teamId ? [{
78371
78752
  key_type: "team",
78372
- hashed_team_id: createHash14("sha256").update(wrapping.teamId).digest("hex"),
78753
+ hashed_team_id: createHash15("sha256").update(wrapping.teamId).digest("hex"),
78373
78754
  team_key_epoch: 1,
78374
78755
  encrypted_project_key: await encryptBytesWithAesGcm(projectKey, wrapping.key),
78375
78756
  wrapper_version: 1,
@@ -79169,16 +79550,16 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79169
79550
  }
79170
79551
  }
79171
79552
  const { mkdir: mkdir2, writeFile: writeFile2 } = await import("fs/promises");
79172
- const { join: join11 } = await import("path");
79553
+ const { join: join13 } = await import("path");
79173
79554
  if (useZip) {
79174
- const tmpDir = join11(outputDir, `.${filenameBase}_tmp`);
79555
+ const tmpDir = join13(outputDir, `.${filenameBase}_tmp`);
79175
79556
  await mkdir2(tmpDir, { recursive: true });
79176
- await writeFile2(join11(tmpDir, `${filenameBase}.yml`), yamlContent);
79177
- await writeFile2(join11(tmpDir, `${filenameBase}.md`), mdContent);
79557
+ await writeFile2(join13(tmpDir, `${filenameBase}.yml`), yamlContent);
79558
+ await writeFile2(join13(tmpDir, `${filenameBase}.md`), mdContent);
79178
79559
  if (codeEmbeds.length > 0) {
79179
79560
  for (const ce of codeEmbeds) {
79180
79561
  const fpath = ce.filePath ?? ce.filename ?? `${ce.embedId.slice(0, 8)}.${getExtForLang(ce.language)}`;
79181
- const fullPath = join11(tmpDir, "code", fpath);
79562
+ const fullPath = join13(tmpDir, "code", fpath);
79182
79563
  await mkdir2(fullPath.substring(0, fullPath.lastIndexOf("/")), {
79183
79564
  recursive: true
79184
79565
  });
@@ -79186,13 +79567,13 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79186
79567
  }
79187
79568
  }
79188
79569
  if (transcriptEmbeds.length > 0) {
79189
- const tDir = join11(tmpDir, "transcripts");
79570
+ const tDir = join13(tmpDir, "transcripts");
79190
79571
  await mkdir2(tDir, { recursive: true });
79191
79572
  for (const te of transcriptEmbeds) {
79192
- await writeFile2(join11(tDir, te.filename), te.content);
79573
+ await writeFile2(join13(tDir, te.filename), te.content);
79193
79574
  }
79194
79575
  }
79195
- const zipPath = join11(outputDir, `${filenameBase}.zip`);
79576
+ const zipPath = join13(outputDir, `${filenameBase}.zip`);
79196
79577
  const { execSync: execSync2 } = await import("child_process");
79197
79578
  try {
79198
79579
  execSync2(`cd "${tmpDir}" && zip -r "${zipPath}" .`, { stdio: "pipe" });
@@ -79207,17 +79588,17 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79207
79588
  );
79208
79589
  }
79209
79590
  } else {
79210
- const chatDir = join11(outputDir, filenameBase);
79591
+ const chatDir = join13(outputDir, filenameBase);
79211
79592
  await mkdir2(chatDir, { recursive: true });
79212
79593
  const written = [];
79213
- await writeFile2(join11(chatDir, `${filenameBase}.yml`), yamlContent);
79594
+ await writeFile2(join13(chatDir, `${filenameBase}.yml`), yamlContent);
79214
79595
  written.push(`${filenameBase}.yml`);
79215
- await writeFile2(join11(chatDir, `${filenameBase}.md`), mdContent);
79596
+ await writeFile2(join13(chatDir, `${filenameBase}.md`), mdContent);
79216
79597
  written.push(`${filenameBase}.md`);
79217
79598
  if (codeEmbeds.length > 0) {
79218
79599
  for (const ce of codeEmbeds) {
79219
79600
  const fpath = ce.filePath ?? ce.filename ?? `${ce.embedId.slice(0, 8)}.${getExtForLang(ce.language)}`;
79220
- const fullPath = join11(chatDir, "code", fpath);
79601
+ const fullPath = join13(chatDir, "code", fpath);
79221
79602
  await mkdir2(fullPath.substring(0, fullPath.lastIndexOf("/")), {
79222
79603
  recursive: true
79223
79604
  });
@@ -79226,10 +79607,10 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79226
79607
  }
79227
79608
  }
79228
79609
  if (transcriptEmbeds.length > 0) {
79229
- const tDir = join11(chatDir, "transcripts");
79610
+ const tDir = join13(chatDir, "transcripts");
79230
79611
  await mkdir2(tDir, { recursive: true });
79231
79612
  for (const te of transcriptEmbeds) {
79232
- await writeFile2(join11(tDir, te.filename), te.content);
79613
+ await writeFile2(join13(tDir, te.filename), te.content);
79233
79614
  written.push(`transcripts/${te.filename}`);
79234
79615
  }
79235
79616
  }
@@ -79265,7 +79646,7 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79265
79646
  printJson2({
79266
79647
  chat_id: chat.id,
79267
79648
  title: chat.title,
79268
- output_dir: useZip ? join11(outputDir, `${filenameBase}.zip`) : join11(outputDir, filenameBase),
79649
+ output_dir: useZip ? join13(outputDir, `${filenameBase}.zip`) : join13(outputDir, filenameBase),
79269
79650
  files,
79270
79651
  code_embeds: codeEmbeds.length,
79271
79652
  transcript_embeds: transcriptEmbeds.length
@@ -79585,7 +79966,7 @@ function normalizeApiKeyLoadedChat(payload) {
79585
79966
  const messages = rawMessages.map((message) => {
79586
79967
  const messageId = String(message.client_message_id ?? message.message_id ?? message.id ?? "");
79587
79968
  const directEmbedIds = Array.isArray(message.embedIds) ? message.embedIds : Array.isArray(message.embed_ids) ? message.embed_ids : [];
79588
- const hashedMessageId = messageId ? createHash14("sha256").update(messageId).digest("hex") : null;
79969
+ const hashedMessageId = messageId ? createHash15("sha256").update(messageId).digest("hex") : null;
79589
79970
  const matchedEmbedIds = hashedMessageId ? rawEmbeds.filter((embed) => embed.hashed_message_id === hashedMessageId && !embed.parent_embed_id).map((embed) => String(embed.embed_id ?? embed.id ?? "")).filter(Boolean) : [];
79590
79971
  return {
79591
79972
  id: messageId,
@@ -79791,7 +80172,7 @@ async function handleWorkflows(client, subcommand, rest, flags, redactor) {
79791
80172
  if (subcommand === "validate") {
79792
80173
  const file = typeof flags.file === "string" ? flags.file : "";
79793
80174
  if (!file) throw new Error("Missing --file. Example: openmates workflows validate --file workflow.yml");
79794
- const validation = await client.validateWorkflowYaml(readFileSync12(file, "utf8"));
80175
+ const validation = await client.validateWorkflowYaml(readFileSync13(file, "utf8"));
79795
80176
  if (flags.json === true) {
79796
80177
  printJson2(validation);
79797
80178
  } else {
@@ -79807,7 +80188,7 @@ async function handleWorkflows(client, subcommand, rest, flags, redactor) {
79807
80188
  if (subcommand === "create") {
79808
80189
  const yamlFile = typeof flags.file === "string" ? flags.file : "";
79809
80190
  if (yamlFile) {
79810
- const result = await client.createWorkflowYaml(readFileSync12(yamlFile, "utf8"));
80191
+ const result = await client.createWorkflowYaml(readFileSync13(yamlFile, "utf8"));
79811
80192
  if (flags.json === true) {
79812
80193
  printJson2(result);
79813
80194
  } else {
@@ -79841,7 +80222,7 @@ async function handleWorkflows(client, subcommand, rest, flags, redactor) {
79841
80222
  const workflowId = rest[0] ? await requiredResolvedWorkflowId(client, rest[0], flags, "update") : void 0;
79842
80223
  const yamlFile = typeof flags.file === "string" ? flags.file : "";
79843
80224
  if (!workflowId || !yamlFile) throw new Error("Missing workflow ID or --file. Example: openmates workflows update <id> --file workflow.yml");
79844
- const result = await client.updateWorkflowYaml(workflowId, readFileSync12(yamlFile, "utf8"));
80225
+ const result = await client.updateWorkflowYaml(workflowId, readFileSync13(yamlFile, "utf8"));
79845
80226
  if (typeof flags.slug === "string") {
79846
80227
  result.workflow = await client.updateWorkflow(result.workflow.id, { slug: flags.slug });
79847
80228
  }
@@ -80291,7 +80672,7 @@ function buildImagesAiDetectionSummary(uploadResult, filePath) {
80291
80672
  const classification = classifyImagesAiDetection(score);
80292
80673
  return {
80293
80674
  file: filePath,
80294
- filename: uploadResult.filename || basename3(filePath),
80675
+ filename: uploadResult.filename || basename4(filePath),
80295
80676
  content_type: uploadResult.content_type,
80296
80677
  embed_id: uploadResult.embed_id,
80297
80678
  deduplicated: uploadResult.deduplicated,
@@ -80443,7 +80824,7 @@ function buildCodeImageToHtmlRequest(positionals, flags) {
80443
80824
  );
80444
80825
  }
80445
80826
  const request = {
80446
- image_base64: readFileSync12(file).toString("base64"),
80827
+ image_base64: readFileSync13(file).toString("base64"),
80447
80828
  mime_type: inferImageToHtmlMimeType(file),
80448
80829
  filename: file.split(/[\\/]/).pop() ?? file
80449
80830
  };
@@ -81322,7 +81703,7 @@ async function handleEmbeds(client, subcommand, rest, flags) {
81322
81703
  throw new Error("Embed version content was not available after local reconstruction.");
81323
81704
  }
81324
81705
  if (typeof flags.output === "string") {
81325
- writeFileSync9(flags.output, result.content, "utf-8");
81706
+ writeFileSync11(flags.output, result.content, "utf-8");
81326
81707
  if (flags.json === true) {
81327
81708
  printJson2({ ...result, output: flags.output });
81328
81709
  } else {
@@ -81606,12 +81987,12 @@ async function parseAccountImportFile(parserFormat, file, flags = {}) {
81606
81987
  const selectedSource = requestedSource;
81607
81988
  if (parserFormat === "generic") {
81608
81989
  if (selectedSource !== "gemini" && selectedSource !== "other") throw new Error("Generic account import requires --source gemini or --source other.");
81609
- return parseGenericImportBuffer(payload, basename3(file), selectedSource);
81990
+ return parseGenericImportBuffer(payload, basename4(file), selectedSource);
81610
81991
  }
81611
- if (parserFormat === "claude") return parseClaudeImportBuffer(payload, basename3(file), selectedSource);
81612
- if (parserFormat === "chatgpt") return parseChatGPTImportBuffer(payload, basename3(file), selectedSource);
81613
- if (parserFormat === "opencode") return parseOpenCodeImportBuffer(payload, basename3(file), selectedSource);
81614
- return parseOpenMatesImportBuffer(payload, basename3(file), typeof flags.password === "string" ? flags.password : void 0, selectedSource);
81992
+ if (parserFormat === "claude") return parseClaudeImportBuffer(payload, basename4(file), selectedSource);
81993
+ if (parserFormat === "chatgpt") return parseChatGPTImportBuffer(payload, basename4(file), selectedSource);
81994
+ if (parserFormat === "opencode") return parseOpenCodeImportBuffer(payload, basename4(file), selectedSource);
81995
+ return parseOpenMatesImportBuffer(payload, basename4(file), typeof flags.password === "string" ? flags.password : void 0, selectedSource);
81615
81996
  }
81616
81997
  async function runAccountImport(client, parserFormat, file, flags) {
81617
81998
  if (parserFormat === "generic" && typeof flags.source !== "string") throw new Error("Generic account import requires --source gemini or --source other.");
@@ -81650,7 +82031,7 @@ async function runAccountImport(client, parserFormat, file, flags) {
81650
82031
  if (flags.yes !== true) {
81651
82032
  await confirmOrExit(`Import ${selectedCount} chat(s) into new encrypted OpenMates chats? [y/N] `);
81652
82033
  }
81653
- const importId = typeof preview.import_id === "string" ? preview.import_id : randomUUID14();
82034
+ const importId = typeof preview.import_id === "string" ? preview.import_id : randomUUID15();
81654
82035
  const selectedChats = parsed.chats.slice(0, selectedCount);
81655
82036
  const selectedFingerprints = selectedChats.map((chat) => chat.source_fingerprint);
81656
82037
  const confirmation = await client.confirmAccountImport(importId, selectedFingerprints);
@@ -81981,11 +82362,11 @@ function parseYamlScalar(value) {
81981
82362
  }
81982
82363
  async function saveDownloadedDocument(document, output) {
81983
82364
  const { mkdir: mkdir2, writeFile: writeFile2 } = await import("fs/promises");
81984
- const { join: join11, basename: basename4, dirname: dirname10 } = await import("path");
82365
+ const { join: join13, basename: basename5, dirname: dirname11 } = await import("path");
81985
82366
  const target = typeof output === "string" ? output : ".";
81986
- const filename = basename4(document.filename || "document.pdf");
81987
- const filePath = target.endsWith(".pdf") ? target : join11(target, filename);
81988
- await mkdir2(dirname10(filePath), { recursive: true });
82367
+ const filename = basename5(document.filename || "document.pdf");
82368
+ const filePath = target.endsWith(".pdf") ? target : join13(target, filename);
82369
+ await mkdir2(dirname11(filePath), { recursive: true });
81989
82370
  await writeFile2(filePath, document.data);
81990
82371
  return filePath;
81991
82372
  }
@@ -82144,8 +82525,8 @@ async function handleFinance(client, subcommand, _rest, flags) {
82144
82525
  refresh_token_envelope: connectedAccount.refreshTokenBundle
82145
82526
  }
82146
82527
  ] : [],
82147
- chatId: `cli-finance-${randomUUID14()}`,
82148
- messageId: `cli-finance-message-${randomUUID14()}`,
82528
+ chatId: `cli-finance-${randomUUID15()}`,
82529
+ messageId: `cli-finance-message-${randomUUID15()}`,
82149
82530
  apiKey: resolveApiKey(flags) ?? void 0,
82150
82531
  promptInjectionProtection: false
82151
82532
  });
@@ -82157,7 +82538,7 @@ async function handleFinance(client, subcommand, _rest, flags) {
82157
82538
  }
82158
82539
  function readFinanceCsvStatements(flags) {
82159
82540
  const paths = parseCsvFlag(flags.csv) ?? [];
82160
- return paths.map((filePath) => ({ filename: basename3(filePath), content: readFileSync12(filePath, "utf8") }));
82541
+ return paths.map((filePath) => ({ filename: basename4(filePath), content: readFileSync13(filePath, "utf8") }));
82161
82542
  }
82162
82543
  function buildFinanceCheckAccountsInput(flags, csvStatements, connectedAccount) {
82163
82544
  const input = {
@@ -82386,7 +82767,7 @@ async function handleRevolutBusinessConnect(client, rest, flags) {
82386
82767
  if (!clientId.trim()) throw new Error("ClientID is required to connect Revolut Business.");
82387
82768
  const code = providedCode || await promptPlainText("Code from OpenMates callback page: ");
82388
82769
  if (!code.trim()) throw new Error("Authorization code is required to connect Revolut Business.");
82389
- const privateKeyPem = readFileSync12(result.privateKeyPath, "utf8");
82770
+ const privateKeyPem = readFileSync13(result.privateKeyPath, "utf8");
82390
82771
  const setup = await client.exchangeRevolutBusinessSetupCode({
82391
82772
  clientId: clientId.trim(),
82392
82773
  code: code.trim(),
@@ -82452,7 +82833,7 @@ async function promptPlainText(question) {
82452
82833
  }
82453
82834
  async function writeSecretFile(filePath, content, force = false) {
82454
82835
  const { mkdir: mkdir2, writeFile: writeFile2, stat: stat2 } = await import("fs/promises");
82455
- const { dirname: dirname10 } = await import("path");
82836
+ const { dirname: dirname11 } = await import("path");
82456
82837
  try {
82457
82838
  await stat2(filePath);
82458
82839
  if (!force) throw new Error(`${filePath} already exists. Use --force to overwrite.`);
@@ -82462,7 +82843,7 @@ async function writeSecretFile(filePath, content, force = false) {
82462
82843
  }
82463
82844
  if (error instanceof Error && !("code" in error)) throw error;
82464
82845
  }
82465
- await mkdir2(dirname10(filePath), { recursive: true });
82846
+ await mkdir2(dirname11(filePath), { recursive: true });
82466
82847
  await writeFile2(filePath, content, { mode: 384 });
82467
82848
  return filePath;
82468
82849
  }
@@ -85607,6 +85988,7 @@ Commands:
85607
85988
 
85608
85989
  Flags:
85609
85990
  --json Output raw JSON instead of formatted output
85991
+ --profile <name> Use an isolated login profile (also OPENMATES_PROFILE)
85610
85992
  --api-url <url> Override API base URL (default: installed self-host server, then https://api.openmates.org)
85611
85993
  --api-key <key> Optional API key override (or set OPENMATES_API_KEY)
85612
85994
  --version Show CLI version and update availability
@@ -85628,12 +86010,16 @@ function printSelfUpdateHelp() {
85628
86010
  openmates update [--version <version|tag>] [--package-manager <name>] [--dry-run] [--verbose] [--json]
85629
86011
  openmates upgrade [--version <version|tag>] [--package-manager <name>] [--dry-run] [--verbose] [--json]
85630
86012
 
85631
- Updates the globally installed openmates package. The default target is latest.
86013
+ Updates the globally installed openmates package using the saved release channel.
86014
+ Stable installations default to stable; prereleases default to dev.
86015
+ Channel changes persist only after a successful update or up-to-date check.
85632
86016
 
85633
86017
  Options:
85634
- --version <version|tag> Install a specific npm version or dist-tag (default: latest)
86018
+ --version <version|tag> Install a specific npm version or dist-tag (one-time override)
85635
86019
  --package-manager <name> npm, pnpm, yarn, or bun (default: detect, then npm)
85636
86020
  --dry-run Print the package-manager command without running it
86021
+ --channel <dev|stable|main> Save release channel (dev uses npm alpha; stable/main uses latest)
86022
+ --allow-downgrade Explicitly allow installing an older version
85637
86023
  --verbose Stream package-manager output during installation
85638
86024
  --json Output the update plan/result as JSON`);
85639
86025
  }
@@ -85969,8 +86355,10 @@ function printTasksHelp() {
85969
86355
  openmates tasks reorder <task-id|short-id> [--before <task-id>] [--after <task-id>] [--position <n>] [--status <status>] [--json]
85970
86356
  openmates tasks dependencies list <task-id|short-id> [--json]
85971
86357
  openmates tasks dependencies add|remove <task-id|short-id> --target plan:<id>|task:<id> [--recovery-root <external-root>] [--json]
85972
- openmates tasks activity list <task-id|short-id> [--limit <n>] [--json]
85973
- openmates tasks activity add <task-id|short-id> --message <text> [--as-assignee] [--json]
86358
+ openmates tasks activity list <task-id|short-id> [--max-entries <1-200>] [--cursor <cursor>] [--newest-first] [--json]
86359
+ openmates tasks activity search <task-id|short-id> --query <text> [--max-entries <1-200>] [--json]
86360
+ openmates tasks activity add <task-id|short-id> --message <text> [--as-assignee] [--delivery-id <sha256>] [--json]
86361
+ openmates tasks activity flush <task-id|short-id> [--as-assignee] [--json]
85974
86362
  openmates tasks activity delete <task-id|short-id> <entry-id> [--json]
85975
86363
 
85976
86364
  Chat-scoped aliases:
@@ -86440,7 +86828,7 @@ async function handleDocs(client, subcommand, rest, flags) {
86440
86828
  }
86441
86829
  if (subcommand === "download") {
86442
86830
  const { writeFile: writeFile2, mkdir: mkdir2 } = await import("fs/promises");
86443
- const { join: join11, dirname: dirname10 } = await import("path");
86831
+ const { join: join13, dirname: dirname11 } = await import("path");
86444
86832
  if (flags.all === true) {
86445
86833
  const outputDir = typeof flags.output === "string" ? flags.output : "./openmates-docs";
86446
86834
  const tree = await client.listDocs();
@@ -86449,8 +86837,8 @@ async function handleDocs(client, subcommand, rest, flags) {
86449
86837
  let count = 0;
86450
86838
  for (const slug2 of slugs) {
86451
86839
  const content2 = await client.getDoc(slug2);
86452
- const filePath = join11(outputDir, `${slug2}.md`);
86453
- await mkdir2(dirname10(filePath), { recursive: true });
86840
+ const filePath = join13(outputDir, `${slug2}.md`);
86841
+ await mkdir2(dirname11(filePath), { recursive: true });
86454
86842
  await writeFile2(filePath, content2, "utf-8");
86455
86843
  count++;
86456
86844
  process.stderr.write(`\r Downloaded ${count}/${slugs.length}`);
@@ -86522,8 +86910,8 @@ function isCliEntrypoint() {
86522
86910
  if (!entrypoint) return false;
86523
86911
  try {
86524
86912
  const invokedPath = realpathSync2(entrypoint);
86525
- const modulePath = realpathSync2(fileURLToPath2(import.meta.url));
86526
- return invokedPath === modulePath || basename3(invokedPath) === "cli.js" && dirname9(invokedPath) === dirname9(modulePath);
86913
+ const modulePath = realpathSync2(fileURLToPath3(import.meta.url));
86914
+ return invokedPath === modulePath || basename4(invokedPath) === "cli.js" && dirname10(invokedPath) === dirname10(modulePath);
86527
86915
  } catch {
86528
86916
  return false;
86529
86917
  }