openmates 0.12.0-alpha.3 → 0.12.0-alpha.5
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-3T6XUXII.js → chunk-RV656PRS.js} +712 -5
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +67 -0
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -2037,6 +2037,33 @@ function buildSubChatConfirmationPayload(params) {
|
|
|
2037
2037
|
approve_count: params.approved ? params.approveCount ?? null : null
|
|
2038
2038
|
};
|
|
2039
2039
|
}
|
|
2040
|
+
function assertNoConnectedAccountSecretLeak(value) {
|
|
2041
|
+
const serialized = JSON.stringify(value ?? {});
|
|
2042
|
+
const forbidden = [
|
|
2043
|
+
"refresh_token",
|
|
2044
|
+
"access_token",
|
|
2045
|
+
"provider_email",
|
|
2046
|
+
"account_email",
|
|
2047
|
+
"provider_account_id"
|
|
2048
|
+
];
|
|
2049
|
+
for (const key of forbidden) {
|
|
2050
|
+
if (serialized.includes(`"${key}"`)) {
|
|
2051
|
+
throw new Error(`Connected account payload contains forbidden field: ${key}`);
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
}
|
|
2055
|
+
function buildConnectedAccountDirectoryPayload(entries) {
|
|
2056
|
+
if (!entries || entries.length === 0) return void 0;
|
|
2057
|
+
assertNoConnectedAccountSecretLeak(entries);
|
|
2058
|
+
return entries.map((entry) => ({ ...entry, capabilities: [...entry.capabilities] }));
|
|
2059
|
+
}
|
|
2060
|
+
function buildTurnTokenRefsRequestPayload(params) {
|
|
2061
|
+
return {
|
|
2062
|
+
chat_id: params.chatId,
|
|
2063
|
+
message_id: params.messageId,
|
|
2064
|
+
refs: params.refs.map((ref) => ({ ...ref }))
|
|
2065
|
+
};
|
|
2066
|
+
}
|
|
2040
2067
|
function categoryFromMemoryKey(key) {
|
|
2041
2068
|
const separator = key.indexOf("-");
|
|
2042
2069
|
if (separator <= 0 || separator === key.length - 1) return null;
|
|
@@ -2581,6 +2608,124 @@ var BLOCKED_SETTINGS_MUTATE_PATHS = /* @__PURE__ */ new Set([
|
|
|
2581
2608
|
"/v1/settings/verify-action-code",
|
|
2582
2609
|
"/v1/settings/user/disable-2fa"
|
|
2583
2610
|
]);
|
|
2611
|
+
function applyUnifiedDiffForEmbedVersion(content, patch) {
|
|
2612
|
+
const contentLines = content.split("\n");
|
|
2613
|
+
const lines = patch.split("\n");
|
|
2614
|
+
const hunks = [];
|
|
2615
|
+
let current = null;
|
|
2616
|
+
for (const line of lines) {
|
|
2617
|
+
const match = /^@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/.exec(line);
|
|
2618
|
+
if (match) {
|
|
2619
|
+
if (current) hunks.push(current);
|
|
2620
|
+
current = { start: Number(match[1]) - 1, oldLines: [], newLines: [] };
|
|
2621
|
+
continue;
|
|
2622
|
+
}
|
|
2623
|
+
if (!current) continue;
|
|
2624
|
+
if (line.startsWith(" ")) {
|
|
2625
|
+
current.oldLines.push(line.slice(1));
|
|
2626
|
+
current.newLines.push(line.slice(1));
|
|
2627
|
+
} else if (line.startsWith("-")) {
|
|
2628
|
+
current.oldLines.push(line.slice(1));
|
|
2629
|
+
} else if (line.startsWith("+")) {
|
|
2630
|
+
current.newLines.push(line.slice(1));
|
|
2631
|
+
}
|
|
2632
|
+
}
|
|
2633
|
+
if (current) hunks.push(current);
|
|
2634
|
+
for (const hunk of hunks.sort((a, b) => b.start - a.start)) {
|
|
2635
|
+
const actual = contentLines.slice(hunk.start, hunk.start + hunk.oldLines.length);
|
|
2636
|
+
if (actual.join("\n") !== hunk.oldLines.join("\n")) {
|
|
2637
|
+
throw new Error("Version patch context does not match local content");
|
|
2638
|
+
}
|
|
2639
|
+
contentLines.splice(hunk.start, hunk.oldLines.length, ...hunk.newLines);
|
|
2640
|
+
}
|
|
2641
|
+
return contentLines.join("\n");
|
|
2642
|
+
}
|
|
2643
|
+
function buildUnifiedDiffForEmbedRestore(currentContent, restoredContent, currentVersion, newVersion) {
|
|
2644
|
+
const currentLines = currentContent.split("\n");
|
|
2645
|
+
const restoredLines = restoredContent.split("\n");
|
|
2646
|
+
return [
|
|
2647
|
+
`--- v${currentVersion}`,
|
|
2648
|
+
`+++ v${newVersion}`,
|
|
2649
|
+
`@@ -1,${Math.max(1, currentLines.length)} +1,${Math.max(1, restoredLines.length)} @@`,
|
|
2650
|
+
...currentLines.map((line) => `-${line}`),
|
|
2651
|
+
...restoredLines.map((line) => `+${line}`)
|
|
2652
|
+
].join("\n");
|
|
2653
|
+
}
|
|
2654
|
+
function parseEmbedContentObject(rawContent) {
|
|
2655
|
+
try {
|
|
2656
|
+
return JSON.parse(rawContent);
|
|
2657
|
+
} catch {
|
|
2658
|
+
return parseYamlLikeContent(rawContent);
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
async function encodeEmbedContentObject(content) {
|
|
2662
|
+
try {
|
|
2663
|
+
const { encode } = await import("@toon-format/toon");
|
|
2664
|
+
return encode(content);
|
|
2665
|
+
} catch {
|
|
2666
|
+
return JSON.stringify(content);
|
|
2667
|
+
}
|
|
2668
|
+
}
|
|
2669
|
+
function extractVersionedEmbedContent(content) {
|
|
2670
|
+
if (typeof content.receiver === "string" || typeof content.subject === "string") {
|
|
2671
|
+
return [
|
|
2672
|
+
`To: ${typeof content.receiver === "string" ? content.receiver : ""}`,
|
|
2673
|
+
`Subject: ${typeof content.subject === "string" ? content.subject : ""}`,
|
|
2674
|
+
"",
|
|
2675
|
+
typeof content.content === "string" ? content.content : "",
|
|
2676
|
+
typeof content.footer === "string" ? content.footer : ""
|
|
2677
|
+
].join("\n").trim();
|
|
2678
|
+
}
|
|
2679
|
+
if (typeof content.remotion_source === "string") return content.remotion_source;
|
|
2680
|
+
if (typeof content.code === "string") return content.code;
|
|
2681
|
+
if (typeof content.html === "string") return content.html;
|
|
2682
|
+
if (typeof content.table === "string") return content.table;
|
|
2683
|
+
if (content.docx_model) return JSON.stringify(content.docx_model, null, 2);
|
|
2684
|
+
if (typeof content.content === "string") return content.content;
|
|
2685
|
+
throw new Error("Unsupported embed content shape for version restore.");
|
|
2686
|
+
}
|
|
2687
|
+
function buildRestoredEmbedContentObject(current, restoredContent, newVersion) {
|
|
2688
|
+
const restored = { ...current, version_number: newVersion };
|
|
2689
|
+
if (typeof current.receiver === "string" || typeof current.subject === "string") {
|
|
2690
|
+
return { ...restored, ...parseMailVersionContent(restoredContent) };
|
|
2691
|
+
}
|
|
2692
|
+
if (typeof current.remotion_source === "string") {
|
|
2693
|
+
return { ...restored, remotion_source: restoredContent, current_source_version: newVersion };
|
|
2694
|
+
}
|
|
2695
|
+
if (typeof current.code === "string") return { ...restored, code: restoredContent };
|
|
2696
|
+
if (typeof current.html === "string") return { ...restored, html: restoredContent };
|
|
2697
|
+
if (typeof current.table === "string") return { ...restored, table: restoredContent };
|
|
2698
|
+
if (current.docx_model) {
|
|
2699
|
+
try {
|
|
2700
|
+
return { ...restored, docx_model: JSON.parse(restoredContent) };
|
|
2701
|
+
} catch {
|
|
2702
|
+
return { ...restored, content: restoredContent };
|
|
2703
|
+
}
|
|
2704
|
+
}
|
|
2705
|
+
if (typeof current.content === "string") return { ...restored, content: restoredContent };
|
|
2706
|
+
throw new Error("Unsupported embed content shape for version restore.");
|
|
2707
|
+
}
|
|
2708
|
+
function parseMailVersionContent(versionContent) {
|
|
2709
|
+
const lines = versionContent.split("\n");
|
|
2710
|
+
let receiver = "";
|
|
2711
|
+
let subject = "";
|
|
2712
|
+
const bodyLines = [];
|
|
2713
|
+
for (const line of lines) {
|
|
2714
|
+
if (line.toLowerCase().startsWith("to:")) {
|
|
2715
|
+
receiver = line.slice(3).trim();
|
|
2716
|
+
} else if (line.toLowerCase().startsWith("subject:")) {
|
|
2717
|
+
subject = line.slice(8).trim();
|
|
2718
|
+
} else {
|
|
2719
|
+
bodyLines.push(line);
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
return {
|
|
2723
|
+
receiver,
|
|
2724
|
+
subject,
|
|
2725
|
+
content: bodyLines.join("\n").trim(),
|
|
2726
|
+
footer: ""
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2584
2729
|
var OpenMatesClient = class _OpenMatesClient {
|
|
2585
2730
|
apiUrl;
|
|
2586
2731
|
session;
|
|
@@ -2600,6 +2745,30 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2600
2745
|
hasSession() {
|
|
2601
2746
|
return this.session !== null;
|
|
2602
2747
|
}
|
|
2748
|
+
async createTurnTokenRefs(params) {
|
|
2749
|
+
if (params.refs.length === 0) return [];
|
|
2750
|
+
const response = await this.http.post(
|
|
2751
|
+
"/v1/token-broker/turn-token-refs",
|
|
2752
|
+
buildTurnTokenRefsRequestPayload(params),
|
|
2753
|
+
this.getCliRequestHeaders()
|
|
2754
|
+
);
|
|
2755
|
+
if (!response.ok || !Array.isArray(response.data.refs)) {
|
|
2756
|
+
throw new Error(`Failed to create connected-account token refs (HTTP ${response.status})`);
|
|
2757
|
+
}
|
|
2758
|
+
return response.data.refs.map((ref) => {
|
|
2759
|
+
const input = params.refs.find(
|
|
2760
|
+
(item) => item.connected_account_id === ref.connected_account_id && item.app_id === ref.app_id
|
|
2761
|
+
);
|
|
2762
|
+
return {
|
|
2763
|
+
connected_account_id: ref.connected_account_id,
|
|
2764
|
+
app_id: ref.app_id,
|
|
2765
|
+
turn_token_ref: ref.turn_token_ref,
|
|
2766
|
+
expires_at: ref.expires_at,
|
|
2767
|
+
allowed_actions: input?.allowed_actions ?? [],
|
|
2768
|
+
action_scope: input?.action_scope
|
|
2769
|
+
};
|
|
2770
|
+
});
|
|
2771
|
+
}
|
|
2603
2772
|
// -------------------------------------------------------------------------
|
|
2604
2773
|
// Auth
|
|
2605
2774
|
// -------------------------------------------------------------------------
|
|
@@ -3362,6 +3531,15 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3362
3531
|
const createdAt = Math.floor(Date.now() / 1e3);
|
|
3363
3532
|
const isNewChat = !params.chatId;
|
|
3364
3533
|
ws.send("set_active_chat", { chat_id: chatId });
|
|
3534
|
+
const connectedAccountDirectory = buildConnectedAccountDirectoryPayload(
|
|
3535
|
+
params.connectedAccountDirectory
|
|
3536
|
+
);
|
|
3537
|
+
const connectedAccountTokenRefs = params.connectedAccountTokenRefInputs?.length ? await this.createTurnTokenRefs({
|
|
3538
|
+
chatId,
|
|
3539
|
+
messageId,
|
|
3540
|
+
refs: params.connectedAccountTokenRefInputs
|
|
3541
|
+
}) : [];
|
|
3542
|
+
assertNoConnectedAccountSecretLeak(connectedAccountTokenRefs);
|
|
3365
3543
|
const messagePayload = {
|
|
3366
3544
|
chat_id: chatId,
|
|
3367
3545
|
is_incognito: Boolean(params.incognito),
|
|
@@ -3379,6 +3557,12 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3379
3557
|
if (memoryMetadataKeys.length > 0) {
|
|
3380
3558
|
messagePayload.app_settings_memories_metadata = memoryMetadataKeys;
|
|
3381
3559
|
}
|
|
3560
|
+
if (connectedAccountDirectory) {
|
|
3561
|
+
messagePayload.connected_account_directory = connectedAccountDirectory;
|
|
3562
|
+
}
|
|
3563
|
+
if (connectedAccountTokenRefs.length > 0) {
|
|
3564
|
+
messagePayload.connected_account_token_refs = connectedAccountTokenRefs;
|
|
3565
|
+
}
|
|
3382
3566
|
let chatKeyBytes = null;
|
|
3383
3567
|
let encryptedChatKey = null;
|
|
3384
3568
|
let baselineMessagesV = 0;
|
|
@@ -3437,7 +3621,16 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3437
3621
|
if (encryptedEmbeds.length > 0) {
|
|
3438
3622
|
messagePayload.encrypted_embeds = encryptedEmbeds;
|
|
3439
3623
|
}
|
|
3440
|
-
ws.
|
|
3624
|
+
const confirmed = ws.waitForMessage(
|
|
3625
|
+
"chat_message_confirmed",
|
|
3626
|
+
(payload) => {
|
|
3627
|
+
const eventPayload = payload;
|
|
3628
|
+
return eventPayload.chat_id === chatId && eventPayload.message_id === messageId;
|
|
3629
|
+
},
|
|
3630
|
+
2e4
|
|
3631
|
+
);
|
|
3632
|
+
await ws.sendAsync("chat_message_added", messagePayload);
|
|
3633
|
+
await confirmed;
|
|
3441
3634
|
if (!params.incognito && chatKeyBytes) {
|
|
3442
3635
|
const encryptedContent = await encryptWithAesGcmCombined(
|
|
3443
3636
|
params.message,
|
|
@@ -3801,6 +3994,22 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
3801
3994
|
created_at: createdAt,
|
|
3802
3995
|
updated_at: updatedAt
|
|
3803
3996
|
});
|
|
3997
|
+
if (Array.isArray(embed.version_history_rows)) {
|
|
3998
|
+
for (const row of embed.version_history_rows) {
|
|
3999
|
+
if (!row.embed_id || typeof row.version_number !== "number") continue;
|
|
4000
|
+
const encryptedSnapshot = typeof row.snapshot === "string" ? await encryptWithAesGcmCombined(row.snapshot, embedKey) : void 0;
|
|
4001
|
+
const encryptedPatch = typeof row.patch === "string" ? await encryptWithAesGcmCombined(row.patch, embedKey) : void 0;
|
|
4002
|
+
if (!encryptedSnapshot && !encryptedPatch) continue;
|
|
4003
|
+
await params.ws.sendAsync("store_embed_diff", {
|
|
4004
|
+
embed_id: row.embed_id,
|
|
4005
|
+
version_number: row.version_number,
|
|
4006
|
+
encrypted_snapshot: encryptedSnapshot ?? null,
|
|
4007
|
+
encrypted_patch: encryptedPatch ?? null,
|
|
4008
|
+
hashed_user_id: hashedUserId,
|
|
4009
|
+
created_at: normalizeUnixSeconds(row.created_at, now)
|
|
4010
|
+
});
|
|
4011
|
+
}
|
|
4012
|
+
}
|
|
3804
4013
|
if (!isChild) {
|
|
3805
4014
|
const keys = [
|
|
3806
4015
|
{
|
|
@@ -4946,6 +5155,188 @@ Required: ${schema.required.join(", ")}`
|
|
|
4946
5155
|
const origin = deriveWebOrigin(session.apiUrl);
|
|
4947
5156
|
return buildEmbedShareUrl(origin, embedId, blob);
|
|
4948
5157
|
}
|
|
5158
|
+
async listEmbedVersions(embedIdOrShort) {
|
|
5159
|
+
const embedId = await this.resolveEmbedId(embedIdOrShort);
|
|
5160
|
+
const response = await this.http.get(
|
|
5161
|
+
`/v1/embeds/${encodeURIComponent(embedId)}/versions`,
|
|
5162
|
+
this.getCliRequestHeaders()
|
|
5163
|
+
);
|
|
5164
|
+
if (!response.ok || !response.data) {
|
|
5165
|
+
throw new Error(this.formatEmbedVersionError(response.data, `Failed to list embed versions (HTTP ${response.status})`));
|
|
5166
|
+
}
|
|
5167
|
+
return response.data;
|
|
5168
|
+
}
|
|
5169
|
+
async getEmbedVersion(embedIdOrShort, version) {
|
|
5170
|
+
const embedId = await this.resolveEmbedId(embedIdOrShort);
|
|
5171
|
+
const response = await this.http.get(
|
|
5172
|
+
`/v1/embeds/${encodeURIComponent(embedId)}/versions/${version}`,
|
|
5173
|
+
this.getCliRequestHeaders()
|
|
5174
|
+
);
|
|
5175
|
+
if (!response.ok || !response.data) {
|
|
5176
|
+
throw new Error(this.formatEmbedVersionError(response.data, `Failed to load embed version ${version} (HTTP ${response.status})`));
|
|
5177
|
+
}
|
|
5178
|
+
if (typeof response.data.content === "string" || !Array.isArray(response.data.rows)) {
|
|
5179
|
+
return response.data;
|
|
5180
|
+
}
|
|
5181
|
+
return {
|
|
5182
|
+
...response.data,
|
|
5183
|
+
content: await this.reconstructEncryptedEmbedVersion(embedId, response.data.rows)
|
|
5184
|
+
};
|
|
5185
|
+
}
|
|
5186
|
+
async restoreEmbedVersion(embedIdOrShort, version) {
|
|
5187
|
+
const embedId = await this.resolveEmbedId(embedIdOrShort);
|
|
5188
|
+
const cache = await this.ensureSynced();
|
|
5189
|
+
const masterKey = this.getMasterKeyBytes();
|
|
5190
|
+
const embed = cache.embeds.find(
|
|
5191
|
+
(entry) => String(entry.embed_id ?? entry.id ?? "") === embedId
|
|
5192
|
+
);
|
|
5193
|
+
if (!embed) {
|
|
5194
|
+
throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
|
|
5195
|
+
}
|
|
5196
|
+
const { createHash: createHash5 } = await import("crypto");
|
|
5197
|
+
const hashedEmbedId = createHash5("sha256").update(embedId).digest("hex");
|
|
5198
|
+
const embedKey = await this.resolveEmbedKey(
|
|
5199
|
+
cache,
|
|
5200
|
+
masterKey,
|
|
5201
|
+
embed,
|
|
5202
|
+
embedId,
|
|
5203
|
+
hashedEmbedId
|
|
5204
|
+
);
|
|
5205
|
+
if (!embedKey) {
|
|
5206
|
+
throw new Error("Could not resolve embed encryption key for version restore.");
|
|
5207
|
+
}
|
|
5208
|
+
const target = await this.getEmbedVersion(embedId, version);
|
|
5209
|
+
if (typeof target.content !== "string") {
|
|
5210
|
+
throw new Error("Embed version content was not available for restore.");
|
|
5211
|
+
}
|
|
5212
|
+
const encryptedCurrentContent = embed.encrypted_content;
|
|
5213
|
+
if (typeof encryptedCurrentContent !== "string") {
|
|
5214
|
+
throw new Error("Current embed content is not available for encrypted restore.");
|
|
5215
|
+
}
|
|
5216
|
+
const currentToon = await decryptWithAesGcmCombined(encryptedCurrentContent, embedKey);
|
|
5217
|
+
if (!currentToon) {
|
|
5218
|
+
throw new Error("Could not decrypt current embed content for restore.");
|
|
5219
|
+
}
|
|
5220
|
+
const currentObject = parseEmbedContentObject(currentToon);
|
|
5221
|
+
const currentVersion = typeof embed.version_number === "number" ? embed.version_number : target.current_version;
|
|
5222
|
+
if (version === currentVersion) {
|
|
5223
|
+
throw new Error("Selected version is already current.");
|
|
5224
|
+
}
|
|
5225
|
+
const currentContent = extractVersionedEmbedContent(currentObject);
|
|
5226
|
+
const newVersion = currentVersion + 1;
|
|
5227
|
+
const restoredObject = buildRestoredEmbedContentObject(currentObject, target.content, newVersion);
|
|
5228
|
+
const restoredToon = await encodeEmbedContentObject(restoredObject);
|
|
5229
|
+
const encryptedRestoredContent = await encryptWithAesGcmCombined(restoredToon, embedKey);
|
|
5230
|
+
const restorePatch = buildUnifiedDiffForEmbedRestore(
|
|
5231
|
+
currentContent,
|
|
5232
|
+
target.content,
|
|
5233
|
+
currentVersion,
|
|
5234
|
+
newVersion
|
|
5235
|
+
);
|
|
5236
|
+
const encryptedPatch = await encryptWithAesGcmCombined(restorePatch, embedKey);
|
|
5237
|
+
const contentHash = computeSHA256(target.content);
|
|
5238
|
+
const now = Math.floor(Date.now() / 1e3);
|
|
5239
|
+
const { ws } = await this.openWsClient();
|
|
5240
|
+
try {
|
|
5241
|
+
await ws.sendAsync("store_embed", {
|
|
5242
|
+
embed_id: embedId,
|
|
5243
|
+
encrypted_type: embed.encrypted_type,
|
|
5244
|
+
encrypted_content: encryptedRestoredContent,
|
|
5245
|
+
encrypted_text_preview: embed.encrypted_text_preview,
|
|
5246
|
+
status: embed.status || "finished",
|
|
5247
|
+
hashed_chat_id: embed.hashed_chat_id,
|
|
5248
|
+
hashed_message_id: embed.hashed_message_id,
|
|
5249
|
+
hashed_task_id: embed.hashed_task_id,
|
|
5250
|
+
hashed_user_id: embed.hashed_user_id,
|
|
5251
|
+
embed_ids: embed.embed_ids,
|
|
5252
|
+
parent_embed_id: embed.parent_embed_id,
|
|
5253
|
+
version_number: newVersion,
|
|
5254
|
+
file_path: embed.file_path,
|
|
5255
|
+
content_hash: contentHash,
|
|
5256
|
+
text_length_chars: target.content.length,
|
|
5257
|
+
is_private: embed.is_private ?? false,
|
|
5258
|
+
is_shared: embed.is_shared ?? false,
|
|
5259
|
+
created_at: normalizeUnixSeconds(embed.created_at, now),
|
|
5260
|
+
updated_at: now
|
|
5261
|
+
});
|
|
5262
|
+
await ws.sendAsync("store_embed_diff", {
|
|
5263
|
+
embed_id: embedId,
|
|
5264
|
+
version_number: newVersion,
|
|
5265
|
+
encrypted_snapshot: null,
|
|
5266
|
+
encrypted_patch: encryptedPatch,
|
|
5267
|
+
hashed_user_id: embed.hashed_user_id,
|
|
5268
|
+
created_at: now
|
|
5269
|
+
});
|
|
5270
|
+
} finally {
|
|
5271
|
+
ws.close();
|
|
5272
|
+
}
|
|
5273
|
+
clearSyncCache();
|
|
5274
|
+
return {
|
|
5275
|
+
embed_id: embedId,
|
|
5276
|
+
restored_from_version: version,
|
|
5277
|
+
version_number: newVersion,
|
|
5278
|
+
content: target.content,
|
|
5279
|
+
content_hash: contentHash
|
|
5280
|
+
};
|
|
5281
|
+
}
|
|
5282
|
+
async reconstructEncryptedEmbedVersion(embedId, rows) {
|
|
5283
|
+
const cache = await this.ensureSynced();
|
|
5284
|
+
const masterKey = this.getMasterKeyBytes();
|
|
5285
|
+
const embed = cache.embeds.find(
|
|
5286
|
+
(entry) => String(entry.embed_id ?? entry.id ?? "") === embedId
|
|
5287
|
+
);
|
|
5288
|
+
if (!embed) {
|
|
5289
|
+
throw new Error(`Embed '${embedId}' not found in local cache. Run 'openmates chats list' to sync first.`);
|
|
5290
|
+
}
|
|
5291
|
+
const { createHash: createHash5 } = await import("crypto");
|
|
5292
|
+
const hashedEmbedId = createHash5("sha256").update(embedId).digest("hex");
|
|
5293
|
+
const embedKey = await this.resolveEmbedKey(
|
|
5294
|
+
cache,
|
|
5295
|
+
masterKey,
|
|
5296
|
+
embed,
|
|
5297
|
+
embedId,
|
|
5298
|
+
hashedEmbedId
|
|
5299
|
+
);
|
|
5300
|
+
if (!embedKey) {
|
|
5301
|
+
throw new Error("Could not resolve embed encryption key for version history.");
|
|
5302
|
+
}
|
|
5303
|
+
const sortedRows = [...rows].sort((a, b) => a.version_number - b.version_number);
|
|
5304
|
+
let content = null;
|
|
5305
|
+
for (const row of sortedRows) {
|
|
5306
|
+
if (row.encrypted_snapshot) {
|
|
5307
|
+
content = await decryptWithAesGcmCombined(row.encrypted_snapshot, embedKey);
|
|
5308
|
+
continue;
|
|
5309
|
+
}
|
|
5310
|
+
if (row.encrypted_patch && content !== null) {
|
|
5311
|
+
const patch = await decryptWithAesGcmCombined(row.encrypted_patch, embedKey);
|
|
5312
|
+
content = applyUnifiedDiffForEmbedVersion(content, patch ?? "");
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
if (content === null) {
|
|
5316
|
+
throw new Error("Version history is missing the initial snapshot");
|
|
5317
|
+
}
|
|
5318
|
+
return content;
|
|
5319
|
+
}
|
|
5320
|
+
formatEmbedVersionError(data, fallback) {
|
|
5321
|
+
if (data && typeof data === "object") {
|
|
5322
|
+
const detail = data.detail;
|
|
5323
|
+
const message = data.message;
|
|
5324
|
+
if (typeof detail === "string" && detail.trim()) return detail;
|
|
5325
|
+
if (typeof message === "string" && message.trim()) return message;
|
|
5326
|
+
}
|
|
5327
|
+
return fallback;
|
|
5328
|
+
}
|
|
5329
|
+
async resolveEmbedId(embedIdOrShort) {
|
|
5330
|
+
if (embedIdOrShort.length >= 32) return embedIdOrShort;
|
|
5331
|
+
const cache = await this.ensureSynced();
|
|
5332
|
+
const embed = cache.embeds.find(
|
|
5333
|
+
(entry) => String(entry.embed_id ?? "").startsWith(embedIdOrShort) || String(entry.id ?? "").startsWith(embedIdOrShort)
|
|
5334
|
+
);
|
|
5335
|
+
if (!embed) {
|
|
5336
|
+
throw new Error(`Embed '${embedIdOrShort}' not found in local cache. Run 'openmates chats list' to sync first.`);
|
|
5337
|
+
}
|
|
5338
|
+
return String(embed.embed_id ?? embed.id ?? "");
|
|
5339
|
+
}
|
|
4949
5340
|
// ── Mention context builder ─────────────────────────────────────────
|
|
4950
5341
|
/**
|
|
4951
5342
|
* Build the context needed for CLI mention resolution.
|
|
@@ -5084,7 +5475,7 @@ Required: ${schema.required.join(", ")}`
|
|
|
5084
5475
|
async refreshWsToken() {
|
|
5085
5476
|
const session = this.requireSession();
|
|
5086
5477
|
try {
|
|
5087
|
-
const res = await this.http.post("/v1/auth/session", {}, this.getCliRequestHeaders());
|
|
5478
|
+
const res = await this.http.post("/v1/auth/session", { session_id: session.sessionId }, this.getCliRequestHeaders());
|
|
5088
5479
|
if (res.ok && res.data.ws_token) {
|
|
5089
5480
|
session.wsToken = res.data.ws_token;
|
|
5090
5481
|
}
|
|
@@ -5427,7 +5818,7 @@ function printLogo() {
|
|
|
5427
5818
|
|
|
5428
5819
|
// src/cli.ts
|
|
5429
5820
|
import { createInterface as createInterface3 } from "readline/promises";
|
|
5430
|
-
import { realpathSync } from "fs";
|
|
5821
|
+
import { realpathSync, writeFileSync as writeFileSync4 } from "fs";
|
|
5431
5822
|
import { fileURLToPath } from "url";
|
|
5432
5823
|
import { basename as basename3, dirname } from "path";
|
|
5433
5824
|
import WebSocket2 from "ws";
|
|
@@ -22361,6 +22752,111 @@ var searchParentPreviewStressTestChat = {
|
|
|
22361
22752
|
}
|
|
22362
22753
|
};
|
|
22363
22754
|
|
|
22755
|
+
// ../ui/src/demo_chats/data/example_chats/rostock-heavy-rain-radar.ts
|
|
22756
|
+
var rostockHeavyRainRadarChat = {
|
|
22757
|
+
chat_id: "example-rostock-heavy-rain-radar",
|
|
22758
|
+
slug: "rostock-heavy-rain-radar",
|
|
22759
|
+
title: "example_chats.rostock_heavy_rain_radar.title",
|
|
22760
|
+
summary: "example_chats.rostock_heavy_rain_radar.summary",
|
|
22761
|
+
icon: "weather",
|
|
22762
|
+
category: "general_knowledge",
|
|
22763
|
+
keywords: ["Rostock rain radar", "DWD weather", "heavy rain", "weather app", "rain map"],
|
|
22764
|
+
follow_up_suggestions: [],
|
|
22765
|
+
messages: [
|
|
22766
|
+
{
|
|
22767
|
+
"id": "cd0dbe83-29cf-476e-b8e7-856ac0c529be",
|
|
22768
|
+
"role": "user",
|
|
22769
|
+
"content": "example_chats.rostock_heavy_rain_radar.message_1",
|
|
22770
|
+
"created_at": 1781465378
|
|
22771
|
+
},
|
|
22772
|
+
{
|
|
22773
|
+
"id": "6c84c422-a71e-47b8-979d-6e2e9e91aad9",
|
|
22774
|
+
"role": "assistant",
|
|
22775
|
+
"content": "example_chats.rostock_heavy_rain_radar.message_2",
|
|
22776
|
+
"created_at": 1781465401,
|
|
22777
|
+
"category": "general_knowledge",
|
|
22778
|
+
"model_name": "Gemini 3 Flash"
|
|
22779
|
+
}
|
|
22780
|
+
],
|
|
22781
|
+
embeds: [
|
|
22782
|
+
{
|
|
22783
|
+
"embed_id": "deb8e4c2-e07c-49c3-ae5c-300a21859563",
|
|
22784
|
+
"type": "app_skill_use",
|
|
22785
|
+
"content": "app_id: weather\nskill_id: rain_radar\nstatus: finished\nembed_id: deb8e4c2-e07c-49c3-ae5c-300a21859563\nquery: Rostock rain radar\nprovider: Deutscher Wetterdienst (DWD) via Bright Sky\nlocation:\n name: Rostock\n country: Germany\n country_code: DE\n admin1: Mecklenburg-Vorpommern\n latitude: 54.0887\n longitude: 12.14049\n timezone: Europe/Berlin\ncoverage:\n status: available\n radius_km: 5\nsummary:\n rain_expected: true\n in_10_min: Heavy rain visible near Rostock.\n next_2_hours: Heavy rain appears in the radar timeline near Rostock.\n peak_intensity: heavy\n preview_frame_id: frame-15\ntimeline[25]{frame_id,timestamp,kind,label,rain_at_location_mm_5min,max_intensity,rain_area_pct}:\n frame-0,2026-06-14T20:25:00+02:00,past,-65 min,0,heavy,12.4\n frame-1,2026-06-14T20:30:00+02:00,past,-60 min,0,heavy,38.8\n frame-2,2026-06-14T20:35:00+02:00,past,-55 min,20.48,heavy,82.6\n frame-3,2026-06-14T20:40:00+02:00,past,-50 min,33.28,heavy,100\n frame-4,2026-06-14T20:45:00+02:00,past,-45 min,115.2,heavy,100\n frame-5,2026-06-14T20:50:00+02:00,past,-40 min,89.6,heavy,100\n frame-6,2026-06-14T20:55:00+02:00,past,-35 min,76.8,heavy,100\n frame-7,2026-06-14T21:00:00+02:00,past,-30 min,102.4,heavy,100\n frame-8,2026-06-14T21:05:00+02:00,past,-25 min,94.72,heavy,100\n frame-9,2026-06-14T21:10:00+02:00,past,-20 min,66.56,heavy,100\n frame-10,2026-06-14T21:15:00+02:00,past,-15 min,64,heavy,100\n frame-11,2026-06-14T21:20:00+02:00,past,-10 min,120.32,heavy,100\n frame-12,2026-06-14T21:25:00+02:00,past,-5 min,130.56,heavy,100\n frame-13,2026-06-14T21:30:00+02:00,forecast,now,58.88,heavy,100\n frame-14,2026-06-14T21:35:00+02:00,forecast,+5 min,87.04,heavy,100\n frame-15,2026-06-14T21:40:00+02:00,forecast,+10 min,87.04,heavy,100\n frame-16,2026-06-14T21:45:00+02:00,forecast,+15 min,61.44,heavy,100\n frame-17,2026-06-14T21:50:00+02:00,forecast,+20 min,92.16,heavy,100\n frame-18,2026-06-14T21:55:00+02:00,forecast,+25 min,94.72,heavy,100\n frame-19,2026-06-14T22:00:00+02:00,forecast,+30 min,99.84,heavy,100\n frame-20,2026-06-14T22:05:00+02:00,forecast,+35 min,140.8,heavy,100\n frame-21,2026-06-14T22:10:00+02:00,forecast,+40 min,107.52,heavy,100\n frame-22,2026-06-14T22:15:00+02:00,forecast,+45 min,145.92,heavy,100\n frame-23,2026-06-14T22:20:00+02:00,forecast,+50 min,120.32,heavy,100\n frame-24,2026-06-14T22:25:00+02:00,forecast,+55 min,151.04,heavy,100",
|
|
22786
|
+
"parent_embed_id": null,
|
|
22787
|
+
"embed_ids": null
|
|
22788
|
+
}
|
|
22789
|
+
],
|
|
22790
|
+
metadata: {
|
|
22791
|
+
featured: true,
|
|
22792
|
+
order: 101,
|
|
22793
|
+
app_skill_examples: ["weather.rain_radar"]
|
|
22794
|
+
}
|
|
22795
|
+
};
|
|
22796
|
+
|
|
22797
|
+
// ../ui/src/demo_chats/data/example_chats/classic-car-reverse-image-search.ts
|
|
22798
|
+
var classicCarReverseImageSearchChat = {
|
|
22799
|
+
chat_id: "example-classic-car-reverse-image",
|
|
22800
|
+
slug: "classic-car-reverse-image-search",
|
|
22801
|
+
title: "example_chats.classic_car_reverse_image_search.title",
|
|
22802
|
+
summary: "example_chats.classic_car_reverse_image_search.summary",
|
|
22803
|
+
icon: "search",
|
|
22804
|
+
category: "general_knowledge",
|
|
22805
|
+
keywords: ["reverse image search", "classic cars", "Mercedes-Benz 300 SL", "car identification", "Google Lens"],
|
|
22806
|
+
follow_up_suggestions: [],
|
|
22807
|
+
messages: [
|
|
22808
|
+
{
|
|
22809
|
+
"id": "0cbd9872-31f9-483a-9159-3361764e7982",
|
|
22810
|
+
"role": "user",
|
|
22811
|
+
"content": "example_chats.classic_car_reverse_image_search.message_1",
|
|
22812
|
+
"created_at": 1781525084
|
|
22813
|
+
},
|
|
22814
|
+
{
|
|
22815
|
+
"id": "c2dd4e8f-6026-4540-9355-13d02d94018c",
|
|
22816
|
+
"role": "assistant",
|
|
22817
|
+
"content": "example_chats.classic_car_reverse_image_search.message_2",
|
|
22818
|
+
"created_at": 1781525128,
|
|
22819
|
+
"category": "general_knowledge",
|
|
22820
|
+
"model_name": "Gemini 3 Flash"
|
|
22821
|
+
}
|
|
22822
|
+
],
|
|
22823
|
+
embeds: [
|
|
22824
|
+
{
|
|
22825
|
+
"embed_id": "d7f2f391-7c8f-44e7-beb4-66bd846dd0ae",
|
|
22826
|
+
"type": "app_skill_use",
|
|
22827
|
+
"content": "app_id: images\nskill_id: search\nresult_count: 0\nembed_ids[0]:\nstatus: finished\nembed_id: d7f2f391-7c8f-44e7-beb4-66bd846dd0ae\nquery: Mercedes-Benz 300 SL Gullwing\nprovider: Brave Search",
|
|
22828
|
+
"parent_embed_id": null,
|
|
22829
|
+
"embed_ids": []
|
|
22830
|
+
},
|
|
22831
|
+
{
|
|
22832
|
+
"embed_id": "45e9c346-341e-466e-aed1-67ee1412dce5",
|
|
22833
|
+
"type": "app_skill_use",
|
|
22834
|
+
"content": "app_id: web\nskill_id: search\nresult_count: 0\nembed_ids[0]:\nstatus: finished\nembed_id: 45e9c346-341e-466e-aed1-67ee1412dce5\nquery: Mercedes-Benz 300 SL Gullwing visual identification features\nprovider: Brave",
|
|
22835
|
+
"parent_embed_id": null,
|
|
22836
|
+
"embed_ids": []
|
|
22837
|
+
},
|
|
22838
|
+
{
|
|
22839
|
+
"embed_id": "228d2a03-63cb-483a-b495-45489e55bc10",
|
|
22840
|
+
"type": "app_skill_use",
|
|
22841
|
+
"content": "app_id: images\nskill_id: search\nresult_count: 0\nembed_ids[0]:\nstatus: finished\nembed_id: 228d2a03-63cb-483a-b495-45489e55bc10\nfile_path: mercedes-300-sl-gullwing-example-jpg-b41-baf4d7\nprovider: Brave Search",
|
|
22842
|
+
"parent_embed_id": null,
|
|
22843
|
+
"embed_ids": []
|
|
22844
|
+
},
|
|
22845
|
+
{
|
|
22846
|
+
"embed_id": "c512601d-508c-44ee-bdb9-b444c18ffe10",
|
|
22847
|
+
"type": "image",
|
|
22848
|
+
"content": "type: image\napp_id: images\nskill_id: upload\nstatus: finished\nfilename: mercedes-300-sl-gullwing-example.jpg\nsrc: https://upload.wikimedia.org/wikipedia/commons/thumb/3/31/Mercedes-Benz_300_SL_Gullwing.jpg/960px-Mercedes-Benz_300_SL_Gullwing.jpg\nembed_ref: mercedes-300-sl-gullwing-example-jpg-b41-baf4d7\ncontent_hash: 6620266404d276d92cf456ad5affb62ce35bf3fb50deca205fd0435d5145b017\nai_detection:\n ai_generated: 0.001\n provider: sightengine",
|
|
22849
|
+
"parent_embed_id": null,
|
|
22850
|
+
"embed_ids": null
|
|
22851
|
+
}
|
|
22852
|
+
],
|
|
22853
|
+
metadata: {
|
|
22854
|
+
featured: true,
|
|
22855
|
+
order: 102,
|
|
22856
|
+
app_skill_examples: ["images.search", "web.search"]
|
|
22857
|
+
}
|
|
22858
|
+
};
|
|
22859
|
+
|
|
22364
22860
|
// ../ui/src/demo_chats/exampleChatData.ts
|
|
22365
22861
|
var ALL_EXAMPLE_CHATS = [
|
|
22366
22862
|
giganticAirplanesChat,
|
|
@@ -22444,7 +22940,9 @@ var ALL_EXAMPLE_CHATS = [
|
|
|
22444
22940
|
productTeaserRemotionVideoChat,
|
|
22445
22941
|
dampedSineWavePlotChat,
|
|
22446
22942
|
berlinCentralStationMapLocationChat,
|
|
22447
|
-
launchReadinessChecklistDocChat
|
|
22943
|
+
launchReadinessChecklistDocChat,
|
|
22944
|
+
rostockHeavyRainRadarChat,
|
|
22945
|
+
classicCarReverseImageSearchChat
|
|
22448
22946
|
].sort((a, b) => a.metadata.order - b.metadata.order);
|
|
22449
22947
|
|
|
22450
22948
|
// ../ui/src/i18n/locales/en.json
|
|
@@ -25064,6 +25562,70 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
25064
25562
|
text: "Rejected memories request."
|
|
25065
25563
|
}
|
|
25066
25564
|
},
|
|
25565
|
+
connected_account_permissions: {
|
|
25566
|
+
title: {
|
|
25567
|
+
text: "Connected account approval"
|
|
25568
|
+
},
|
|
25569
|
+
question: {
|
|
25570
|
+
text: "Allow this chat to use your connected account for this {action} action?"
|
|
25571
|
+
},
|
|
25572
|
+
approve: {
|
|
25573
|
+
text: "Approve once"
|
|
25574
|
+
},
|
|
25575
|
+
loading: {
|
|
25576
|
+
text: "Approving..."
|
|
25577
|
+
}
|
|
25578
|
+
},
|
|
25579
|
+
connected_account_receipts: {
|
|
25580
|
+
completed: {
|
|
25581
|
+
text: "{action} completed."
|
|
25582
|
+
},
|
|
25583
|
+
undone: {
|
|
25584
|
+
text: "Action undone."
|
|
25585
|
+
},
|
|
25586
|
+
rejected: {
|
|
25587
|
+
text: "{action} rejected."
|
|
25588
|
+
},
|
|
25589
|
+
cancelled: {
|
|
25590
|
+
text: "{action} cancelled."
|
|
25591
|
+
},
|
|
25592
|
+
failed: {
|
|
25593
|
+
text: "{action} could not be completed."
|
|
25594
|
+
},
|
|
25595
|
+
undo: {
|
|
25596
|
+
text: "Undo"
|
|
25597
|
+
},
|
|
25598
|
+
undoing: {
|
|
25599
|
+
text: "Undoing..."
|
|
25600
|
+
},
|
|
25601
|
+
undo_started: {
|
|
25602
|
+
text: "Undo completed"
|
|
25603
|
+
},
|
|
25604
|
+
undo_failed: {
|
|
25605
|
+
text: "Could not undo this action."
|
|
25606
|
+
},
|
|
25607
|
+
undo_available: {
|
|
25608
|
+
text: "Undo is available."
|
|
25609
|
+
},
|
|
25610
|
+
action_read: {
|
|
25611
|
+
text: "Calendar read"
|
|
25612
|
+
},
|
|
25613
|
+
action_write: {
|
|
25614
|
+
text: "Calendar write"
|
|
25615
|
+
},
|
|
25616
|
+
action_update: {
|
|
25617
|
+
text: "Calendar update"
|
|
25618
|
+
},
|
|
25619
|
+
action_delete: {
|
|
25620
|
+
text: "Calendar delete"
|
|
25621
|
+
},
|
|
25622
|
+
action_undo: {
|
|
25623
|
+
text: "Calendar undo"
|
|
25624
|
+
},
|
|
25625
|
+
action_unknown: {
|
|
25626
|
+
text: "Connected account action"
|
|
25627
|
+
}
|
|
25628
|
+
},
|
|
25067
25629
|
report_bad_answer: {
|
|
25068
25630
|
button_text: {
|
|
25069
25631
|
text: "Report bad answer"
|
|
@@ -29067,6 +29629,20 @@ Only output the final Markdown table. Do NOT include explanations, notes, or any
|
|
|
29067
29629
|
text: '```json\n{"type":"app_skill_use","embed_id":"d7f21a46-8f13-4f14-82c4-d868ba62bb8a","app_id":"nutrition","skill_id":"search_recipes","query":"chickpea and spinach dinner","provider":"Edamam","status":"finished"}\n```\n\nHere are three delicious, protein-packed vegetarian chickpea and spinach dinner recipes that you can whip up in under 35 minutes.\n\n### 1. 10-Minute Chickpea and Spinach Curry\nThis is an incredibly fast [curry](wiki:Curry) that relies on aromatics and pantry staples for a rich, satisfying flavor in record time.\n\n[!](embed:thefieryvegetarian.com-3sr)\n[!](embed:vegancocotte.com-IZt)\n[!](embed:vegancocotte.com-rFn)\n\n* **Total Time:** 10 minutes\n* **Protein Content:** ~10.5g per serving\n* **Servings:** 4\n\n**Ingredients:**\n* 1 (15 oz) can [chickpeas](wiki:Chickpea) (drained and rinsed)\n* 1 (15 oz) can diced [tomatoes](wiki:Tomato)\n* 1 [onion](wiki:Onion), 4 [garlic](wiki:Garlic) cloves, 1 red chili, 1 tbsp ginger (all finely diced)\n* 3 tbsp curry paste & 2 tbsp [tomato paste](wiki:Tomato_paste)\n* 3 cups fresh [spinach](wiki:Spinach)\n* 1 tsp vegetable oil\n\n**Instructions:**\n1. **Saut\xE9 Aromatics:** Heat oil in a large pot. Add diced onion, garlic, ginger, and chili. Saut\xE9 for about 1 minute until fragrant.\n2. **Build Flavor:** Stir in the tomato paste and curry paste, saut\xE9ing for another minute to release the oils.\n3. **Simmer:** Pour in the canned tomatoes and chickpeas. Cover and simmer on medium-high heat for 3\u20135 minutes.\n4. **Wilt Spinach:** Stir in the fresh spinach leaves until they wilt (about 1\u20132 minutes). Serve immediately.\n\n---\n\n### 2. 15-Minute Skillet Chickpeas with Spinach and Tomatoes\nThis Mediterranean-style dish uses spices like [turmeric](wiki:Turmeric) and a finishing touch of lemon juice for a bright, healthy meal.\n\n[!](embed:runningonrealfood.com-R8H)\n[!](embed:americanhomecook.com-Alt)\n[!](embed:shortgirltallorder.com-QXv)\n\n* **Total Time:** 15 minutes\n* **Protein Content:** ~23.1g per serving\n* **Servings:** 2\n\n**Ingredients:**\n* 1 (15 oz) can chickpeas (drained and rinsed)\n* 1 (14.5 oz) can diced tomatoes\n* 5 oz baby spinach\n* 1 small onion (diced) & 4 garlic cloves (sliced)\n* \xBD tsp ground turmeric & \xBD tsp [red pepper flakes](wiki:Crushed_red_pepper)\n* 3 tbsp [olive oil](wiki:Olive_oil), lemon juice, and fresh herbs ([cilantro](wiki:Coriander) or [parsley](wiki:Parsley))\n\n**Instructions:**\n1. **Fry Aromatics:** Heat oil in a skillet over low heat. Cook onion and garlic until translucent and slightly browned (3\u20135 minutes).\n2. **Toast Spices:** Add turmeric and red pepper flakes. Toast for 1 minute, then add chickpeas with a drizzle of oil and salt/pepper. Sizzle for 5\u20137 minutes.\n3. **Combine:** Stir in the tomatoes and simmer until the liquid reduces slightly.\n4. **Finish:** Add spinach and stir until wilted. Off the heat, stir in lemon juice and top with fresh herbs and a dollop of [yogurt](wiki:Yogurt).\n\n---\n\n### 3. 20-Minute Middle Eastern Spinach and Chickpeas\nA simpler, cleaner version of the classic [tapas](wiki:Tapas) dish, focusing on the natural sweetness of saut\xE9ed onions and white beans.\n\n[!](embed:carolinescooking.com-Dy7)\n[!](embed:simpleveganblog.com-8dt)\n[!](embed:ourplantbasedworld.com-Krr)\n\n* **Total Time:** 20 minutes\n* **Protein Content:** ~8.7g per serving\n* **Servings:** 4\n\n**Ingredients:**\n* 3 cups low-sodium canned chickpeas (drained and rinsed)\n* 2 cups baby spinach\n* 2/3 cup diced tomatoes\n* \xBD large white onion (diced)\n* 2 tsp minced garlic\n* 1\xBD tbsp [extra-virgin olive oil](wiki:Olive_oil#Grades)\n\n**Instructions:**\n1. **Soften Onions:** Heat olive oil in a pan. Saut\xE9 the onion for 2\u20133 minutes.\n2. **Add Seasoning:** Stir in the garlic and saut\xE9 for another minute.\n3. **Cook Down:** Pour in the tomatoes and cook for 1 minute.\n4. **Simmer Beans:** Add the chickpeas and about 3\u20134 tablespoons of water (or [vegetable broth](wiki:Broth)). Cook for 10 minutes.\n5. **Fold Spinach:** Gently fold in the baby spinach until just wilted. Season to taste and serve.\n\n---\n\n**Next steps for your meal prep:**\n* Would you like to find some side dish suggestions, like a [basmati rice](wiki:Basmati) or [naan](wiki:Naan) recipe, to go with these?\n* Are you interested in seeing some high-protein vegan dessert ideas to round out your dinner?'
|
|
29068
29630
|
}
|
|
29069
29631
|
},
|
|
29632
|
+
classic_car_reverse_image_search: {
|
|
29633
|
+
title: {
|
|
29634
|
+
text: "Identify a Classic Car from a Photo"
|
|
29635
|
+
},
|
|
29636
|
+
summary: {
|
|
29637
|
+
text: "Use reverse image search to identify a Mercedes-Benz 300 SL Gullwing from an uploaded photo."
|
|
29638
|
+
},
|
|
29639
|
+
message_1: {
|
|
29640
|
+
text: "Use reverse image search on this uploaded classic car photo and identify the exact car model. Reply with the model name and one sentence explaining the visual clues.\n[!](embed:mercedes-300-sl-gullwing-example-jpg-b41-baf4d7)"
|
|
29641
|
+
},
|
|
29642
|
+
message_2: {
|
|
29643
|
+
text: '```json\n{"type": "app_skill_use", "embed_id": "228d2a03-63cb-483a-b495-45489e55bc10", "app_id": "images", "skill_id": "search", "provider": "Brave Search"}\n```\n\n```json\n{"type": "app_skill_use", "embed_id": "45e9c346-341e-466e-aed1-67ee1412dce5", "app_id": "web", "skill_id": "search", "query": "Mercedes-Benz 300 SL Gullwing visual identification features", "provider": "Brave"}\n```\n\n```json\n{"type": "app_skill_use", "embed_id": "d7f2f391-7c8f-44e7-beb4-66bd846dd0ae", "app_id": "images", "skill_id": "search", "query": "Mercedes-Benz 300 SL Gullwing", "provider": "Brave Search"}\n```\n\nThe car in the photo is a **[Mercedes-Benz 300 SL Gullwing](wiki:Mercedes-Benz_300_SL)**.\n\nThis legendary model is most easily identified by its signature upward-opening "gullwing" doors, the prominent horizontal chrome bars flanking the large star in the grille, and the distinctive aerodynamic "eyebrows" positioned over the wheel arches.\n\nBased on the current date of June 15, 2026, this vehicle remains one of the most coveted and valuable collector cars in history.\n\n**Would you like to know more about this car?**\n- I can look up the current [market value and auction history](/#settings/apps/web) for the 300 SL.\n- I can find [technical specifications](/#settings/apps/web) or the history of its development as a racing car.'
|
|
29644
|
+
}
|
|
29645
|
+
},
|
|
29070
29646
|
creativity_drawing_meetups_berlin: {
|
|
29071
29647
|
title: {
|
|
29072
29648
|
text: "Creativity Drawing Meetups Berlin"
|
|
@@ -30201,6 +30777,20 @@ Like [Denis Villeneuve](wiki:Denis_Villeneuve)\u2019s work, *Contact* avoids the
|
|
|
30201
30777
|
text: '```json\n{"type": "app_skill_use", "embed_id": "3031e3c2-e832-40fa-99e0-126fe7a5077f", "app_id": "news", "skill_id": "search", "query": "right-to-repair laws EU recent news June 2026", "provider": "Brave"}\n```\n\n```json\n{"type": "app_skill_use", "embed_id": "e7366974-55a3-4025-8a0b-6d40f600fa6a", "app_id": "news", "skill_id": "search", "query": "right-to-repair laws US recent news June 2026", "provider": "Brave"}\n```\n\n```json\n{"type": "app_skill_use", "embed_id": "af660c08-2d9e-4684-ad3c-deceafd8ab0c", "app_id": "web", "skill_id": "search", "query": "expert analysis right-to-repair laws 2026 EU US", "provider": "Brave"}\n```\n\n```json\n{"type": "app_skill_use", "embed_id": "e4146998-76de-4683-a010-ceffcdb53005", "app_id": "images", "skill_id": "search", "query": "right to repair electronics repairability label EU", "provider": "Brave Search"}\n```\n\nAs of June 2026, the "Right to Repair" movement has reached a critical implementation phase in both the [European Union](wiki:European_Union) and the [United States](wiki:United_States). While the EU is moving toward a unified continental mandate, the US continues to see a patchwork of state-level victories and intense lobbying battles.\n\n### Recent Developments in the European Union\n\nThe most significant recent event is the EU\'s unveiling of its **"Tech Sovereignty" package** on June 3, 2026. This initiative aims to reduce dependence on US Big Tech by fostering homegrown alternatives and strengthening consumer rights.\n\n* **Implementation Countdown:** The landmark [EU Right to Repair Directive](wiki:Right_to_repair) (Directive (EU) 2024/1799) is set to be fully enforceable across all member states by **July 31, 2026**. \n* **Digital Product Passports:** Starting July 19, 2026, the EU will launch the **Digital Product Passport (DPP)** registry. This will require products (starting with batteries and textiles) to carry a QR code that provides data on durability and repairability.\n* **Legal Warnings:** On June 4, 2026, the European Commission issued formal warnings to **20 member states** that missed the March 2026 deadline to transpose the "Green Transition" directive into national law. This directive is crucial for preventing [greenwashing](wiki:Greenwashing) and "premature [obsolescence](wiki:Obsolescence)."\n\n[!](embed:euronewsweek.co.uk-aq1)\n[!](embed:termopasty.com-0tB)\n[!](embed:ecomondo.com-9xC)\n\n### Recent Developments in the United States\n\nIn the US, the movement remains focused on the state level, as federal legislation remains stalled in [Congress](wiki:United_States_Congress).\n\n* **State-Level Expansion:** As of mid-2026, over **one-quarter of Americans** live in states with enforceable Right to Repair laws. This is expected to jump to **35% by fall 2026** as new laws in **Texas** and **Connecticut** take full effect.\n* **Corporate Lobbying:** Advocacy groups like the [Public Interest Research Group (PIRG)](wiki:Public_Interest_Research_Group) have reported continued lobbying efforts from companies like [Google](wiki:Google) and [Apple](wiki:Apple_Inc.) against "parts pairing" restrictions\u2014a practice where software locks replacement parts to a specific device.\n\n### What Experts are Saying\n\nLegal and technical experts are offering a mix of optimism and caution regarding these new laws:\n\n1. **The "Tech Liberation" Debate:** While EU officials have hailed June 3, 2026, as "Tech Liberation Day," experts from the *Computer & Communications Industry Association* argue that true independence from [Big Tech](wiki:Big_Tech) will take decades, noting that Europe still lacks a rival to [Nvidia](wiki:Nvidia) or [TSMC](wiki:TSMC).\n2. **Loopholes and Parts Pairing:** Experts at *Cybernews* and *Earth911* warn that while the laws mandate the *right* to repair, manufacturers still "hold the cards" through the high cost of original parts and [software locks](wiki:Digital_rights_management) (parts pairing). They argue that without stricter price controls on spare parts, repair may remain more expensive than replacement.\n3. **Warranty Incentives:** Legal analysts from *Fieldfisher* highlight a "hidden gem" in the EU law: if a consumer chooses repair over replacement during the warranty period, the **warranty is extended by 12 months**. This is viewed as a major incentive to shift consumer behavior away from a "throwaway culture."\n\n> [Manufacturers will be obliged to repair in scope products, even if those products were purchased by consumers before 31 July 2026.](embed:fieldfisher.com-e9q)\n\n### Summary Table: 2026 Milestones\n\n```json\n{"type": "sheet", "embed_id": "e4d7b998-1c4c-4680-9ef6-2a0f34ce0249"}\n```\n\n\nHow would you like to proceed? I can help you look into specific state laws in the US or provide more details on the Digital Product Passport requirements.\n\n* [Check US state-by-state status](/#settings/apps/web)\n* [Find a local repair shop](/#settings/apps/maps)\n---\n*\u26A0\uFE0F This is educational information only and not legal advice. Consult a lawyer for legal advice.*'
|
|
30202
30778
|
}
|
|
30203
30779
|
},
|
|
30780
|
+
rostock_heavy_rain_radar: {
|
|
30781
|
+
title: {
|
|
30782
|
+
text: "Rostock Heavy Rain Radar"
|
|
30783
|
+
},
|
|
30784
|
+
summary: {
|
|
30785
|
+
text: "A live weather chat that checks DWD rain radar for Rostock and shows heavy rain on the radar timeline."
|
|
30786
|
+
},
|
|
30787
|
+
message_1: {
|
|
30788
|
+
text: "Is it raining in Rostock right now? Show me the rain radar."
|
|
30789
|
+
},
|
|
30790
|
+
message_2: {
|
|
30791
|
+
text: '```json\n{"type": "app_skill_use", "embed_id": "deb8e4c2-e07c-49c3-ae5c-300a21859563", "app_id": "weather", "skill_id": "rain_radar", "query": "Rostock rain radar", "provider": "Deutscher Wetterdienst (DWD) via Bright Sky", "location": "Rostock"}\n```\n\nYes, it is currently raining heavily in [Rostock](wiki:Rostock). According to the latest data from the [Deutscher Wetterdienst](wiki:Deutscher_Wetterdienst), the city is experiencing significant precipitation, which is expected to continue for at least the next two hours.\n\nThe radar indicates a 100% rain area coverage in your vicinity, with intensities reaching peak levels. You might want to stay indoors for a while!\n\nDo you want to see the [detailed hourly forecast](/#settings/apps/weather) for the rest of the night?'
|
|
30792
|
+
}
|
|
30793
|
+
},
|
|
30204
30794
|
rust_vector_database_repos: {
|
|
30205
30795
|
title: {
|
|
30206
30796
|
text: "Rust Vector Database Repos"
|
|
@@ -34718,6 +35308,47 @@ As of mid-2026, the severe supply shocks from the 2024\u20132025 avian flu have
|
|
|
34718
35308
|
text: "Content type not found."
|
|
34719
35309
|
}
|
|
34720
35310
|
},
|
|
35311
|
+
connected_accounts: {
|
|
35312
|
+
title: {
|
|
35313
|
+
text: "Connected accounts"
|
|
35314
|
+
},
|
|
35315
|
+
description: {
|
|
35316
|
+
text: "Connect provider accounts so mates can ask for permission before using private app data."
|
|
35317
|
+
},
|
|
35318
|
+
google_calendar_title: {
|
|
35319
|
+
text: "Google Calendar"
|
|
35320
|
+
},
|
|
35321
|
+
loading: {
|
|
35322
|
+
text: "Checking connected accounts..."
|
|
35323
|
+
},
|
|
35324
|
+
connected_count: {
|
|
35325
|
+
text: "{count} connected"
|
|
35326
|
+
},
|
|
35327
|
+
not_connected: {
|
|
35328
|
+
text: "Not connected yet."
|
|
35329
|
+
},
|
|
35330
|
+
connect_button: {
|
|
35331
|
+
text: "Connect Google Calendar"
|
|
35332
|
+
},
|
|
35333
|
+
finalizing: {
|
|
35334
|
+
text: "Encrypting and saving your connected account..."
|
|
35335
|
+
},
|
|
35336
|
+
connected_success: {
|
|
35337
|
+
text: "Google Calendar connected. Your token was encrypted before saving."
|
|
35338
|
+
},
|
|
35339
|
+
sign_in_required: {
|
|
35340
|
+
text: "Sign in again before connecting Google Calendar."
|
|
35341
|
+
},
|
|
35342
|
+
load_error: {
|
|
35343
|
+
text: "Could not load connected accounts."
|
|
35344
|
+
},
|
|
35345
|
+
start_error: {
|
|
35346
|
+
text: "Could not start Google Calendar connection."
|
|
35347
|
+
},
|
|
35348
|
+
finalize_error: {
|
|
35349
|
+
text: "Could not finish connecting Google Calendar."
|
|
35350
|
+
}
|
|
35351
|
+
},
|
|
34721
35352
|
focus_modes: {
|
|
34722
35353
|
title: {
|
|
34723
35354
|
text: "Focus Modes"
|
|
@@ -41039,6 +41670,75 @@ async function handleEmbeds(client, subcommand, rest, flags) {
|
|
|
41039
41670
|
}
|
|
41040
41671
|
return;
|
|
41041
41672
|
}
|
|
41673
|
+
if (subcommand === "versions") {
|
|
41674
|
+
const action = rest[0];
|
|
41675
|
+
const embedId = rest[1];
|
|
41676
|
+
if (!action || !embedId || !["list", "show", "restore"].includes(action)) {
|
|
41677
|
+
console.error("Usage: openmates embeds versions <list|show|restore> <embed-id> [--version <n>]\n");
|
|
41678
|
+
printEmbedsHelp();
|
|
41679
|
+
process.exit(1);
|
|
41680
|
+
}
|
|
41681
|
+
if (action === "list") {
|
|
41682
|
+
const versions = await client.listEmbedVersions(embedId);
|
|
41683
|
+
if (flags.json === true) {
|
|
41684
|
+
printJson2(versions);
|
|
41685
|
+
} else {
|
|
41686
|
+
process.stdout.write(`
|
|
41687
|
+
\x1B[1mEmbed versions\x1B[0m ${versions.embed_id}
|
|
41688
|
+
`);
|
|
41689
|
+
for (const version2 of versions.versions) {
|
|
41690
|
+
const marker = version2.version_number === versions.current_version ? " (current)" : "";
|
|
41691
|
+
const date = new Date(version2.created_at * 1e3).toISOString();
|
|
41692
|
+
process.stdout.write(` v${version2.version_number}${marker} ${date}
|
|
41693
|
+
`);
|
|
41694
|
+
}
|
|
41695
|
+
if (versions.readonly) process.stdout.write("\x1B[2mRead-only shared history\x1B[0m\n");
|
|
41696
|
+
}
|
|
41697
|
+
return;
|
|
41698
|
+
}
|
|
41699
|
+
const version = typeof flags.version === "string" ? parseInt(flags.version, 10) : NaN;
|
|
41700
|
+
if (!Number.isFinite(version) || version <= 0) {
|
|
41701
|
+
console.error("Missing or invalid --version <n>.");
|
|
41702
|
+
process.exit(1);
|
|
41703
|
+
}
|
|
41704
|
+
if (action === "show") {
|
|
41705
|
+
const result = await client.getEmbedVersion(embedId, version);
|
|
41706
|
+
if (typeof result.content !== "string") {
|
|
41707
|
+
throw new Error("Embed version content was not available after local reconstruction.");
|
|
41708
|
+
}
|
|
41709
|
+
if (typeof flags.output === "string") {
|
|
41710
|
+
writeFileSync4(flags.output, result.content, "utf-8");
|
|
41711
|
+
if (flags.json === true) {
|
|
41712
|
+
printJson2({ ...result, output: flags.output });
|
|
41713
|
+
} else {
|
|
41714
|
+
process.stdout.write(`Wrote ${result.embed_id} v${result.version_number} to ${flags.output}
|
|
41715
|
+
`);
|
|
41716
|
+
}
|
|
41717
|
+
} else if (flags.json === true) {
|
|
41718
|
+
printJson2(result);
|
|
41719
|
+
} else {
|
|
41720
|
+
process.stdout.write(`
|
|
41721
|
+
\x1B[1m${result.embed_id} v${result.version_number}\x1B[0m
|
|
41722
|
+
`);
|
|
41723
|
+
process.stdout.write(`${result.content}
|
|
41724
|
+
`);
|
|
41725
|
+
}
|
|
41726
|
+
return;
|
|
41727
|
+
}
|
|
41728
|
+
if (flags.yes !== true) {
|
|
41729
|
+
await confirmOrExit(`Restore embed ${embedId} to version ${version}? This creates a new latest version. [y/N] `);
|
|
41730
|
+
}
|
|
41731
|
+
const restored = await client.restoreEmbedVersion(embedId, version);
|
|
41732
|
+
if (flags.json === true) {
|
|
41733
|
+
printJson2(restored);
|
|
41734
|
+
} else {
|
|
41735
|
+
process.stdout.write(
|
|
41736
|
+
`Restored v${restored.restored_from_version} as new v${restored.version_number} for ${restored.embed_id}.
|
|
41737
|
+
`
|
|
41738
|
+
);
|
|
41739
|
+
}
|
|
41740
|
+
return;
|
|
41741
|
+
}
|
|
41042
41742
|
console.error(`Unknown embeds subcommand '${subcommand}'.
|
|
41043
41743
|
`);
|
|
41044
41744
|
printEmbedsHelp();
|
|
@@ -44115,13 +44815,20 @@ function printEmbedsHelp() {
|
|
|
44115
44815
|
console.log(`Embeds commands:
|
|
44116
44816
|
openmates embeds show <embed-id> [--json]
|
|
44117
44817
|
openmates embeds share <embed-id> [--expires <seconds>] [--password <pwd>] [--json]
|
|
44818
|
+
openmates embeds versions list <embed-id> [--json]
|
|
44819
|
+
openmates embeds versions show <embed-id> --version <n> [--output <path>] [--json]
|
|
44820
|
+
openmates embeds versions restore <embed-id> --version <n> [--yes] [--json]
|
|
44118
44821
|
|
|
44119
44822
|
'show' displays the full decrypted content of an embed.
|
|
44823
|
+
The 'versions' commands list, inspect, and non-destructively restore history.
|
|
44120
44824
|
The embed ID can be the full UUID or just the first 8 characters.
|
|
44121
44825
|
Embed IDs are shown when viewing chat conversations (openmates chats show).
|
|
44122
44826
|
|
|
44123
44827
|
Examples:
|
|
44124
|
-
openmates embeds show a3f2b1c4
|
|
44828
|
+
openmates embeds show a3f2b1c4
|
|
44829
|
+
openmates embeds versions list a3f2b1c4
|
|
44830
|
+
openmates embeds versions show a3f2b1c4 --version 1
|
|
44831
|
+
openmates embeds versions restore a3f2b1c4 --version 1 --yes`);
|
|
44125
44832
|
}
|
|
44126
44833
|
function printInspirationsHelp() {
|
|
44127
44834
|
console.log(`Inspirations command:
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -226,6 +226,29 @@ interface SubChatApprovalRequest {
|
|
|
226
226
|
existingSubChats: number | null;
|
|
227
227
|
remainingSubChats: number | null;
|
|
228
228
|
}
|
|
229
|
+
interface ConnectedAccountDirectoryEntry {
|
|
230
|
+
connected_account_id: string;
|
|
231
|
+
app_id: string;
|
|
232
|
+
account_ref: string;
|
|
233
|
+
label: string;
|
|
234
|
+
capabilities: string[];
|
|
235
|
+
runtime_modes?: Record<string, string>;
|
|
236
|
+
}
|
|
237
|
+
interface ConnectedAccountTurnTokenRefInput {
|
|
238
|
+
connected_account_id: string;
|
|
239
|
+
app_id: string;
|
|
240
|
+
allowed_actions: string[];
|
|
241
|
+
refresh_token_envelope: Record<string, unknown>;
|
|
242
|
+
action_scope?: Record<string, unknown>;
|
|
243
|
+
}
|
|
244
|
+
interface ConnectedAccountTurnTokenRef {
|
|
245
|
+
connected_account_id: string;
|
|
246
|
+
app_id: string;
|
|
247
|
+
turn_token_ref: string;
|
|
248
|
+
allowed_actions: string[];
|
|
249
|
+
action_scope?: Record<string, unknown>;
|
|
250
|
+
expires_at: number;
|
|
251
|
+
}
|
|
229
252
|
/** A single field definition within a memory type schema. */
|
|
230
253
|
interface MemoryFieldDef {
|
|
231
254
|
type: string;
|
|
@@ -298,6 +321,35 @@ interface DecryptedEmbed {
|
|
|
298
321
|
skillId: string | null;
|
|
299
322
|
createdAt: number | null;
|
|
300
323
|
}
|
|
324
|
+
interface EmbedVersionMeta {
|
|
325
|
+
version_number: number;
|
|
326
|
+
created_at: number;
|
|
327
|
+
has_snapshot: boolean;
|
|
328
|
+
has_patch: boolean;
|
|
329
|
+
encrypted_snapshot?: string | null;
|
|
330
|
+
encrypted_patch?: string | null;
|
|
331
|
+
}
|
|
332
|
+
interface EmbedVersionsResponse {
|
|
333
|
+
embed_id: string;
|
|
334
|
+
current_version: number;
|
|
335
|
+
versions: EmbedVersionMeta[];
|
|
336
|
+
readonly: boolean;
|
|
337
|
+
}
|
|
338
|
+
interface EmbedVersionContentResponse {
|
|
339
|
+
embed_id: string;
|
|
340
|
+
version_number: number;
|
|
341
|
+
current_version: number;
|
|
342
|
+
content?: string;
|
|
343
|
+
rows?: EmbedVersionMeta[];
|
|
344
|
+
readonly: boolean;
|
|
345
|
+
}
|
|
346
|
+
interface EmbedVersionRestoreResponse {
|
|
347
|
+
embed_id: string;
|
|
348
|
+
restored_from_version: number;
|
|
349
|
+
version_number: number;
|
|
350
|
+
content: string;
|
|
351
|
+
content_hash: string;
|
|
352
|
+
}
|
|
301
353
|
/** Video metadata attached to a daily inspiration. */
|
|
302
354
|
interface DailyInspirationVideo {
|
|
303
355
|
youtube_id: string;
|
|
@@ -450,6 +502,11 @@ declare class OpenMatesClient {
|
|
|
450
502
|
constructor(options?: OpenMatesClientOptions);
|
|
451
503
|
static load(options?: OpenMatesClientOptions): OpenMatesClient;
|
|
452
504
|
hasSession(): boolean;
|
|
505
|
+
createTurnTokenRefs(params: {
|
|
506
|
+
chatId: string;
|
|
507
|
+
messageId: string;
|
|
508
|
+
refs: ConnectedAccountTurnTokenRefInput[];
|
|
509
|
+
}): Promise<ConnectedAccountTurnTokenRef[]>;
|
|
453
510
|
loginWithPairAuth(): Promise<void>;
|
|
454
511
|
whoAmI(): Promise<Record<string, unknown>>;
|
|
455
512
|
logout(): Promise<void>;
|
|
@@ -590,6 +647,10 @@ declare class OpenMatesClient {
|
|
|
590
647
|
encryptedEmbeds?: EncryptedEmbed[];
|
|
591
648
|
/** Prepared embeds to encrypt after the real chat/message IDs are known. */
|
|
592
649
|
preparedEmbeds?: PreparedEmbed[];
|
|
650
|
+
/** Redacted connected-account directory for AI-visible account selection. */
|
|
651
|
+
connectedAccountDirectory?: ConnectedAccountDirectoryEntry[];
|
|
652
|
+
/** Refresh-token envelopes to convert into short-lived token refs before send. */
|
|
653
|
+
connectedAccountTokenRefInputs?: ConnectedAccountTurnTokenRefInput[];
|
|
593
654
|
}): Promise<{
|
|
594
655
|
status: "completed" | "waiting_for_user";
|
|
595
656
|
chatId: string;
|
|
@@ -824,6 +885,12 @@ declare class OpenMatesClient {
|
|
|
824
885
|
* @returns Full share URL, e.g. https://openmates.org/share/embed/{id}#key={blob}
|
|
825
886
|
*/
|
|
826
887
|
createEmbedShareLink(embedIdOrShort: string, durationSeconds?: ShareDuration, password?: string): Promise<string>;
|
|
888
|
+
listEmbedVersions(embedIdOrShort: string): Promise<EmbedVersionsResponse>;
|
|
889
|
+
getEmbedVersion(embedIdOrShort: string, version: number): Promise<EmbedVersionContentResponse>;
|
|
890
|
+
restoreEmbedVersion(embedIdOrShort: string, version: number): Promise<EmbedVersionRestoreResponse>;
|
|
891
|
+
private reconstructEncryptedEmbedVersion;
|
|
892
|
+
private formatEmbedVersionError;
|
|
893
|
+
private resolveEmbedId;
|
|
827
894
|
/**
|
|
828
895
|
* Build the context needed for CLI mention resolution.
|
|
829
896
|
* Fetches apps (with skills, focus modes, memory categories) and
|
package/dist/index.js
CHANGED