openmates 0.18.0-alpha.2 → 0.18.0-alpha.4

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;
@@ -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,
@@ -13974,8 +14039,9 @@ Required: ${schema.required.join(", ")}`
13974
14039
  const session = this.requireSession();
13975
14040
  const currentCookies = this.http.getCookieMap();
13976
14041
  if (JSON.stringify(session.cookies) !== JSON.stringify(currentCookies)) {
14042
+ const previousRefreshToken = session.cookies.auth_refresh_token;
13977
14043
  session.cookies = currentCookies;
13978
- saveSession(session);
14044
+ saveSession(session, { expectedRefreshToken: previousRefreshToken });
13979
14045
  }
13980
14046
  return session;
13981
14047
  }
@@ -14445,9 +14511,30 @@ Required: ${schema.required.join(", ")}`
14445
14511
  session.userEmailSalt
14446
14512
  );
14447
14513
  }
14514
+ async withFreshStoredSession(operation) {
14515
+ return withSessionRefreshLock(async () => {
14516
+ const previous = this.requireSession();
14517
+ const current = loadSession();
14518
+ if (!current && !this.explicitSession) {
14519
+ throw new Error("Login session was removed. Log in to the current profile before retrying.");
14520
+ }
14521
+ if (current && !this.explicitSession) {
14522
+ if (current.hashedEmail !== previous.hashedEmail || current.apiUrl !== previous.apiUrl) {
14523
+ throw new Error("Login account changed in another process. Retry using the current profile.");
14524
+ }
14525
+ this.session = current;
14526
+ this.http.replaceCookies(current.cookies);
14527
+ }
14528
+ return operation();
14529
+ });
14530
+ }
14531
+ loginRecoveryCommand() {
14532
+ const profile = process.env.OPENMATES_PROFILE?.trim();
14533
+ return profile ? `openmates --profile ${profile} login` : "openmates login";
14534
+ }
14448
14535
  requireSession() {
14449
14536
  if (!this.session) {
14450
- throw new Error("Not logged in. Run `openmates login`.");
14537
+ throw new Error(`Not logged in. Run \`${this.loginRecoveryCommand()}\`.`);
14451
14538
  }
14452
14539
  return this.session;
14453
14540
  }
@@ -14527,24 +14614,30 @@ Required: ${schema.required.join(", ")}`
14527
14614
  * This method fetches a fresh ws_token and captures any rotated cookies.
14528
14615
  */
14529
14616
  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;
14617
+ return this.withFreshStoredSession(async () => {
14618
+ const session = this.requireSession();
14619
+ const previousRefreshToken = session.cookies.auth_refresh_token;
14620
+ let res;
14621
+ try {
14622
+ res = await this.http.post("/v1/auth/session", { session_id: session.sessionId }, this.getCliRequestHeaders());
14623
+ } catch {
14624
+ return null;
14625
+ }
14626
+ if (res.status >= 500) {
14627
+ throw new Error(`Session validation temporarily unavailable (HTTP ${res.status}). Retry shortly.`);
14628
+ }
14629
+ if (!res.ok || res.data.success === false) {
14630
+ purgeLocalPrivateData();
14631
+ this.session = null;
14632
+ throw new Error(`Session expired or invalid. Please run \`${this.loginRecoveryCommand()}\` to re-authenticate.`);
14633
+ }
14634
+ if (res.data.ws_token) {
14635
+ session.wsToken = res.data.ws_token;
14636
+ }
14637
+ session.cookies = this.http.getCookieMap();
14638
+ saveSession(session, { expectedRefreshToken: previousRefreshToken });
14639
+ return typeof res.data.user?.id === "string" ? res.data.user.id : typeof res.data.user?.user_id === "string" ? res.data.user.user_id : null;
14640
+ });
14548
14641
  }
14549
14642
  /**
14550
14643
  * Ensure the local sync cache is up to date. If the cache is fresh,
@@ -20131,12 +20224,12 @@ var ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS = [
20131
20224
  ];
20132
20225
  async function writeAccountExportArchive(bundle, flags = {}) {
20133
20226
  const { mkdir: mkdir2, writeFile: writeFile2 } = await import("fs/promises");
20134
- const { join: join11, dirname: dirname10 } = await import("path");
20227
+ const { join: join12, dirname: dirname11 } = await import("path");
20135
20228
  const exportId = safeArchiveSegment(String(bundle.export.export_id ?? `export-${Date.now()}`));
20136
20229
  const archiveFormat = flags.format === "directory" ? "directory" : "zip";
20137
20230
  const password = typeof flags.password === "string" && flags.password.length > 0 ? flags.password : null;
20138
20231
  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}`);
20232
+ const outputPath = typeof flags.output === "string" ? flags.output : join12(process.cwd(), archiveFormat === "zip" ? `openmates-account-export-${exportId}${password ? ".zip.enc" : ".zip"}` : `openmates-account-export-${exportId}`);
20140
20233
  const zip = archiveFormat === "zip" ? new JSZip3() : null;
20141
20234
  const writtenFiles = [];
20142
20235
  async function writeArchiveText(relativePath, content) {
@@ -20147,8 +20240,8 @@ async function writeAccountExportArchive(bundle, flags = {}) {
20147
20240
  return;
20148
20241
  }
20149
20242
  const stagingDir = outputPath;
20150
- const fullPath = join11(stagingDir, relativePath);
20151
- await mkdir2(dirname10(fullPath), { recursive: true });
20243
+ const fullPath = join12(stagingDir, relativePath);
20244
+ await mkdir2(dirname11(fullPath), { recursive: true });
20152
20245
  await writeFile2(fullPath, content, "utf-8");
20153
20246
  writtenFiles.push(relativePath);
20154
20247
  }
@@ -20165,7 +20258,7 @@ async function writeAccountExportArchive(bundle, flags = {}) {
20165
20258
  }
20166
20259
  await writeAccountExportChatFiles(domainPayloads.chats ?? [], writeArchiveText);
20167
20260
  if (archiveFormat === "directory") return { output: outputPath, format: "directory", files: writtenFiles.length };
20168
- await mkdir2(dirname10(outputPath), { recursive: true });
20261
+ await mkdir2(dirname11(outputPath), { recursive: true });
20169
20262
  const zipBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
20170
20263
  await writeFile2(outputPath, password ? encryptZipBuffer(zipBuffer, password) : zipBuffer);
20171
20264
  return { output: outputPath, format: "zip", files: writtenFiles.length, ...password ? { encrypted: true } : {} };
@@ -24298,9 +24391,9 @@ var OpenMatesBenchmark = class {
24298
24391
  // src/cli.ts
24299
24392
  import { createInterface as createInterface5 } from "readline/promises";
24300
24393
  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";
24394
+ import { existsSync as existsSync13, readFileSync as readFileSync12, realpathSync as realpathSync2, writeFileSync as writeFileSync10 } from "fs";
24395
+ import { fileURLToPath as fileURLToPath3 } from "url";
24396
+ import { basename as basename4, dirname as dirname10 } from "path";
24304
24397
  import { createHash as createHash14, randomBytes as randomBytes9, randomUUID as randomUUID15 } from "crypto";
24305
24398
  import { arch as arch2, platform as platform3 } from "os";
24306
24399
  import { parse as parseYaml } from "yaml";
@@ -27194,7 +27287,7 @@ function shouldAutoInstallRuntimeMonitoringServices(env) {
27194
27287
 
27195
27288
  // src/serverBackupArchive.ts
27196
27289
  import { execFileSync as execFileSync3 } from "child_process";
27197
- import { chmodSync as chmodSync3, lstatSync, readdirSync as readdirSync3, renameSync, rmSync as rmSync4 } from "fs";
27290
+ import { chmodSync as chmodSync3, lstatSync, readdirSync as readdirSync3, renameSync as renameSync2, rmSync as rmSync4 } from "fs";
27198
27291
  import { randomUUID as randomUUID8 } from "crypto";
27199
27292
  function assertRegularBackupTree(path2) {
27200
27293
  const stat2 = lstatSync(path2);
@@ -27213,7 +27306,7 @@ function publishServerBackupArchive(sourceDir, archivePath, options = {}) {
27213
27306
  try {
27214
27307
  execFileSync3(options.tarCommand ?? "tar", ["-czf", temporaryArchivePath, "-C", sourceDir, "."], { stdio: "pipe" });
27215
27308
  chmodSync3(temporaryArchivePath, 384);
27216
- renameSync(temporaryArchivePath, archivePath);
27309
+ renameSync2(temporaryArchivePath, archivePath);
27217
27310
  chmodSync3(archivePath, 384);
27218
27311
  } finally {
27219
27312
  process.umask(previousUmask);
@@ -27815,7 +27908,7 @@ function signRuntimeWebhookPayload(payload, secret, timestamp, eventId) {
27815
27908
 
27816
27909
  // src/serverUpdateState.ts
27817
27910
  import { randomBytes as randomBytes7 } from "crypto";
27818
- import { chmodSync as chmodSync4, closeSync, existsSync as existsSync8, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync6, renameSync as renameSync2, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "fs";
27911
+ import { chmodSync as chmodSync4, closeSync, existsSync as existsSync8, mkdirSync as mkdirSync5, openSync, readFileSync as readFileSync6, renameSync as renameSync3, rmSync as rmSync5, writeFileSync as writeFileSync4 } from "fs";
27819
27912
  import { dirname as dirname5, join as join6 } from "path";
27820
27913
  function serverUpdateStatusFile(installPath, role) {
27821
27914
  return join6(installPath, ".openmates", `${role}-update-status.json`);
@@ -27839,7 +27932,7 @@ function writeServerUpdateStatus(installPath, role, status) {
27839
27932
  const temporaryPath = `${filePath}.${process.pid}.${randomBytes7(4).toString("hex")}.tmp`;
27840
27933
  writeFileSync4(temporaryPath, `${JSON.stringify({ role, updated_at: (/* @__PURE__ */ new Date()).toISOString(), ...status }, null, 2)}
27841
27934
  `, { mode: 384 });
27842
- renameSync2(temporaryPath, filePath);
27935
+ renameSync3(temporaryPath, filePath);
27843
27936
  chmodSync4(filePath, 384);
27844
27937
  }
27845
27938
  function acquireServerUpdateLock(installPath) {
@@ -48948,9 +49041,9 @@ function basenameFromUrl(url) {
48948
49041
  return withoutQuery.split("/").pop()?.replace(/[^a-zA-Z0-9._-]/g, "_") ?? "";
48949
49042
  }
48950
49043
  function extensionFromUrl(url) {
48951
- const basename4 = basenameFromUrl(url);
48952
- const dotIndex = basename4.lastIndexOf(".");
48953
- return dotIndex > 0 ? basename4.slice(dotIndex + 1).toLowerCase() : "";
49044
+ const basename5 = basenameFromUrl(url);
49045
+ const dotIndex = basename5.lastIndexOf(".");
49046
+ return dotIndex > 0 ? basename5.slice(dotIndex + 1).toLowerCase() : "";
48954
49047
  }
48955
49048
  function extensionFromLanguage(language) {
48956
49049
  const normalized = language.toLowerCase();
@@ -74429,9 +74522,64 @@ function remoteErrorMessage(code) {
74429
74522
 
74430
74523
  // src/selfUpdate.ts
74431
74524
  import { spawnSync as spawnSync3 } from "child_process";
74432
- import { readFileSync as readFileSync10 } from "fs";
74525
+ import { readFileSync as readFileSync10, mkdirSync as mkdirSync8, writeFileSync as writeFileSync8, renameSync as renameSync4 } from "fs";
74526
+ import { homedir as homedir8 } from "os";
74527
+ import { dirname as dirname8, basename as basename3, join as join10 } from "path";
74528
+ import { fileURLToPath as fileURLToPath2 } from "url";
74433
74529
  var PACKAGE_NAME = "openmates";
74434
- var DEFAULT_TARGET = "latest";
74530
+ function channelFile() {
74531
+ return join10(homedir8(), ".openmates", "updates.json");
74532
+ }
74533
+ function parseChannel(value) {
74534
+ if (value === "dev") return "dev";
74535
+ if (value === "stable" || value === "main") return "stable";
74536
+ throw new Error("--channel must be dev, stable, or main (alias for stable)");
74537
+ }
74538
+ function savedChannel() {
74539
+ try {
74540
+ return parseChannel(JSON.parse(readFileSync10(channelFile(), "utf8")).channel);
74541
+ } catch (error) {
74542
+ if (error.code === "ENOENT") {
74543
+ return getCliPackageVersion().includes("-") ? "dev" : "stable";
74544
+ }
74545
+ throw error;
74546
+ }
74547
+ }
74548
+ function persistSelfUpdateChannel(plan) {
74549
+ if (plan.dryRun || !plan.persistChannel) return;
74550
+ const path2 = channelFile();
74551
+ mkdirSync8(dirname8(path2), { recursive: true, mode: 448 });
74552
+ const temporary = `${path2}.${process.pid}.tmp`;
74553
+ writeFileSync8(temporary, JSON.stringify({ channel: plan.channel }) + "\n", { mode: 384 });
74554
+ renameSync4(temporary, path2);
74555
+ }
74556
+ function installedNpmPrefix(moduleUrl = import.meta.url, platform4 = process.platform) {
74557
+ const packageRoot = dirname8(dirname8(fileURLToPath2(moduleUrl)));
74558
+ const modules = dirname8(packageRoot);
74559
+ if (basename3(packageRoot) !== PACKAGE_NAME || basename3(modules) !== "node_modules") return null;
74560
+ if (platform4 === "win32") return dirname8(modules);
74561
+ return basename3(dirname8(modules)) === "lib" ? dirname8(dirname8(modules)) : null;
74562
+ }
74563
+ function compareVersions(left, right) {
74564
+ const parse = (value) => {
74565
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(value);
74566
+ if (!match) throw new Error(`Cannot compare CLI version '${value}'`);
74567
+ return { core: match.slice(1, 4).map(BigInt), pre: match[4]?.split(".") };
74568
+ };
74569
+ const a = parse(left), b = parse(right);
74570
+ for (let i = 0; i < 3; i++) if (a.core[i] !== b.core[i]) return a.core[i] > b.core[i] ? 1 : -1;
74571
+ if (!a.pre || !b.pre) return a.pre ? -1 : b.pre ? 1 : 0;
74572
+ for (let i = 0; i < Math.max(a.pre.length, b.pre.length); i++) {
74573
+ const x = a.pre[i], y = b.pre[i];
74574
+ if (x === y) continue;
74575
+ if (x === void 0 || y === void 0) return x === void 0 ? -1 : 1;
74576
+ const nx = /^\d+$/.test(x), ny = /^\d+$/.test(y);
74577
+ if (nx && ny) return BigInt(x) > BigInt(y) ? 1 : BigInt(x) < BigInt(y) ? -1 : 0;
74578
+ if (nx !== ny) return nx ? -1 : 1;
74579
+ return x > y ? 1 : -1;
74580
+ }
74581
+ return 0;
74582
+ }
74435
74583
  var SAFE_TARGET_RE = /^[a-zA-Z0-9._@+~:-]+$/;
74436
74584
  function getCliPackageVersion() {
74437
74585
  try {
@@ -74443,10 +74591,14 @@ function getCliPackageVersion() {
74443
74591
  }
74444
74592
  function buildSelfUpdatePlan(flags) {
74445
74593
  const packageManager = parsePackageManager(flags["package-manager"]);
74446
- const target = parseTarget(flags.version);
74594
+ const channel = flags.channel === void 0 ? savedChannel() : parseChannel(flags.channel);
74595
+ const target = parseTarget(flags.version ?? (channel === "dev" ? "alpha" : "latest"));
74447
74596
  const packageSpec = `${PACKAGE_NAME}@${target}`;
74448
74597
  const { command, args } = commandForPackageManager(packageManager, packageSpec);
74449
74598
  return {
74599
+ channel,
74600
+ persistChannel: flags.channel !== void 0,
74601
+ allowDowngrade: flags["allow-downgrade"] === true,
74450
74602
  packageManager,
74451
74603
  command,
74452
74604
  args,
@@ -74469,7 +74621,7 @@ function checkSelfUpdateStatus(plan) {
74469
74621
  return {
74470
74622
  currentVersion: plan.currentVersion,
74471
74623
  latestVersion: latest.version,
74472
- updateAvailable: plan.currentVersion !== latest.version,
74624
+ updateAvailable: compareVersions(latest.version, plan.currentVersion) > 0 || plan.allowDowngrade && compareVersions(latest.version, plan.currentVersion) !== 0,
74473
74625
  checkError: null
74474
74626
  };
74475
74627
  }
@@ -74499,7 +74651,8 @@ function resolveTargetVersion(target) {
74499
74651
  }
74500
74652
  const result = spawnSync3(commandName("npm"), ["view", `${PACKAGE_NAME}@${target}`, "version"], {
74501
74653
  encoding: "utf-8",
74502
- stdio: ["ignore", "pipe", "pipe"]
74654
+ stdio: ["ignore", "pipe", "pipe"],
74655
+ timeout: 3e4
74503
74656
  });
74504
74657
  if (result.error) return { version: null, error: result.error.message };
74505
74658
  if (result.status !== 0) {
@@ -74515,7 +74668,7 @@ function parsePackageManager(value) {
74515
74668
  throw new Error("--package-manager must be one of npm, pnpm, yarn, or bun");
74516
74669
  }
74517
74670
  function parseTarget(value) {
74518
- if (value === void 0) return DEFAULT_TARGET;
74671
+ if (value === void 0) return "latest";
74519
74672
  if (typeof value !== "string" || value.length === 0) {
74520
74673
  throw new Error("--version requires a version or npm dist-tag");
74521
74674
  }
@@ -74535,15 +74688,16 @@ function commandForPackageManager(packageManager, packageSpec) {
74535
74688
  if (packageManager === "pnpm") return { command: commandName("pnpm"), args: ["add", "-g", packageSpec] };
74536
74689
  if (packageManager === "yarn") return { command: commandName("yarn"), args: ["global", "add", packageSpec] };
74537
74690
  if (packageManager === "bun") return { command: commandName("bun"), args: ["install", "-g", packageSpec] };
74538
- return { command: commandName("npm"), args: ["install", "-g", packageSpec] };
74691
+ const prefix = installedNpmPrefix();
74692
+ return { command: commandName("npm"), args: ["install", "-g", packageSpec, ...prefix ? ["--prefix", prefix] : []] };
74539
74693
  }
74540
74694
  function commandName(name) {
74541
74695
  return process.platform === "win32" ? `${name}.cmd` : name;
74542
74696
  }
74543
74697
 
74544
74698
  // src/workRecovery.ts
74545
- import { mkdirSync as mkdirSync8, renameSync as renameSync3, writeFileSync as writeFileSync8 } from "fs";
74546
- import { dirname as dirname8 } from "path";
74699
+ import { mkdirSync as mkdirSync9, renameSync as renameSync5, writeFileSync as writeFileSync9 } from "fs";
74700
+ import { dirname as dirname9 } from "path";
74547
74701
  import { stringify } from "yaml";
74548
74702
  async function projectPlanAssumption(plan, masterKey, assumption) {
74549
74703
  const key = await planKeyFromRecord(plan.encrypted, masterKey);
@@ -74722,18 +74876,18 @@ function collectMismatchPaths(left, right, path2, mismatches) {
74722
74876
  }
74723
74877
  function writeWorkRecoveryAtomically(path2, document) {
74724
74878
  validateWorkRecoveryDocument(document);
74725
- mkdirSync8(dirname8(path2), { recursive: true, mode: 448 });
74879
+ mkdirSync9(dirname9(path2), { recursive: true, mode: 448 });
74726
74880
  const temporaryPath = `${path2}.tmp`;
74727
- writeFileSync8(temporaryPath, stringify(document, { sortMapEntries: true, lineWidth: 0 }), { encoding: "utf8", mode: 384 });
74728
- renameSync3(temporaryPath, path2);
74881
+ writeFileSync9(temporaryPath, stringify(document, { sortMapEntries: true, lineWidth: 0 }), { encoding: "utf8", mode: 384 });
74882
+ renameSync5(temporaryPath, path2);
74729
74883
  }
74730
74884
 
74731
74885
  // src/revolutBusinessCertificate.ts
74732
74886
  import { execFileSync as execFileSync5 } from "child_process";
74733
74887
  import { createSign, randomUUID as randomUUID14 } from "crypto";
74734
- import { chmodSync as chmodSync7, existsSync as existsSync12, mkdirSync as mkdirSync9, readFileSync as readFileSync11 } from "fs";
74735
- import { homedir as homedir8 } from "os";
74736
- import { join as join10, resolve as resolve10 } from "path";
74888
+ import { chmodSync as chmodSync7, existsSync as existsSync12, mkdirSync as mkdirSync10, readFileSync as readFileSync11 } from "fs";
74889
+ import { homedir as homedir9 } from "os";
74890
+ import { join as join11, resolve as resolve10 } from "path";
74737
74891
  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";
74738
74892
  var REVOLUT_BUSINESS_DEFAULT_SANDBOX_REDIRECT_URI = "https://app.dev.openmates.org/oauth/revolut-business/callback";
74739
74893
  var REVOLUT_BUSINESS_DEFAULT_PRODUCTION_REDIRECT_URI = "https://openmates.org/oauth/revolut-business/callback";
@@ -74743,7 +74897,7 @@ var REVOLUT_BUSINESS_AUDIENCE = "https://revolut.com";
74743
74897
  var CERTIFICATE_DAYS = "1825";
74744
74898
  var RSA_BITS = "2048";
74745
74899
  function defaultRevolutBusinessCertificateDir(environment) {
74746
- return join10(homedir8(), ".openmates", "revolut-business", environment);
74900
+ return join11(homedir9(), ".openmates", "revolut-business", environment);
74747
74901
  }
74748
74902
  function defaultRevolutBusinessRedirectUri(environment) {
74749
74903
  return environment === "sandbox" ? REVOLUT_BUSINESS_DEFAULT_SANDBOX_REDIRECT_URI : REVOLUT_BUSINESS_DEFAULT_PRODUCTION_REDIRECT_URI;
@@ -74766,7 +74920,7 @@ async function exchangeRevolutBusinessAuthorizationCode(options) {
74766
74920
  if (!clientId) throw new Error("Missing Revolut Business client ID.");
74767
74921
  const code = extractRevolutBusinessAuthorizationCode(options.codeOrRedirectUrl);
74768
74922
  if (!code) throw new Error("Missing Revolut Business authorization code.");
74769
- const privateKeyPem = options.privateKeyPem ?? readFileSync11(options.privateKeyPath ?? join10(defaultRevolutBusinessCertificateDir(environment), "privatecert.pem"), "utf8");
74923
+ const privateKeyPem = options.privateKeyPem ?? readFileSync11(options.privateKeyPath ?? join11(defaultRevolutBusinessCertificateDir(environment), "privatecert.pem"), "utf8");
74770
74924
  const redirectUri = (options.redirectUri ?? defaultRevolutBusinessRedirectUri(environment)).trim();
74771
74925
  const clientAssertion = generateRevolutBusinessClientAssertion({ clientId, privateKeyPem, redirectUri });
74772
74926
  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", {
@@ -74819,10 +74973,10 @@ function generateRevolutBusinessCertificate(options = {}) {
74819
74973
  const redirectUri = (options.redirectUri ?? defaultRevolutBusinessRedirectUri(environment)).trim();
74820
74974
  if (!title) throw new Error("Revolut certificate title must not be empty.");
74821
74975
  if (!redirectUri.startsWith("https://")) throw new Error("Revolut OAuth redirect URI must start with https://.");
74822
- mkdirSync9(outputDir, { recursive: true, mode: 448 });
74976
+ mkdirSync10(outputDir, { recursive: true, mode: 448 });
74823
74977
  chmodSync7(outputDir, 448);
74824
- const privateKeyPath = join10(outputDir, "privatecert.pem");
74825
- const publicCertificatePath = join10(outputDir, "publiccert.cer");
74978
+ const privateKeyPath = join11(outputDir, "privatecert.pem");
74979
+ const publicCertificatePath = join11(outputDir, "publiccert.cer");
74826
74980
  if (!options.overwrite && (existsSync12(privateKeyPath) || existsSync12(publicCertificatePath))) {
74827
74981
  throw new Error(`Revolut certificate files already exist in ${outputDir}. Re-run with --overwrite to replace them.`);
74828
74982
  }
@@ -74883,6 +75037,15 @@ async function main() {
74883
75037
  assertTrustedAccountGuardEnvironment(parsed.flags);
74884
75038
  assertTrustedAccountCommandAllowed(command);
74885
75039
  }
75040
+ if (parsed.flags.profile !== void 0) {
75041
+ const profile = parsed.flags.profile;
75042
+ if (typeof profile !== "string" || !profile) throw new Error("--profile requires a profile name");
75043
+ resolveStateDir({ profile, stateDir: "" });
75044
+ if (process.env.OPENMATES_STATE_DIR?.trim()) {
75045
+ throw new Error("--profile cannot be combined with OPENMATES_STATE_DIR");
75046
+ }
75047
+ process.env.OPENMATES_PROFILE = profile;
75048
+ }
74886
75049
  const client = OpenMatesClient.load({
74887
75050
  apiUrl: typeof parsed.flags["api-url"] === "string" ? parsed.flags["api-url"] : void 0
74888
75051
  });
@@ -75248,6 +75411,9 @@ function assertTrustedAccountCommandAllowed(command) {
75248
75411
  }
75249
75412
  }
75250
75413
  function assertTrustedAccountGuardEnvironment(flags, environment = process.env) {
75414
+ if (flags.profile !== void 0 && flags.profile !== TRUST_GUARD_PROFILE) {
75415
+ throw new Error(`Trusted OpenCode CLI commands cannot override profile ${TRUST_GUARD_PROFILE}.`);
75416
+ }
75251
75417
  if (environment.OPENMATES_PROFILE !== TRUST_GUARD_PROFILE) {
75252
75418
  throw new Error(`Trusted OpenCode CLI commands require OPENMATES_PROFILE=${TRUST_GUARD_PROFILE}.`);
75253
75419
  }
@@ -75287,11 +75453,14 @@ function handleSupport(flags) {
75287
75453
  function handleSelfUpdate(command, flags) {
75288
75454
  const plan = buildSelfUpdatePlan(flags);
75289
75455
  const status = checkSelfUpdateStatus(plan);
75290
- const shouldInstall = status.updateAvailable !== false;
75456
+ if (status.checkError) throw new Error(`Update check failed: ${status.checkError}`);
75457
+ const shouldInstall = status.updateAvailable === true;
75291
75458
  if (flags.json === true) {
75292
75459
  if (!plan.dryRun && shouldInstall) runSelfUpdate(plan, { verbose: flags.verbose === true });
75460
+ persistSelfUpdateChannel(plan);
75293
75461
  printJson2({
75294
75462
  command,
75463
+ channel: plan.channel,
75295
75464
  status: status.updateAvailable === false ? "up_to_date" : plan.dryRun ? "planned" : "success",
75296
75465
  current_version: plan.currentVersion,
75297
75466
  latest_version: status.latestVersion,
@@ -75308,6 +75477,7 @@ function handleSelfUpdate(command, flags) {
75308
75477
  console.log("");
75309
75478
  console.log("Checking for updates...");
75310
75479
  console.log("");
75480
+ console.log(`Release channel: ${plan.channel}`);
75311
75481
  console.log(`Current version: ${plan.currentVersion}`);
75312
75482
  console.log(`Latest version: ${status.latestVersion ?? "unknown"}`);
75313
75483
  if (status.checkError) {
@@ -75323,11 +75493,13 @@ function handleSelfUpdate(command, flags) {
75323
75493
  return;
75324
75494
  }
75325
75495
  if (status.updateAvailable === false) {
75496
+ persistSelfUpdateChannel(plan);
75326
75497
  console.log("OpenMates CLI is already up to date.");
75327
75498
  return;
75328
75499
  }
75329
75500
  console.log(`Updating OpenMates CLI with ${plan.packageManager}...`);
75330
75501
  runSelfUpdate(plan, { verbose: flags.verbose === true });
75502
+ persistSelfUpdateChannel(plan);
75331
75503
  console.log(`Installed OpenMates CLI ${status.latestVersion ?? plan.target}.`);
75332
75504
  console.log("");
75333
75505
  console.log("OpenMates is up to date.");
@@ -75340,6 +75512,7 @@ function handleCliVersion(flags) {
75340
75512
  if (flags.json === true) {
75341
75513
  printJson2({
75342
75514
  command: "version",
75515
+ channel: plan.channel,
75343
75516
  current_version: status.currentVersion,
75344
75517
  latest_version: status.latestVersion,
75345
75518
  update_available: status.updateAvailable,
@@ -75349,6 +75522,7 @@ function handleCliVersion(flags) {
75349
75522
  return;
75350
75523
  }
75351
75524
  console.log(`OpenMates CLI ${status.currentVersion}`);
75525
+ console.log(`Release channel: ${plan.channel}`);
75352
75526
  if (status.checkError) {
75353
75527
  console.log(`Update check failed: ${status.checkError}`);
75354
75528
  return;
@@ -77740,7 +77914,7 @@ async function handleTeams(client, subcommand, rest, flags) {
77740
77914
  if (action === "get") {
77741
77915
  const output = requiredStringFlag(flags.output ?? flags.file ?? rest[2], "--output <path>");
77742
77916
  const image = await client.getTeamProfileImage(teamId);
77743
- writeFileSync9(output, image.data, { mode: 384 });
77917
+ writeFileSync10(output, image.data, { mode: 384 });
77744
77918
  if (flags.json === true) printJson2({ output, content_type: image.contentType, size_bytes: image.data.byteLength });
77745
77919
  else console.log(`Team profile image written: ${output}`);
77746
77920
  return;
@@ -77899,7 +78073,7 @@ async function handleTeams(client, subcommand, rest, flags) {
77899
78073
  const output = requiredStringFlag(flags.output, "--output <path>");
77900
78074
  const result = await client.exportTeamData(teamId);
77901
78075
  const artifact = result.artifact && typeof result.artifact === "object" ? result.artifact : result;
77902
- writeFileSync9(output, `${JSON.stringify(artifact, null, 2)}
78076
+ writeFileSync10(output, `${JSON.stringify(artifact, null, 2)}
77903
78077
  `, { encoding: "utf-8", mode: 384 });
77904
78078
  if (flags.json === true) printJson2({ ...result, output });
77905
78079
  else console.log(`Team export written: ${output}`);
@@ -78349,7 +78523,7 @@ async function resolveRemoteAccessBindings(client, masterKey, projects, candidat
78349
78523
  const answer = (await promptLine2(`Create ${unresolved.length} missing Project${unresolved.length === 1 ? "" : "s"}? [y/N] `)).trim().toLowerCase();
78350
78524
  if (answer !== "y" && answer !== "yes") throw new Error("Remote access Project creation cancelled.");
78351
78525
  for (const rootPath of unresolved) {
78352
- const project = await createEncryptedRemoteAccessProject(client, masterKey, basename3(rootPath), context);
78526
+ const project = await createEncryptedRemoteAccessProject(client, masterKey, basename4(rootPath), context);
78353
78527
  resolved.push({ rootPath, project, sourceId: randomUUID15() });
78354
78528
  }
78355
78529
  }
@@ -78361,7 +78535,7 @@ async function resolveRemoteAccessBindings(client, masterKey, projects, candidat
78361
78535
  projectId: item.project.projectId,
78362
78536
  rootPath: item.rootPath,
78363
78537
  sourceType,
78364
- displayName: basename3(item.rootPath)
78538
+ displayName: basename4(item.rootPath)
78365
78539
  });
78366
78540
  const remoteSources = await client.listProjectSources(item.project.projectId, context);
78367
78541
  const remoteSource = remoteSources.find((entry) => entry.source_id === source.sourceId);
@@ -79204,16 +79378,16 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79204
79378
  }
79205
79379
  }
79206
79380
  const { mkdir: mkdir2, writeFile: writeFile2 } = await import("fs/promises");
79207
- const { join: join11 } = await import("path");
79381
+ const { join: join12 } = await import("path");
79208
79382
  if (useZip) {
79209
- const tmpDir = join11(outputDir, `.${filenameBase}_tmp`);
79383
+ const tmpDir = join12(outputDir, `.${filenameBase}_tmp`);
79210
79384
  await mkdir2(tmpDir, { recursive: true });
79211
- await writeFile2(join11(tmpDir, `${filenameBase}.yml`), yamlContent);
79212
- await writeFile2(join11(tmpDir, `${filenameBase}.md`), mdContent);
79385
+ await writeFile2(join12(tmpDir, `${filenameBase}.yml`), yamlContent);
79386
+ await writeFile2(join12(tmpDir, `${filenameBase}.md`), mdContent);
79213
79387
  if (codeEmbeds.length > 0) {
79214
79388
  for (const ce of codeEmbeds) {
79215
79389
  const fpath = ce.filePath ?? ce.filename ?? `${ce.embedId.slice(0, 8)}.${getExtForLang(ce.language)}`;
79216
- const fullPath = join11(tmpDir, "code", fpath);
79390
+ const fullPath = join12(tmpDir, "code", fpath);
79217
79391
  await mkdir2(fullPath.substring(0, fullPath.lastIndexOf("/")), {
79218
79392
  recursive: true
79219
79393
  });
@@ -79221,13 +79395,13 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79221
79395
  }
79222
79396
  }
79223
79397
  if (transcriptEmbeds.length > 0) {
79224
- const tDir = join11(tmpDir, "transcripts");
79398
+ const tDir = join12(tmpDir, "transcripts");
79225
79399
  await mkdir2(tDir, { recursive: true });
79226
79400
  for (const te of transcriptEmbeds) {
79227
- await writeFile2(join11(tDir, te.filename), te.content);
79401
+ await writeFile2(join12(tDir, te.filename), te.content);
79228
79402
  }
79229
79403
  }
79230
- const zipPath = join11(outputDir, `${filenameBase}.zip`);
79404
+ const zipPath = join12(outputDir, `${filenameBase}.zip`);
79231
79405
  const { execSync: execSync2 } = await import("child_process");
79232
79406
  try {
79233
79407
  execSync2(`cd "${tmpDir}" && zip -r "${zipPath}" .`, { stdio: "pipe" });
@@ -79242,17 +79416,17 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79242
79416
  );
79243
79417
  }
79244
79418
  } else {
79245
- const chatDir = join11(outputDir, filenameBase);
79419
+ const chatDir = join12(outputDir, filenameBase);
79246
79420
  await mkdir2(chatDir, { recursive: true });
79247
79421
  const written = [];
79248
- await writeFile2(join11(chatDir, `${filenameBase}.yml`), yamlContent);
79422
+ await writeFile2(join12(chatDir, `${filenameBase}.yml`), yamlContent);
79249
79423
  written.push(`${filenameBase}.yml`);
79250
- await writeFile2(join11(chatDir, `${filenameBase}.md`), mdContent);
79424
+ await writeFile2(join12(chatDir, `${filenameBase}.md`), mdContent);
79251
79425
  written.push(`${filenameBase}.md`);
79252
79426
  if (codeEmbeds.length > 0) {
79253
79427
  for (const ce of codeEmbeds) {
79254
79428
  const fpath = ce.filePath ?? ce.filename ?? `${ce.embedId.slice(0, 8)}.${getExtForLang(ce.language)}`;
79255
- const fullPath = join11(chatDir, "code", fpath);
79429
+ const fullPath = join12(chatDir, "code", fpath);
79256
79430
  await mkdir2(fullPath.substring(0, fullPath.lastIndexOf("/")), {
79257
79431
  recursive: true
79258
79432
  });
@@ -79261,10 +79435,10 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79261
79435
  }
79262
79436
  }
79263
79437
  if (transcriptEmbeds.length > 0) {
79264
- const tDir = join11(chatDir, "transcripts");
79438
+ const tDir = join12(chatDir, "transcripts");
79265
79439
  await mkdir2(tDir, { recursive: true });
79266
79440
  for (const te of transcriptEmbeds) {
79267
- await writeFile2(join11(tDir, te.filename), te.content);
79441
+ await writeFile2(join12(tDir, te.filename), te.content);
79268
79442
  written.push(`transcripts/${te.filename}`);
79269
79443
  }
79270
79444
  }
@@ -79300,7 +79474,7 @@ ${deleted}/${resolved.length} chat(s) deleted.`);
79300
79474
  printJson2({
79301
79475
  chat_id: chat.id,
79302
79476
  title: chat.title,
79303
- output_dir: useZip ? join11(outputDir, `${filenameBase}.zip`) : join11(outputDir, filenameBase),
79477
+ output_dir: useZip ? join12(outputDir, `${filenameBase}.zip`) : join12(outputDir, filenameBase),
79304
79478
  files,
79305
79479
  code_embeds: codeEmbeds.length,
79306
79480
  transcript_embeds: transcriptEmbeds.length
@@ -80326,7 +80500,7 @@ function buildImagesAiDetectionSummary(uploadResult, filePath) {
80326
80500
  const classification = classifyImagesAiDetection(score);
80327
80501
  return {
80328
80502
  file: filePath,
80329
- filename: uploadResult.filename || basename3(filePath),
80503
+ filename: uploadResult.filename || basename4(filePath),
80330
80504
  content_type: uploadResult.content_type,
80331
80505
  embed_id: uploadResult.embed_id,
80332
80506
  deduplicated: uploadResult.deduplicated,
@@ -81357,7 +81531,7 @@ async function handleEmbeds(client, subcommand, rest, flags) {
81357
81531
  throw new Error("Embed version content was not available after local reconstruction.");
81358
81532
  }
81359
81533
  if (typeof flags.output === "string") {
81360
- writeFileSync9(flags.output, result.content, "utf-8");
81534
+ writeFileSync10(flags.output, result.content, "utf-8");
81361
81535
  if (flags.json === true) {
81362
81536
  printJson2({ ...result, output: flags.output });
81363
81537
  } else {
@@ -81641,12 +81815,12 @@ async function parseAccountImportFile(parserFormat, file, flags = {}) {
81641
81815
  const selectedSource = requestedSource;
81642
81816
  if (parserFormat === "generic") {
81643
81817
  if (selectedSource !== "gemini" && selectedSource !== "other") throw new Error("Generic account import requires --source gemini or --source other.");
81644
- return parseGenericImportBuffer(payload, basename3(file), selectedSource);
81818
+ return parseGenericImportBuffer(payload, basename4(file), selectedSource);
81645
81819
  }
81646
- if (parserFormat === "claude") return parseClaudeImportBuffer(payload, basename3(file), selectedSource);
81647
- if (parserFormat === "chatgpt") return parseChatGPTImportBuffer(payload, basename3(file), selectedSource);
81648
- if (parserFormat === "opencode") return parseOpenCodeImportBuffer(payload, basename3(file), selectedSource);
81649
- return parseOpenMatesImportBuffer(payload, basename3(file), typeof flags.password === "string" ? flags.password : void 0, selectedSource);
81820
+ if (parserFormat === "claude") return parseClaudeImportBuffer(payload, basename4(file), selectedSource);
81821
+ if (parserFormat === "chatgpt") return parseChatGPTImportBuffer(payload, basename4(file), selectedSource);
81822
+ if (parserFormat === "opencode") return parseOpenCodeImportBuffer(payload, basename4(file), selectedSource);
81823
+ return parseOpenMatesImportBuffer(payload, basename4(file), typeof flags.password === "string" ? flags.password : void 0, selectedSource);
81650
81824
  }
81651
81825
  async function runAccountImport(client, parserFormat, file, flags) {
81652
81826
  if (parserFormat === "generic" && typeof flags.source !== "string") throw new Error("Generic account import requires --source gemini or --source other.");
@@ -82016,11 +82190,11 @@ function parseYamlScalar(value) {
82016
82190
  }
82017
82191
  async function saveDownloadedDocument(document, output) {
82018
82192
  const { mkdir: mkdir2, writeFile: writeFile2 } = await import("fs/promises");
82019
- const { join: join11, basename: basename4, dirname: dirname10 } = await import("path");
82193
+ const { join: join12, basename: basename5, dirname: dirname11 } = await import("path");
82020
82194
  const target = typeof output === "string" ? output : ".";
82021
- const filename = basename4(document.filename || "document.pdf");
82022
- const filePath = target.endsWith(".pdf") ? target : join11(target, filename);
82023
- await mkdir2(dirname10(filePath), { recursive: true });
82195
+ const filename = basename5(document.filename || "document.pdf");
82196
+ const filePath = target.endsWith(".pdf") ? target : join12(target, filename);
82197
+ await mkdir2(dirname11(filePath), { recursive: true });
82024
82198
  await writeFile2(filePath, document.data);
82025
82199
  return filePath;
82026
82200
  }
@@ -82192,7 +82366,7 @@ async function handleFinance(client, subcommand, _rest, flags) {
82192
82366
  }
82193
82367
  function readFinanceCsvStatements(flags) {
82194
82368
  const paths = parseCsvFlag(flags.csv) ?? [];
82195
- return paths.map((filePath) => ({ filename: basename3(filePath), content: readFileSync12(filePath, "utf8") }));
82369
+ return paths.map((filePath) => ({ filename: basename4(filePath), content: readFileSync12(filePath, "utf8") }));
82196
82370
  }
82197
82371
  function buildFinanceCheckAccountsInput(flags, csvStatements, connectedAccount) {
82198
82372
  const input = {
@@ -82487,7 +82661,7 @@ async function promptPlainText(question) {
82487
82661
  }
82488
82662
  async function writeSecretFile(filePath, content, force = false) {
82489
82663
  const { mkdir: mkdir2, writeFile: writeFile2, stat: stat2 } = await import("fs/promises");
82490
- const { dirname: dirname10 } = await import("path");
82664
+ const { dirname: dirname11 } = await import("path");
82491
82665
  try {
82492
82666
  await stat2(filePath);
82493
82667
  if (!force) throw new Error(`${filePath} already exists. Use --force to overwrite.`);
@@ -82497,7 +82671,7 @@ async function writeSecretFile(filePath, content, force = false) {
82497
82671
  }
82498
82672
  if (error instanceof Error && !("code" in error)) throw error;
82499
82673
  }
82500
- await mkdir2(dirname10(filePath), { recursive: true });
82674
+ await mkdir2(dirname11(filePath), { recursive: true });
82501
82675
  await writeFile2(filePath, content, { mode: 384 });
82502
82676
  return filePath;
82503
82677
  }
@@ -85642,6 +85816,7 @@ Commands:
85642
85816
 
85643
85817
  Flags:
85644
85818
  --json Output raw JSON instead of formatted output
85819
+ --profile <name> Use an isolated login profile (also OPENMATES_PROFILE)
85645
85820
  --api-url <url> Override API base URL (default: installed self-host server, then https://api.openmates.org)
85646
85821
  --api-key <key> Optional API key override (or set OPENMATES_API_KEY)
85647
85822
  --version Show CLI version and update availability
@@ -85663,12 +85838,16 @@ function printSelfUpdateHelp() {
85663
85838
  openmates update [--version <version|tag>] [--package-manager <name>] [--dry-run] [--verbose] [--json]
85664
85839
  openmates upgrade [--version <version|tag>] [--package-manager <name>] [--dry-run] [--verbose] [--json]
85665
85840
 
85666
- Updates the globally installed openmates package. The default target is latest.
85841
+ Updates the globally installed openmates package using the saved release channel.
85842
+ Stable installations default to stable; prereleases default to dev.
85843
+ Channel changes persist only after a successful update or up-to-date check.
85667
85844
 
85668
85845
  Options:
85669
- --version <version|tag> Install a specific npm version or dist-tag (default: latest)
85846
+ --version <version|tag> Install a specific npm version or dist-tag (one-time override)
85670
85847
  --package-manager <name> npm, pnpm, yarn, or bun (default: detect, then npm)
85671
85848
  --dry-run Print the package-manager command without running it
85849
+ --channel <dev|stable|main> Save release channel (dev uses npm alpha; stable/main uses latest)
85850
+ --allow-downgrade Explicitly allow installing an older version
85672
85851
  --verbose Stream package-manager output during installation
85673
85852
  --json Output the update plan/result as JSON`);
85674
85853
  }
@@ -86475,7 +86654,7 @@ async function handleDocs(client, subcommand, rest, flags) {
86475
86654
  }
86476
86655
  if (subcommand === "download") {
86477
86656
  const { writeFile: writeFile2, mkdir: mkdir2 } = await import("fs/promises");
86478
- const { join: join11, dirname: dirname10 } = await import("path");
86657
+ const { join: join12, dirname: dirname11 } = await import("path");
86479
86658
  if (flags.all === true) {
86480
86659
  const outputDir = typeof flags.output === "string" ? flags.output : "./openmates-docs";
86481
86660
  const tree = await client.listDocs();
@@ -86484,8 +86663,8 @@ async function handleDocs(client, subcommand, rest, flags) {
86484
86663
  let count = 0;
86485
86664
  for (const slug2 of slugs) {
86486
86665
  const content2 = await client.getDoc(slug2);
86487
- const filePath = join11(outputDir, `${slug2}.md`);
86488
- await mkdir2(dirname10(filePath), { recursive: true });
86666
+ const filePath = join12(outputDir, `${slug2}.md`);
86667
+ await mkdir2(dirname11(filePath), { recursive: true });
86489
86668
  await writeFile2(filePath, content2, "utf-8");
86490
86669
  count++;
86491
86670
  process.stderr.write(`\r Downloaded ${count}/${slugs.length}`);
@@ -86557,8 +86736,8 @@ function isCliEntrypoint() {
86557
86736
  if (!entrypoint) return false;
86558
86737
  try {
86559
86738
  const invokedPath = realpathSync2(entrypoint);
86560
- const modulePath = realpathSync2(fileURLToPath2(import.meta.url));
86561
- return invokedPath === modulePath || basename3(invokedPath) === "cli.js" && dirname9(invokedPath) === dirname9(modulePath);
86739
+ const modulePath = realpathSync2(fileURLToPath3(import.meta.url));
86740
+ return invokedPath === modulePath || basename4(invokedPath) === "cli.js" && dirname10(invokedPath) === dirname10(modulePath);
86562
86741
  } catch {
86563
86742
  return false;
86564
86743
  }
@@ -1734,6 +1734,7 @@ declare class OpenMatesClient {
1734
1734
  readonly apiUrl: string;
1735
1735
  private session;
1736
1736
  private readonly http;
1737
+ private readonly explicitSession;
1737
1738
  constructor(options?: OpenMatesClientOptions);
1738
1739
  static load(options?: OpenMatesClientOptions): OpenMatesClient;
1739
1740
  hasSession(): boolean;
@@ -2741,6 +2742,8 @@ declare class OpenMatesClient {
2741
2742
  private downloadPaymentPdf;
2742
2743
  private ensureEmailEncryptionKey;
2743
2744
  private hydrateEmailEncryptionKey;
2745
+ private withFreshStoredSession;
2746
+ private loginRecoveryCommand;
2744
2747
  private requireSession;
2745
2748
  getMasterKeyBytes(): Uint8Array;
2746
2749
  private decryptTopicPreferences;
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- export { aa as ImagesAiDetectionClassification, ab as ImagesAiDetectionSummary, aA as assertTrustedAccountCommandAllowed, aB as assertTrustedAccountGuardEnvironment, as as buildImagesAiDetectionSummary, aC as buildTravelConnectionsRequest, at as classifyImagesAiDetection, av as formatImagesAiDetectionLabel, aw as getExtForLang, aD as requireExactConfirmation, aE as resolveProjectContext, az as serializeToYaml, aF as shouldRequireTrustedAccountGuard } from './cli-BMrmfKIF.js';
2
+ export { aa as ImagesAiDetectionClassification, ab as ImagesAiDetectionSummary, aA as assertTrustedAccountCommandAllowed, aB as assertTrustedAccountGuardEnvironment, as as buildImagesAiDetectionSummary, aC as buildTravelConnectionsRequest, at as classifyImagesAiDetection, av as formatImagesAiDetectionLabel, aw as getExtForLang, aD as requireExactConfirmation, aE as resolveProjectContext, az as serializeToYaml, aF as shouldRequireTrustedAccountGuard } from './cli-BLFXUeuE.js';
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  resolveProjectContext,
12
12
  serializeToYaml,
13
13
  shouldRequireTrustedAccountGuard
14
- } from "./chunk-XKIFO4JO.js";
14
+ } from "./chunk-EKALVG2T.js";
15
15
  import "./chunk-IWKP55ZB.js";
16
16
  import "./chunk-BBKJZ23O.js";
17
17
  export {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { U as UserTaskStatus, a as UserTaskAssigneeType, b as UserTaskAssigneeIdentity, c as UserTaskRecord, d as UserTaskActivityRecord, e as UserPlanStatus, f as UserPlanRecord, g as UserPlanVerificationStatus, h as UserPlanLearningType, i as UserPlanLearningTargetKind, j as UserPlanLearningStatus, k as UserPlanLearningLevel, l as UserPlanLearningRecord, m as UserPlanCriterionRecord, n as UserPlanVerificationRecord, o as UserPlanCreateInput, p as UserPlanUpdateInput, P as ProjectItemRecord, q as UserPlanLearningCreateTasksInput, r as UserPlanLearningCreateTasksResult, s as UserTaskReorderInput, W as WorkflowSummary, t as WorkflowCapability, u as WorkflowDetail, v as WorkflowInputStartParams, w as WorkflowInputSessionResult, x as WorkflowInputSessionDetail, y as WorkflowInputEvent, z as WorkflowGraph, A as WorkflowRunContentRetention, B as WorkflowRunDetail, C as WorkflowRunCancellationResult, D as WorkflowTemplateProjectionUpsertParams, E as WorkflowTemplateProjectionResult, F as PublicWorkflowTemplateProjection, G as WorkflowTemplateProjectionRevocationResult, H as WorkflowTemplateBindingCompletionParams, I as WorkflowTemplateBindingCompletionResult, J as WorkflowTemplateShortUrlParams, K as WorkflowTemplateShortUrlResult, S as ShortUrlRevokeResult, L as WorkflowTemplateImportPayload, M as ImportedWorkflowTemplate } from './cli-BMrmfKIF.js';
2
- export { N as AuthMethodsStatus, O as AuthoritativeChatReconciliation, Q as BackupCodesResult, R as BankTransferOrderDetails, T as BankTransferStatus, V as CachedChat, X as CachedNewChatSuggestion, Y as ChatListPage, Z as CliSignupResult, _ as DecryptedDraft, $ as DecryptedEmbed, a0 as DecryptedMemoryEntry, a1 as DecryptedMessage, a2 as DecryptedNewChatSuggestion, a3 as DocsFile, a4 as DocsFolder, a5 as DocsSearchResult, a6 as DocsTree, a7 as EncryptedDraft, a8 as GiftCardBankTransferStatus, a9 as INTEREST_TAG_IDS, aa as ImagesAiDetectionClassification, ab as ImagesAiDetectionSummary, ac as InterestTagId, ad as MATE_NAMES, ae as MEMORY_TYPE_REGISTRY, af as MemoryFieldDef, ag as MemoryTypeDef, ah as OpenMatesClient, ai as OpenMatesClientOptions, aj as OpenMatesSession, ak as SyncCache, al as TopicPreferencesPayload, am as TotpSetupStartResult, an as WorkflowEdge, ao as WorkflowNode, ap as WorkflowNodeRun, aq as WorkflowNodeType, ar as WorkflowRunContentStorage, as as buildImagesAiDetectionSummary, at as classifyImagesAiDetection, au as deriveAppUrl, av as formatImagesAiDetectionLabel, aw as getExtForLang, ax as normalizeInterestTagIds, ay as reconcileAuthoritativeChats, az as serializeToYaml } from './cli-BMrmfKIF.js';
1
+ import { U as UserTaskStatus, a as UserTaskAssigneeType, b as UserTaskAssigneeIdentity, c as UserTaskRecord, d as UserTaskActivityRecord, e as UserPlanStatus, f as UserPlanRecord, g as UserPlanVerificationStatus, h as UserPlanLearningType, i as UserPlanLearningTargetKind, j as UserPlanLearningStatus, k as UserPlanLearningLevel, l as UserPlanLearningRecord, m as UserPlanCriterionRecord, n as UserPlanVerificationRecord, o as UserPlanCreateInput, p as UserPlanUpdateInput, P as ProjectItemRecord, q as UserPlanLearningCreateTasksInput, r as UserPlanLearningCreateTasksResult, s as UserTaskReorderInput, W as WorkflowSummary, t as WorkflowCapability, u as WorkflowDetail, v as WorkflowInputStartParams, w as WorkflowInputSessionResult, x as WorkflowInputSessionDetail, y as WorkflowInputEvent, z as WorkflowGraph, A as WorkflowRunContentRetention, B as WorkflowRunDetail, C as WorkflowRunCancellationResult, D as WorkflowTemplateProjectionUpsertParams, E as WorkflowTemplateProjectionResult, F as PublicWorkflowTemplateProjection, G as WorkflowTemplateProjectionRevocationResult, H as WorkflowTemplateBindingCompletionParams, I as WorkflowTemplateBindingCompletionResult, J as WorkflowTemplateShortUrlParams, K as WorkflowTemplateShortUrlResult, S as ShortUrlRevokeResult, L as WorkflowTemplateImportPayload, M as ImportedWorkflowTemplate } from './cli-BLFXUeuE.js';
2
+ export { N as AuthMethodsStatus, O as AuthoritativeChatReconciliation, Q as BackupCodesResult, R as BankTransferOrderDetails, T as BankTransferStatus, V as CachedChat, X as CachedNewChatSuggestion, Y as ChatListPage, Z as CliSignupResult, _ as DecryptedDraft, $ as DecryptedEmbed, a0 as DecryptedMemoryEntry, a1 as DecryptedMessage, a2 as DecryptedNewChatSuggestion, a3 as DocsFile, a4 as DocsFolder, a5 as DocsSearchResult, a6 as DocsTree, a7 as EncryptedDraft, a8 as GiftCardBankTransferStatus, a9 as INTEREST_TAG_IDS, aa as ImagesAiDetectionClassification, ab as ImagesAiDetectionSummary, ac as InterestTagId, ad as MATE_NAMES, ae as MEMORY_TYPE_REGISTRY, af as MemoryFieldDef, ag as MemoryTypeDef, ah as OpenMatesClient, ai as OpenMatesClientOptions, aj as OpenMatesSession, ak as SyncCache, al as TopicPreferencesPayload, am as TotpSetupStartResult, an as WorkflowEdge, ao as WorkflowNode, ap as WorkflowNodeRun, aq as WorkflowNodeType, ar as WorkflowRunContentStorage, as as buildImagesAiDetectionSummary, at as classifyImagesAiDetection, au as deriveAppUrl, av as formatImagesAiDetectionLabel, aw as getExtForLang, ax as normalizeInterestTagIds, ay as reconcileAuthoritativeChats, az as serializeToYaml } from './cli-BLFXUeuE.js';
3
3
 
4
4
  interface ProjectedAssistantSpeechSegment {
5
5
  sequence: number;
package/dist/index.js CHANGED
@@ -42,7 +42,7 @@ import {
42
42
  selectAssistantMessagesForSpeech,
43
43
  serializeToYaml,
44
44
  summarizeAssistantSpeech
45
- } from "./chunk-XKIFO4JO.js";
45
+ } from "./chunk-EKALVG2T.js";
46
46
  import "./chunk-IWKP55ZB.js";
47
47
  import "./chunk-BBKJZ23O.js";
48
48
  export {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.18.0-alpha.2",
3
+ "version": "0.18.0-alpha.4",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -73,6 +73,7 @@
73
73
  "@toon-format/toon": "2.3.1",
74
74
  "ahocorasick": "1.0.2",
75
75
  "jszip": "^3.10.1",
76
+ "proper-lockfile": "^4.1.2",
76
77
  "qrcode-terminal": "^0.12.0",
77
78
  "tweetnacl": "^1.0.3",
78
79
  "ws": "8.21.0",
@@ -81,6 +82,7 @@
81
82
  "devDependencies": {
82
83
  "@repo/eslint-config": "workspace:*",
83
84
  "@types/node": "^24.5.0",
85
+ "@types/proper-lockfile": "^4.1.4",
84
86
  "@types/qrcode-terminal": "^0.12.2",
85
87
  "@types/ws": "^8.18.1",
86
88
  "eslint": "^9.35.0",