openmates 0.12.0-alpha.27 → 0.12.0-alpha.29
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.
- package/dist/{chunk-4S3EN6KA.js → chunk-FCSXUBOU.js} +656 -41
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +31 -0
- package/dist/index.js +1 -1
- package/package.json +3 -2
|
@@ -4,7 +4,7 @@ import {
|
|
|
4
4
|
} from "./chunk-AXNRPVLE.js";
|
|
5
5
|
|
|
6
6
|
// src/client.ts
|
|
7
|
-
import { randomUUID as
|
|
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 =
|
|
3016
|
+
anonymousId = randomUUID3();
|
|
2860
3017
|
saveAnonymousId(anonymousId);
|
|
2861
3018
|
}
|
|
2862
|
-
const chatId = `anonymous-${
|
|
2863
|
-
const messageId = `anonymous-message-${
|
|
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 =
|
|
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:
|
|
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:
|
|
3420
|
-
const hashed =
|
|
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:
|
|
3468
|
-
const hashedEmbedId =
|
|
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:
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 ??
|
|
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 ??
|
|
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:
|
|
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 =
|
|
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:
|
|
5500
|
-
const hashedEmbedId =
|
|
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:
|
|
5595
|
-
const hashedEmbedId =
|
|
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:
|
|
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
|
|
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 =
|
|
7452
|
+
const contentHash = createHash5("sha256").update(content).digest("hex");
|
|
7243
7453
|
const embed = {
|
|
7244
7454
|
embedId,
|
|
7245
7455
|
embedRef,
|
|
@@ -8754,8 +8964,8 @@ function formatTs(ts) {
|
|
|
8754
8964
|
}
|
|
8755
8965
|
|
|
8756
8966
|
// src/server.ts
|
|
8757
|
-
import { execSync, spawn as nodeSpawn } from "child_process";
|
|
8758
|
-
import { createHash as
|
|
8967
|
+
import { execFileSync as execFileSync2, execSync, spawn as nodeSpawn } from "child_process";
|
|
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";
|
|
@@ -8894,6 +9104,49 @@ function planRestore(input) {
|
|
|
8894
9104
|
steps: input.yes === true ? ["stop", "restore", "start", "health-check"] : ["confirm", "stop", "restore", "start", "health-check"]
|
|
8895
9105
|
};
|
|
8896
9106
|
}
|
|
9107
|
+
function parseSecretEnvKey(envKey) {
|
|
9108
|
+
if (!envKey.startsWith("SECRET__")) return null;
|
|
9109
|
+
const parts = envKey.slice("SECRET__".length).split("__", 2);
|
|
9110
|
+
if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
|
|
9111
|
+
return {
|
|
9112
|
+
envKey,
|
|
9113
|
+
vaultPath: `kv/data/providers/${parts[0].toLowerCase()}`,
|
|
9114
|
+
vaultKey: parts[1].toLowerCase()
|
|
9115
|
+
};
|
|
9116
|
+
}
|
|
9117
|
+
function summarizeSecretPreflight(input) {
|
|
9118
|
+
const inlineSecretEnvKeys = [];
|
|
9119
|
+
const importedSecretEnvKeys = [];
|
|
9120
|
+
const emptySecretEnvKeys = [];
|
|
9121
|
+
const importedVaultPresent = [];
|
|
9122
|
+
const importedVaultMissing = [];
|
|
9123
|
+
const importedVaultUnavailable = [];
|
|
9124
|
+
for (const [envKey, rawValue] of Object.entries(input.env).sort(([a], [b]) => a.localeCompare(b))) {
|
|
9125
|
+
if (!parseSecretEnvKey(envKey)) continue;
|
|
9126
|
+
const value = rawValue.trim();
|
|
9127
|
+
if (!value) {
|
|
9128
|
+
emptySecretEnvKeys.push(envKey);
|
|
9129
|
+
continue;
|
|
9130
|
+
}
|
|
9131
|
+
if (value !== "IMPORTED_TO_VAULT") {
|
|
9132
|
+
inlineSecretEnvKeys.push(envKey);
|
|
9133
|
+
continue;
|
|
9134
|
+
}
|
|
9135
|
+
importedSecretEnvKeys.push(envKey);
|
|
9136
|
+
const presence = input.vaultPresence?.[envKey];
|
|
9137
|
+
if (presence === "present") importedVaultPresent.push(envKey);
|
|
9138
|
+
else if (presence === "missing") importedVaultMissing.push(envKey);
|
|
9139
|
+
else importedVaultUnavailable.push(envKey);
|
|
9140
|
+
}
|
|
9141
|
+
return {
|
|
9142
|
+
inlineSecretEnvKeys,
|
|
9143
|
+
importedSecretEnvKeys,
|
|
9144
|
+
emptySecretEnvKeys,
|
|
9145
|
+
importedVaultPresent,
|
|
9146
|
+
importedVaultMissing,
|
|
9147
|
+
importedVaultUnavailable
|
|
9148
|
+
};
|
|
9149
|
+
}
|
|
8897
9150
|
function planCaddyCommand(input) {
|
|
8898
9151
|
const role = parseServerRole(input.role);
|
|
8899
9152
|
const templatePath = `templates/caddy/${role}/Caddyfile`;
|
|
@@ -9321,7 +9574,7 @@ function packagedCaddyTemplatePath(role) {
|
|
|
9321
9574
|
}
|
|
9322
9575
|
function fileHash(path) {
|
|
9323
9576
|
if (!existsSync5(path)) return null;
|
|
9324
|
-
return
|
|
9577
|
+
return createHash6("sha256").update(readFileSync5(path)).digest("hex");
|
|
9325
9578
|
}
|
|
9326
9579
|
async function loadSelfHostComposeTemplate(templateRef, role) {
|
|
9327
9580
|
const templateDir = process.env.OPENMATES_SELFHOST_TEMPLATE_DIR;
|
|
@@ -9586,6 +9839,89 @@ function missingRequiredEnvKeys(installPath, role) {
|
|
|
9586
9839
|
const env = readEnvMap(installPath);
|
|
9587
9840
|
return requiredRuntimeEnvKeys(role).filter((key) => !env[key]);
|
|
9588
9841
|
}
|
|
9842
|
+
function vaultCheckContainerForRole(role) {
|
|
9843
|
+
if (role === "core") return "api";
|
|
9844
|
+
if (role === "upload") return "app-uploads";
|
|
9845
|
+
return null;
|
|
9846
|
+
}
|
|
9847
|
+
function readImportedVaultSecretPresence(role, importedSecrets) {
|
|
9848
|
+
const checks = importedSecrets.filter((item) => item !== null);
|
|
9849
|
+
if (checks.length === 0) return { presence: {}, checked: true, unavailableReason: null };
|
|
9850
|
+
const container = vaultCheckContainerForRole(role);
|
|
9851
|
+
if (!container) {
|
|
9852
|
+
return { presence: {}, checked: false, unavailableReason: `${role} role does not expose a Vault-backed app container for secret verification.` };
|
|
9853
|
+
}
|
|
9854
|
+
const script = `
|
|
9855
|
+
import json
|
|
9856
|
+
import os
|
|
9857
|
+
import urllib.error
|
|
9858
|
+
import urllib.request
|
|
9859
|
+
|
|
9860
|
+
checks = json.loads(os.environ["OPENMATES_SECRET_CHECKS"])
|
|
9861
|
+
token_path = "/vault-data/api.token"
|
|
9862
|
+
try:
|
|
9863
|
+
with open(token_path, "r", encoding="utf-8") as token_file:
|
|
9864
|
+
token = token_file.read().strip()
|
|
9865
|
+
except Exception as exc:
|
|
9866
|
+
print(json.dumps({"ok": False, "reason": f"could not read {token_path}: {exc}", "presence": {}}))
|
|
9867
|
+
raise SystemExit(0)
|
|
9868
|
+
|
|
9869
|
+
presence = {}
|
|
9870
|
+
for item in checks:
|
|
9871
|
+
request = urllib.request.Request(
|
|
9872
|
+
f"http://vault:8200/v1/{item['vaultPath']}",
|
|
9873
|
+
headers={"X-Vault-Token": token},
|
|
9874
|
+
)
|
|
9875
|
+
try:
|
|
9876
|
+
with urllib.request.urlopen(request, timeout=3) as response:
|
|
9877
|
+
payload = json.loads(response.read().decode("utf-8"))
|
|
9878
|
+
value = payload.get("data", {}).get("data", {}).get(item["vaultKey"])
|
|
9879
|
+
presence[item["envKey"]] = "present" if value else "missing"
|
|
9880
|
+
except urllib.error.HTTPError as exc:
|
|
9881
|
+
presence[item["envKey"]] = "missing" if exc.code == 404 else "unavailable"
|
|
9882
|
+
except Exception:
|
|
9883
|
+
presence[item["envKey"]] = "unavailable"
|
|
9884
|
+
|
|
9885
|
+
print(json.dumps({"ok": True, "presence": presence}))
|
|
9886
|
+
`;
|
|
9887
|
+
try {
|
|
9888
|
+
const output = execFileSync2(
|
|
9889
|
+
"docker",
|
|
9890
|
+
["exec", "-e", `OPENMATES_SECRET_CHECKS=${JSON.stringify(checks)}`, container, "python", "-c", script],
|
|
9891
|
+
{ encoding: "utf-8", timeout: 1e4, stdio: ["pipe", "pipe", "pipe"] }
|
|
9892
|
+
).trim();
|
|
9893
|
+
const parsed = JSON.parse(output);
|
|
9894
|
+
if (parsed.ok === false) return { presence: {}, checked: false, unavailableReason: parsed.reason ?? "Vault check failed." };
|
|
9895
|
+
return { presence: parsed.presence ?? {}, checked: true, unavailableReason: null };
|
|
9896
|
+
} catch (error) {
|
|
9897
|
+
return {
|
|
9898
|
+
presence: {},
|
|
9899
|
+
checked: false,
|
|
9900
|
+
unavailableReason: `Vault check unavailable. Is the ${container} container running? ${error instanceof Error ? error.message : String(error)}`
|
|
9901
|
+
};
|
|
9902
|
+
}
|
|
9903
|
+
}
|
|
9904
|
+
function runtimeSecretPreflight(installPath, role) {
|
|
9905
|
+
const env = readEnvMap(installPath);
|
|
9906
|
+
const importedSecrets = Object.entries(env).filter(([, value]) => value.trim() === "IMPORTED_TO_VAULT").map(([envKey]) => parseSecretEnvKey(envKey)).filter((item) => item !== null);
|
|
9907
|
+
const vault = readImportedVaultSecretPresence(role, importedSecrets);
|
|
9908
|
+
return {
|
|
9909
|
+
...summarizeSecretPreflight({ env, vaultPresence: vault.presence }),
|
|
9910
|
+
vaultChecked: vault.checked,
|
|
9911
|
+
vaultUnavailableReason: vault.unavailableReason
|
|
9912
|
+
};
|
|
9913
|
+
}
|
|
9914
|
+
function formatSecretPreflight(preflight) {
|
|
9915
|
+
const parts = [
|
|
9916
|
+
`${preflight.importedVaultPresent.length}/${preflight.importedSecretEnvKeys.length} imported verified`
|
|
9917
|
+
];
|
|
9918
|
+
if (preflight.importedVaultMissing.length) parts.push(`missing in Vault: ${preflight.importedVaultMissing.join(", ")}`);
|
|
9919
|
+
if (preflight.importedVaultUnavailable.length) parts.push(`unverified: ${preflight.importedVaultUnavailable.join(", ")}`);
|
|
9920
|
+
if (preflight.inlineSecretEnvKeys.length) parts.push(`inline SECRET entries: ${preflight.inlineSecretEnvKeys.length}`);
|
|
9921
|
+
if (preflight.emptySecretEnvKeys.length) parts.push(`empty SECRET entries: ${preflight.emptySecretEnvKeys.length}`);
|
|
9922
|
+
if (preflight.vaultUnavailableReason) parts.push(preflight.vaultUnavailableReason);
|
|
9923
|
+
return parts.join("; ");
|
|
9924
|
+
}
|
|
9589
9925
|
function writeChecksums(rootDir) {
|
|
9590
9926
|
const lines = [];
|
|
9591
9927
|
const walk = (dir) => {
|
|
@@ -9597,7 +9933,7 @@ function writeChecksums(rootDir) {
|
|
|
9597
9933
|
}
|
|
9598
9934
|
if (entry.name === "checksums.sha256") continue;
|
|
9599
9935
|
const relative2 = path.slice(rootDir.length + 1);
|
|
9600
|
-
const hash =
|
|
9936
|
+
const hash = createHash6("sha256").update(readFileSync5(path)).digest("hex");
|
|
9601
9937
|
lines.push(`${hash} ${relative2}`);
|
|
9602
9938
|
}
|
|
9603
9939
|
};
|
|
@@ -9619,7 +9955,7 @@ function verifyChecksums(rootDir) {
|
|
|
9619
9955
|
}
|
|
9620
9956
|
const filePath = join3(rootDir, relative2);
|
|
9621
9957
|
if (!existsSync5(filePath)) throw new Error(`Backup archive is missing checksummed file: ${relative2}`);
|
|
9622
|
-
const actual =
|
|
9958
|
+
const actual = createHash6("sha256").update(readFileSync5(filePath)).digest("hex");
|
|
9623
9959
|
if (actual !== match[1]) throw new Error(`Backup checksum mismatch for ${relative2}.`);
|
|
9624
9960
|
}
|
|
9625
9961
|
}
|
|
@@ -10129,6 +10465,12 @@ async function serverUpdate(rest, flags) {
|
|
|
10129
10465
|
const filterRequested = hasServiceFilter(flags);
|
|
10130
10466
|
const selectedServices = filterRequested ? selectedComposeServices(role, flags) : [];
|
|
10131
10467
|
const missingEnvKeys = missingRequiredEnvKeys(installPath, role);
|
|
10468
|
+
const secretPreflight = runtimeSecretPreflight(installPath, role);
|
|
10469
|
+
const missingOrUnverifiedSecrets = [
|
|
10470
|
+
...missingEnvKeys,
|
|
10471
|
+
...secretPreflight.importedVaultMissing,
|
|
10472
|
+
...secretPreflight.importedVaultUnavailable
|
|
10473
|
+
];
|
|
10132
10474
|
if (installMode === "source" && (flags["image-tag"] !== void 0 || flags.channel !== void 0)) {
|
|
10133
10475
|
throw new Error("--image-tag and --channel only apply to image-mode installs. Source-mode installs update from Git.");
|
|
10134
10476
|
}
|
|
@@ -10138,7 +10480,7 @@ async function serverUpdate(rest, flags) {
|
|
|
10138
10480
|
const currentTag = getImageTagFromEnv(installPath, config);
|
|
10139
10481
|
const target = resolveTargetImageTag(flags, currentTag, getPackageVersion());
|
|
10140
10482
|
const templateRef = templateRefForImageTag(target.tag, getPackageVersion());
|
|
10141
|
-
const safetyPlan = planUpdate({ role, selectedServices, dryRun, skipBackup: flags["skip-backup"] === true, continuous: false, missingRequiredSecrets:
|
|
10483
|
+
const safetyPlan = planUpdate({ role, selectedServices, dryRun, skipBackup: flags["skip-backup"] === true, continuous: false, missingRequiredSecrets: missingOrUnverifiedSecrets });
|
|
10142
10484
|
const plan = {
|
|
10143
10485
|
command: "update",
|
|
10144
10486
|
role,
|
|
@@ -10152,6 +10494,7 @@ async function serverUpdate(rest, flags) {
|
|
|
10152
10494
|
steps: safetyPlan.steps,
|
|
10153
10495
|
backupName: safetyPlan.backupName,
|
|
10154
10496
|
missingRequiredEnvKeys: missingEnvKeys,
|
|
10497
|
+
secretPreflight,
|
|
10155
10498
|
blocked: safetyPlan.blocked,
|
|
10156
10499
|
blockReason: safetyPlan.blockReason,
|
|
10157
10500
|
dryRun
|
|
@@ -10170,13 +10513,16 @@ async function serverUpdate(rest, flags) {
|
|
|
10170
10513
|
console.log(` Backup: ${safetyPlan.backupName ?? "none"}`);
|
|
10171
10514
|
console.log(` Steps: ${safetyPlan.steps.join(" -> ")}`);
|
|
10172
10515
|
console.log(` Env preflight: ${missingEnvKeys.length ? `missing ${missingEnvKeys.join(", ")}` : "ok"}`);
|
|
10516
|
+
console.log(` Vault secrets: ${formatSecretPreflight(secretPreflight)}`);
|
|
10173
10517
|
console.log(" Commands: refresh compose, docker compose pull, docker compose up -d, health checks");
|
|
10174
10518
|
}
|
|
10175
10519
|
return;
|
|
10176
10520
|
}
|
|
10177
10521
|
if (safetyPlan.blocked) throw new Error(safetyPlan.blockReason ?? "Update blocked by preflight.");
|
|
10178
|
-
if (
|
|
10179
|
-
throw new Error(
|
|
10522
|
+
if (missingOrUnverifiedSecrets.length && flags.yes !== true) {
|
|
10523
|
+
throw new Error(
|
|
10524
|
+
`Required runtime secret checks failed: ${missingOrUnverifiedSecrets.join(", ")}. Add missing non-SECRET values to .env, ensure IMPORTED_TO_VAULT markers exist in Vault, or rerun with --yes after reviewing.`
|
|
10525
|
+
);
|
|
10180
10526
|
}
|
|
10181
10527
|
console.error(`Mode: image`);
|
|
10182
10528
|
console.error(`Current image tag: ${currentTag || "unknown"}`);
|
|
@@ -10729,8 +11075,14 @@ async function serverPreflight(flags) {
|
|
|
10729
11075
|
const config = loadConfigForInstallPath(installPath);
|
|
10730
11076
|
const role = getServerRole(flags, config);
|
|
10731
11077
|
const services = hasServiceFilter(flags) ? selectedComposeServices(role, flags) : [];
|
|
10732
|
-
const updatePlan = planUpdate({ role, selectedServices: services, dryRun: true });
|
|
10733
11078
|
const missingEnvKeys = missingRequiredEnvKeys(installPath, role);
|
|
11079
|
+
const secretPreflight = runtimeSecretPreflight(installPath, role);
|
|
11080
|
+
const missingOrUnverifiedSecrets = [
|
|
11081
|
+
...missingEnvKeys,
|
|
11082
|
+
...secretPreflight.importedVaultMissing,
|
|
11083
|
+
...secretPreflight.importedVaultUnavailable
|
|
11084
|
+
];
|
|
11085
|
+
const updatePlan = planUpdate({ role, selectedServices: services, dryRun: true, continuous: true, missingRequiredSecrets: missingOrUnverifiedSecrets });
|
|
10734
11086
|
const runtimePlan = planServerRuntime({
|
|
10735
11087
|
role,
|
|
10736
11088
|
profile: getCoreProfile(flags, config),
|
|
@@ -10748,6 +11100,10 @@ async function serverPreflight(flags) {
|
|
|
10748
11100
|
updateSteps: updatePlan.steps,
|
|
10749
11101
|
backupName: updatePlan.backupName,
|
|
10750
11102
|
missingRequiredEnvKeys: missingEnvKeys,
|
|
11103
|
+
secretPreflight,
|
|
11104
|
+
missingOrUnverifiedSecrets,
|
|
11105
|
+
blocked: updatePlan.blocked,
|
|
11106
|
+
blockReason: updatePlan.blockReason,
|
|
10751
11107
|
caddy: caddyPlan
|
|
10752
11108
|
};
|
|
10753
11109
|
if (flags.json === true) {
|
|
@@ -10760,6 +11116,8 @@ async function serverPreflight(flags) {
|
|
|
10760
11116
|
console.log(` Services: ${hasServiceFilter(flags) ? services.join(", ") : "all"}`);
|
|
10761
11117
|
console.log(` Backup: ${updatePlan.backupName ?? "none"}`);
|
|
10762
11118
|
console.log(` Env preflight: ${missingEnvKeys.length ? `missing ${missingEnvKeys.join(", ")}` : "ok"}`);
|
|
11119
|
+
console.log(` Vault secrets: ${formatSecretPreflight(secretPreflight)}`);
|
|
11120
|
+
if (updatePlan.blocked) console.log(` Blocked: ${updatePlan.blockReason}`);
|
|
10763
11121
|
console.log(` Health checks: ${runtimePlan.healthChecks.join(", ")}`);
|
|
10764
11122
|
console.log(` Caddy steps: ${caddyPlan.steps.join(" -> ")}`);
|
|
10765
11123
|
}
|
|
@@ -10812,14 +11170,16 @@ WARNING: This will replace ${appliedPath} with the packaged ${role} Caddyfile.`)
|
|
|
10812
11170
|
}
|
|
10813
11171
|
}
|
|
10814
11172
|
const backupPath = `${appliedPath}.openmates-backup-${nowStamp()}`;
|
|
11173
|
+
const hadExistingCaddyfile = existsSync5(appliedPath);
|
|
10815
11174
|
try {
|
|
10816
|
-
|
|
11175
|
+
mkdirSync3(dirname(appliedPath), { recursive: true });
|
|
11176
|
+
if (hadExistingCaddyfile) copyFileSync(appliedPath, backupPath);
|
|
10817
11177
|
copyFileSync(templatePath, appliedPath);
|
|
10818
11178
|
execSync("systemctl reload caddy", { stdio: "inherit" });
|
|
10819
11179
|
} catch (error) {
|
|
10820
11180
|
throw new Error(`Could not apply Caddyfile. Run with sudo or use --config <writable path>. ${error instanceof Error ? error.message : String(error)}`);
|
|
10821
11181
|
}
|
|
10822
|
-
console.log(`Applied Caddyfile for ${role}. Backup: ${backupPath}`);
|
|
11182
|
+
console.log(`Applied Caddyfile for ${role}. ${hadExistingCaddyfile ? `Backup: ${backupPath}` : "No previous Caddyfile existed."}`);
|
|
10823
11183
|
return;
|
|
10824
11184
|
}
|
|
10825
11185
|
console.log(`Caddy ${action} plan:`);
|
|
@@ -24990,6 +25350,118 @@ var berlinMorningBikeForecastChat = {
|
|
|
24990
25350
|
}
|
|
24991
25351
|
};
|
|
24992
25352
|
|
|
25353
|
+
// ../ui/src/demo_chats/data/example_chats/privacy-first-product-launch-mind-map.ts
|
|
25354
|
+
var privacyFirstProductLaunchMindMapChat = {
|
|
25355
|
+
chat_id: "example-privacy-first-product-launch",
|
|
25356
|
+
slug: "privacy-first-product-launch-mind-map",
|
|
25357
|
+
title: "example_chats.privacy_first_product_launch_mind_map.title",
|
|
25358
|
+
summary: "example_chats.privacy_first_product_launch_mind_map.summary",
|
|
25359
|
+
icon: "workflow",
|
|
25360
|
+
category: "design",
|
|
25361
|
+
keywords: ["mind map", "privacy-first product launch", "product planning", "privacy UX", "launch strategy"],
|
|
25362
|
+
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"],
|
|
25363
|
+
messages: [
|
|
25364
|
+
{
|
|
25365
|
+
"id": "15e39975-bc0e-4184-bf14-473d87131a6a",
|
|
25366
|
+
"role": "user",
|
|
25367
|
+
"content": "example_chats.privacy_first_product_launch_mind_map.message_1",
|
|
25368
|
+
"created_at": 1782037289
|
|
25369
|
+
},
|
|
25370
|
+
{
|
|
25371
|
+
"id": "d07c4f4f-76b2-4e71-a863-9f8baf0a359e",
|
|
25372
|
+
"role": "assistant",
|
|
25373
|
+
"content": "example_chats.privacy_first_product_launch_mind_map.message_2",
|
|
25374
|
+
"created_at": 1782037316,
|
|
25375
|
+
"user_message_id": "15e39975-bc0e-4184-bf14-473d87131a6a",
|
|
25376
|
+
"category": "design",
|
|
25377
|
+
"model_name": "Gemini 3.1 Pro"
|
|
25378
|
+
}
|
|
25379
|
+
],
|
|
25380
|
+
embeds: [
|
|
25381
|
+
{
|
|
25382
|
+
"embed_id": "f9540460-1fce-441a-9abc-f627efaf1c0f",
|
|
25383
|
+
"type": "mindmap",
|
|
25384
|
+
"content": `type: mindmap
|
|
25385
|
+
app_id: mindmaps
|
|
25386
|
+
skill_id: mindmap
|
|
25387
|
+
title: Privacy-First Product Launch Planning
|
|
25388
|
+
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}"
|
|
25389
|
+
model:
|
|
25390
|
+
openmatesType: mindmap
|
|
25391
|
+
schemaVersion: 1
|
|
25392
|
+
title: Privacy-First Product Launch Planning
|
|
25393
|
+
rootId: root
|
|
25394
|
+
nodes[20]:
|
|
25395
|
+
- id: root
|
|
25396
|
+
label: Privacy-First Product Launch
|
|
25397
|
+
children[5]: prod-eng,legal,ux,marketing,logistics
|
|
25398
|
+
- id: prod-eng
|
|
25399
|
+
label: Product & Engineering
|
|
25400
|
+
children[3]: pe-data,pe-e2e,pe-audit
|
|
25401
|
+
- id: pe-data
|
|
25402
|
+
label: Data Minimization Specs
|
|
25403
|
+
- id: pe-e2e
|
|
25404
|
+
label: End-to-End Encryption
|
|
25405
|
+
- id: pe-audit
|
|
25406
|
+
label: 3rd-Party Security Audits
|
|
25407
|
+
- id: legal
|
|
25408
|
+
label: Legal & Compliance
|
|
25409
|
+
children[3]: leg-gdpr,leg-policy,leg-tos
|
|
25410
|
+
- id: leg-gdpr
|
|
25411
|
+
label: GDPR / CCPA Readiness
|
|
25412
|
+
- id: leg-policy
|
|
25413
|
+
label: Plain-English Privacy Policy
|
|
25414
|
+
- id: leg-tos
|
|
25415
|
+
label: Transparent Terms of Service
|
|
25416
|
+
- id: ux
|
|
25417
|
+
label: User Experience (UX)
|
|
25418
|
+
children[3]: ux-consent,ux-dash,ux-delete
|
|
25419
|
+
- id: ux-consent
|
|
25420
|
+
label: Clear Consent Flows (No Dark Patterns)
|
|
25421
|
+
- id: ux-dash
|
|
25422
|
+
label: Centralized Privacy Dashboard
|
|
25423
|
+
- id: ux-delete
|
|
25424
|
+
label: 1-Click Account Deletion
|
|
25425
|
+
- id: marketing
|
|
25426
|
+
label: Marketing & Messaging
|
|
25427
|
+
children[3]: mkt-core,mkt-press,mkt-advocates
|
|
25428
|
+
- id: mkt-core
|
|
25429
|
+
label: "Core Message: 'Your Data is Yours'"
|
|
25430
|
+
- id: mkt-press
|
|
25431
|
+
label: Pitching Privacy Tech Journalists
|
|
25432
|
+
- id: mkt-advocates
|
|
25433
|
+
label: Partnering w/ Privacy Advocates
|
|
25434
|
+
- id: logistics
|
|
25435
|
+
label: Launch Logistics
|
|
25436
|
+
children[2]: log-beta,log-rollout
|
|
25437
|
+
- id: log-beta
|
|
25438
|
+
label: Closed Beta & Bug Bounty
|
|
25439
|
+
- id: log-rollout
|
|
25440
|
+
label: Phased Public Rollout
|
|
25441
|
+
edges[0]:
|
|
25442
|
+
view:
|
|
25443
|
+
layout: radial-tree
|
|
25444
|
+
collapsedNodeIds[0]:
|
|
25445
|
+
embed_ref: mind-map-f95404
|
|
25446
|
+
status: finished
|
|
25447
|
+
node_count: 20
|
|
25448
|
+
edge_count: 0
|
|
25449
|
+
validation:
|
|
25450
|
+
status: valid
|
|
25451
|
+
warnings[0]:
|
|
25452
|
+
parse_error: null
|
|
25453
|
+
version_number: 1`,
|
|
25454
|
+
"parent_embed_id": null,
|
|
25455
|
+
"embed_ids": null
|
|
25456
|
+
}
|
|
25457
|
+
],
|
|
25458
|
+
metadata: {
|
|
25459
|
+
featured: true,
|
|
25460
|
+
order: 105,
|
|
25461
|
+
content_embed_examples: ["mindmaps.mindmap"]
|
|
25462
|
+
}
|
|
25463
|
+
};
|
|
25464
|
+
|
|
24993
25465
|
// ../ui/src/demo_chats/exampleChatData.ts
|
|
24994
25466
|
var ALL_EXAMPLE_CHATS = [
|
|
24995
25467
|
giganticAirplanesChat,
|
|
@@ -25077,7 +25549,8 @@ var ALL_EXAMPLE_CHATS = [
|
|
|
25077
25549
|
rostockHeavyRainRadarChat,
|
|
25078
25550
|
classicCarReverseImageSearchChat,
|
|
25079
25551
|
usbC3v3LdoPcbSchematicChat,
|
|
25080
|
-
berlinMorningBikeForecastChat
|
|
25552
|
+
berlinMorningBikeForecastChat,
|
|
25553
|
+
privacyFirstProductLaunchMindMapChat
|
|
25081
25554
|
].sort((a, b) => a.metadata.order - b.metadata.order);
|
|
25082
25555
|
|
|
25083
25556
|
// ../ui/src/i18n/locales/en.json
|
|
@@ -27711,6 +28184,12 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
27711
28184
|
continue: {
|
|
27712
28185
|
text: "Continue"
|
|
27713
28186
|
},
|
|
28187
|
+
skip: {
|
|
28188
|
+
text: "Skip"
|
|
28189
|
+
},
|
|
28190
|
+
select_interests: {
|
|
28191
|
+
text: "Select interests"
|
|
28192
|
+
},
|
|
27714
28193
|
software_development: {
|
|
27715
28194
|
text: "software development"
|
|
27716
28195
|
},
|
|
@@ -27740,6 +28219,21 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
27740
28219
|
},
|
|
27741
28220
|
learn_anything: {
|
|
27742
28221
|
text: "learn anything"
|
|
28222
|
+
},
|
|
28223
|
+
plan_trips: {
|
|
28224
|
+
text: "plan trips"
|
|
28225
|
+
},
|
|
28226
|
+
create_images: {
|
|
28227
|
+
text: "create images"
|
|
28228
|
+
},
|
|
28229
|
+
follow_news: {
|
|
28230
|
+
text: "follow news"
|
|
28231
|
+
},
|
|
28232
|
+
stay_healthy: {
|
|
28233
|
+
text: "stay healthy"
|
|
28234
|
+
},
|
|
28235
|
+
write_better: {
|
|
28236
|
+
text: "write better"
|
|
27743
28237
|
}
|
|
27744
28238
|
},
|
|
27745
28239
|
new_chat: {
|
|
@@ -33210,6 +33704,38 @@ Like [Denis Villeneuve](wiki:Denis_Villeneuve)\u2019s work, *Contact* avoids the
|
|
|
33210
33704
|
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?'
|
|
33211
33705
|
}
|
|
33212
33706
|
},
|
|
33707
|
+
privacy_first_product_launch_mind_map: {
|
|
33708
|
+
title: {
|
|
33709
|
+
text: "Privacy-First Product Launch Mind Map"
|
|
33710
|
+
},
|
|
33711
|
+
summary: {
|
|
33712
|
+
text: "A structured mind map for planning a privacy-first product launch across product, legal, marketing, UX, and logistics."
|
|
33713
|
+
},
|
|
33714
|
+
message_1: {
|
|
33715
|
+
text: "Create a visual mind map artifact for planning a privacy-first product launch. Use a central idea with clear branches and sub-branches."
|
|
33716
|
+
},
|
|
33717
|
+
message_2: {
|
|
33718
|
+
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!'
|
|
33719
|
+
},
|
|
33720
|
+
follow_up_1: {
|
|
33721
|
+
text: "Search for upcoming privacy-focused product launch events"
|
|
33722
|
+
},
|
|
33723
|
+
follow_up_2: {
|
|
33724
|
+
text: "Explain the importance of visual transparency in UI design"
|
|
33725
|
+
},
|
|
33726
|
+
follow_up_3: {
|
|
33727
|
+
text: "Find tutorials on designing privacy dashboards"
|
|
33728
|
+
},
|
|
33729
|
+
follow_up_4: {
|
|
33730
|
+
text: "List key principles for a privacy-first product launch"
|
|
33731
|
+
},
|
|
33732
|
+
follow_up_5: {
|
|
33733
|
+
text: "Generate mockups for a clean privacy dashboard"
|
|
33734
|
+
},
|
|
33735
|
+
follow_up_6: {
|
|
33736
|
+
text: "Help me brainstorm marketing assets for a privacy-focused product"
|
|
33737
|
+
}
|
|
33738
|
+
},
|
|
33213
33739
|
privacy_website_hero_background: {
|
|
33214
33740
|
title: {
|
|
33215
33741
|
text: "Privacy Website Hero Background"
|
|
@@ -40105,6 +40631,33 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
40105
40631
|
},
|
|
40106
40632
|
finalize_error: {
|
|
40107
40633
|
text: "Could not finish updating connected account."
|
|
40634
|
+
},
|
|
40635
|
+
cli_export_title: {
|
|
40636
|
+
text: "CLI test-account import"
|
|
40637
|
+
},
|
|
40638
|
+
cli_export_warning: {
|
|
40639
|
+
text: "This is for testing only. Anyone with the generated command and passcode can add this provider account to another OpenMates account."
|
|
40640
|
+
},
|
|
40641
|
+
cli_export_passcode_placeholder: {
|
|
40642
|
+
text: "Passcode for the CLI import"
|
|
40643
|
+
},
|
|
40644
|
+
cli_export_passcode_required: {
|
|
40645
|
+
text: "Enter a passcode before generating the CLI import command."
|
|
40646
|
+
},
|
|
40647
|
+
cli_export_button: {
|
|
40648
|
+
text: "Generate CLI import command"
|
|
40649
|
+
},
|
|
40650
|
+
cli_export_command_label: {
|
|
40651
|
+
text: "Generated CLI import command"
|
|
40652
|
+
},
|
|
40653
|
+
cli_export_copied: {
|
|
40654
|
+
text: "CLI import command copied. Paste it while logged into the test account in OpenMates CLI."
|
|
40655
|
+
},
|
|
40656
|
+
cli_export_generated: {
|
|
40657
|
+
text: "CLI import command generated. Copy it and paste it while logged into the test account in OpenMates CLI."
|
|
40658
|
+
},
|
|
40659
|
+
cli_export_error: {
|
|
40660
|
+
text: "Could not generate the CLI import command."
|
|
40108
40661
|
}
|
|
40109
40662
|
},
|
|
40110
40663
|
nearby_by_default: {
|
|
@@ -43277,7 +43830,7 @@ function buildAssistantFeedbackDecision(rating) {
|
|
|
43277
43830
|
}
|
|
43278
43831
|
|
|
43279
43832
|
// src/benchmark.ts
|
|
43280
|
-
import { randomUUID as
|
|
43833
|
+
import { randomUUID as randomUUID4 } from "crypto";
|
|
43281
43834
|
import { existsSync as existsSync6, mkdtempSync as mkdtempSync2, readFileSync as readFileSync6, readdirSync as readdirSync2, writeFileSync as writeFileSync4 } from "fs";
|
|
43282
43835
|
import { tmpdir } from "os";
|
|
43283
43836
|
import { dirname as dirname2, join as join4, resolve as resolve5 } from "path";
|
|
@@ -43577,7 +44130,7 @@ async function handleBenchmark(client, subcommand, rest, flags) {
|
|
|
43577
44130
|
const caseIds = parseCaseIds(flags.case);
|
|
43578
44131
|
const dryRun = flags["dry-run"] === true;
|
|
43579
44132
|
const output = typeof flags.output === "string" ? flags.output : void 0;
|
|
43580
|
-
const runId = typeof flags["run-id"] === "string" ? flags["run-id"] :
|
|
44133
|
+
const runId = typeof flags["run-id"] === "string" ? flags["run-id"] : randomUUID4();
|
|
43581
44134
|
const imagePath = typeof flags.image === "string" ? resolve5(flags.image) : defaultImageFixturePath();
|
|
43582
44135
|
if (!dryRun && flags["confirm-spend-credits"] !== true) {
|
|
43583
44136
|
throw new Error(
|
|
@@ -43987,7 +44540,7 @@ function buildLongContextHistory() {
|
|
|
43987
44540
|
}
|
|
43988
44541
|
function appendHistory(history, role, content) {
|
|
43989
44542
|
history.push({
|
|
43990
|
-
message_id:
|
|
44543
|
+
message_id: randomUUID4(),
|
|
43991
44544
|
role,
|
|
43992
44545
|
sender_name: role === "user" ? "User" : "Assistant",
|
|
43993
44546
|
content,
|
|
@@ -44326,6 +44879,10 @@ async function main() {
|
|
|
44326
44879
|
printSettingsHelp(client);
|
|
44327
44880
|
return;
|
|
44328
44881
|
}
|
|
44882
|
+
if (command === "connected-accounts") {
|
|
44883
|
+
printConnectedAccountsHelp();
|
|
44884
|
+
return;
|
|
44885
|
+
}
|
|
44329
44886
|
if (command === "learning-mode") {
|
|
44330
44887
|
printLearningModeHelp();
|
|
44331
44888
|
return;
|
|
@@ -44428,6 +44985,10 @@ async function main() {
|
|
|
44428
44985
|
await handleSettings(client, subcommand, rest, parsed.flags);
|
|
44429
44986
|
return;
|
|
44430
44987
|
}
|
|
44988
|
+
if (command === "connected-accounts") {
|
|
44989
|
+
await handleConnectedAccounts(client, subcommand, parsed.flags);
|
|
44990
|
+
return;
|
|
44991
|
+
}
|
|
44431
44992
|
if (command === "learning-mode") {
|
|
44432
44993
|
await handleLearningMode(client, subcommand, parsed.flags);
|
|
44433
44994
|
return;
|
|
@@ -46152,6 +46713,43 @@ async function promptSecret(question) {
|
|
|
46152
46713
|
stdin2.on("data", onData);
|
|
46153
46714
|
});
|
|
46154
46715
|
}
|
|
46716
|
+
async function handleConnectedAccounts(client, subcommand, flags) {
|
|
46717
|
+
if (!subcommand || subcommand === "help" || flags.help === true) {
|
|
46718
|
+
printConnectedAccountsHelp();
|
|
46719
|
+
return;
|
|
46720
|
+
}
|
|
46721
|
+
if (subcommand !== "import") {
|
|
46722
|
+
throw new Error(`Unknown connected-accounts command '${subcommand}'. Run 'openmates connected-accounts --help'.`);
|
|
46723
|
+
}
|
|
46724
|
+
if (flags.passcode !== void 0) {
|
|
46725
|
+
throw new Error("Connected account import passcodes must be entered through the hidden prompt, not a command-line flag.");
|
|
46726
|
+
}
|
|
46727
|
+
const payload = typeof flags.payload === "string" ? flags.payload.trim() : "";
|
|
46728
|
+
if (!payload) {
|
|
46729
|
+
throw new Error("Missing --payload. Paste the command generated by connected account settings.");
|
|
46730
|
+
}
|
|
46731
|
+
if (!client.hasSession()) {
|
|
46732
|
+
throw new Error("Not logged in. Run `openmates login` before importing a connected account.");
|
|
46733
|
+
}
|
|
46734
|
+
const passcode = await promptSecret("Connected account import passcode: ");
|
|
46735
|
+
const result = await client.importConnectedAccountFromCliPayload({
|
|
46736
|
+
encryptedPayload: payload,
|
|
46737
|
+
passcode
|
|
46738
|
+
});
|
|
46739
|
+
if (flags.json === true) {
|
|
46740
|
+
printJson2({
|
|
46741
|
+
id: result.id,
|
|
46742
|
+
provider_id: result.providerId,
|
|
46743
|
+
app_id: result.appId,
|
|
46744
|
+
validation: result.validation
|
|
46745
|
+
});
|
|
46746
|
+
return;
|
|
46747
|
+
}
|
|
46748
|
+
console.log("Connected account imported.");
|
|
46749
|
+
console.log(`Provider: ${result.providerId}`);
|
|
46750
|
+
console.log(`App: ${result.appId}`);
|
|
46751
|
+
console.log("Validation: harmless read succeeded");
|
|
46752
|
+
}
|
|
46155
46753
|
async function writeSecretFile(filePath, content, force = false) {
|
|
46156
46754
|
const { mkdir, writeFile, stat: stat2 } = await import("fs/promises");
|
|
46157
46755
|
const { dirname: dirname4 } = await import("path");
|
|
@@ -48891,6 +49489,7 @@ Commands:
|
|
|
48891
49489
|
openmates mentions [--help] List available @mentions
|
|
48892
49490
|
openmates embeds [--help] Embed commands (show)
|
|
48893
49491
|
openmates settings [--help] Predefined settings commands
|
|
49492
|
+
openmates connected-accounts [--help] Connected account import helpers
|
|
48894
49493
|
openmates learning-mode [--help] Account-wide Learning Mode controls
|
|
48895
49494
|
openmates inspirations [--lang <code>] [--json] Daily inspirations
|
|
48896
49495
|
openmates newchatsuggestions [--limit <n>] [--json] Personalized new chat suggestions
|
|
@@ -48906,6 +49505,22 @@ Flags:
|
|
|
48906
49505
|
--api-key <key> Optional API key override (or set OPENMATES_API_KEY)
|
|
48907
49506
|
--help Show contextual help for any command`);
|
|
48908
49507
|
}
|
|
49508
|
+
function printConnectedAccountsHelp() {
|
|
49509
|
+
console.log(`Connected account commands:
|
|
49510
|
+
openmates connected-accounts import --payload <OMCA1...> [--json]
|
|
49511
|
+
|
|
49512
|
+
Imports one passcode-protected connected account generated from web settings.
|
|
49513
|
+
The CLI prompts for the passcode interactively, validates the provider token with
|
|
49514
|
+
a harmless read, then re-encrypts the account for the currently logged-in CLI
|
|
49515
|
+
account before storing it.
|
|
49516
|
+
|
|
49517
|
+
Options:
|
|
49518
|
+
--payload <OMCA1...> Required encrypted import payload from web settings
|
|
49519
|
+
--json Output a redacted JSON summary
|
|
49520
|
+
|
|
49521
|
+
Security:
|
|
49522
|
+
Do not pass the passcode as a flag. It is always entered through a hidden prompt.`);
|
|
49523
|
+
}
|
|
48909
49524
|
function printFeedbackHelp() {
|
|
48910
49525
|
console.log(`Feedback commands:
|
|
48911
49526
|
openmates feedback assistant-response --rating <1-5> [--json]
|
package/dist/cli.js
CHANGED
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openmates",
|
|
3
|
-
"version": "0.12.0-alpha.
|
|
3
|
+
"version": "0.12.0-alpha.29",
|
|
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",
|