openmates 0.12.0-alpha.26 → 0.12.0-alpha.28

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.
@@ -4,7 +4,7 @@ import {
4
4
  } from "./chunk-AXNRPVLE.js";
5
5
 
6
6
  // src/client.ts
7
- import { randomUUID as randomUUID2 } from "crypto";
7
+ import { randomUUID as randomUUID3 } from "crypto";
8
8
  import { platform as platform2, release } from "os";
9
9
  import { createInterface } from "readline/promises";
10
10
  import { stdin, stdout } from "process";
@@ -2048,6 +2048,163 @@ function buildEmbedShareUrl(origin, embedId, blob) {
2048
2048
  return `${origin}/share/embed/${embedId}#key=${blob}`;
2049
2049
  }
2050
2050
 
2051
+ // src/connectedAccountImport.ts
2052
+ import { createHash as createHash4, randomUUID as randomUUID2, webcrypto as webcrypto5 } from "crypto";
2053
+ var TRANSFER_PREFIX = "OMCA1.";
2054
+ var SUPPORTED_KDF_ITERATIONS = 1e5;
2055
+ async function decryptConnectedAccountCliTransferPayload(encryptedPayload, passcode) {
2056
+ if (!encryptedPayload.startsWith(TRANSFER_PREFIX)) {
2057
+ throw new Error("Connected account import payload must start with OMCA1.");
2058
+ }
2059
+ if (!passcode.trim()) {
2060
+ throw new Error("A passcode is required to import a connected account.");
2061
+ }
2062
+ const envelope = parseEnvelope(encryptedPayload.slice(TRANSFER_PREFIX.length));
2063
+ if (envelope.version !== 1 || envelope.kdf.name !== "PBKDF2-SHA256" || envelope.kdf.iterations !== SUPPORTED_KDF_ITERATIONS || envelope.cipher.name !== "AES-256-GCM") {
2064
+ throw new Error("Unsupported connected account import payload format.");
2065
+ }
2066
+ try {
2067
+ const key = await deriveTransferKey(passcode, base64UrlToBytes(envelope.kdf.salt), envelope.kdf.iterations);
2068
+ const plaintext = await webcrypto5.subtle.decrypt(
2069
+ { name: "AES-GCM", iv: toArrayBuffer3(base64UrlToBytes(envelope.cipher.iv)) },
2070
+ key,
2071
+ toArrayBuffer3(base64UrlToBytes(envelope.cipher.text))
2072
+ );
2073
+ return validateTransferPayload(JSON.parse(new TextDecoder().decode(plaintext)));
2074
+ } catch (error) {
2075
+ if (error instanceof Error && error.message.startsWith("Unsupported")) {
2076
+ throw error;
2077
+ }
2078
+ throw new Error("Could not decrypt connected account import payload. Check the passcode and payload.");
2079
+ }
2080
+ }
2081
+ async function buildEncryptedConnectedAccountImportRow(params) {
2082
+ const accountId = randomUUID2();
2083
+ const providerId = params.payload.provider_id;
2084
+ const appId = normalizeAppId(params.payload.app_id || appIdForProvider(providerId));
2085
+ const capabilities = normalizeCapabilities(params.payload.capabilities);
2086
+ const allowedActions = actionsForCapabilities(capabilities);
2087
+ const scopes = Array.isArray(params.payload.refresh_token_bundle.scopes) ? params.payload.refresh_token_bundle.scopes.filter((item) => typeof item === "string") : [];
2088
+ const label = params.payload.label.trim() || defaultProviderLabel(providerId);
2089
+ const accountRef = params.payload.account_ref?.trim() || accountId;
2090
+ return {
2091
+ id: accountId,
2092
+ hashed_user_id: sha256Hex(params.userId),
2093
+ encrypted_provider_type: await encryptJsonOrString(providerId, params.masterKey),
2094
+ provider_type_hash: sha256Hex(providerId),
2095
+ encrypted_account_label: await encryptJsonOrString(label, params.masterKey),
2096
+ encrypted_refresh_token_bundle: await encryptJsonOrString(params.payload.refresh_token_bundle, params.masterKey),
2097
+ encrypted_capabilities: await encryptJsonOrString(capabilities, params.masterKey),
2098
+ encrypted_app_permissions: await encryptJsonOrString(
2099
+ {
2100
+ app_id: appId,
2101
+ allowed_actions: allowedActions,
2102
+ scopes
2103
+ },
2104
+ params.masterKey
2105
+ ),
2106
+ encrypted_account_directory_hint: await encryptJsonOrString(
2107
+ {
2108
+ account_ref: accountRef,
2109
+ label,
2110
+ capabilities,
2111
+ runtime_modes: Object.keys(params.payload.runtime_modes).length ? params.payload.runtime_modes : runtimeModesForActions(allowedActions)
2112
+ },
2113
+ params.masterKey
2114
+ )
2115
+ };
2116
+ }
2117
+ function parseEnvelope(encodedEnvelope) {
2118
+ try {
2119
+ return JSON.parse(new TextDecoder().decode(base64UrlToBytes(encodedEnvelope)));
2120
+ } catch {
2121
+ throw new Error("Connected account import payload is malformed.");
2122
+ }
2123
+ }
2124
+ function validateTransferPayload(value) {
2125
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
2126
+ throw new Error("Connected account import payload is malformed.");
2127
+ }
2128
+ const payload = value;
2129
+ if (payload.version !== 1) throw new Error("Unsupported connected account import payload format.");
2130
+ if (typeof payload.provider_id !== "string" || !payload.provider_id) throw new Error("Import payload is missing provider_id.");
2131
+ if (typeof payload.app_id !== "string" || !payload.app_id) throw new Error("Import payload is missing app_id.");
2132
+ if (typeof payload.label !== "string") throw new Error("Import payload is missing label.");
2133
+ if (!Array.isArray(payload.capabilities)) throw new Error("Import payload is missing capabilities.");
2134
+ if (!payload.runtime_modes || typeof payload.runtime_modes !== "object" || Array.isArray(payload.runtime_modes)) {
2135
+ throw new Error("Import payload is missing runtime_modes.");
2136
+ }
2137
+ if (!payload.refresh_token_bundle || typeof payload.refresh_token_bundle !== "object" || Array.isArray(payload.refresh_token_bundle) || typeof payload.refresh_token_bundle.refresh_token !== "string" || !payload.refresh_token_bundle.refresh_token) {
2138
+ throw new Error("Import payload is missing refresh token material.");
2139
+ }
2140
+ return payload;
2141
+ }
2142
+ async function deriveTransferKey(passcode, salt, iterations) {
2143
+ const keyMaterial = await webcrypto5.subtle.importKey(
2144
+ "raw",
2145
+ new TextEncoder().encode(passcode),
2146
+ "PBKDF2",
2147
+ false,
2148
+ ["deriveKey"]
2149
+ );
2150
+ return webcrypto5.subtle.deriveKey(
2151
+ {
2152
+ name: "PBKDF2",
2153
+ salt: toArrayBuffer3(salt),
2154
+ iterations,
2155
+ hash: "SHA-256"
2156
+ },
2157
+ keyMaterial,
2158
+ { name: "AES-GCM", length: 256 },
2159
+ false,
2160
+ ["decrypt"]
2161
+ );
2162
+ }
2163
+ async function encryptJsonOrString(value, masterKey) {
2164
+ const plaintext = typeof value === "string" ? value : JSON.stringify(value);
2165
+ return encryptWithAesGcmCombined(plaintext, masterKey);
2166
+ }
2167
+ function normalizeCapabilities(value) {
2168
+ if (!Array.isArray(value)) return ["read"];
2169
+ const capabilities = value.filter((item) => typeof item === "string" && item.trim().length > 0);
2170
+ return capabilities.length ? Array.from(new Set(capabilities)) : ["read"];
2171
+ }
2172
+ function actionsForCapabilities(capabilities) {
2173
+ const actions = /* @__PURE__ */ new Set();
2174
+ for (const capability of capabilities) {
2175
+ if (capability === "read") actions.add("read");
2176
+ if (capability === "write") {
2177
+ actions.add("write");
2178
+ actions.add("update");
2179
+ }
2180
+ if (capability === "delete") actions.add("delete");
2181
+ }
2182
+ return actions.size ? Array.from(actions) : ["read"];
2183
+ }
2184
+ function runtimeModesForActions(actions) {
2185
+ return Object.fromEntries(actions.map((action) => [action, action === "read" ? "allow_automatically" : "always_ask"]));
2186
+ }
2187
+ function normalizeAppId(appId) {
2188
+ return appId === "google_calendar" ? "calendar" : appId;
2189
+ }
2190
+ function appIdForProvider(providerId) {
2191
+ return providerId === "google_calendar" ? "calendar" : providerId;
2192
+ }
2193
+ function defaultProviderLabel(providerId) {
2194
+ return providerId === "google_calendar" ? "Google Calendar" : "Connected account";
2195
+ }
2196
+ function sha256Hex(value) {
2197
+ return createHash4("sha256").update(value).digest("hex");
2198
+ }
2199
+ function base64UrlToBytes(value) {
2200
+ return base64ToBytes(value.replace(/-/g, "+").replace(/_/g, "/"));
2201
+ }
2202
+ function toArrayBuffer3(input) {
2203
+ const output = new ArrayBuffer(input.byteLength);
2204
+ new Uint8Array(output).set(input);
2205
+ return output;
2206
+ }
2207
+
2051
2208
  // src/client.ts
2052
2209
  function normalizeUnixSeconds(value, fallback) {
2053
2210
  if (typeof value !== "number" || !Number.isFinite(value) || value <= 0) {
@@ -2856,11 +3013,11 @@ var OpenMatesClient = class _OpenMatesClient {
2856
3013
  }
2857
3014
  let anonymousId = loadAnonymousId();
2858
3015
  if (!anonymousId) {
2859
- anonymousId = randomUUID2();
3016
+ anonymousId = randomUUID3();
2860
3017
  saveAnonymousId(anonymousId);
2861
3018
  }
2862
- const chatId = `anonymous-${randomUUID2()}`;
2863
- const messageId = `anonymous-message-${randomUUID2()}`;
3019
+ const chatId = `anonymous-${randomUUID3()}`;
3020
+ const messageId = `anonymous-message-${randomUUID3()}`;
2864
3021
  const requestBody = {
2865
3022
  anonymous_id: anonymousId,
2866
3023
  client_chat_id: chatId,
@@ -2918,6 +3075,59 @@ var OpenMatesClient = class _OpenMatesClient {
2918
3075
  };
2919
3076
  });
2920
3077
  }
3078
+ async importConnectedAccountFromCliPayload(params) {
3079
+ this.requireSession();
3080
+ const payload = await decryptConnectedAccountCliTransferPayload(params.encryptedPayload, params.passcode);
3081
+ const validation = await this.validateConnectedAccountImportPayload(payload);
3082
+ const user = await this.whoAmI();
3083
+ const userId = typeof user.id === "string" ? user.id : typeof user.user_id === "string" ? user.user_id : "";
3084
+ if (!userId) {
3085
+ throw new Error("Could not resolve current user id for connected account import.");
3086
+ }
3087
+ const row = await buildEncryptedConnectedAccountImportRow({
3088
+ payload,
3089
+ userId,
3090
+ masterKey: this.getMasterKeyBytes()
3091
+ });
3092
+ const stored = await this.createConnectedAccountImportRow(row);
3093
+ return {
3094
+ id: stored.id,
3095
+ providerId: payload.provider_id,
3096
+ appId: payload.app_id,
3097
+ label: payload.label,
3098
+ validation
3099
+ };
3100
+ }
3101
+ async validateConnectedAccountImportPayload(payload) {
3102
+ this.requireSession();
3103
+ const response = await this.http.post(
3104
+ "/v1/connected-accounts/validate-import",
3105
+ {
3106
+ provider_id: payload.provider_id,
3107
+ app_id: payload.app_id,
3108
+ capabilities: payload.capabilities,
3109
+ refresh_token_envelope: payload.refresh_token_bundle
3110
+ },
3111
+ this.getCliRequestHeaders()
3112
+ );
3113
+ if (!response.ok || response.data.valid !== true) {
3114
+ throw new Error(`Connected account validation failed (HTTP ${response.status})`);
3115
+ }
3116
+ assertNoConnectedAccountSecretLeak(response.data);
3117
+ return response.data;
3118
+ }
3119
+ async createConnectedAccountImportRow(row) {
3120
+ assertNoConnectedAccountSecretLeak(row);
3121
+ const response = await this.http.post(
3122
+ "/v1/connected-accounts",
3123
+ row,
3124
+ this.getCliRequestHeaders()
3125
+ );
3126
+ if (!response.ok || !response.data.id) {
3127
+ throw new Error(`Failed to store connected account import (HTTP ${response.status})`);
3128
+ }
3129
+ return response.data;
3130
+ }
2921
3131
  async cancelConnectedAccountAction(params) {
2922
3132
  const response = await this.http.post(
2923
3133
  `/v1/connected-accounts/actions/${encodeURIComponent(params.actionId)}/cancel`,
@@ -2994,7 +3204,7 @@ var OpenMatesClient = class _OpenMatesClient {
2994
3204
  pin: pin.trim().toUpperCase(),
2995
3205
  token
2996
3206
  });
2997
- const sessionId = randomUUID2();
3207
+ const sessionId = randomUUID3();
2998
3208
  const login = await this.http.post(
2999
3209
  "/v1/auth/login",
3000
3210
  {
@@ -3169,7 +3379,7 @@ var OpenMatesClient = class _OpenMatesClient {
3169
3379
  }
3170
3380
  const session = {
3171
3381
  apiUrl: this.apiUrl,
3172
- sessionId: randomUUID2(),
3382
+ sessionId: randomUUID3(),
3173
3383
  wsToken: null,
3174
3384
  cookies: this.http.getCookieMap(),
3175
3385
  masterKeyExportedB64: material.masterKeyB64,
@@ -3416,8 +3626,8 @@ var OpenMatesClient = class _OpenMatesClient {
3416
3626
  );
3417
3627
  let msgEmbedIds = [];
3418
3628
  if (clientMsgId && cache.embeds.length > 0) {
3419
- const { createHash: createHash6 } = await import("crypto");
3420
- const hashed = createHash6("sha256").update(clientMsgId).digest("hex");
3629
+ const { createHash: createHash7 } = await import("crypto");
3630
+ const hashed = createHash7("sha256").update(clientMsgId).digest("hex");
3421
3631
  msgEmbedIds = cache.embeds.filter(
3422
3632
  (e) => e.hashed_message_id === hashed && // Only include parent embeds (no parent_embed_id).
3423
3633
  // Child embeds inherit the parent's key and are loaded
@@ -3464,8 +3674,8 @@ var OpenMatesClient = class _OpenMatesClient {
3464
3674
  );
3465
3675
  }
3466
3676
  const embedId = String(embed.embed_id ?? embed.id ?? "");
3467
- const { createHash: createHash6 } = await import("crypto");
3468
- const hashedEmbedId = createHash6("sha256").update(embedId).digest("hex");
3677
+ const { createHash: createHash7 } = await import("crypto");
3678
+ const hashedEmbedId = createHash7("sha256").update(embedId).digest("hex");
3469
3679
  const embedKeyBytes = await this.resolveEmbedKey(
3470
3680
  cache,
3471
3681
  masterKey,
@@ -3573,7 +3783,7 @@ var OpenMatesClient = class _OpenMatesClient {
3573
3783
  async resolveEmbedKey(cache, masterKey, embed, embedId, hashedEmbedId, visited = /* @__PURE__ */ new Set()) {
3574
3784
  if (visited.has(embedId)) return null;
3575
3785
  visited.add(embedId);
3576
- const { createHash: createHash6 } = await import("crypto");
3786
+ const { createHash: createHash7 } = await import("crypto");
3577
3787
  const masterKeyEntry = cache.embedKeys.find(
3578
3788
  (ek) => ek.hashed_embed_id === hashedEmbedId && String(ek.key_type) === "master"
3579
3789
  );
@@ -3590,7 +3800,7 @@ var OpenMatesClient = class _OpenMatesClient {
3590
3800
  if (chatKeyEntry && typeof chatKeyEntry.encrypted_embed_key === "string") {
3591
3801
  const hashedChatId = String(chatKeyEntry.hashed_chat_id ?? "");
3592
3802
  const owningChat = cache.chats.find((c) => {
3593
- const chatHash = createHash6("sha256").update(String(c.details.id ?? "")).digest("hex");
3803
+ const chatHash = createHash7("sha256").update(String(c.details.id ?? "")).digest("hex");
3594
3804
  return chatHash === hashedChatId;
3595
3805
  });
3596
3806
  if (owningChat) {
@@ -3619,7 +3829,7 @@ var OpenMatesClient = class _OpenMatesClient {
3619
3829
  const parentFullId = String(
3620
3830
  parentEmbed2.embed_id ?? parentEmbed2.id ?? ""
3621
3831
  );
3622
- const parentHashedId = createHash6("sha256").update(parentFullId).digest("hex");
3832
+ const parentHashedId = createHash7("sha256").update(parentFullId).digest("hex");
3623
3833
  const parentKey = await this.resolveEmbedKey(
3624
3834
  cache,
3625
3835
  masterKey,
@@ -3734,7 +3944,7 @@ var OpenMatesClient = class _OpenMatesClient {
3734
3944
  async sendMessage(params) {
3735
3945
  let chatId;
3736
3946
  if (!params.chatId) {
3737
- chatId = randomUUID2();
3947
+ chatId = randomUUID3();
3738
3948
  } else if (params.chatId.length < 36) {
3739
3949
  const resolved = await this.resolveFullChatId(params.chatId);
3740
3950
  if (!resolved) {
@@ -3764,7 +3974,7 @@ var OpenMatesClient = class _OpenMatesClient {
3764
3974
  }
3765
3975
  }
3766
3976
  const { ws, session } = await this.openWsClient();
3767
- const messageId = randomUUID2();
3977
+ const messageId = randomUUID3();
3768
3978
  const createdAt = Math.floor(Date.now() / 1e3);
3769
3979
  const isNewChat = !params.chatId;
3770
3980
  ws.send("set_active_chat", { chat_id: chatId });
@@ -4151,7 +4361,7 @@ var OpenMatesClient = class _OpenMatesClient {
4151
4361
  );
4152
4362
  const encryptedCategory = category ? await encryptWithAesGcmCombined(category, chatKeyBytes) : void 0;
4153
4363
  const encryptedModelName = modelName ? await encryptWithAesGcmCombined(modelName, chatKeyBytes) : void 0;
4154
- const persistedAssistantMessageId = assistantMessageId ?? randomUUID2();
4364
+ const persistedAssistantMessageId = assistantMessageId ?? randomUUID3();
4155
4365
  ws.send("ai_response_completed", {
4156
4366
  chat_id: chatId,
4157
4367
  message: {
@@ -5294,7 +5504,7 @@ Required: ${schema.required.join(", ")}`
5294
5504
  );
5295
5505
  }
5296
5506
  }
5297
- const entryId = params.entryId ?? randomUUID2();
5507
+ const entryId = params.entryId ?? randomUUID3();
5298
5508
  const now = Math.floor(Date.now() / 1e3);
5299
5509
  const hashedKey = hashItemKey(params.appId, params.itemType);
5300
5510
  const plaintextPayload = {
@@ -5428,7 +5638,7 @@ Required: ${schema.required.join(", ")}`
5428
5638
  const session = this.requireSession();
5429
5639
  const masterKey = base64ToBytes(session.masterKeyExportedB64);
5430
5640
  const cache = await this.ensureSynced();
5431
- const { createHash: createHash6 } = await import("crypto");
5641
+ const { createHash: createHash7 } = await import("crypto");
5432
5642
  const embed = cache.embeds.find(
5433
5643
  (e) => String(e.embed_id ?? "").startsWith(embedIdOrShort) || String(e.id ?? "").startsWith(embedIdOrShort)
5434
5644
  );
@@ -5436,7 +5646,7 @@ Required: ${schema.required.join(", ")}`
5436
5646
  throw new Error(`Embed '${embedIdOrShort}' not found in local cache.`);
5437
5647
  }
5438
5648
  const embedId = String(embed.embed_id ?? embed.id ?? "");
5439
- const hashedEmbedId = createHash6("sha256").update(embedId).digest("hex");
5649
+ const hashedEmbedId = createHash7("sha256").update(embedId).digest("hex");
5440
5650
  const embedKeyBytes = await this.resolveEmbedKey(
5441
5651
  cache,
5442
5652
  masterKey,
@@ -5496,8 +5706,8 @@ Required: ${schema.required.join(", ")}`
5496
5706
  if (!embed) {
5497
5707
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
5498
5708
  }
5499
- const { createHash: createHash6 } = await import("crypto");
5500
- const hashedEmbedId = createHash6("sha256").update(embedId).digest("hex");
5709
+ const { createHash: createHash7 } = await import("crypto");
5710
+ const hashedEmbedId = createHash7("sha256").update(embedId).digest("hex");
5501
5711
  const embedKey = await this.resolveEmbedKey(
5502
5712
  cache,
5503
5713
  masterKey,
@@ -5591,8 +5801,8 @@ Required: ${schema.required.join(", ")}`
5591
5801
  if (!embed) {
5592
5802
  throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
5593
5803
  }
5594
- const { createHash: createHash6 } = await import("crypto");
5595
- const hashedEmbedId = createHash6("sha256").update(embedId).digest("hex");
5804
+ const { createHash: createHash7 } = await import("crypto");
5805
+ const hashedEmbedId = createHash7("sha256").update(embedId).digest("hex");
5596
5806
  const embedKey = await this.resolveEmbedKey(
5597
5807
  cache,
5598
5808
  masterKey,
@@ -5769,7 +5979,7 @@ Required: ${schema.required.join(", ")}`
5769
5979
  makeWsClient(session) {
5770
5980
  return new OpenMatesWsClient({
5771
5981
  apiUrl: session.apiUrl,
5772
- sessionId: randomUUID2(),
5982
+ sessionId: randomUUID3(),
5773
5983
  wsToken: session.wsToken,
5774
5984
  refreshToken: session.cookies.auth_refresh_token ?? null,
5775
5985
  // Same User-Agent as login so OS-based device fingerprint hash matches.
@@ -6929,7 +7139,7 @@ var OutputRedactor = class {
6929
7139
  import { readFileSync as readFileSync4, statSync, existsSync as existsSync4 } from "fs";
6930
7140
  import { basename, extname, resolve as resolve2 } from "path";
6931
7141
  import { homedir as homedir4 } from "os";
6932
- import { createHash as createHash4 } from "crypto";
7142
+ import { createHash as createHash5 } from "crypto";
6933
7143
  var MAX_PER_FILE_SIZE = 100 * 1024 * 1024;
6934
7144
  var BLOCKED_EXTENSIONS = /* @__PURE__ */ new Set([
6935
7145
  ".pem",
@@ -7239,7 +7449,7 @@ function processCodeFile(filePath, filename, redactor) {
7239
7449
  line_count: lineCount
7240
7450
  });
7241
7451
  const textPreview = `${filename} (${language}, ${lineCount} lines)`;
7242
- const contentHash = createHash4("sha256").update(content).digest("hex");
7452
+ const contentHash = createHash5("sha256").update(content).digest("hex");
7243
7453
  const embed = {
7244
7454
  embedId,
7245
7455
  embedRef,
@@ -8755,7 +8965,7 @@ function formatTs(ts) {
8755
8965
 
8756
8966
  // src/server.ts
8757
8967
  import { execSync, spawn as nodeSpawn } from "child_process";
8758
- import { createHash as createHash5, randomBytes as randomBytes2 } from "crypto";
8968
+ import { createHash as createHash6, randomBytes as randomBytes2 } from "crypto";
8759
8969
  import { chmodSync as chmodSync2, copyFileSync, cpSync, existsSync as existsSync5, mkdirSync as mkdirSync3, mkdtempSync, readFileSync as readFileSync5, readdirSync, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "fs";
8760
8970
  import { createInterface as createInterface2 } from "readline";
8761
8971
  import { createInterface as createPromptInterface } from "readline/promises";
@@ -9321,7 +9531,7 @@ function packagedCaddyTemplatePath(role) {
9321
9531
  }
9322
9532
  function fileHash(path) {
9323
9533
  if (!existsSync5(path)) return null;
9324
- return createHash5("sha256").update(readFileSync5(path)).digest("hex");
9534
+ return createHash6("sha256").update(readFileSync5(path)).digest("hex");
9325
9535
  }
9326
9536
  async function loadSelfHostComposeTemplate(templateRef, role) {
9327
9537
  const templateDir = process.env.OPENMATES_SELFHOST_TEMPLATE_DIR;
@@ -9597,7 +9807,7 @@ function writeChecksums(rootDir) {
9597
9807
  }
9598
9808
  if (entry.name === "checksums.sha256") continue;
9599
9809
  const relative2 = path.slice(rootDir.length + 1);
9600
- const hash = createHash5("sha256").update(readFileSync5(path)).digest("hex");
9810
+ const hash = createHash6("sha256").update(readFileSync5(path)).digest("hex");
9601
9811
  lines.push(`${hash} ${relative2}`);
9602
9812
  }
9603
9813
  };
@@ -9619,7 +9829,7 @@ function verifyChecksums(rootDir) {
9619
9829
  }
9620
9830
  const filePath = join3(rootDir, relative2);
9621
9831
  if (!existsSync5(filePath)) throw new Error(`Backup archive is missing checksummed file: ${relative2}`);
9622
- const actual = createHash5("sha256").update(readFileSync5(filePath)).digest("hex");
9832
+ const actual = createHash6("sha256").update(readFileSync5(filePath)).digest("hex");
9623
9833
  if (actual !== match[1]) throw new Error(`Backup checksum mismatch for ${relative2}.`);
9624
9834
  }
9625
9835
  }
@@ -9699,9 +9909,10 @@ function restoreServerBackup(installPath, role, file) {
9699
9909
  rmSync3(tempDir, { recursive: true, force: true });
9700
9910
  }
9701
9911
  }
9702
- function restoreStopServices(role) {
9912
+ function restoreStopServices(installPath, withOverrides, installMode, role) {
9703
9913
  if (role !== "core") return [];
9704
- return resolveServiceSelection("core", { exclude: "cms-database" });
9914
+ const command = ["docker", ...composeArgs(installPath, withOverrides, installMode, role), "config", "--services"].map(shellQuote).join(" ");
9915
+ return execSync(command, { cwd: installPath, encoding: "utf-8" }).split("\n").map((service) => service.trim()).filter((service) => service && service !== "cms-database");
9705
9916
  }
9706
9917
  async function promptText(question, defaultValue = "") {
9707
9918
  const rl = createPromptInterface({ input: process.stdin, output: process.stderr });
@@ -10657,7 +10868,7 @@ WARNING: This will restore ${role} data from ${file}.`);
10657
10868
  }
10658
10869
  const withOverrides = config?.composeProfile === "full";
10659
10870
  const installMode = getInstallMode(installPath, config);
10660
- const stopArgs = [...composeArgs(installPath, withOverrides, installMode, role), "stop", ...restoreStopServices(role)];
10871
+ const stopArgs = [...composeArgs(installPath, withOverrides, installMode, role), "stop", ...restoreStopServices(installPath, withOverrides, installMode, role)];
10661
10872
  let code = await runInteractive("docker", stopArgs, installPath);
10662
10873
  if (code !== 0) process.exit(code);
10663
10874
  restoreServerBackup(installPath, role, file);
@@ -24989,6 +25200,117 @@ var berlinMorningBikeForecastChat = {
24989
25200
  }
24990
25201
  };
24991
25202
 
25203
+ // ../ui/src/demo_chats/data/example_chats/privacy-first-product-launch-mind-map.ts
25204
+ var privacyFirstProductLaunchMindMapChat = {
25205
+ chat_id: "example-privacy-first-product-launch",
25206
+ slug: "privacy-first-product-launch-mind-map",
25207
+ title: "example_chats.privacy_first_product_launch_mind_map.title",
25208
+ summary: "example_chats.privacy_first_product_launch_mind_map.summary",
25209
+ icon: "workflow",
25210
+ category: "design",
25211
+ keywords: ["mind map", "privacy-first product launch", "product planning", "privacy UX", "launch strategy"],
25212
+ follow_up_suggestions: ["example_chats.privacy_first_product_launch_mind_map.follow_up_1", "example_chats.privacy_first_product_launch_mind_map.follow_up_2", "example_chats.privacy_first_product_launch_mind_map.follow_up_3", "example_chats.privacy_first_product_launch_mind_map.follow_up_4", "example_chats.privacy_first_product_launch_mind_map.follow_up_5", "example_chats.privacy_first_product_launch_mind_map.follow_up_6"],
25213
+ messages: [
25214
+ {
25215
+ "id": "15e39975-bc0e-4184-bf14-473d87131a6a",
25216
+ "role": "user",
25217
+ "content": "example_chats.privacy_first_product_launch_mind_map.message_1",
25218
+ "created_at": 1782037289
25219
+ },
25220
+ {
25221
+ "id": "d07c4f4f-76b2-4e71-a863-9f8baf0a359e",
25222
+ "role": "assistant",
25223
+ "content": "example_chats.privacy_first_product_launch_mind_map.message_2",
25224
+ "created_at": 1782037316,
25225
+ "user_message_id": "15e39975-bc0e-4184-bf14-473d87131a6a",
25226
+ "category": "design",
25227
+ "model_name": "Gemini 3.1 Pro"
25228
+ }
25229
+ ],
25230
+ embeds: [
25231
+ {
25232
+ "embed_id": "f9540460-1fce-441a-9abc-f627efaf1c0f",
25233
+ "type": "mindmap",
25234
+ "content": `type: mindmap
25235
+ app_id: mindmaps
25236
+ skill_id: mindmap
25237
+ title: Privacy-First Product Launch Planning
25238
+ source_json: "{\\n \\"openmatesType\\": \\"mindmap\\",\\n \\"schemaVersion\\": 1,\\n \\"title\\": \\"Privacy-First Product Launch Planning\\",\\n \\"rootId\\": \\"root\\",\\n \\"nodes\\": [\\n {\\n \\"id\\": \\"root\\",\\n \\"label\\": \\"Privacy-First Product Launch\\",\\n \\"children\\": [\\n \\"prod-eng\\",\\n \\"legal\\",\\n \\"ux\\",\\n \\"marketing\\",\\n \\"logistics\\"\\n ]\\n },\\n {\\n \\"id\\": \\"prod-eng\\",\\n \\"label\\": \\"Product & Engineering\\",\\n \\"children\\": [\\n \\"pe-data\\",\\n \\"pe-e2e\\",\\n \\"pe-audit\\"\\n ]\\n },\\n {\\n \\"id\\": \\"pe-data\\",\\n \\"label\\": \\"Data Minimization Specs\\"\\n },\\n {\\n \\"id\\": \\"pe-e2e\\",\\n \\"label\\": \\"End-to-End Encryption\\"\\n },\\n {\\n \\"id\\": \\"pe-audit\\",\\n \\"label\\": \\"3rd-Party Security Audits\\"\\n },\\n {\\n \\"id\\": \\"legal\\",\\n \\"label\\": \\"Legal & Compliance\\",\\n \\"children\\": [\\n \\"leg-gdpr\\",\\n \\"leg-policy\\",\\n \\"leg-tos\\"\\n ]\\n },\\n {\\n \\"id\\": \\"leg-gdpr\\",\\n \\"label\\": \\"GDPR / CCPA Readiness\\"\\n },\\n {\\n \\"id\\": \\"leg-policy\\",\\n \\"label\\": \\"Plain-English Privacy Policy\\"\\n },\\n {\\n \\"id\\": \\"leg-tos\\",\\n \\"label\\": \\"Transparent Terms of Service\\"\\n },\\n {\\n \\"id\\": \\"ux\\",\\n \\"label\\": \\"User Experience (UX)\\",\\n \\"children\\": [\\n \\"ux-consent\\",\\n \\"ux-dash\\",\\n \\"ux-delete\\"\\n ]\\n },\\n {\\n \\"id\\": \\"ux-consent\\",\\n \\"label\\": \\"Clear Consent Flows (No Dark Patterns)\\"\\n },\\n {\\n \\"id\\": \\"ux-dash\\",\\n \\"label\\": \\"Centralized Privacy Dashboard\\"\\n },\\n {\\n \\"id\\": \\"ux-delete\\",\\n \\"label\\": \\"1-Click Account Deletion\\"\\n },\\n {\\n \\"id\\": \\"marketing\\",\\n \\"label\\": \\"Marketing & Messaging\\",\\n \\"children\\": [\\n \\"mkt-core\\",\\n \\"mkt-press\\",\\n \\"mkt-advocates\\"\\n ]\\n },\\n {\\n \\"id\\": \\"mkt-core\\",\\n \\"label\\": \\"Core Message: 'Your Data is Yours'\\"\\n },\\n {\\n \\"id\\": \\"mkt-press\\",\\n \\"label\\": \\"Pitching Privacy Tech Journalists\\"\\n },\\n {\\n \\"id\\": \\"mkt-advocates\\",\\n \\"label\\": \\"Partnering w/ Privacy Advocates\\"\\n },\\n {\\n \\"id\\": \\"logistics\\",\\n \\"label\\": \\"Launch Logistics\\",\\n \\"children\\": [\\n \\"log-beta\\",\\n \\"log-rollout\\"\\n ]\\n },\\n {\\n \\"id\\": \\"log-beta\\",\\n \\"label\\": \\"Closed Beta & Bug Bounty\\"\\n },\\n {\\n \\"id\\": \\"log-rollout\\",\\n \\"label\\": \\"Phased Public Rollout\\"\\n }\\n ],\\n \\"edges\\": [],\\n \\"view\\": {\\n \\"layout\\": \\"radial-tree\\",\\n \\"collapsedNodeIds\\": []\\n }\\n}"
25239
+ model:
25240
+ openmatesType: mindmap
25241
+ schemaVersion: 1
25242
+ title: Privacy-First Product Launch Planning
25243
+ rootId: root
25244
+ nodes[20]:
25245
+ - id: root
25246
+ label: Privacy-First Product Launch
25247
+ children[5]: prod-eng,legal,ux,marketing,logistics
25248
+ - id: prod-eng
25249
+ label: Product & Engineering
25250
+ children[3]: pe-data,pe-e2e,pe-audit
25251
+ - id: pe-data
25252
+ label: Data Minimization Specs
25253
+ - id: pe-e2e
25254
+ label: End-to-End Encryption
25255
+ - id: pe-audit
25256
+ label: 3rd-Party Security Audits
25257
+ - id: legal
25258
+ label: Legal & Compliance
25259
+ children[3]: leg-gdpr,leg-policy,leg-tos
25260
+ - id: leg-gdpr
25261
+ label: GDPR / CCPA Readiness
25262
+ - id: leg-policy
25263
+ label: Plain-English Privacy Policy
25264
+ - id: leg-tos
25265
+ label: Transparent Terms of Service
25266
+ - id: ux
25267
+ label: User Experience (UX)
25268
+ children[3]: ux-consent,ux-dash,ux-delete
25269
+ - id: ux-consent
25270
+ label: Clear Consent Flows (No Dark Patterns)
25271
+ - id: ux-dash
25272
+ label: Centralized Privacy Dashboard
25273
+ - id: ux-delete
25274
+ label: 1-Click Account Deletion
25275
+ - id: marketing
25276
+ label: Marketing & Messaging
25277
+ children[3]: mkt-core,mkt-press,mkt-advocates
25278
+ - id: mkt-core
25279
+ label: "Core Message: 'Your Data is Yours'"
25280
+ - id: mkt-press
25281
+ label: Pitching Privacy Tech Journalists
25282
+ - id: mkt-advocates
25283
+ label: Partnering w/ Privacy Advocates
25284
+ - id: logistics
25285
+ label: Launch Logistics
25286
+ children[2]: log-beta,log-rollout
25287
+ - id: log-beta
25288
+ label: Closed Beta & Bug Bounty
25289
+ - id: log-rollout
25290
+ label: Phased Public Rollout
25291
+ edges[0]:
25292
+ view:
25293
+ layout: radial-tree
25294
+ collapsedNodeIds[0]:
25295
+ embed_ref: mind-map-f95404
25296
+ status: finished
25297
+ node_count: 20
25298
+ edge_count: 0
25299
+ validation:
25300
+ status: valid
25301
+ warnings[0]:
25302
+ parse_error: null
25303
+ version_number: 1`,
25304
+ "parent_embed_id": null,
25305
+ "embed_ids": null
25306
+ }
25307
+ ],
25308
+ metadata: {
25309
+ featured: true,
25310
+ order: 105
25311
+ }
25312
+ };
25313
+
24992
25314
  // ../ui/src/demo_chats/exampleChatData.ts
24993
25315
  var ALL_EXAMPLE_CHATS = [
24994
25316
  giganticAirplanesChat,
@@ -25076,7 +25398,8 @@ var ALL_EXAMPLE_CHATS = [
25076
25398
  rostockHeavyRainRadarChat,
25077
25399
  classicCarReverseImageSearchChat,
25078
25400
  usbC3v3LdoPcbSchematicChat,
25079
- berlinMorningBikeForecastChat
25401
+ berlinMorningBikeForecastChat,
25402
+ privacyFirstProductLaunchMindMapChat
25080
25403
  ].sort((a, b) => a.metadata.order - b.metadata.order);
25081
25404
 
25082
25405
  // ../ui/src/i18n/locales/en.json
@@ -25332,20 +25655,6 @@ var en_default = {
25332
25655
  }
25333
25656
  }
25334
25657
  },
25335
- pcb_design: {
25336
- create_schematic: {
25337
- text: "Create schematic",
25338
- description: {
25339
- text: "Plan & create a PCB schematic."
25340
- }
25341
- },
25342
- create_layout: {
25343
- text: "Create layout",
25344
- description: {
25345
- text: "Plan & create a PCB layout."
25346
- }
25347
- }
25348
- },
25349
25658
  plants: {
25350
25659
  plant_care_guide: {
25351
25660
  text: "Plant Care Guide",
@@ -27453,12 +27762,6 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
27453
27762
  text: "Find components and design electronic circuits."
27454
27763
  }
27455
27764
  },
27456
- pcb_design: {
27457
- text: "PCB Design",
27458
- description: {
27459
- text: "Plan schematics and layouts for printed circuit boards."
27460
- }
27461
- },
27462
27765
  nutrition: {
27463
27766
  text: "Nutrition",
27464
27767
  description: {
@@ -27759,6 +28062,21 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
27759
28062
  },
27760
28063
  learn_anything: {
27761
28064
  text: "learn anything"
28065
+ },
28066
+ plan_trips: {
28067
+ text: "plan trips"
28068
+ },
28069
+ create_images: {
28070
+ text: "create images"
28071
+ },
28072
+ follow_news: {
28073
+ text: "follow news"
28074
+ },
28075
+ stay_healthy: {
28076
+ text: "stay healthy"
28077
+ },
28078
+ write_better: {
28079
+ text: "write better"
27762
28080
  }
27763
28081
  },
27764
28082
  new_chat: {
@@ -33229,6 +33547,38 @@ Like [Denis Villeneuve](wiki:Denis_Villeneuve)\u2019s work, *Contact* avoids the
33229
33547
  text: '```json\n{"type": "app_skill_use", "embed_id": "54a8d949-0a13-463f-bc6a-958e1f746f79", "app_id": "pdf", "skill_id": "view", "query": "Please describe the visual layout of this page, including the title and the structure of any bulleted lists."}\n```\n\n_\nThe first page of sample.pdf features a clean, professional layout with the following structure:\n\n### **Visual Layout Overview**\n* **Title:** The document begins with a prominent, bold heading: **"OpenMates PDF Test Document"**.\n* **Subheading:** Immediately below the title is a line indicating the page and section: "Page 1 of 2 \u2014 Introduction".\n* **Introductory Paragraph:** A brief paragraph explains that the document is intended for testing PDF upload and AI reading features.\n* **Bullet List Structure:** \n * The list is introduced by the phrase "Key facts on this page:".\n * It consists of **three bullet points**, each marked with a standard black circular bullet (\u2022).\n * The bullets highlight the document title, a "secret word" (ALPHA), and the nature of the text on the page.\n* **Closing Text:** A final sentence notes that an AI assistant should be able to read the content and identify the secret word.\n\nWould you like me to [read the text](/#settings/apps/pdf) from page 2 or [search for specific keywords](/#settings/apps/pdf) within the document?'
33230
33548
  }
33231
33549
  },
33550
+ privacy_first_product_launch_mind_map: {
33551
+ title: {
33552
+ text: "Privacy-First Product Launch Mind Map"
33553
+ },
33554
+ summary: {
33555
+ text: "A structured mind map for planning a privacy-first product launch across product, legal, marketing, UX, and logistics."
33556
+ },
33557
+ message_1: {
33558
+ text: "Create a visual mind map artifact for planning a privacy-first product launch. Use a central idea with clear branches and sub-branches."
33559
+ },
33560
+ message_2: {
33561
+ text: 'Here is a visual mind map to help you organize and plan your privacy-first product launch. I\'ve broken it down into key areas: Product/Engineering, Legal, Marketing, UX, and Logistics. \n\n```json\n{"type": "mindmap", "embed_id": "f9540460-1fce-441a-9abc-f627efaf1c0f"}\n```\n\n### Design Notes for a Privacy-First Launch\nSince privacy is deeply connected to user trust, your UI and UX design choices will be just as important as your backend engineering. When you\'re designing the interface (especially the **UX** branch of this map), keep these principles in mind:\n* **Visual Transparency:** Avoid hiding data toggles in complex sub-menus. A clean, spacious privacy dashboard gives users a sense of control. \n* **Plain Language Design:** Pair your "Plain-English Privacy Policy" with simple iconography. E.g., an open padlock vs. a closed padlock.\n* **Combine AI and Your Own Craft:** If you need placeholder icons, UI mockups, or wireframes for this launch, you can sketch them out on paper or in a simple tool, upload them here, and we can use AI to refine those rough concepts together. It\u2019s a great way to keep the personal touch while moving quickly!\n\nWhich area of the launch map feels like the biggest hurdle for you right now? I\'d be happy to help you brainstorm marketing assets or sketch out some UI flows for your consent screens!'
33562
+ },
33563
+ follow_up_1: {
33564
+ text: "Search for upcoming privacy-focused product launch events"
33565
+ },
33566
+ follow_up_2: {
33567
+ text: "Explain the importance of visual transparency in UI design"
33568
+ },
33569
+ follow_up_3: {
33570
+ text: "Find tutorials on designing privacy dashboards"
33571
+ },
33572
+ follow_up_4: {
33573
+ text: "List key principles for a privacy-first product launch"
33574
+ },
33575
+ follow_up_5: {
33576
+ text: "Generate mockups for a clean privacy dashboard"
33577
+ },
33578
+ follow_up_6: {
33579
+ text: "Help me brainstorm marketing assets for a privacy-focused product"
33580
+ }
33581
+ },
33232
33582
  privacy_website_hero_background: {
33233
33583
  title: {
33234
33584
  text: "Privacy Website Hero Background"
@@ -33746,30 +34096,6 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
33746
34096
  text: "You are Suki, the onboarding guide for OpenMates. You are helping a user discover what OpenMates can do for them.\n\nCONVERSATION FLOW:\n1. UNDERSTAND: The user may have already seen your welcome message asking what they want to use OpenMates for. Listen carefully to their response. If their answer is vague, ask ONE follow-up question to clarify.\n\n2. INTRODUCE MATES: Based on their use cases, introduce the 2-3 most relevant specialist mates by name. Keep it to one sentence per mate, focused on what they can do for THIS user specifically.\n\n3. INTRODUCE FEATURES: Naturally weave in 1-2 relevant features:\n - Focus modes: specialized conversation modes for deep work (e.g., deep research, code review, guided learning). They keep the conversation focused on one task.\n - Memories: save your preferences and context so mates remember things like your preferred programming language, dietary restrictions, or work domain.\n - Apps: web search, image generation, news, maps, reminders, and more \u2014 mates use these automatically when they are helpful.\n Only mention features relevant to their stated needs. Do not list everything.\n\n4. SUMMARIZE & OFFER SHARING: After you understand their use cases well enough, provide a brief summary of what they told you. Then ask if they would like to (optionally and anonymously) share this summary with the OpenMates team to help improve the product. Make clear that:\n - Sharing is completely optional\n - The summary is anonymous (no account info is attached)\n - They can tell you to adjust the summary text before sharing\n If the user agrees, use the share-usecase skill with the summary.\n\nRULES:\n- Use simple, friendly language. Assume a non-technical audience.\n- Keep responses SHORT (3-5 sentences per message). Do not overwhelm.\n- Never mention technical details like encryption, WebSockets, APIs, or architecture.\n- If the user asks something unrelated to onboarding, briefly answer it, then gently guide back to getting them set up.\n- Do not pressure the user to share their summary. One clear offer is enough.\n- If the user seems done or says thanks, wish them well and suggest they start a new chat with any question to get going. Mention they can always type @focus:openmates:welcome to return to this conversation at any time.\n\nDYNAMIC CONTEXT:\nThe system will provide you with up-to-date information about available mates, apps, skills, and focus modes below. Use this information instead of guessing."
33747
34097
  }
33748
34098
  },
33749
- pcb_design_create_layout: {
33750
- text: "Create layout",
33751
- description: {
33752
- text: "Plan & create a PCB layout."
33753
- },
33754
- process: {
33755
- text: "- ask users clearifying question to make the requirements clear and create requirements.md file\n- {'for more complex tasks': 'create todo.md file and keep it updated with progress. Include filepaths or function names / variable names relevant.'}\n- if problem can't be solved within a few steps, ask if new approach should be tried or if requirements should be redefined\n- guide user throw the process of creating the PCB layout using Python scripts for KiCad or manually via the KiCad UI"
33756
- },
33757
- systemprompt: {
33758
- text: "You are a PCB design expert."
33759
- }
33760
- },
33761
- pcb_design_create_schematic: {
33762
- text: "Create schematic",
33763
- description: {
33764
- text: "Plan & create a PCB schematic."
33765
- },
33766
- process: {
33767
- text: "- ask users clearifying question to make the requirements clear and create requirements.md file\n- {'for more complex tasks': 'create todo.md file and keep it updated with progress. Include filepaths or function names / variable names relevant.'}\n- if problem can't be solved within a few steps, ask if new approach should be tried or if requirements should be redefined\n- guide user throw the process of creating the atopile code for a pcb schematic, or alternatively guide throw how to create a schematic in KiCad"
33768
- },
33769
- systemprompt: {
33770
- text: "You are a PCB design expert."
33771
- }
33772
- },
33773
34099
  plants_plant_care_guide: {
33774
34100
  text: "Plant Care Guide",
33775
34101
  description: {
@@ -40148,6 +40474,33 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
40148
40474
  },
40149
40475
  finalize_error: {
40150
40476
  text: "Could not finish updating connected account."
40477
+ },
40478
+ cli_export_title: {
40479
+ text: "CLI test-account import"
40480
+ },
40481
+ cli_export_warning: {
40482
+ text: "This is for testing only. Anyone with the generated command and passcode can add this provider account to another OpenMates account."
40483
+ },
40484
+ cli_export_passcode_placeholder: {
40485
+ text: "Passcode for the CLI import"
40486
+ },
40487
+ cli_export_passcode_required: {
40488
+ text: "Enter a passcode before generating the CLI import command."
40489
+ },
40490
+ cli_export_button: {
40491
+ text: "Generate CLI import command"
40492
+ },
40493
+ cli_export_command_label: {
40494
+ text: "Generated CLI import command"
40495
+ },
40496
+ cli_export_copied: {
40497
+ text: "CLI import command copied. Paste it while logged into the test account in OpenMates CLI."
40498
+ },
40499
+ cli_export_generated: {
40500
+ text: "CLI import command generated. Copy it and paste it while logged into the test account in OpenMates CLI."
40501
+ },
40502
+ cli_export_error: {
40503
+ text: "Could not generate the CLI import command."
40151
40504
  }
40152
40505
  },
40153
40506
  nearby_by_default: {
@@ -43320,7 +43673,7 @@ function buildAssistantFeedbackDecision(rating) {
43320
43673
  }
43321
43674
 
43322
43675
  // src/benchmark.ts
43323
- import { randomUUID as randomUUID3 } from "crypto";
43676
+ import { randomUUID as randomUUID4 } from "crypto";
43324
43677
  import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
43325
43678
  import { tmpdir } from "os";
43326
43679
  import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
@@ -43620,7 +43973,7 @@ async function handleBenchmark(client, subcommand, rest, flags) {
43620
43973
  const caseIds = parseCaseIds(flags.case);
43621
43974
  const dryRun = flags["dry-run"] === true;
43622
43975
  const output = typeof flags.output === "string" ? flags.output : void 0;
43623
- const runId = typeof flags["run-id"] === "string" ? flags["run-id"] : randomUUID3();
43976
+ const runId = typeof flags["run-id"] === "string" ? flags["run-id"] : randomUUID4();
43624
43977
  const imagePath = typeof flags.image === "string" ? resolve5(flags.image) : defaultImageFixturePath();
43625
43978
  if (!dryRun && flags["confirm-spend-credits"] !== true) {
43626
43979
  throw new Error(
@@ -44030,7 +44383,7 @@ function buildLongContextHistory() {
44030
44383
  }
44031
44384
  function appendHistory(history, role, content) {
44032
44385
  history.push({
44033
- message_id: randomUUID3(),
44386
+ message_id: randomUUID4(),
44034
44387
  role,
44035
44388
  sender_name: role === "user" ? "User" : "Assistant",
44036
44389
  content,
@@ -44369,6 +44722,10 @@ async function main() {
44369
44722
  printSettingsHelp(client);
44370
44723
  return;
44371
44724
  }
44725
+ if (command === "connected-accounts") {
44726
+ printConnectedAccountsHelp();
44727
+ return;
44728
+ }
44372
44729
  if (command === "learning-mode") {
44373
44730
  printLearningModeHelp();
44374
44731
  return;
@@ -44471,6 +44828,10 @@ async function main() {
44471
44828
  await handleSettings(client, subcommand, rest, parsed.flags);
44472
44829
  return;
44473
44830
  }
44831
+ if (command === "connected-accounts") {
44832
+ await handleConnectedAccounts(client, subcommand, parsed.flags);
44833
+ return;
44834
+ }
44474
44835
  if (command === "learning-mode") {
44475
44836
  await handleLearningMode(client, subcommand, parsed.flags);
44476
44837
  return;
@@ -46195,6 +46556,43 @@ async function promptSecret(question) {
46195
46556
  stdin2.on("data", onData);
46196
46557
  });
46197
46558
  }
46559
+ async function handleConnectedAccounts(client, subcommand, flags) {
46560
+ if (!subcommand || subcommand === "help" || flags.help === true) {
46561
+ printConnectedAccountsHelp();
46562
+ return;
46563
+ }
46564
+ if (subcommand !== "import") {
46565
+ throw new Error(`Unknown connected-accounts command '${subcommand}'. Run 'openmates connected-accounts --help'.`);
46566
+ }
46567
+ if (flags.passcode !== void 0) {
46568
+ throw new Error("Connected account import passcodes must be entered through the hidden prompt, not a command-line flag.");
46569
+ }
46570
+ const payload = typeof flags.payload === "string" ? flags.payload.trim() : "";
46571
+ if (!payload) {
46572
+ throw new Error("Missing --payload. Paste the command generated by connected account settings.");
46573
+ }
46574
+ if (!client.hasSession()) {
46575
+ throw new Error("Not logged in. Run `openmates login` before importing a connected account.");
46576
+ }
46577
+ const passcode = await promptSecret("Connected account import passcode: ");
46578
+ const result = await client.importConnectedAccountFromCliPayload({
46579
+ encryptedPayload: payload,
46580
+ passcode
46581
+ });
46582
+ if (flags.json === true) {
46583
+ printJson2({
46584
+ id: result.id,
46585
+ provider_id: result.providerId,
46586
+ app_id: result.appId,
46587
+ validation: result.validation
46588
+ });
46589
+ return;
46590
+ }
46591
+ console.log("Connected account imported.");
46592
+ console.log(`Provider: ${result.providerId}`);
46593
+ console.log(`App: ${result.appId}`);
46594
+ console.log("Validation: harmless read succeeded");
46595
+ }
46198
46596
  async function writeSecretFile(filePath, content, force = false) {
46199
46597
  const { mkdir, writeFile, stat: stat2 } = await import("fs/promises");
46200
46598
  const { dirname: dirname4 } = await import("path");
@@ -48934,6 +49332,7 @@ Commands:
48934
49332
  openmates mentions [--help] List available @mentions
48935
49333
  openmates embeds [--help] Embed commands (show)
48936
49334
  openmates settings [--help] Predefined settings commands
49335
+ openmates connected-accounts [--help] Connected account import helpers
48937
49336
  openmates learning-mode [--help] Account-wide Learning Mode controls
48938
49337
  openmates inspirations [--lang <code>] [--json] Daily inspirations
48939
49338
  openmates newchatsuggestions [--limit <n>] [--json] Personalized new chat suggestions
@@ -48949,6 +49348,22 @@ Flags:
48949
49348
  --api-key <key> Optional API key override (or set OPENMATES_API_KEY)
48950
49349
  --help Show contextual help for any command`);
48951
49350
  }
49351
+ function printConnectedAccountsHelp() {
49352
+ console.log(`Connected account commands:
49353
+ openmates connected-accounts import --payload <OMCA1...> [--json]
49354
+
49355
+ Imports one passcode-protected connected account generated from web settings.
49356
+ The CLI prompts for the passcode interactively, validates the provider token with
49357
+ a harmless read, then re-encrypts the account for the currently logged-in CLI
49358
+ account before storing it.
49359
+
49360
+ Options:
49361
+ --payload <OMCA1...> Required encrypted import payload from web settings
49362
+ --json Output a redacted JSON summary
49363
+
49364
+ Security:
49365
+ Do not pass the passcode as a flag. It is always entered through a hidden prompt.`);
49366
+ }
48952
49367
  function printFeedbackHelp() {
48953
49368
  console.log(`Feedback commands:
48954
49369
  openmates feedback assistant-response --rating <1-5> [--json]
package/dist/cli.js CHANGED
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getExtForLang,
4
4
  serializeToYaml
5
- } from "./chunk-KYTZNUYQ.js";
5
+ } from "./chunk-TTQIZ476.js";
6
6
  import "./chunk-AXNRPVLE.js";
7
7
  export {
8
8
  getExtForLang,
package/dist/index.d.ts CHANGED
@@ -209,6 +209,18 @@ interface EmbedKeyWrapper {
209
209
  */
210
210
  type ShareDuration = 0 | 60 | 3600 | 86400 | 604800 | 1209600 | 2592000 | 7776000;
211
211
 
212
+ interface ConnectedAccountCliTransferPayload {
213
+ version: 1;
214
+ provider_id: string;
215
+ app_id: string;
216
+ label: string;
217
+ account_ref?: string;
218
+ capabilities: string[];
219
+ runtime_modes: Record<string, string>;
220
+ refresh_token_bundle: Record<string, unknown>;
221
+ created_at: string;
222
+ }
223
+
212
224
  interface CliSubChatRequest {
213
225
  id?: string;
214
226
  chat_id?: string;
@@ -249,6 +261,19 @@ interface ConnectedAccountTurnTokenRef {
249
261
  action_scope?: Record<string, unknown>;
250
262
  expires_at: number;
251
263
  }
264
+ interface ConnectedAccountImportValidationResult {
265
+ valid: boolean;
266
+ provider_id: string;
267
+ app_id: string;
268
+ checked_at: number;
269
+ }
270
+ interface ConnectedAccountImportResult {
271
+ id: string;
272
+ providerId: string;
273
+ appId: string;
274
+ label: string;
275
+ validation: ConnectedAccountImportValidationResult;
276
+ }
252
277
  type LearningModeAgeGroup = "under_10" | "10_12" | "13_15" | "16_18" | "adult";
253
278
  interface LearningModeStatus {
254
279
  enabled: boolean;
@@ -565,6 +590,12 @@ declare class OpenMatesClient {
565
590
  messageId: string;
566
591
  refs: ConnectedAccountTurnTokenRefInput[];
567
592
  }): Promise<ConnectedAccountTurnTokenRef[]>;
593
+ importConnectedAccountFromCliPayload(params: {
594
+ encryptedPayload: string;
595
+ passcode: string;
596
+ }): Promise<ConnectedAccountImportResult>;
597
+ validateConnectedAccountImportPayload(payload: ConnectedAccountCliTransferPayload): Promise<ConnectedAccountImportValidationResult>;
598
+ private createConnectedAccountImportRow;
568
599
  cancelConnectedAccountAction(params: {
569
600
  actionId: string;
570
601
  chatId: string;
package/dist/index.js CHANGED
@@ -11,7 +11,7 @@ import {
11
11
  getExtForLang,
12
12
  normalizeInterestTagIds,
13
13
  serializeToYaml
14
- } from "./chunk-KYTZNUYQ.js";
14
+ } from "./chunk-TTQIZ476.js";
15
15
  import "./chunk-AXNRPVLE.js";
16
16
  export {
17
17
  ASSISTANT_FEEDBACK_REPORT_TITLE,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openmates",
3
- "version": "0.12.0-alpha.26",
3
+ "version": "0.12.0-alpha.28",
4
4
  "description": "OpenMates CLI and SDK",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -30,8 +30,9 @@
30
30
  "test:unit:billing": "node --test tests/billing.test.ts",
31
31
  "test:unit:account-delete": "node --test tests/account-delete.test.ts",
32
32
  "test:unit:cli": "node --test tests/cli.test.ts",
33
+ "test:unit:connected-account-import": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/connectedAccountImport.test.ts",
33
34
  "test:unit:benchmark": "node --test --experimental-strip-types --loader ./tests/loader.mjs src/__tests__/benchmark.test.ts",
34
- "test": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/crypto.test.ts tests/storage.test.ts tests/keychain.test.ts tests/mentions.test.ts tests/outputRedactor.test.ts tests/fileEmbed.test.ts tests/embedCreator.test.ts tests/shareEncryption.test.ts tests/server.test.ts tests/ws.test.ts tests/urlEmbed.test.ts src/__tests__/benchmark.test.ts && node --test --experimental-strip-types tests/embedRenderers.test.ts && node --test tests/cli.test.ts tests/billing.test.ts tests/account-delete.test.ts tests/signup.test.ts tests/security-setup.test.ts tests/e2e-provisioning.test.ts"
35
+ "test": "node --test --experimental-strip-types --loader ./tests/loader.mjs tests/crypto.test.ts tests/storage.test.ts tests/keychain.test.ts tests/mentions.test.ts tests/outputRedactor.test.ts tests/fileEmbed.test.ts tests/embedCreator.test.ts tests/shareEncryption.test.ts tests/server.test.ts tests/ws.test.ts tests/urlEmbed.test.ts src/__tests__/benchmark.test.ts && node --test --experimental-strip-types --loader ./tests/loader.mjs tests/embedRenderers.test.ts tests/connectedAccountImport.test.ts && node --test tests/cli.test.ts tests/billing.test.ts tests/account-delete.test.ts tests/signup.test.ts tests/security-setup.test.ts tests/e2e-provisioning.test.ts"
35
36
  },
36
37
  "keywords": [
37
38
  "openmates",