openmates 0.15.0-alpha.39 → 0.15.0-alpha.40
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-N2TP5WZI.js → chunk-PAGWFAZH.js} +286 -242
- package/dist/cli.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -1
- package/package.json +2 -1
|
@@ -15122,6 +15122,249 @@ async function writeExportFile(path, data) {
|
|
|
15122
15122
|
await writeFile(path, data);
|
|
15123
15123
|
}
|
|
15124
15124
|
|
|
15125
|
+
// src/accountExportArchive.ts
|
|
15126
|
+
import JSZip from "jszip";
|
|
15127
|
+
var ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
15128
|
+
"access_token",
|
|
15129
|
+
"api_key",
|
|
15130
|
+
"backup_code_hash",
|
|
15131
|
+
"chat_key",
|
|
15132
|
+
"credential_secret",
|
|
15133
|
+
"device_key",
|
|
15134
|
+
"encrypted_master_key",
|
|
15135
|
+
"lookup_hash",
|
|
15136
|
+
"master_key",
|
|
15137
|
+
"password_hash",
|
|
15138
|
+
"private_key",
|
|
15139
|
+
"raw_key",
|
|
15140
|
+
"refresh_token",
|
|
15141
|
+
"share_key",
|
|
15142
|
+
"signing_secret",
|
|
15143
|
+
"token_hash",
|
|
15144
|
+
"totp_seed",
|
|
15145
|
+
"webhook_secret"
|
|
15146
|
+
]);
|
|
15147
|
+
var ACCOUNT_EXPORT_REDACTION_CATEGORIES = [
|
|
15148
|
+
"api_credentials",
|
|
15149
|
+
"authentication_tokens",
|
|
15150
|
+
"key_material",
|
|
15151
|
+
"password_and_recovery_hashes",
|
|
15152
|
+
"webhook_secrets"
|
|
15153
|
+
];
|
|
15154
|
+
var ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS = [
|
|
15155
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
15156
|
+
/(?:^|[^a-z0-9])sk-(?:api|proj|live|test)[-_a-z0-9]{6,}/i,
|
|
15157
|
+
/#key=[A-Za-z0-9_-]{8,}/
|
|
15158
|
+
];
|
|
15159
|
+
async function writeAccountExportArchive(bundle, flags = {}) {
|
|
15160
|
+
const { mkdir: mkdir2, writeFile: writeFile2 } = await import("fs/promises");
|
|
15161
|
+
const { join: join7, dirname: dirname6 } = await import("path");
|
|
15162
|
+
const exportId = safeArchiveSegment(String(bundle.export.export_id ?? `export-${Date.now()}`));
|
|
15163
|
+
const archiveFormat = flags.format === "directory" ? "directory" : "zip";
|
|
15164
|
+
const outputPath = typeof flags.output === "string" ? flags.output : join7(process.cwd(), archiveFormat === "zip" ? `openmates-account-export-${exportId}.zip` : `openmates-account-export-${exportId}`);
|
|
15165
|
+
const zip = archiveFormat === "zip" ? new JSZip() : null;
|
|
15166
|
+
const writtenFiles = [];
|
|
15167
|
+
async function writeArchiveText(relativePath, content) {
|
|
15168
|
+
assertAccountExportTextSafe(content, relativePath);
|
|
15169
|
+
if (zip) {
|
|
15170
|
+
zip.file(relativePath, content);
|
|
15171
|
+
writtenFiles.push(relativePath);
|
|
15172
|
+
return;
|
|
15173
|
+
}
|
|
15174
|
+
const stagingDir = outputPath;
|
|
15175
|
+
const fullPath = join7(stagingDir, relativePath);
|
|
15176
|
+
await mkdir2(dirname6(fullPath), { recursive: true });
|
|
15177
|
+
await writeFile2(fullPath, content, "utf-8");
|
|
15178
|
+
writtenFiles.push(relativePath);
|
|
15179
|
+
}
|
|
15180
|
+
await writeArchiveText("README.md", buildAccountExportReadme(bundle));
|
|
15181
|
+
await writeArchiveText("manifest.yml", serializeArchiveYaml(bundle.manifest));
|
|
15182
|
+
await writeArchiveText("manifest.json", `${JSON.stringify(bundle.manifest, null, 2)}
|
|
15183
|
+
`);
|
|
15184
|
+
await writeArchiveText("export-report.yml", serializeArchiveYaml(buildAccountExportReport(bundle)));
|
|
15185
|
+
const domainPayloads = collectAccountExportDomainPayloads(bundle.chunks);
|
|
15186
|
+
for (const [domain, payloads] of Object.entries(domainPayloads)) {
|
|
15187
|
+
const domainFile = payloads.length === 1 ? payloads[0] : { chunks: payloads };
|
|
15188
|
+
await writeArchiveText(`domains/${safeArchiveSegment(domain)}.json`, `${JSON.stringify(domainFile, null, 2)}
|
|
15189
|
+
`);
|
|
15190
|
+
}
|
|
15191
|
+
await writeAccountExportChatFiles(domainPayloads.chats ?? [], writeArchiveText);
|
|
15192
|
+
if (archiveFormat === "directory") return { output: outputPath, format: "directory", files: writtenFiles.length };
|
|
15193
|
+
await mkdir2(dirname6(outputPath), { recursive: true });
|
|
15194
|
+
const zipBuffer = await zip.generateAsync({ type: "nodebuffer", compression: "DEFLATE" });
|
|
15195
|
+
await writeFile2(outputPath, zipBuffer);
|
|
15196
|
+
return { output: outputPath, format: "zip", files: writtenFiles.length };
|
|
15197
|
+
}
|
|
15198
|
+
function assertAccountExportPayloadSafe(value, path = "$export") {
|
|
15199
|
+
if (value === null || value === void 0) return;
|
|
15200
|
+
if (typeof value === "string") {
|
|
15201
|
+
for (const pattern of ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS) {
|
|
15202
|
+
if (pattern.test(value)) throw new Error(`Account export contains forbidden secret-like value at ${path}`);
|
|
15203
|
+
}
|
|
15204
|
+
return;
|
|
15205
|
+
}
|
|
15206
|
+
if (typeof value !== "object") return;
|
|
15207
|
+
if (Array.isArray(value)) {
|
|
15208
|
+
value.forEach((item, index) => assertAccountExportPayloadSafe(item, `${path}[${index}]`));
|
|
15209
|
+
return;
|
|
15210
|
+
}
|
|
15211
|
+
for (const [key, child] of Object.entries(value)) {
|
|
15212
|
+
const normalizedKey = key.toLowerCase();
|
|
15213
|
+
if (ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES.has(normalizedKey)) {
|
|
15214
|
+
throw new Error(`Account export contains forbidden secret field '${key}' at ${path}`);
|
|
15215
|
+
}
|
|
15216
|
+
assertAccountExportPayloadSafe(child, `${path}.${key}`);
|
|
15217
|
+
}
|
|
15218
|
+
}
|
|
15219
|
+
function sanitizeAccountExportManifest(manifest) {
|
|
15220
|
+
const copy = JSON.parse(JSON.stringify(manifest));
|
|
15221
|
+
const report = copy.report;
|
|
15222
|
+
if (report && typeof report === "object" && !Array.isArray(report)) {
|
|
15223
|
+
const reportObject = report;
|
|
15224
|
+
if (Array.isArray(reportObject.redactions)) {
|
|
15225
|
+
reportObject.redactions = ACCOUNT_EXPORT_REDACTION_CATEGORIES;
|
|
15226
|
+
}
|
|
15227
|
+
}
|
|
15228
|
+
assertAccountExportPayloadSafe(copy, "$manifest");
|
|
15229
|
+
return copy;
|
|
15230
|
+
}
|
|
15231
|
+
function assertAccountExportTextSafe(content, relativePath) {
|
|
15232
|
+
for (const pattern of ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS) {
|
|
15233
|
+
if (pattern.test(content)) throw new Error(`Account export file ${relativePath} contains forbidden secret-like content`);
|
|
15234
|
+
}
|
|
15235
|
+
for (const field of ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES) {
|
|
15236
|
+
if (new RegExp(`"?${field}"?\\s*:`, "i").test(content)) {
|
|
15237
|
+
throw new Error(`Account export file ${relativePath} contains forbidden secret field '${field}'`);
|
|
15238
|
+
}
|
|
15239
|
+
}
|
|
15240
|
+
}
|
|
15241
|
+
function buildAccountExportReadme(bundle) {
|
|
15242
|
+
const selectedDomains = Array.isArray(bundle.manifest.selected_domains) ? bundle.manifest.selected_domains.map(String) : [];
|
|
15243
|
+
return [
|
|
15244
|
+
"# OpenMates Account Export",
|
|
15245
|
+
"",
|
|
15246
|
+
`Export ID: ${String(bundle.export.export_id ?? "unknown")}`,
|
|
15247
|
+
`Status: ${String(bundle.export.status ?? "unknown")}`,
|
|
15248
|
+
`Schema: ${String(bundle.manifest.schema_version ?? "account-export-v1")}`,
|
|
15249
|
+
`Exported at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
15250
|
+
"",
|
|
15251
|
+
"## Contents",
|
|
15252
|
+
"",
|
|
15253
|
+
"- `manifest.yml` records selected domains, filters, and domain status.",
|
|
15254
|
+
"- `export-report.yml` summarizes completion, failures, and redacted categories.",
|
|
15255
|
+
"- `domains/` contains one JSON file per exported domain.",
|
|
15256
|
+
"- `chats/` contains one Markdown and one YAML file per chat when chat rows include readable content.",
|
|
15257
|
+
"",
|
|
15258
|
+
"## Selected Domains",
|
|
15259
|
+
"",
|
|
15260
|
+
...selectedDomains.length > 0 ? selectedDomains.map((domain) => `- ${domain}`) : ["- none"],
|
|
15261
|
+
"",
|
|
15262
|
+
"Reusable credentials, token hashes, private keys, and raw secrets are intentionally excluded.",
|
|
15263
|
+
""
|
|
15264
|
+
].join("\n");
|
|
15265
|
+
}
|
|
15266
|
+
function buildAccountExportReport(bundle) {
|
|
15267
|
+
const report = bundle.manifest.report && typeof bundle.manifest.report === "object" && !Array.isArray(bundle.manifest.report) ? bundle.manifest.report : {};
|
|
15268
|
+
return {
|
|
15269
|
+
export_id: bundle.export.export_id ?? null,
|
|
15270
|
+
status: bundle.export.status ?? report.status ?? "unknown",
|
|
15271
|
+
selected_domains: Array.isArray(bundle.manifest.selected_domains) ? bundle.manifest.selected_domains : [],
|
|
15272
|
+
failures: Array.isArray(report.failures) ? report.failures : [],
|
|
15273
|
+
redacted_secret_categories: ACCOUNT_EXPORT_REDACTION_CATEGORIES,
|
|
15274
|
+
partial_requires_acceptance: Boolean(report.partial_requires_acceptance)
|
|
15275
|
+
};
|
|
15276
|
+
}
|
|
15277
|
+
function collectAccountExportDomainPayloads(chunks) {
|
|
15278
|
+
const grouped = {};
|
|
15279
|
+
for (const chunk of chunks) {
|
|
15280
|
+
const domain = safeArchiveSegment(String(chunk.domain ?? "unknown"));
|
|
15281
|
+
const payload = chunk.payload && typeof chunk.payload === "object" && !Array.isArray(chunk.payload) ? chunk.payload : { value: chunk.payload ?? null };
|
|
15282
|
+
grouped[domain] = grouped[domain] ?? [];
|
|
15283
|
+
grouped[domain].push({
|
|
15284
|
+
chunk_id: chunk.chunk_id ?? null,
|
|
15285
|
+
sequence: chunk.sequence ?? null,
|
|
15286
|
+
status: chunk.status ?? null,
|
|
15287
|
+
...payload
|
|
15288
|
+
});
|
|
15289
|
+
}
|
|
15290
|
+
return grouped;
|
|
15291
|
+
}
|
|
15292
|
+
async function writeAccountExportChatFiles(chatPayloads, writeArchiveText) {
|
|
15293
|
+
const chats = [];
|
|
15294
|
+
for (const payload of chatPayloads) {
|
|
15295
|
+
if (Array.isArray(payload.items)) {
|
|
15296
|
+
chats.push(...payload.items.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item)));
|
|
15297
|
+
}
|
|
15298
|
+
}
|
|
15299
|
+
for (const [index, chat] of chats.entries()) {
|
|
15300
|
+
const chatId = safeArchiveSegment(String(chat.id ?? chat.chat_id ?? `chat-${index + 1}`));
|
|
15301
|
+
await writeArchiveText(`chats/${chatId}.yml`, serializeArchiveYaml({ chat, messages: Array.isArray(chat.messages) ? chat.messages : [] }));
|
|
15302
|
+
await writeArchiveText(`chats/${chatId}.md`, buildAccountExportChatMarkdown(chat));
|
|
15303
|
+
}
|
|
15304
|
+
}
|
|
15305
|
+
function buildAccountExportChatMarkdown(chat) {
|
|
15306
|
+
const title = String(chat.title ?? chat.name ?? chat.id ?? chat.chat_id ?? "Untitled Chat");
|
|
15307
|
+
const lines = [`# ${title}`, ""];
|
|
15308
|
+
if (typeof chat.summary === "string" && chat.summary.trim()) {
|
|
15309
|
+
lines.push("## Summary", "", chat.summary.trim(), "");
|
|
15310
|
+
}
|
|
15311
|
+
const messages = Array.isArray(chat.messages) ? chat.messages : [];
|
|
15312
|
+
if (messages.length > 0) {
|
|
15313
|
+
lines.push("## Messages", "");
|
|
15314
|
+
for (const message of messages) {
|
|
15315
|
+
if (!message || typeof message !== "object" || Array.isArray(message)) continue;
|
|
15316
|
+
const record = message;
|
|
15317
|
+
lines.push(`### ${String(record.role ?? record.sender ?? "message")}`, "", String(record.content ?? record.text ?? ""), "");
|
|
15318
|
+
}
|
|
15319
|
+
} else {
|
|
15320
|
+
lines.push("No readable message records were included in this export chunk.", "");
|
|
15321
|
+
}
|
|
15322
|
+
return `${lines.join("\n")}
|
|
15323
|
+
`;
|
|
15324
|
+
}
|
|
15325
|
+
function serializeArchiveYaml(data, indent = 0) {
|
|
15326
|
+
const pad2 = " ".repeat(indent);
|
|
15327
|
+
let out = "";
|
|
15328
|
+
for (const [key, val] of Object.entries(data)) {
|
|
15329
|
+
if (val === null || val === void 0) {
|
|
15330
|
+
out += `${pad2}${key}: null
|
|
15331
|
+
`;
|
|
15332
|
+
} else if (typeof val === "boolean" || typeof val === "number") {
|
|
15333
|
+
out += `${pad2}${key}: ${val}
|
|
15334
|
+
`;
|
|
15335
|
+
} else if (typeof val === "string") {
|
|
15336
|
+
out += val.includes("\n") ? `${pad2}${key}: |
|
|
15337
|
+
${val.split("\n").map((line) => `${pad2} ${line}`).join("\n")}
|
|
15338
|
+
` : `${pad2}${key}: ${quoteYamlString(val)}
|
|
15339
|
+
`;
|
|
15340
|
+
} else if (Array.isArray(val)) {
|
|
15341
|
+
out += `${pad2}${key}:
|
|
15342
|
+
`;
|
|
15343
|
+
for (const item of val) {
|
|
15344
|
+
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
15345
|
+
out += `${pad2}-
|
|
15346
|
+
${serializeArchiveYaml(item, indent + 2)}`;
|
|
15347
|
+
} else {
|
|
15348
|
+
out += `${pad2}- ${typeof item === "string" ? quoteYamlString(item) : String(item)}
|
|
15349
|
+
`;
|
|
15350
|
+
}
|
|
15351
|
+
}
|
|
15352
|
+
} else if (typeof val === "object") {
|
|
15353
|
+
out += `${pad2}${key}:
|
|
15354
|
+
${serializeArchiveYaml(val, indent + 1)}`;
|
|
15355
|
+
}
|
|
15356
|
+
}
|
|
15357
|
+
return out;
|
|
15358
|
+
}
|
|
15359
|
+
function quoteYamlString(value) {
|
|
15360
|
+
const needsQuote = value.includes(":") || value.includes("#") || value.startsWith("{") || value.startsWith("[") || value === "" || ["true", "false", "null"].includes(value);
|
|
15361
|
+
return needsQuote ? `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : value;
|
|
15362
|
+
}
|
|
15363
|
+
function safeArchiveSegment(value) {
|
|
15364
|
+
const cleaned = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
15365
|
+
return cleaned || "unknown";
|
|
15366
|
+
}
|
|
15367
|
+
|
|
15125
15368
|
// src/sdk.ts
|
|
15126
15369
|
var DEFAULT_API_URL2 = "https://api.openmates.org";
|
|
15127
15370
|
var DEFAULT_RECOVERY_POLL_INTERVAL_MS = 500;
|
|
@@ -15934,7 +16177,9 @@ var OpenMatesAccount = class {
|
|
|
15934
16177
|
}
|
|
15935
16178
|
async exportChunk(exportId, chunkId) {
|
|
15936
16179
|
const result = await this.client.get(`/v1/account-exports/${encodeURIComponent(exportId)}/chunks/${encodeURIComponent(chunkId)}`);
|
|
15937
|
-
|
|
16180
|
+
const chunk = result.chunk ?? {};
|
|
16181
|
+
assertAccountExportPayloadSafe(chunk);
|
|
16182
|
+
return chunk;
|
|
15938
16183
|
}
|
|
15939
16184
|
async *iterExportChunks(exportId) {
|
|
15940
16185
|
const listed = await this.exportChunks(exportId);
|
|
@@ -15959,8 +16204,23 @@ var OpenMatesAccount = class {
|
|
|
15959
16204
|
this.exportJobManifest(exportId),
|
|
15960
16205
|
this.exportChunks(exportId)
|
|
15961
16206
|
]);
|
|
15962
|
-
const
|
|
15963
|
-
|
|
16207
|
+
const downloadedChunks = [];
|
|
16208
|
+
try {
|
|
16209
|
+
for (const chunk of chunks.chunks) {
|
|
16210
|
+
const chunkId = String(chunk.chunk_id ?? "");
|
|
16211
|
+
downloadedChunks.push(chunkId ? await this.exportChunk(exportId, chunkId) : chunk);
|
|
16212
|
+
}
|
|
16213
|
+
} catch (error) {
|
|
16214
|
+
await this.cancelExport(exportId).catch(() => void 0);
|
|
16215
|
+
throw error;
|
|
16216
|
+
}
|
|
16217
|
+
let completed = await this.completeExport(exportId);
|
|
16218
|
+
const status = String(completed.export.status ?? "");
|
|
16219
|
+
if (status === "partial") {
|
|
16220
|
+
if (options.acceptPartial !== true) throw new Error(`Account export ${exportId} is partial. Pass acceptPartial: true to accept it explicitly.`);
|
|
16221
|
+
completed = await this.acceptPartialExport(exportId);
|
|
16222
|
+
}
|
|
16223
|
+
return { export: completed.export, manifest: sanitizeAccountExportManifest(manifest.manifest), chunks: downloadedChunks };
|
|
15964
16224
|
}
|
|
15965
16225
|
async exportManifest() {
|
|
15966
16226
|
return this.client.get("/v1/sdk/account/export/manifest");
|
|
@@ -21788,243 +22048,6 @@ function redactUrl(rawUrl) {
|
|
|
21788
22048
|
return url.toString();
|
|
21789
22049
|
}
|
|
21790
22050
|
|
|
21791
|
-
// src/accountExportArchive.ts
|
|
21792
|
-
var ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES = /* @__PURE__ */ new Set([
|
|
21793
|
-
"access_token",
|
|
21794
|
-
"api_key",
|
|
21795
|
-
"backup_code_hash",
|
|
21796
|
-
"encrypted_master_key",
|
|
21797
|
-
"lookup_hash",
|
|
21798
|
-
"password_hash",
|
|
21799
|
-
"private_key",
|
|
21800
|
-
"raw_key",
|
|
21801
|
-
"refresh_token",
|
|
21802
|
-
"share_key",
|
|
21803
|
-
"signing_secret",
|
|
21804
|
-
"token_hash",
|
|
21805
|
-
"totp_seed",
|
|
21806
|
-
"webhook_secret"
|
|
21807
|
-
]);
|
|
21808
|
-
var ACCOUNT_EXPORT_REDACTION_CATEGORIES = [
|
|
21809
|
-
"api_credentials",
|
|
21810
|
-
"authentication_tokens",
|
|
21811
|
-
"key_material",
|
|
21812
|
-
"password_and_recovery_hashes",
|
|
21813
|
-
"webhook_secrets"
|
|
21814
|
-
];
|
|
21815
|
-
var ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS = [
|
|
21816
|
-
/-----BEGIN [A-Z ]*PRIVATE KEY-----/,
|
|
21817
|
-
/(?:^|[^a-z0-9])sk-(?:api|proj|live|test)[-_a-z0-9]{6,}/i,
|
|
21818
|
-
/#key=[A-Za-z0-9_-]{8,}/
|
|
21819
|
-
];
|
|
21820
|
-
async function writeAccountExportArchive(bundle, flags = {}) {
|
|
21821
|
-
const { mkdir: mkdir2, mkdtemp, rm, writeFile: writeFile2 } = await import("fs/promises");
|
|
21822
|
-
const { tmpdir: tmpdir2 } = await import("os");
|
|
21823
|
-
const { execFileSync: execFileSync3 } = await import("child_process");
|
|
21824
|
-
const { join: join7, dirname: dirname6 } = await import("path");
|
|
21825
|
-
const exportId = safeArchiveSegment(String(bundle.export.export_id ?? `export-${Date.now()}`));
|
|
21826
|
-
const archiveFormat = flags.format === "directory" ? "directory" : "zip";
|
|
21827
|
-
const outputPath = typeof flags.output === "string" ? flags.output : join7(process.cwd(), archiveFormat === "zip" ? `openmates-account-export-${exportId}.zip` : `openmates-account-export-${exportId}`);
|
|
21828
|
-
const stagingDir = archiveFormat === "directory" ? outputPath : await mkdtemp(join7(tmpdir2(), `openmates-account-export-${exportId}-`));
|
|
21829
|
-
const writtenFiles = [];
|
|
21830
|
-
async function writeArchiveText(relativePath, content) {
|
|
21831
|
-
assertAccountExportTextSafe(content, relativePath);
|
|
21832
|
-
const fullPath = join7(stagingDir, relativePath);
|
|
21833
|
-
await mkdir2(dirname6(fullPath), { recursive: true });
|
|
21834
|
-
await writeFile2(fullPath, content, "utf-8");
|
|
21835
|
-
writtenFiles.push(relativePath);
|
|
21836
|
-
}
|
|
21837
|
-
await writeArchiveText("README.md", buildAccountExportReadme(bundle));
|
|
21838
|
-
await writeArchiveText("manifest.yml", serializeArchiveYaml(bundle.manifest));
|
|
21839
|
-
await writeArchiveText("manifest.json", `${JSON.stringify(bundle.manifest, null, 2)}
|
|
21840
|
-
`);
|
|
21841
|
-
await writeArchiveText("export-report.yml", serializeArchiveYaml(buildAccountExportReport(bundle)));
|
|
21842
|
-
const domainPayloads = collectAccountExportDomainPayloads(bundle.chunks);
|
|
21843
|
-
for (const [domain, payloads] of Object.entries(domainPayloads)) {
|
|
21844
|
-
const domainFile = payloads.length === 1 ? payloads[0] : { chunks: payloads };
|
|
21845
|
-
await writeArchiveText(`domains/${safeArchiveSegment(domain)}.json`, `${JSON.stringify(domainFile, null, 2)}
|
|
21846
|
-
`);
|
|
21847
|
-
}
|
|
21848
|
-
await writeAccountExportChatFiles(domainPayloads.chats ?? [], writeArchiveText);
|
|
21849
|
-
if (archiveFormat === "directory") return { output: outputPath, format: "directory", files: writtenFiles.length };
|
|
21850
|
-
await mkdir2(dirname6(outputPath), { recursive: true });
|
|
21851
|
-
try {
|
|
21852
|
-
execFileSync3("zip", ["-r", outputPath, "."], { cwd: stagingDir, stdio: "pipe" });
|
|
21853
|
-
return { output: outputPath, format: "zip", files: writtenFiles.length };
|
|
21854
|
-
} finally {
|
|
21855
|
-
await rm(stagingDir, { recursive: true, force: true });
|
|
21856
|
-
}
|
|
21857
|
-
}
|
|
21858
|
-
function assertAccountExportPayloadSafe(value, path = "$export") {
|
|
21859
|
-
if (value === null || value === void 0) return;
|
|
21860
|
-
if (typeof value === "string") {
|
|
21861
|
-
for (const pattern of ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS) {
|
|
21862
|
-
if (pattern.test(value)) throw new Error(`Account export contains forbidden secret-like value at ${path}`);
|
|
21863
|
-
}
|
|
21864
|
-
return;
|
|
21865
|
-
}
|
|
21866
|
-
if (typeof value !== "object") return;
|
|
21867
|
-
if (Array.isArray(value)) {
|
|
21868
|
-
value.forEach((item, index) => assertAccountExportPayloadSafe(item, `${path}[${index}]`));
|
|
21869
|
-
return;
|
|
21870
|
-
}
|
|
21871
|
-
for (const [key, child] of Object.entries(value)) {
|
|
21872
|
-
const normalizedKey = key.toLowerCase();
|
|
21873
|
-
if (ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES.has(normalizedKey)) {
|
|
21874
|
-
throw new Error(`Account export contains forbidden secret field '${key}' at ${path}`);
|
|
21875
|
-
}
|
|
21876
|
-
assertAccountExportPayloadSafe(child, `${path}.${key}`);
|
|
21877
|
-
}
|
|
21878
|
-
}
|
|
21879
|
-
function sanitizeAccountExportManifest(manifest) {
|
|
21880
|
-
const copy = JSON.parse(JSON.stringify(manifest));
|
|
21881
|
-
const report = copy.report;
|
|
21882
|
-
if (report && typeof report === "object" && !Array.isArray(report)) {
|
|
21883
|
-
const reportObject = report;
|
|
21884
|
-
if (Array.isArray(reportObject.redactions)) {
|
|
21885
|
-
reportObject.redactions = ACCOUNT_EXPORT_REDACTION_CATEGORIES;
|
|
21886
|
-
}
|
|
21887
|
-
}
|
|
21888
|
-
assertAccountExportPayloadSafe(copy, "$manifest");
|
|
21889
|
-
return copy;
|
|
21890
|
-
}
|
|
21891
|
-
function assertAccountExportTextSafe(content, relativePath) {
|
|
21892
|
-
for (const pattern of ACCOUNT_EXPORT_FORBIDDEN_VALUE_PATTERNS) {
|
|
21893
|
-
if (pattern.test(content)) throw new Error(`Account export file ${relativePath} contains forbidden secret-like content`);
|
|
21894
|
-
}
|
|
21895
|
-
for (const field of ACCOUNT_EXPORT_FORBIDDEN_FIELD_NAMES) {
|
|
21896
|
-
if (new RegExp(`"?${field}"?\\s*:`, "i").test(content)) {
|
|
21897
|
-
throw new Error(`Account export file ${relativePath} contains forbidden secret field '${field}'`);
|
|
21898
|
-
}
|
|
21899
|
-
}
|
|
21900
|
-
}
|
|
21901
|
-
function buildAccountExportReadme(bundle) {
|
|
21902
|
-
const selectedDomains = Array.isArray(bundle.manifest.selected_domains) ? bundle.manifest.selected_domains.map(String) : [];
|
|
21903
|
-
return [
|
|
21904
|
-
"# OpenMates Account Export",
|
|
21905
|
-
"",
|
|
21906
|
-
`Export ID: ${String(bundle.export.export_id ?? "unknown")}`,
|
|
21907
|
-
`Status: ${String(bundle.export.status ?? "unknown")}`,
|
|
21908
|
-
`Schema: ${String(bundle.manifest.schema_version ?? "account-export-v1")}`,
|
|
21909
|
-
`Exported at: ${(/* @__PURE__ */ new Date()).toISOString()}`,
|
|
21910
|
-
"",
|
|
21911
|
-
"## Contents",
|
|
21912
|
-
"",
|
|
21913
|
-
"- `manifest.yml` records selected domains, filters, and domain status.",
|
|
21914
|
-
"- `export-report.yml` summarizes completion, failures, and redacted categories.",
|
|
21915
|
-
"- `domains/` contains one JSON file per exported domain.",
|
|
21916
|
-
"- `chats/` contains one Markdown and one YAML file per chat when chat rows include readable content.",
|
|
21917
|
-
"",
|
|
21918
|
-
"## Selected Domains",
|
|
21919
|
-
"",
|
|
21920
|
-
...selectedDomains.length > 0 ? selectedDomains.map((domain) => `- ${domain}`) : ["- none"],
|
|
21921
|
-
"",
|
|
21922
|
-
"Reusable credentials, token hashes, private keys, and raw secrets are intentionally excluded.",
|
|
21923
|
-
""
|
|
21924
|
-
].join("\n");
|
|
21925
|
-
}
|
|
21926
|
-
function buildAccountExportReport(bundle) {
|
|
21927
|
-
const report = bundle.manifest.report && typeof bundle.manifest.report === "object" && !Array.isArray(bundle.manifest.report) ? bundle.manifest.report : {};
|
|
21928
|
-
return {
|
|
21929
|
-
export_id: bundle.export.export_id ?? null,
|
|
21930
|
-
status: bundle.export.status ?? report.status ?? "unknown",
|
|
21931
|
-
selected_domains: Array.isArray(bundle.manifest.selected_domains) ? bundle.manifest.selected_domains : [],
|
|
21932
|
-
failures: Array.isArray(report.failures) ? report.failures : [],
|
|
21933
|
-
redacted_secret_categories: ACCOUNT_EXPORT_REDACTION_CATEGORIES,
|
|
21934
|
-
partial_requires_acceptance: Boolean(report.partial_requires_acceptance)
|
|
21935
|
-
};
|
|
21936
|
-
}
|
|
21937
|
-
function collectAccountExportDomainPayloads(chunks) {
|
|
21938
|
-
const grouped = {};
|
|
21939
|
-
for (const chunk of chunks) {
|
|
21940
|
-
const domain = safeArchiveSegment(String(chunk.domain ?? "unknown"));
|
|
21941
|
-
const payload = chunk.payload && typeof chunk.payload === "object" && !Array.isArray(chunk.payload) ? chunk.payload : { value: chunk.payload ?? null };
|
|
21942
|
-
grouped[domain] = grouped[domain] ?? [];
|
|
21943
|
-
grouped[domain].push({
|
|
21944
|
-
chunk_id: chunk.chunk_id ?? null,
|
|
21945
|
-
sequence: chunk.sequence ?? null,
|
|
21946
|
-
status: chunk.status ?? null,
|
|
21947
|
-
...payload
|
|
21948
|
-
});
|
|
21949
|
-
}
|
|
21950
|
-
return grouped;
|
|
21951
|
-
}
|
|
21952
|
-
async function writeAccountExportChatFiles(chatPayloads, writeArchiveText) {
|
|
21953
|
-
const chats = [];
|
|
21954
|
-
for (const payload of chatPayloads) {
|
|
21955
|
-
if (Array.isArray(payload.items)) {
|
|
21956
|
-
chats.push(...payload.items.filter((item) => item !== null && typeof item === "object" && !Array.isArray(item)));
|
|
21957
|
-
}
|
|
21958
|
-
}
|
|
21959
|
-
for (const [index, chat] of chats.entries()) {
|
|
21960
|
-
const chatId = safeArchiveSegment(String(chat.id ?? chat.chat_id ?? `chat-${index + 1}`));
|
|
21961
|
-
await writeArchiveText(`chats/${chatId}.yml`, serializeArchiveYaml({ chat, messages: Array.isArray(chat.messages) ? chat.messages : [] }));
|
|
21962
|
-
await writeArchiveText(`chats/${chatId}.md`, buildAccountExportChatMarkdown(chat));
|
|
21963
|
-
}
|
|
21964
|
-
}
|
|
21965
|
-
function buildAccountExportChatMarkdown(chat) {
|
|
21966
|
-
const title = String(chat.title ?? chat.name ?? chat.id ?? chat.chat_id ?? "Untitled Chat");
|
|
21967
|
-
const lines = [`# ${title}`, ""];
|
|
21968
|
-
if (typeof chat.summary === "string" && chat.summary.trim()) {
|
|
21969
|
-
lines.push("## Summary", "", chat.summary.trim(), "");
|
|
21970
|
-
}
|
|
21971
|
-
const messages = Array.isArray(chat.messages) ? chat.messages : [];
|
|
21972
|
-
if (messages.length > 0) {
|
|
21973
|
-
lines.push("## Messages", "");
|
|
21974
|
-
for (const message of messages) {
|
|
21975
|
-
if (!message || typeof message !== "object" || Array.isArray(message)) continue;
|
|
21976
|
-
const record = message;
|
|
21977
|
-
lines.push(`### ${String(record.role ?? record.sender ?? "message")}`, "", String(record.content ?? record.text ?? ""), "");
|
|
21978
|
-
}
|
|
21979
|
-
} else {
|
|
21980
|
-
lines.push("No readable message records were included in this export chunk.", "");
|
|
21981
|
-
}
|
|
21982
|
-
return `${lines.join("\n")}
|
|
21983
|
-
`;
|
|
21984
|
-
}
|
|
21985
|
-
function serializeArchiveYaml(data, indent = 0) {
|
|
21986
|
-
const pad2 = " ".repeat(indent);
|
|
21987
|
-
let out = "";
|
|
21988
|
-
for (const [key, val] of Object.entries(data)) {
|
|
21989
|
-
if (val === null || val === void 0) {
|
|
21990
|
-
out += `${pad2}${key}: null
|
|
21991
|
-
`;
|
|
21992
|
-
} else if (typeof val === "boolean" || typeof val === "number") {
|
|
21993
|
-
out += `${pad2}${key}: ${val}
|
|
21994
|
-
`;
|
|
21995
|
-
} else if (typeof val === "string") {
|
|
21996
|
-
out += val.includes("\n") ? `${pad2}${key}: |
|
|
21997
|
-
${val.split("\n").map((line) => `${pad2} ${line}`).join("\n")}
|
|
21998
|
-
` : `${pad2}${key}: ${quoteYamlString(val)}
|
|
21999
|
-
`;
|
|
22000
|
-
} else if (Array.isArray(val)) {
|
|
22001
|
-
out += `${pad2}${key}:
|
|
22002
|
-
`;
|
|
22003
|
-
for (const item of val) {
|
|
22004
|
-
if (item && typeof item === "object" && !Array.isArray(item)) {
|
|
22005
|
-
out += `${pad2}-
|
|
22006
|
-
${serializeArchiveYaml(item, indent + 2)}`;
|
|
22007
|
-
} else {
|
|
22008
|
-
out += `${pad2}- ${typeof item === "string" ? quoteYamlString(item) : String(item)}
|
|
22009
|
-
`;
|
|
22010
|
-
}
|
|
22011
|
-
}
|
|
22012
|
-
} else if (typeof val === "object") {
|
|
22013
|
-
out += `${pad2}${key}:
|
|
22014
|
-
${serializeArchiveYaml(val, indent + 1)}`;
|
|
22015
|
-
}
|
|
22016
|
-
}
|
|
22017
|
-
return out;
|
|
22018
|
-
}
|
|
22019
|
-
function quoteYamlString(value) {
|
|
22020
|
-
const needsQuote = value.includes(":") || value.includes("#") || value.startsWith("{") || value.startsWith("[") || value === "" || ["true", "false", "null"].includes(value);
|
|
22021
|
-
return needsQuote ? `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"` : value;
|
|
22022
|
-
}
|
|
22023
|
-
function safeArchiveSegment(value) {
|
|
22024
|
-
const cleaned = value.trim().replace(/[^a-zA-Z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
22025
|
-
return cleaned || "unknown";
|
|
22026
|
-
}
|
|
22027
|
-
|
|
22028
22051
|
// ../ui/src/demo_chats/data/example_chats/gigantic-airplanes.ts
|
|
22029
22052
|
var giganticAirplanesChat = {
|
|
22030
22053
|
chat_id: "example-gigantic-airplanes",
|
|
@@ -63099,6 +63122,8 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
63099
63122
|
{ path: ["account", "export", "manifest"], description: "Show account export manifest", examples: ["openmates settings account export manifest <export-id> --json"] },
|
|
63100
63123
|
{ path: ["account", "export", "chunks"], description: "List account export chunks", examples: ["openmates settings account export chunks <export-id> --json"] },
|
|
63101
63124
|
{ path: ["account", "export", "data"], description: "Fetch account export chunks", examples: ["openmates settings account export data <export-id> --json"] },
|
|
63125
|
+
{ path: ["account", "export", "accept-partial"], description: "Accept a partial account export", examples: ["openmates settings account export accept-partial <export-id> --json"] },
|
|
63126
|
+
{ path: ["account", "export", "cancel"], description: "Cancel an account export job", examples: ["openmates settings account export cancel <export-id> --json"] },
|
|
63102
63127
|
{ path: ["account", "import-chat"], description: "Import a CLI chat export file", examples: ["openmates settings account import-chat ./chat.yml", "openmates settings account import-chat ./payload.json"] },
|
|
63103
63128
|
{ path: ["account", "username", "set"], description: "Change account username", examples: ["openmates settings account username set alice_123"] },
|
|
63104
63129
|
{ path: ["account", "profile-picture", "set"], description: "Upload a profile picture", examples: ["openmates settings account profile-picture set ./avatar.jpg"] },
|
|
@@ -63958,7 +63983,7 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
63958
63983
|
printTopicPreferences(preferences, flags, "Interests cleared");
|
|
63959
63984
|
return;
|
|
63960
63985
|
}
|
|
63961
|
-
if (matches(tokens, ["account", "export"]) || matches(tokens, ["account", "export", "start"])) {
|
|
63986
|
+
if (tokens.length === 2 && matches(tokens, ["account", "export"]) || tokens.length === 3 && matches(tokens, ["account", "export", "start"])) {
|
|
63962
63987
|
printAccountExportBundle(await runAccountExport(client, flags), flags);
|
|
63963
63988
|
return;
|
|
63964
63989
|
}
|
|
@@ -63989,7 +64014,26 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
63989
64014
|
await printSettingsResult(client.settingsGet("export-account-data"), flags);
|
|
63990
64015
|
return;
|
|
63991
64016
|
}
|
|
63992
|
-
await
|
|
64017
|
+
const listed = await client.listAccountExportChunks(exportId);
|
|
64018
|
+
const chunks = [];
|
|
64019
|
+
for (const chunk of listed.chunks) {
|
|
64020
|
+
const chunkId = String(chunk.chunk_id ?? "");
|
|
64021
|
+
chunks.push(chunkId ? await client.getAccountExportChunk(exportId, chunkId) : chunk);
|
|
64022
|
+
}
|
|
64023
|
+
if (flags.json === true) printJson2({ chunks });
|
|
64024
|
+
else printGenericObject({ chunks });
|
|
64025
|
+
return;
|
|
64026
|
+
}
|
|
64027
|
+
if (matches(tokens, ["account", "export", "accept-partial"])) {
|
|
64028
|
+
const exportId = rest[2];
|
|
64029
|
+
if (!exportId) throw new Error("Missing export ID. Example: openmates settings account export accept-partial <export-id>");
|
|
64030
|
+
await printSettingsResult(client.acceptPartialAccountExport(exportId), flags);
|
|
64031
|
+
return;
|
|
64032
|
+
}
|
|
64033
|
+
if (matches(tokens, ["account", "export", "cancel"])) {
|
|
64034
|
+
const exportId = rest[2];
|
|
64035
|
+
if (!exportId) throw new Error("Missing export ID. Example: openmates settings account export cancel <export-id>");
|
|
64036
|
+
await printSettingsResult(client.cancelAccountExport(exportId), flags);
|
|
63993
64037
|
return;
|
|
63994
64038
|
}
|
|
63995
64039
|
if (matches(tokens, ["account", "import-chat"])) {
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -5164,6 +5164,7 @@ interface AccountExportStartOptions {
|
|
|
5164
5164
|
filters?: Record<string, unknown>;
|
|
5165
5165
|
format?: "zip" | "directory";
|
|
5166
5166
|
includeAdvancedMetadata?: boolean;
|
|
5167
|
+
acceptPartial?: boolean;
|
|
5167
5168
|
}
|
|
5168
5169
|
interface AccountExportResponse {
|
|
5169
5170
|
export: Record<string, unknown>;
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openmates",
|
|
3
|
-
"version": "0.15.0-alpha.
|
|
3
|
+
"version": "0.15.0-alpha.40",
|
|
4
4
|
"description": "OpenMates CLI and SDK",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -68,6 +68,7 @@
|
|
|
68
68
|
"@resvg/resvg-js": "^2.6.2",
|
|
69
69
|
"@toon-format/toon": "2.1.0",
|
|
70
70
|
"ahocorasick": "1.0.2",
|
|
71
|
+
"jszip": "^3.10.1",
|
|
71
72
|
"qrcode-terminal": "^0.12.0",
|
|
72
73
|
"tweetnacl": "^1.0.3",
|
|
73
74
|
"ws": "8.21.0"
|