openmates 0.11.0-alpha.7 → 0.11.0-alpha.8
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-T77MVTFX.js → chunk-2YHQ7NP4.js} +348 -101
- package/dist/chunk-SFTCIVE2.js +131 -0
- package/dist/cli.js +2 -1
- package/dist/index.d.ts +37 -0
- package/dist/index.js +2 -1
- package/dist/uploadService-3CAJXU4L.js +8 -0
- package/package.json +1 -1
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import {
|
|
2
|
+
uploadFile
|
|
3
|
+
} from "./chunk-SFTCIVE2.js";
|
|
4
|
+
|
|
1
5
|
// src/client.ts
|
|
2
6
|
import { randomUUID } from "crypto";
|
|
3
7
|
import { platform as platform2, release } from "os";
|
|
@@ -186,6 +190,23 @@ var OpenMatesHttpClient = class {
|
|
|
186
190
|
async patch(path, body, headers = {}) {
|
|
187
191
|
return this.request("PATCH", path, body, headers);
|
|
188
192
|
}
|
|
193
|
+
async getBinary(path, headers = {}) {
|
|
194
|
+
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
195
|
+
const requestHeaders = {
|
|
196
|
+
Accept: "application/pdf,application/octet-stream",
|
|
197
|
+
...headers
|
|
198
|
+
};
|
|
199
|
+
const cookieHeader = this.formatCookieHeader();
|
|
200
|
+
if (cookieHeader) requestHeaders.Cookie = cookieHeader;
|
|
201
|
+
const response = await fetch(url, { method: "GET", headers: requestHeaders });
|
|
202
|
+
this.captureCookies(response);
|
|
203
|
+
return {
|
|
204
|
+
ok: response.ok,
|
|
205
|
+
status: response.status,
|
|
206
|
+
data: new Uint8Array(await response.arrayBuffer()),
|
|
207
|
+
headers: response.headers
|
|
208
|
+
};
|
|
209
|
+
}
|
|
189
210
|
async request(method, path, body, headers = {}) {
|
|
190
211
|
const url = `${this.apiUrl}${path.startsWith("/") ? path : `/${path}`}`;
|
|
191
212
|
const requestHeaders = {
|
|
@@ -2767,6 +2788,123 @@ var OpenMatesClient = class _OpenMatesClient {
|
|
|
2767
2788
|
}
|
|
2768
2789
|
return response.data;
|
|
2769
2790
|
}
|
|
2791
|
+
async listInvoices() {
|
|
2792
|
+
this.requireSession();
|
|
2793
|
+
const response = await this.http.get(
|
|
2794
|
+
"/v1/payments/invoices",
|
|
2795
|
+
this.getCliRequestHeaders()
|
|
2796
|
+
);
|
|
2797
|
+
if (!response.ok) {
|
|
2798
|
+
throw new Error(`Failed to fetch invoices (HTTP ${response.status})`);
|
|
2799
|
+
}
|
|
2800
|
+
return { invoices: response.data.invoices ?? [] };
|
|
2801
|
+
}
|
|
2802
|
+
async downloadInvoice(invoiceId) {
|
|
2803
|
+
return this.downloadPaymentPdf(
|
|
2804
|
+
`/v1/payments/invoices/${encodeURIComponent(invoiceId)}/download`,
|
|
2805
|
+
`Invoice_${invoiceId}.pdf`
|
|
2806
|
+
);
|
|
2807
|
+
}
|
|
2808
|
+
async downloadCreditNote(invoiceId) {
|
|
2809
|
+
return this.downloadPaymentPdf(
|
|
2810
|
+
`/v1/payments/invoices/${encodeURIComponent(invoiceId)}/credit-note/download`,
|
|
2811
|
+
`CreditNote_${invoiceId}.pdf`
|
|
2812
|
+
);
|
|
2813
|
+
}
|
|
2814
|
+
async requestRefund(invoiceId, emailEncryptionKey) {
|
|
2815
|
+
this.requireSession();
|
|
2816
|
+
const response = await this.http.post(
|
|
2817
|
+
"/v1/payments/refund",
|
|
2818
|
+
{ invoice_id: invoiceId, email_encryption_key: emailEncryptionKey },
|
|
2819
|
+
this.getCliRequestHeaders()
|
|
2820
|
+
);
|
|
2821
|
+
if (!response.ok) {
|
|
2822
|
+
throw new Error(`Refund request failed (HTTP ${response.status})`);
|
|
2823
|
+
}
|
|
2824
|
+
return response.data;
|
|
2825
|
+
}
|
|
2826
|
+
async updateUsername(username) {
|
|
2827
|
+
return this.settingsPost("user/username", { username });
|
|
2828
|
+
}
|
|
2829
|
+
async updateProfileImage(filePath) {
|
|
2830
|
+
const { uploadProfileImage } = await import("./uploadService-3CAJXU4L.js");
|
|
2831
|
+
const result = await uploadProfileImage(filePath, this.requireSession());
|
|
2832
|
+
if (result.status === "rejected") {
|
|
2833
|
+
throw new Error(result.detail ?? "Profile image rejected by content safety checks.");
|
|
2834
|
+
}
|
|
2835
|
+
if (result.status === "account_deleted") {
|
|
2836
|
+
throw new Error("Account deleted due to repeated profile image policy violations.");
|
|
2837
|
+
}
|
|
2838
|
+
if (result.status !== "ok") {
|
|
2839
|
+
throw new Error(result.detail ?? `Profile image upload failed with status '${result.status}'.`);
|
|
2840
|
+
}
|
|
2841
|
+
return result;
|
|
2842
|
+
}
|
|
2843
|
+
async getNewsletterCategories() {
|
|
2844
|
+
this.requireSession();
|
|
2845
|
+
const response = await this.http.get(
|
|
2846
|
+
"/v1/newsletter/categories",
|
|
2847
|
+
this.getCliRequestHeaders()
|
|
2848
|
+
);
|
|
2849
|
+
if (!response.ok) {
|
|
2850
|
+
throw new Error(`Failed to fetch newsletter categories (HTTP ${response.status})`);
|
|
2851
|
+
}
|
|
2852
|
+
return response.data;
|
|
2853
|
+
}
|
|
2854
|
+
async updateNewsletterCategories(categories) {
|
|
2855
|
+
this.requireSession();
|
|
2856
|
+
const response = await this.http.patch(
|
|
2857
|
+
"/v1/newsletter/categories",
|
|
2858
|
+
{ categories },
|
|
2859
|
+
this.getCliRequestHeaders()
|
|
2860
|
+
);
|
|
2861
|
+
if (!response.ok) {
|
|
2862
|
+
throw new Error(`Failed to update newsletter categories (HTTP ${response.status})`);
|
|
2863
|
+
}
|
|
2864
|
+
return response.data;
|
|
2865
|
+
}
|
|
2866
|
+
async subscribeNewsletter(email, language = "en", darkmode = false) {
|
|
2867
|
+
const response = await this.http.post(
|
|
2868
|
+
"/v1/newsletter/subscribe",
|
|
2869
|
+
{ email, language, darkmode },
|
|
2870
|
+
this.getCliRequestHeaders()
|
|
2871
|
+
);
|
|
2872
|
+
if (!response.ok) {
|
|
2873
|
+
throw new Error(`Newsletter subscribe failed (HTTP ${response.status})`);
|
|
2874
|
+
}
|
|
2875
|
+
return response.data;
|
|
2876
|
+
}
|
|
2877
|
+
async confirmNewsletter(token) {
|
|
2878
|
+
const response = await this.http.get(
|
|
2879
|
+
`/v1/newsletter/confirm/${encodeURIComponent(token)}`,
|
|
2880
|
+
this.getCliRequestHeaders()
|
|
2881
|
+
);
|
|
2882
|
+
if (!response.ok) {
|
|
2883
|
+
throw new Error(`Newsletter confirmation failed (HTTP ${response.status})`);
|
|
2884
|
+
}
|
|
2885
|
+
return response.data;
|
|
2886
|
+
}
|
|
2887
|
+
async unsubscribeNewsletter(token) {
|
|
2888
|
+
const response = await this.http.get(
|
|
2889
|
+
`/v1/newsletter/unsubscribe/${encodeURIComponent(token)}`,
|
|
2890
|
+
this.getCliRequestHeaders()
|
|
2891
|
+
);
|
|
2892
|
+
if (!response.ok) {
|
|
2893
|
+
throw new Error(`Newsletter unsubscribe failed (HTTP ${response.status})`);
|
|
2894
|
+
}
|
|
2895
|
+
return response.data;
|
|
2896
|
+
}
|
|
2897
|
+
async updateEmailNotificationSettings(payload) {
|
|
2898
|
+
const { ws } = await this.openWsClient();
|
|
2899
|
+
try {
|
|
2900
|
+
const ackPromise = ws.waitForMessage("email_notification_settings_ack");
|
|
2901
|
+
ws.send("email_notification_settings", payload);
|
|
2902
|
+
const ack = await ackPromise;
|
|
2903
|
+
return ack.payload;
|
|
2904
|
+
} finally {
|
|
2905
|
+
ws.close();
|
|
2906
|
+
}
|
|
2907
|
+
}
|
|
2770
2908
|
// -------------------------------------------------------------------------
|
|
2771
2909
|
// Daily Inspirations
|
|
2772
2910
|
// -------------------------------------------------------------------------
|
|
@@ -3231,6 +3369,17 @@ Required: ${schema.required.join(", ")}`
|
|
|
3231
3369
|
if (path.startsWith("/")) return path;
|
|
3232
3370
|
return `/v1/settings/${path}`;
|
|
3233
3371
|
}
|
|
3372
|
+
async downloadPaymentPdf(path, fallbackFilename) {
|
|
3373
|
+
this.requireSession();
|
|
3374
|
+
const response = await this.http.getBinary(path, this.getCliRequestHeaders());
|
|
3375
|
+
if (!response.ok) {
|
|
3376
|
+
throw new Error(`Download failed (HTTP ${response.status})`);
|
|
3377
|
+
}
|
|
3378
|
+
return {
|
|
3379
|
+
filename: filenameFromContentDisposition(response.headers.get("content-disposition")) ?? fallbackFilename,
|
|
3380
|
+
data: response.data
|
|
3381
|
+
};
|
|
3382
|
+
}
|
|
3234
3383
|
requireSession() {
|
|
3235
3384
|
if (!this.session) {
|
|
3236
3385
|
throw new Error("Not logged in. Run `openmates login`.");
|
|
@@ -3606,6 +3755,15 @@ function parseNewChatSuggestionText(text) {
|
|
|
3606
3755
|
const skillId = raw.slice(dashIdx + 1);
|
|
3607
3756
|
return { body, appId, skillId };
|
|
3608
3757
|
}
|
|
3758
|
+
function filenameFromContentDisposition(header2) {
|
|
3759
|
+
if (!header2) return null;
|
|
3760
|
+
const encoded = /filename\*=UTF-8''([^;]+)/i.exec(header2)?.[1];
|
|
3761
|
+
if (encoded) return decodeURIComponent(encoded);
|
|
3762
|
+
const quoted = /filename="([^"]+)"/i.exec(header2)?.[1];
|
|
3763
|
+
if (quoted) return quoted;
|
|
3764
|
+
const plain = /filename=([^;]+)/i.exec(header2)?.[1];
|
|
3765
|
+
return plain?.trim() ?? null;
|
|
3766
|
+
}
|
|
3609
3767
|
function sleep(ms) {
|
|
3610
3768
|
return new Promise((resolve4) => setTimeout(resolve4, ms));
|
|
3611
3769
|
}
|
|
@@ -4819,97 +4977,6 @@ function formatEmbedsForMessage(embeds) {
|
|
|
4819
4977
|
return "\n" + embeds.map((e) => e.referenceBlock).join("\n");
|
|
4820
4978
|
}
|
|
4821
4979
|
|
|
4822
|
-
// src/uploadService.ts
|
|
4823
|
-
import { readFileSync as readFileSync4 } from "fs";
|
|
4824
|
-
import { basename as basename2 } from "path";
|
|
4825
|
-
var UPLOAD_MAX_ATTEMPTS = 3;
|
|
4826
|
-
var UPLOAD_RETRY_DELAY_MS = 2e3;
|
|
4827
|
-
function getUploadUrl(apiUrl) {
|
|
4828
|
-
try {
|
|
4829
|
-
const url = new URL(apiUrl);
|
|
4830
|
-
if (url.hostname === "localhost") return "http://localhost:8001";
|
|
4831
|
-
} catch {
|
|
4832
|
-
}
|
|
4833
|
-
return "https://upload.openmates.org";
|
|
4834
|
-
}
|
|
4835
|
-
function getUploadOrigin(apiUrl) {
|
|
4836
|
-
try {
|
|
4837
|
-
const url = new URL(apiUrl);
|
|
4838
|
-
if (url.hostname === "localhost") return "http://localhost:5173";
|
|
4839
|
-
if (url.hostname.startsWith("api.")) {
|
|
4840
|
-
return `${url.protocol}//app.${url.hostname.slice(4)}`;
|
|
4841
|
-
}
|
|
4842
|
-
} catch {
|
|
4843
|
-
}
|
|
4844
|
-
return "https://app.openmates.org";
|
|
4845
|
-
}
|
|
4846
|
-
async function uploadFile(filePath, session) {
|
|
4847
|
-
const filename = basename2(filePath);
|
|
4848
|
-
const fileBytes = readFileSync4(filePath);
|
|
4849
|
-
const uploadUrl = `${getUploadUrl(session.apiUrl)}/v1/upload/file`;
|
|
4850
|
-
const origin = getUploadOrigin(session.apiUrl);
|
|
4851
|
-
const cookies = [];
|
|
4852
|
-
if (session.cookies?.auth_refresh_token) {
|
|
4853
|
-
cookies.push(`auth_refresh_token=${session.cookies.auth_refresh_token}`);
|
|
4854
|
-
}
|
|
4855
|
-
let response;
|
|
4856
|
-
let lastError;
|
|
4857
|
-
for (let attempt = 1; attempt <= UPLOAD_MAX_ATTEMPTS; attempt++) {
|
|
4858
|
-
try {
|
|
4859
|
-
const blob = new Blob([fileBytes]);
|
|
4860
|
-
const formData = new FormData();
|
|
4861
|
-
formData.append("file", blob, filename);
|
|
4862
|
-
response = await fetch(uploadUrl, {
|
|
4863
|
-
method: "POST",
|
|
4864
|
-
body: formData,
|
|
4865
|
-
headers: {
|
|
4866
|
-
Origin: origin,
|
|
4867
|
-
...cookies.length > 0 ? { Cookie: cookies.join("; ") } : {}
|
|
4868
|
-
},
|
|
4869
|
-
signal: AbortSignal.timeout(10 * 60 * 1e3)
|
|
4870
|
-
// 10-minute timeout
|
|
4871
|
-
});
|
|
4872
|
-
break;
|
|
4873
|
-
} catch (error) {
|
|
4874
|
-
lastError = error;
|
|
4875
|
-
if (attempt === UPLOAD_MAX_ATTEMPTS) break;
|
|
4876
|
-
await new Promise((resolve4) => setTimeout(resolve4, UPLOAD_RETRY_DELAY_MS));
|
|
4877
|
-
}
|
|
4878
|
-
}
|
|
4879
|
-
if (!response) {
|
|
4880
|
-
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
4881
|
-
throw new Error(message || "Upload request failed.");
|
|
4882
|
-
}
|
|
4883
|
-
if (!response.ok) {
|
|
4884
|
-
const status = response.status;
|
|
4885
|
-
let errorMessage;
|
|
4886
|
-
switch (status) {
|
|
4887
|
-
case 401:
|
|
4888
|
-
errorMessage = "Authentication failed. Run `openmates login` to re-authenticate.";
|
|
4889
|
-
break;
|
|
4890
|
-
case 413:
|
|
4891
|
-
errorMessage = "File too large (maximum 100 MB).";
|
|
4892
|
-
break;
|
|
4893
|
-
case 415:
|
|
4894
|
-
errorMessage = "Unsupported file type.";
|
|
4895
|
-
break;
|
|
4896
|
-
case 422: {
|
|
4897
|
-
const body = await response.text().catch(() => "");
|
|
4898
|
-
errorMessage = body.includes("malware") ? "File rejected: malware detected." : body.includes("content_safety") ? "File rejected: content safety violation." : `Upload validation failed: ${body}`;
|
|
4899
|
-
break;
|
|
4900
|
-
}
|
|
4901
|
-
case 429:
|
|
4902
|
-
errorMessage = "Upload rate limit exceeded. Try again in a minute.";
|
|
4903
|
-
break;
|
|
4904
|
-
default:
|
|
4905
|
-
errorMessage = `Upload failed (HTTP ${status}).`;
|
|
4906
|
-
}
|
|
4907
|
-
throw new Error(errorMessage);
|
|
4908
|
-
}
|
|
4909
|
-
const data = await response.json();
|
|
4910
|
-
return data;
|
|
4911
|
-
}
|
|
4912
|
-
|
|
4913
4980
|
// src/embedRenderers.ts
|
|
4914
4981
|
var str = (v) => typeof v === "string" && v.length > 0 ? v : null;
|
|
4915
4982
|
var DIRECT_TYPES = /* @__PURE__ */ new Set([
|
|
@@ -5959,13 +6026,13 @@ function formatTs(ts) {
|
|
|
5959
6026
|
|
|
5960
6027
|
// src/server.ts
|
|
5961
6028
|
import { execSync, spawn as nodeSpawn } from "child_process";
|
|
5962
|
-
import { copyFileSync, existsSync as existsSync5, readFileSync as
|
|
6029
|
+
import { copyFileSync, existsSync as existsSync5, readFileSync as readFileSync5, rmSync as rmSync3 } from "fs";
|
|
5963
6030
|
import { createInterface as createInterface2 } from "readline";
|
|
5964
6031
|
import { homedir as homedir5 } from "os";
|
|
5965
6032
|
import { join as join3, resolve as resolve3 } from "path";
|
|
5966
6033
|
|
|
5967
6034
|
// src/serverConfig.ts
|
|
5968
|
-
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as
|
|
6035
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync2, readFileSync as readFileSync4, rmSync as rmSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
5969
6036
|
import { homedir as homedir4 } from "os";
|
|
5970
6037
|
import { join as join2, resolve as resolve2 } from "path";
|
|
5971
6038
|
var STATE_DIR = join2(homedir4(), ".openmates");
|
|
@@ -5986,7 +6053,7 @@ function loadServerConfig() {
|
|
|
5986
6053
|
const filePath = join2(STATE_DIR, CONFIG_FILE);
|
|
5987
6054
|
if (!existsSync4(filePath)) return null;
|
|
5988
6055
|
try {
|
|
5989
|
-
return JSON.parse(
|
|
6056
|
+
return JSON.parse(readFileSync4(filePath, "utf-8"));
|
|
5990
6057
|
} catch {
|
|
5991
6058
|
return null;
|
|
5992
6059
|
}
|
|
@@ -6064,7 +6131,7 @@ function requireGit() {
|
|
|
6064
6131
|
}
|
|
6065
6132
|
function hasLlmCredentials(envPath) {
|
|
6066
6133
|
if (!existsSync5(envPath)) return false;
|
|
6067
|
-
const content =
|
|
6134
|
+
const content = readFileSync5(envPath, "utf-8");
|
|
6068
6135
|
for (const line of content.split("\n")) {
|
|
6069
6136
|
const trimmed = line.trim();
|
|
6070
6137
|
if (trimmed.startsWith("#") || !trimmed) continue;
|
|
@@ -7673,6 +7740,8 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
7673
7740
|
{ path: ["account", "export", "manifest"], description: "Show account export manifest", examples: ["openmates settings account export manifest --json"] },
|
|
7674
7741
|
{ path: ["account", "export", "data"], description: "Fetch account export data", examples: ["openmates settings account export data --json"] },
|
|
7675
7742
|
{ 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"] },
|
|
7743
|
+
{ path: ["account", "username", "set"], description: "Change account username", examples: ["openmates settings account username set alice_123"] },
|
|
7744
|
+
{ path: ["account", "profile-picture", "set"], description: "Upload a profile picture", examples: ["openmates settings account profile-picture set ./avatar.jpg"] },
|
|
7676
7745
|
{ path: ["account", "chats", "stats"], description: "Show chat statistics", examples: ["openmates settings account chats stats"] },
|
|
7677
7746
|
{ path: ["account", "delete", "preview"], description: "Preview account deletion impact", examples: ["openmates settings account delete preview"] },
|
|
7678
7747
|
{ path: ["account", "storage", "overview"], description: "Show storage overview", examples: ["openmates settings account storage overview"] },
|
|
@@ -7689,9 +7758,16 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
7689
7758
|
{ path: ["billing", "usage", "summaries"], description: "Show usage summaries", examples: ["openmates settings billing usage summaries"] },
|
|
7690
7759
|
{ path: ["billing", "usage", "daily"], description: "Show daily usage overview", examples: ["openmates settings billing usage daily"] },
|
|
7691
7760
|
{ path: ["billing", "usage", "export"], description: "Export usage data", examples: ["openmates settings billing usage export --json"] },
|
|
7761
|
+
{ path: ["billing", "invoices", "list"], description: "List invoices", examples: ["openmates settings billing invoices list --json"] },
|
|
7762
|
+
{ path: ["billing", "invoices", "download"], description: "Download an invoice PDF", examples: ["openmates settings billing invoices download <invoice-id> --output ./invoices"] },
|
|
7763
|
+
{ path: ["billing", "invoices", "credit-note"], description: "Download a credit note PDF", examples: ["openmates settings billing invoices credit-note <invoice-id> --output ./invoices"] },
|
|
7764
|
+
{ path: ["billing", "invoices", "refund"], description: "Request a refund for an invoice", examples: ["openmates settings billing invoices refund <invoice-id> --email-encryption-key <base64>"] },
|
|
7692
7765
|
{ path: ["billing", "gift-card", "redeem"], description: "Redeem a gift card", examples: ["openmates settings billing gift-card redeem ABCD-1234"] },
|
|
7693
7766
|
{ path: ["billing", "gift-card", "list"], description: "List redeemed gift cards", examples: ["openmates settings billing gift-card list"] },
|
|
7694
7767
|
{ path: ["billing", "auto-topup", "low-balance", "set"], description: "Configure low-balance auto top-up", examples: ["openmates settings billing auto-topup low-balance set --enabled true --amount 1000 --currency eur --email you@example.com"] },
|
|
7768
|
+
{ path: ["notifications", "status"], description: "Show notification settings", examples: ["openmates settings notifications status --json"] },
|
|
7769
|
+
{ path: ["notifications", "email", "set"], description: "Configure email notifications", examples: ["openmates settings notifications email set --enabled true --email you@example.com --ai-responses true --backup-reminder true --webhook-chats true"] },
|
|
7770
|
+
{ path: ["notifications", "backup", "set"], description: "Configure backup reminder emails", examples: ["openmates settings notifications backup set --enabled true --interval 30 --email you@example.com"] },
|
|
7695
7771
|
{ path: ["reminders", "list"], description: "List active reminders", examples: ["openmates settings reminders list"] },
|
|
7696
7772
|
{ path: ["reminders", "update"], description: "Update a reminder", examples: ["openmates settings reminders update <id> --enabled false"] },
|
|
7697
7773
|
{ path: ["reminders", "delete"], description: "Delete a reminder", examples: ["openmates settings reminders delete <id> --yes"] },
|
|
@@ -7699,12 +7775,18 @@ var SETTINGS_EXECUTABLE_COMMANDS = [
|
|
|
7699
7775
|
{ path: ["developers", "api-keys", "revoke"], description: "Revoke an API key", examples: ["openmates settings developers api-keys revoke <key-id> --yes"] },
|
|
7700
7776
|
{ path: ["report-issue", "create"], description: "Report an issue", examples: ['openmates settings report-issue create --title "Bug" --body "What happened"'] },
|
|
7701
7777
|
{ path: ["report-issue", "status"], description: "Show issue status", examples: ["openmates settings report-issue status <issue-id>"] },
|
|
7778
|
+
{ path: ["mates", "list"], description: "List available mates", examples: ["openmates settings mates list"] },
|
|
7779
|
+
{ path: ["mates", "info"], description: "Show mate details", examples: ["openmates settings mates info software_development"] },
|
|
7780
|
+
{ path: ["mates", "consent"], description: "Record mate settings consent", examples: ["openmates settings mates consent --yes"] },
|
|
7781
|
+
{ path: ["newsletter", "categories"], description: "Show newsletter category preferences", examples: ["openmates settings newsletter categories"] },
|
|
7782
|
+
{ path: ["newsletter", "categories", "set"], description: "Set newsletter category preferences", examples: ["openmates settings newsletter categories set --updates true --tips true --daily false"] },
|
|
7783
|
+
{ path: ["newsletter", "subscribe"], description: "Subscribe an email to the newsletter", examples: ["openmates settings newsletter subscribe you@example.com --language en"] },
|
|
7784
|
+
{ path: ["newsletter", "confirm"], description: "Confirm newsletter subscription token", examples: ["openmates settings newsletter confirm <token>"] },
|
|
7785
|
+
{ path: ["newsletter", "unsubscribe"], description: "Unsubscribe with newsletter token", examples: ["openmates settings newsletter unsubscribe <token>"] },
|
|
7702
7786
|
{ path: ["memories"], description: "Manage encrypted memories", examples: ["openmates settings memories list", `openmates settings memories create --app-id code --item-type projects --data '{"name":"OpenMates"}'`] }
|
|
7703
7787
|
];
|
|
7704
7788
|
var SETTINGS_INFO_COMMANDS = [
|
|
7705
|
-
{ path: ["account", "username"], description: "Username management is not CLI-ready yet", webPath: "account/username", reason: "The current web flow writes encrypted profile fields; CLI support needs a dedicated encryption UX first.", examples: ["openmates settings account username --help"] },
|
|
7706
7789
|
{ path: ["account", "email"], description: "Email changes are web-only", webPath: "account/email", reason: "Email changes require a guided identity verification flow.", examples: ["openmates settings account email"] },
|
|
7707
|
-
{ path: ["account", "profile-picture"], description: "Profile picture changes are not CLI-ready yet", webPath: "account/profile-picture", reason: "The upload/update flow needs image validation and parity with the browser uploader.", examples: ["openmates settings account profile-picture"] },
|
|
7708
7790
|
{ path: ["account", "delete"], description: "Account deletion is web-only", webPath: "account/delete", reason: "Account deletion requires browser-based reauthentication and explicit confirmation.", examples: ["openmates settings account delete"] },
|
|
7709
7791
|
{ path: ["security"], description: "Security settings are web-only", webPath: "account/security", reason: "Security settings require browser APIs or high-risk reauthentication.", examples: ["openmates settings security"] },
|
|
7710
7792
|
{ path: ["security", "passkeys"], description: "Passkeys are web-only", webPath: "account/security/passkeys", reason: "Passkeys require WebAuthn browser APIs.", examples: ["openmates settings security passkeys"] },
|
|
@@ -7715,16 +7797,12 @@ var SETTINGS_INFO_COMMANDS = [
|
|
|
7715
7797
|
{ path: ["billing", "buy-credits"], description: "Credit purchase is web-only", webPath: "billing/buy-credits", reason: "Payment checkout must use the browser/payment provider UI.", examples: ["openmates settings billing buy-credits"] },
|
|
7716
7798
|
{ path: ["billing", "gift-card", "buy"], description: "Gift card purchase is web-only", webPath: "billing/gift-cards/buy", reason: "Payment checkout must use the browser/payment provider UI.", examples: ["openmates settings billing gift-card buy"] },
|
|
7717
7799
|
{ path: ["billing", "auto-topup", "monthly"], description: "Monthly auto top-up is web-only for now", webPath: "billing/auto-topup/monthly", reason: "Recurring payment setup needs a payment-flow audit before CLI support.", examples: ["openmates settings billing auto-topup monthly"] },
|
|
7718
|
-
{ path: ["billing", "invoices"], description: "Invoice management is web-only for now", webPath: "billing/invoices", reason: "Invoice download support needs auth-gated file streaming parity first.", examples: ["openmates settings billing invoices"] },
|
|
7719
7800
|
{ path: ["privacy", "personal-data"], description: "Personal data management is not CLI-ready yet", webPath: "privacy/hide-personal-data", reason: "The CLI needs a dedicated encrypted personal-data UX before exposing writes.", examples: ["openmates settings privacy personal-data"] },
|
|
7720
|
-
{ path: ["notifications"], description: "Notification preferences are not CLI-ready yet", webPath: "notifications", reason: "Backend preference endpoints need an audit before terminal writes are exposed.", examples: ["openmates settings notifications"] },
|
|
7721
7801
|
{ path: ["shared", "tip"], description: "Tips are web-only", webPath: "shared/tip", reason: "Payment checkout must use the browser/payment provider UI.", examples: ["openmates settings shared tip"] },
|
|
7722
|
-
{ path: ["mates"], description: "Mate browsing is available through mentions for now", webPath: "mates", reason: "Use @mate mentions in chat; rich mate settings remain browser-first.", examples: ["openmates mentions list --type mate"] },
|
|
7723
7802
|
{ path: ["developers", "api-keys", "create"], description: "API key creation is web-only", webPath: "developers/api-keys", reason: "API key secrets are shown once and need the browser approval flow.", examples: ["openmates settings developers api-keys create"] },
|
|
7724
7803
|
{ path: ["developers", "devices"], description: "Developer devices are web-only", webPath: "developers/devices", reason: "Device approvals and revocations are sensitive.", examples: ["openmates settings developers devices"] },
|
|
7725
7804
|
{ path: ["developers", "webhooks"], description: "Developer webhooks are not CLI-ready yet", webPath: "developers/webhooks", reason: "Webhook CRUD needs a backend/API audit before CLI support.", examples: ["openmates settings developers webhooks"] },
|
|
7726
7805
|
{ path: ["support"], description: "Support payments are web-only", webPath: "support", reason: "Payment flows must use the browser/payment provider UI.", examples: ["openmates settings support"] },
|
|
7727
|
-
{ path: ["newsletter"], description: "Newsletter settings are not CLI-ready yet", webPath: "newsletter", reason: "Newsletter API flow needs an audit before CLI support.", examples: ["openmates settings newsletter"] },
|
|
7728
7806
|
{ path: ["incognito", "info"], description: "Explain incognito mode", reason: "Incognito chats are sent without saving chat history. The CLI stores no incognito transcript.", examples: ['openmates chats incognito "Private question"'] },
|
|
7729
7807
|
{ path: ["server"], description: "Server admin settings are web/admin-only", webPath: "server", reason: "Use `openmates server --help` for self-hosted terminal server management.", examples: ["openmates server status"] }
|
|
7730
7808
|
];
|
|
@@ -7762,6 +7840,11 @@ function parseRequiredNumber(value, flag) {
|
|
|
7762
7840
|
if (!Number.isFinite(parsed)) throw new Error(`Invalid ${flag}: ${value}`);
|
|
7763
7841
|
return parsed;
|
|
7764
7842
|
}
|
|
7843
|
+
function parseOptionalBoolean(value, fallback, label) {
|
|
7844
|
+
if (value === void 0) return fallback;
|
|
7845
|
+
if (typeof value === "boolean") return value;
|
|
7846
|
+
return parseOnOff(value, label);
|
|
7847
|
+
}
|
|
7765
7848
|
function parseDataOrFlags(flags, booleanFlags) {
|
|
7766
7849
|
if (typeof flags.data === "string") return JSON.parse(flags.data);
|
|
7767
7850
|
const body = {};
|
|
@@ -7873,6 +7956,37 @@ function parseYamlScalar(value) {
|
|
|
7873
7956
|
}
|
|
7874
7957
|
return trimmed;
|
|
7875
7958
|
}
|
|
7959
|
+
async function saveDownloadedDocument(document, output) {
|
|
7960
|
+
const { mkdir, writeFile } = await import("fs/promises");
|
|
7961
|
+
const { join: join4, basename: basename2, dirname } = await import("path");
|
|
7962
|
+
const target = typeof output === "string" ? output : ".";
|
|
7963
|
+
const filename = basename2(document.filename || "document.pdf");
|
|
7964
|
+
const filePath = target.endsWith(".pdf") ? target : join4(target, filename);
|
|
7965
|
+
await mkdir(dirname(filePath), { recursive: true });
|
|
7966
|
+
await writeFile(filePath, document.data);
|
|
7967
|
+
return filePath;
|
|
7968
|
+
}
|
|
7969
|
+
function printMates(json) {
|
|
7970
|
+
const mates = Object.entries(MATE_NAMES).map(([id, name]) => ({ id, name, mention: `@mate:${id}` }));
|
|
7971
|
+
if (json) {
|
|
7972
|
+
printJson2(mates);
|
|
7973
|
+
return;
|
|
7974
|
+
}
|
|
7975
|
+
header("Mates");
|
|
7976
|
+
for (const mate of mates) console.log(`${mate.id.padEnd(28)} ${mate.name.padEnd(10)} ${mate.mention}`);
|
|
7977
|
+
}
|
|
7978
|
+
function printMateInfo(mateId, json) {
|
|
7979
|
+
const name = MATE_NAMES[mateId];
|
|
7980
|
+
if (!name) throw new Error(`Unknown mate '${mateId}'. Run 'openmates settings mates list'.`);
|
|
7981
|
+
const data = { id: mateId, name, mention: `@mate:${mateId}` };
|
|
7982
|
+
if (json) {
|
|
7983
|
+
printJson2(data);
|
|
7984
|
+
return;
|
|
7985
|
+
}
|
|
7986
|
+
header(`${name} (${mateId})`);
|
|
7987
|
+
console.log(`Mention: ${data.mention}`);
|
|
7988
|
+
console.log(`Use: openmates chats send "${data.mention} <message>"`);
|
|
7989
|
+
}
|
|
7876
7990
|
async function confirmOrExit(question) {
|
|
7877
7991
|
const rl = await import("readline");
|
|
7878
7992
|
const iface = rl.createInterface({ input: process.stdin, output: process.stdout });
|
|
@@ -7957,6 +8071,18 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
7957
8071
|
);
|
|
7958
8072
|
return;
|
|
7959
8073
|
}
|
|
8074
|
+
if (matches(tokens, ["account", "username", "set"])) {
|
|
8075
|
+
const username = rest[2];
|
|
8076
|
+
if (!username) throw new Error("Missing username. Example: openmates settings account username set alice_123");
|
|
8077
|
+
await printSettingsMutationResult(client.updateUsername(username), flags);
|
|
8078
|
+
return;
|
|
8079
|
+
}
|
|
8080
|
+
if (matches(tokens, ["account", "profile-picture", "set"])) {
|
|
8081
|
+
const file = rest[2];
|
|
8082
|
+
if (!file) throw new Error("Missing image file. Example: openmates settings account profile-picture set ./avatar.jpg");
|
|
8083
|
+
await printSettingsMutationResult(client.updateProfileImage(file), flags);
|
|
8084
|
+
return;
|
|
8085
|
+
}
|
|
7960
8086
|
if (matches(tokens, ["account", "chats", "stats"])) {
|
|
7961
8087
|
await printSettingsResult(client.settingsGet("chats"), flags);
|
|
7962
8088
|
return;
|
|
@@ -8046,6 +8172,37 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
8046
8172
|
await printSettingsResult(client.settingsGet("usage"), flags);
|
|
8047
8173
|
return;
|
|
8048
8174
|
}
|
|
8175
|
+
if (matches(tokens, ["billing", "invoices", "list"])) {
|
|
8176
|
+
await printSettingsResult(client.listInvoices(), flags);
|
|
8177
|
+
return;
|
|
8178
|
+
}
|
|
8179
|
+
if (matches(tokens, ["billing", "invoices", "download"])) {
|
|
8180
|
+
const invoiceId = rest[2];
|
|
8181
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
8182
|
+
const document = await client.downloadInvoice(invoiceId);
|
|
8183
|
+
const filePath = await saveDownloadedDocument(document, flags.output);
|
|
8184
|
+
if (flags.json === true) printJson2({ path: filePath, filename: document.filename });
|
|
8185
|
+
else console.log(`\x1B[32m\u2713\x1B[0m Invoice saved to ${filePath}`);
|
|
8186
|
+
return;
|
|
8187
|
+
}
|
|
8188
|
+
if (matches(tokens, ["billing", "invoices", "credit-note"])) {
|
|
8189
|
+
const invoiceId = rest[2];
|
|
8190
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
8191
|
+
const document = await client.downloadCreditNote(invoiceId);
|
|
8192
|
+
const filePath = await saveDownloadedDocument(document, flags.output);
|
|
8193
|
+
if (flags.json === true) printJson2({ path: filePath, filename: document.filename });
|
|
8194
|
+
else console.log(`\x1B[32m\u2713\x1B[0m Credit note saved to ${filePath}`);
|
|
8195
|
+
return;
|
|
8196
|
+
}
|
|
8197
|
+
if (matches(tokens, ["billing", "invoices", "refund"])) {
|
|
8198
|
+
const invoiceId = rest[2];
|
|
8199
|
+
const emailEncryptionKey = typeof flags["email-encryption-key"] === "string" ? flags["email-encryption-key"] : void 0;
|
|
8200
|
+
if (!invoiceId) throw new Error("Missing invoice ID.");
|
|
8201
|
+
if (!emailEncryptionKey) throw new Error("Missing --email-encryption-key. The backend requires the encrypted email key for refund requests.");
|
|
8202
|
+
if (flags.yes !== true) await confirmOrExit(`Request refund for invoice ${invoiceId}? [y/N] `);
|
|
8203
|
+
await printSettingsMutationResult(client.requestRefund(invoiceId, emailEncryptionKey), flags);
|
|
8204
|
+
return;
|
|
8205
|
+
}
|
|
8049
8206
|
if (matches(tokens, ["billing", "gift-card", "redeem"]) || subcommand === "gift-card" && rest[0] === "redeem") {
|
|
8050
8207
|
const code = matches(tokens, ["billing", "gift-card", "redeem"]) ? rest[2] : rest[1];
|
|
8051
8208
|
if (!code) throw new Error("Missing gift card code.");
|
|
@@ -8079,6 +8236,48 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
8079
8236
|
);
|
|
8080
8237
|
return;
|
|
8081
8238
|
}
|
|
8239
|
+
if (matches(tokens, ["notifications", "status"])) {
|
|
8240
|
+
const user = await client.whoAmI();
|
|
8241
|
+
const status = {
|
|
8242
|
+
enabled: user.email_notifications_enabled ?? false,
|
|
8243
|
+
preferences: user.email_notification_preferences ?? {},
|
|
8244
|
+
backup_reminder_interval_days: user.backup_reminder_interval_days ?? null,
|
|
8245
|
+
encrypted_notification_email_configured: Boolean(user.encrypted_notification_email)
|
|
8246
|
+
};
|
|
8247
|
+
flags.json === true ? printJson2(status) : printGenericObject(status);
|
|
8248
|
+
return;
|
|
8249
|
+
}
|
|
8250
|
+
if (matches(tokens, ["notifications", "email", "set"])) {
|
|
8251
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "email notifications");
|
|
8252
|
+
const email = typeof flags.email === "string" ? flags.email : null;
|
|
8253
|
+
if (enabled && !email) throw new Error("Provide --email when enabling email notifications.");
|
|
8254
|
+
const preferences = {
|
|
8255
|
+
aiResponses: parseOptionalBoolean(flags["ai-responses"], true, "AI response notifications"),
|
|
8256
|
+
backupReminder: parseOptionalBoolean(flags["backup-reminder"], false, "backup reminder notifications"),
|
|
8257
|
+
webhookChats: parseOptionalBoolean(flags["webhook-chats"], false, "webhook chat notifications")
|
|
8258
|
+
};
|
|
8259
|
+
await printSettingsMutationResult(
|
|
8260
|
+
client.updateEmailNotificationSettings({ enabled, email, preferences }),
|
|
8261
|
+
flags
|
|
8262
|
+
);
|
|
8263
|
+
return;
|
|
8264
|
+
}
|
|
8265
|
+
if (matches(tokens, ["notifications", "backup", "set"])) {
|
|
8266
|
+
const enabled = parseOnOff(String(flags.enabled ?? ""), "backup reminders");
|
|
8267
|
+
const email = typeof flags.email === "string" ? flags.email : null;
|
|
8268
|
+
const interval = parseRequiredNumber(flags.interval, "--interval");
|
|
8269
|
+
if (enabled && !email) throw new Error("Provide --email when enabling backup reminders.");
|
|
8270
|
+
await printSettingsMutationResult(
|
|
8271
|
+
client.updateEmailNotificationSettings({
|
|
8272
|
+
enabled,
|
|
8273
|
+
email,
|
|
8274
|
+
preferences: { aiResponses: false, backupReminder: enabled },
|
|
8275
|
+
backup_reminder_interval_days: interval
|
|
8276
|
+
}),
|
|
8277
|
+
flags
|
|
8278
|
+
);
|
|
8279
|
+
return;
|
|
8280
|
+
}
|
|
8082
8281
|
if (matches(tokens, ["reminders", "list"])) {
|
|
8083
8282
|
await printSettingsResult(client.settingsGet("reminders"), flags);
|
|
8084
8283
|
return;
|
|
@@ -8121,6 +8320,54 @@ async function handleSettings(client, subcommand, rest, flags) {
|
|
|
8121
8320
|
await printSettingsResult(client.settingsGet(`issues/${id}/status`), flags);
|
|
8122
8321
|
return;
|
|
8123
8322
|
}
|
|
8323
|
+
if (matches(tokens, ["mates", "list"])) {
|
|
8324
|
+
printMates(flags.json === true);
|
|
8325
|
+
return;
|
|
8326
|
+
}
|
|
8327
|
+
if (matches(tokens, ["mates", "info"])) {
|
|
8328
|
+
const mateId = rest[1];
|
|
8329
|
+
if (!mateId) throw new Error("Missing mate ID. Example: openmates settings mates info software_development");
|
|
8330
|
+
printMateInfo(mateId, flags.json === true);
|
|
8331
|
+
return;
|
|
8332
|
+
}
|
|
8333
|
+
if (matches(tokens, ["mates", "consent"])) {
|
|
8334
|
+
if (flags.yes !== true) await confirmOrExit("Record consent for mate settings? [y/N] ");
|
|
8335
|
+
await printSettingsMutationResult(client.settingsPost("user/consent/mates", { consent: true }), flags);
|
|
8336
|
+
return;
|
|
8337
|
+
}
|
|
8338
|
+
if (matches(tokens, ["newsletter", "categories"]) && tokens.length === 2) {
|
|
8339
|
+
await printSettingsResult(client.getNewsletterCategories(), flags);
|
|
8340
|
+
return;
|
|
8341
|
+
}
|
|
8342
|
+
if (matches(tokens, ["newsletter", "categories", "set"])) {
|
|
8343
|
+
const categories = {
|
|
8344
|
+
updates_and_announcements: parseOptionalBoolean(flags.updates, true, "updates newsletter"),
|
|
8345
|
+
tips_and_tricks: parseOptionalBoolean(flags.tips, true, "tips newsletter"),
|
|
8346
|
+
daily_inspirations: parseOptionalBoolean(flags.daily, true, "daily inspirations newsletter")
|
|
8347
|
+
};
|
|
8348
|
+
await printSettingsMutationResult(client.updateNewsletterCategories(categories), flags);
|
|
8349
|
+
return;
|
|
8350
|
+
}
|
|
8351
|
+
if (matches(tokens, ["newsletter", "subscribe"])) {
|
|
8352
|
+
const email = rest[1];
|
|
8353
|
+
if (!email) throw new Error("Missing email. Example: openmates settings newsletter subscribe you@example.com");
|
|
8354
|
+
const language = typeof flags.language === "string" ? flags.language : "en";
|
|
8355
|
+
const darkmode = parseOptionalBoolean(flags.darkmode, false, "newsletter dark mode");
|
|
8356
|
+
await printSettingsMutationResult(client.subscribeNewsletter(email, language, darkmode), flags);
|
|
8357
|
+
return;
|
|
8358
|
+
}
|
|
8359
|
+
if (matches(tokens, ["newsletter", "confirm"])) {
|
|
8360
|
+
const token = rest[1];
|
|
8361
|
+
if (!token) throw new Error("Missing confirmation token.");
|
|
8362
|
+
await printSettingsMutationResult(client.confirmNewsletter(token), flags);
|
|
8363
|
+
return;
|
|
8364
|
+
}
|
|
8365
|
+
if (matches(tokens, ["newsletter", "unsubscribe"])) {
|
|
8366
|
+
const token = rest[1];
|
|
8367
|
+
if (!token) throw new Error("Missing unsubscribe token.");
|
|
8368
|
+
await printSettingsMutationResult(client.unsubscribeNewsletter(token), flags);
|
|
8369
|
+
return;
|
|
8370
|
+
}
|
|
8124
8371
|
if (subcommand === "memories") {
|
|
8125
8372
|
await handleMemories(client, rest, flags);
|
|
8126
8373
|
return;
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
// src/uploadService.ts
|
|
2
|
+
import { readFileSync } from "fs";
|
|
3
|
+
import { basename, extname } from "path";
|
|
4
|
+
var UPLOAD_MAX_ATTEMPTS = 3;
|
|
5
|
+
var UPLOAD_RETRY_DELAY_MS = 2e3;
|
|
6
|
+
var PROFILE_IMAGE_MAX_SIZE_BYTES = 300 * 1024;
|
|
7
|
+
function getUploadUrl(apiUrl) {
|
|
8
|
+
try {
|
|
9
|
+
const url = new URL(apiUrl);
|
|
10
|
+
if (url.hostname === "localhost") return "http://localhost:8001";
|
|
11
|
+
} catch {
|
|
12
|
+
}
|
|
13
|
+
return "https://upload.openmates.org";
|
|
14
|
+
}
|
|
15
|
+
function getUploadOrigin(apiUrl) {
|
|
16
|
+
try {
|
|
17
|
+
const url = new URL(apiUrl);
|
|
18
|
+
if (url.hostname === "localhost") return "http://localhost:5173";
|
|
19
|
+
if (url.hostname.startsWith("api.")) {
|
|
20
|
+
return `${url.protocol}//app.${url.hostname.slice(4)}`;
|
|
21
|
+
}
|
|
22
|
+
} catch {
|
|
23
|
+
}
|
|
24
|
+
return "https://app.openmates.org";
|
|
25
|
+
}
|
|
26
|
+
async function uploadFile(filePath, session) {
|
|
27
|
+
const filename = basename(filePath);
|
|
28
|
+
const fileBytes = readFileSync(filePath);
|
|
29
|
+
const uploadUrl = `${getUploadUrl(session.apiUrl)}/v1/upload/file`;
|
|
30
|
+
const origin = getUploadOrigin(session.apiUrl);
|
|
31
|
+
const cookies = [];
|
|
32
|
+
if (session.cookies?.auth_refresh_token) {
|
|
33
|
+
cookies.push(`auth_refresh_token=${session.cookies.auth_refresh_token}`);
|
|
34
|
+
}
|
|
35
|
+
let response;
|
|
36
|
+
let lastError;
|
|
37
|
+
for (let attempt = 1; attempt <= UPLOAD_MAX_ATTEMPTS; attempt++) {
|
|
38
|
+
try {
|
|
39
|
+
const blob = new Blob([fileBytes]);
|
|
40
|
+
const formData = new FormData();
|
|
41
|
+
formData.append("file", blob, filename);
|
|
42
|
+
response = await fetch(uploadUrl, {
|
|
43
|
+
method: "POST",
|
|
44
|
+
body: formData,
|
|
45
|
+
headers: {
|
|
46
|
+
Origin: origin,
|
|
47
|
+
...cookies.length > 0 ? { Cookie: cookies.join("; ") } : {}
|
|
48
|
+
},
|
|
49
|
+
signal: AbortSignal.timeout(10 * 60 * 1e3)
|
|
50
|
+
// 10-minute timeout
|
|
51
|
+
});
|
|
52
|
+
break;
|
|
53
|
+
} catch (error) {
|
|
54
|
+
lastError = error;
|
|
55
|
+
if (attempt === UPLOAD_MAX_ATTEMPTS) break;
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, UPLOAD_RETRY_DELAY_MS));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
if (!response) {
|
|
60
|
+
const message = lastError instanceof Error ? lastError.message : String(lastError);
|
|
61
|
+
throw new Error(message || "Upload request failed.");
|
|
62
|
+
}
|
|
63
|
+
if (!response.ok) {
|
|
64
|
+
const status = response.status;
|
|
65
|
+
let errorMessage;
|
|
66
|
+
switch (status) {
|
|
67
|
+
case 401:
|
|
68
|
+
errorMessage = "Authentication failed. Run `openmates login` to re-authenticate.";
|
|
69
|
+
break;
|
|
70
|
+
case 413:
|
|
71
|
+
errorMessage = "File too large (maximum 100 MB).";
|
|
72
|
+
break;
|
|
73
|
+
case 415:
|
|
74
|
+
errorMessage = "Unsupported file type.";
|
|
75
|
+
break;
|
|
76
|
+
case 422: {
|
|
77
|
+
const body = await response.text().catch(() => "");
|
|
78
|
+
errorMessage = body.includes("malware") ? "File rejected: malware detected." : body.includes("content_safety") ? "File rejected: content safety violation." : `Upload validation failed: ${body}`;
|
|
79
|
+
break;
|
|
80
|
+
}
|
|
81
|
+
case 429:
|
|
82
|
+
errorMessage = "Upload rate limit exceeded. Try again in a minute.";
|
|
83
|
+
break;
|
|
84
|
+
default:
|
|
85
|
+
errorMessage = `Upload failed (HTTP ${status}).`;
|
|
86
|
+
}
|
|
87
|
+
throw new Error(errorMessage);
|
|
88
|
+
}
|
|
89
|
+
const data = await response.json();
|
|
90
|
+
return data;
|
|
91
|
+
}
|
|
92
|
+
function getProfileImageMime(filename) {
|
|
93
|
+
const ext = extname(filename).toLowerCase();
|
|
94
|
+
if (ext === ".jpg" || ext === ".jpeg") return "image/jpeg";
|
|
95
|
+
if (ext === ".png") return "image/png";
|
|
96
|
+
throw new Error("Profile images must be JPEG or PNG files.");
|
|
97
|
+
}
|
|
98
|
+
async function uploadProfileImage(filePath, session) {
|
|
99
|
+
const filename = basename(filePath);
|
|
100
|
+
const fileBytes = readFileSync(filePath);
|
|
101
|
+
const contentType = getProfileImageMime(filename);
|
|
102
|
+
if (fileBytes.byteLength > PROFILE_IMAGE_MAX_SIZE_BYTES) {
|
|
103
|
+
throw new Error("Profile image must be 300 KB or smaller. Resize/compress the image and try again.");
|
|
104
|
+
}
|
|
105
|
+
const uploadUrl = `${getUploadUrl(session.apiUrl)}/v1/upload/profile-image`;
|
|
106
|
+
const origin = getUploadOrigin(session.apiUrl);
|
|
107
|
+
const cookies = [];
|
|
108
|
+
if (session.cookies?.auth_refresh_token) {
|
|
109
|
+
cookies.push(`auth_refresh_token=${session.cookies.auth_refresh_token}`);
|
|
110
|
+
}
|
|
111
|
+
const formData = new FormData();
|
|
112
|
+
formData.append("file", new Blob([fileBytes], { type: contentType }), filename);
|
|
113
|
+
const response = await fetch(uploadUrl, {
|
|
114
|
+
method: "POST",
|
|
115
|
+
body: formData,
|
|
116
|
+
headers: {
|
|
117
|
+
Origin: origin,
|
|
118
|
+
...cookies.length > 0 ? { Cookie: cookies.join("; ") } : {}
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
const data = await response.json().catch(() => ({}));
|
|
122
|
+
if (!response.ok && !data.status) {
|
|
123
|
+
throw new Error(data.detail ?? `Profile image upload failed (HTTP ${response.status}).`);
|
|
124
|
+
}
|
|
125
|
+
return data;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
uploadFile,
|
|
130
|
+
uploadProfileImage
|
|
131
|
+
};
|
package/dist/cli.js
CHANGED
package/dist/index.d.ts
CHANGED
|
@@ -293,6 +293,23 @@ interface OpenMatesClientOptions {
|
|
|
293
293
|
apiUrl?: string;
|
|
294
294
|
session?: OpenMatesSession;
|
|
295
295
|
}
|
|
296
|
+
interface InvoiceListItem {
|
|
297
|
+
id: string;
|
|
298
|
+
order_id?: string | null;
|
|
299
|
+
date: string;
|
|
300
|
+
amount: string;
|
|
301
|
+
credits_purchased: number;
|
|
302
|
+
filename: string;
|
|
303
|
+
is_gift_card?: boolean;
|
|
304
|
+
refunded_at?: string | null;
|
|
305
|
+
refund_status?: string | null;
|
|
306
|
+
currency?: string | null;
|
|
307
|
+
provider?: string | null;
|
|
308
|
+
}
|
|
309
|
+
interface DownloadedDocument {
|
|
310
|
+
filename: string;
|
|
311
|
+
data: Uint8Array;
|
|
312
|
+
}
|
|
296
313
|
/**
|
|
297
314
|
* Derive the web app URL from the API URL so the pair token is always looked
|
|
298
315
|
* up on the same backend the CLI created it on.
|
|
@@ -467,6 +484,25 @@ declare class OpenMatesClient {
|
|
|
467
484
|
message: string;
|
|
468
485
|
}>;
|
|
469
486
|
listRedeemedGiftCards(): Promise<unknown>;
|
|
487
|
+
listInvoices(): Promise<{
|
|
488
|
+
invoices: InvoiceListItem[];
|
|
489
|
+
}>;
|
|
490
|
+
downloadInvoice(invoiceId: string): Promise<DownloadedDocument>;
|
|
491
|
+
downloadCreditNote(invoiceId: string): Promise<DownloadedDocument>;
|
|
492
|
+
requestRefund(invoiceId: string, emailEncryptionKey: string): Promise<unknown>;
|
|
493
|
+
updateUsername(username: string): Promise<unknown>;
|
|
494
|
+
updateProfileImage(filePath: string): Promise<unknown>;
|
|
495
|
+
getNewsletterCategories(): Promise<unknown>;
|
|
496
|
+
updateNewsletterCategories(categories: Record<string, boolean>): Promise<unknown>;
|
|
497
|
+
subscribeNewsletter(email: string, language?: string, darkmode?: boolean): Promise<unknown>;
|
|
498
|
+
confirmNewsletter(token: string): Promise<unknown>;
|
|
499
|
+
unsubscribeNewsletter(token: string): Promise<unknown>;
|
|
500
|
+
updateEmailNotificationSettings(payload: {
|
|
501
|
+
enabled: boolean;
|
|
502
|
+
email?: string | null;
|
|
503
|
+
preferences: Record<string, boolean>;
|
|
504
|
+
backup_reminder_interval_days?: number;
|
|
505
|
+
}): Promise<unknown>;
|
|
470
506
|
/**
|
|
471
507
|
* Fetch today's daily inspirations.
|
|
472
508
|
*
|
|
@@ -600,6 +636,7 @@ declare class OpenMatesClient {
|
|
|
600
636
|
*/
|
|
601
637
|
buildMentionContext(): Promise<MentionContext>;
|
|
602
638
|
private normalizePath;
|
|
639
|
+
private downloadPaymentPdf;
|
|
603
640
|
private requireSession;
|
|
604
641
|
private getMasterKeyBytes;
|
|
605
642
|
private getValidSessionFromDisk;
|
package/dist/index.js
CHANGED