@emailthing/cli 0.0.0-alpha.1

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.
@@ -0,0 +1,180 @@
1
+ // @bun
2
+ // ../../packages/const/urls.ts
3
+ var API_URL = "https://api.emailthing.app";
4
+
5
+ // src/api/client.ts
6
+ class EmailThingCLI {
7
+ token = null;
8
+ refreshToken = null;
9
+ tokenExpiresAt = null;
10
+ baseURL;
11
+ constructor(baseURL = API_URL) {
12
+ this.baseURL = baseURL;
13
+ }
14
+ setAuth(token, refreshToken, tokenExpiresAt) {
15
+ this.token = token, this.refreshToken = refreshToken, this.tokenExpiresAt = new Date(tokenExpiresAt);
16
+ }
17
+ async internalFetch(pathname, method = "GET", body, extraHeaders) {
18
+ let headers = {
19
+ "Content-Type": "application/json",
20
+ Origin: "https://emailthing.app",
21
+ ...extraHeaders
22
+ };
23
+ if (this.token)
24
+ headers.Authorization = `session ${this.token}`;
25
+ return fetch(`${this.baseURL}/api/internal${pathname}`, {
26
+ method,
27
+ headers,
28
+ body: body ? JSON.stringify(body) : void 0
29
+ });
30
+ }
31
+ async login(username, password) {
32
+ let res = await this.internalFetch("/login?type=password", "POST", {
33
+ username,
34
+ password
35
+ });
36
+ if (!res.ok) {
37
+ let text = await res.text();
38
+ throw Error(text || "Login failed");
39
+ }
40
+ let data = await res.json();
41
+ if (!data || !data.token)
42
+ throw Error("Invalid login response: " + JSON.stringify(data));
43
+ return this.setAuth(data.token, data.refreshToken, data.tokenExpiresAt), data;
44
+ }
45
+ async refreshTokenIfNeeded() {
46
+ if (!this.token || !this.refreshToken || !this.tokenExpiresAt)
47
+ throw Error("Not authenticated");
48
+ let ONE_MINUTE_MS = 60000;
49
+ if (this.tokenExpiresAt.getTime() > Date.now() + ONE_MINUTE_MS)
50
+ return;
51
+ let res = await fetch(`${this.baseURL}/api/internal/refresh-token`, {
52
+ method: "POST",
53
+ headers: {
54
+ Authorization: `refresh ${this.refreshToken}`,
55
+ Origin: "https://emailthing.app"
56
+ }
57
+ });
58
+ if (!res.ok)
59
+ throw Error("Token refresh failed");
60
+ let data = await res.json();
61
+ this.setAuth(data.token, data.refreshToken, data.tokenExpiresAt);
62
+ }
63
+ async sync(lastSync, minimal = !1) {
64
+ await this.refreshTokenIfNeeded();
65
+ let params = new URLSearchParams;
66
+ if (minimal)
67
+ params.set("minimal", "true");
68
+ let extraHeaders = {};
69
+ if (lastSync) {
70
+ let lastSyncTime = new Date(lastSync).getTime();
71
+ extraHeaders["x-last-sync"] = (lastSyncTime + 1).toString();
72
+ }
73
+ let res = await this.internalFetch(`/sync?${params}`, "GET", void 0, extraHeaders);
74
+ if (!res.ok)
75
+ throw Error(`Sync failed: ${await res.text()}`);
76
+ return res.json();
77
+ }
78
+ async sendDraft(draft) {
79
+ await this.refreshTokenIfNeeded();
80
+ let res = await this.internalFetch("/send-draft", "POST", draft);
81
+ if (!res.ok)
82
+ throw Error(`Send failed: ${await res.text()}`);
83
+ return res.json();
84
+ }
85
+ async getRawEmail(mailboxId, mailId) {
86
+ await this.refreshTokenIfNeeded();
87
+ let res = await this.internalFetch(`/mailbox/${mailboxId}/mail/${mailId}/raw`);
88
+ if (!res.ok)
89
+ throw Error(`Failed to fetch raw email: ${await res.text()}`);
90
+ return res.text();
91
+ }
92
+ async modifyEmail(updates) {
93
+ await this.refreshTokenIfNeeded();
94
+ let payload = {
95
+ emails: [{
96
+ id: updates.id,
97
+ mailboxId: updates.mailboxId,
98
+ isRead: updates.isRead,
99
+ isStarred: updates.isStarred,
100
+ categoryId: updates.categoryId,
101
+ lastUpdated: (/* @__PURE__ */ new Date()).toISOString()
102
+ }]
103
+ }, res = await this.internalFetch("/sync", "POST", payload);
104
+ if (!res.ok)
105
+ throw Error(`Modify email failed: ${await res.text()}`);
106
+ return res.json();
107
+ }
108
+ async logout() {
109
+ if (!this.refreshToken)
110
+ return;
111
+ await this.internalFetch("/revoke-token", "DELETE"), this.token = null, this.refreshToken = null, this.tokenExpiresAt = null;
112
+ }
113
+ }
114
+
115
+ // src/utils/sync.ts
116
+ async function syncData(client, db, silent = !1) {
117
+ if (!silent)
118
+ console.error("Syncing...");
119
+ try {
120
+ let lastSync = db.query("SELECT lastSync FROM sync_state WHERE id = 1").get()?.lastSync;
121
+ if (!silent)
122
+ console.error(`Fetching from server (lastSync: ${lastSync || "none"})...`);
123
+ let fetchStart = Date.now(), syncData2 = await client.sync(lastSync || void 0, !1), fetchTime = Date.now() - fetchStart;
124
+ if (!silent)
125
+ console.error(`Got ${syncData2.emails.length} emails from server (fetch took ${fetchTime}ms)`);
126
+ if (!lastSync) {
127
+ if (!silent)
128
+ console.error("Clearing old data...");
129
+ db.run("DELETE FROM emails"), db.run("DELETE FROM mailboxes"), db.run("DELETE FROM categories"), db.run("DELETE FROM drafts");
130
+ }
131
+ let dbStart = Date.now();
132
+ if (db.run("BEGIN TRANSACTION"), !silent)
133
+ console.error("Inserting emails...");
134
+ let emailStmt = db.prepare(`
135
+ INSERT OR REPLACE INTO emails (id, mailboxId, subject, from_addr, to_addr, cc, bcc, replyTo, body, html, snippet, isRead, isStarred, createdAt, headers, categoryId, isDeleted)
136
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
137
+ `);
138
+ for (let email of syncData2.emails) {
139
+ let from = email.sender?.address || "", to = email.recipients?.[0]?.address || "", cc = email.recipients?.filter((r) => r.cc).map((r) => r.address).join(", ") || null;
140
+ emailStmt.run(email.id, email.mailboxId, email.subject || null, from, to, cc, email.bcc || null, email.replyTo || null, email.body || null, email.html || null, email.snippet || null, email.isRead ? !0 : !1, email.isStarred ? !0 : !1, email.createdAt, email.headers ? JSON.stringify(email.headers) : null, email.categoryId || null, email.isDeleted ? !0 : !1);
141
+ }
142
+ if (!silent)
143
+ console.error("Inserting mailboxes...");
144
+ let mailboxStmt = db.prepare(`
145
+ INSERT OR REPLACE INTO mailboxes (id, isDeleted)
146
+ VALUES (?, ?)
147
+ `);
148
+ for (let mailbox of syncData2.mailboxes)
149
+ mailboxStmt.run(mailbox.id, mailbox.isDeleted ? !0 : !1);
150
+ if (!silent)
151
+ console.error("Inserting categories...");
152
+ let categoryStmt = db.prepare(`
153
+ INSERT OR REPLACE INTO categories (id, mailboxId, name, color, order_num, isDeleted)
154
+ VALUES (?, ?, ?, ?, ?, ?)
155
+ `);
156
+ for (let category of syncData2.mailboxCategories)
157
+ categoryStmt.run(category.id, category.mailboxId, category.name, category.color || null, category.order || 0, category.isDeleted ? !0 : !1);
158
+ if (!silent)
159
+ console.error("Inserting mailbox aliases...");
160
+ let aliasStmt = db.prepare(`
161
+ INSERT OR REPLACE INTO mailbox_aliases (id, mailboxId, alias, name, createdAt, updatedAt, "default", isDeleted)
162
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?)
163
+ `);
164
+ for (let alias of syncData2.mailboxAliases || [])
165
+ aliasStmt.run(alias.id, alias.mailboxId, alias.alias, alias.name || null, alias.createdAt, alias.updatedAt, alias.default ? !0 : !1, alias.isDeleted ? !0 : !1);
166
+ db.run("COMMIT");
167
+ let dbTime = Date.now() - dbStart;
168
+ if (syncData2.time) {
169
+ if (db.run("INSERT OR REPLACE INTO sync_state (id, lastSync) VALUES (1, ?)", [syncData2.time]), !silent)
170
+ console.error(`Synced ${syncData2.emails.length} emails, ${syncData2.mailboxes.length} mailboxes, ${syncData2.mailboxCategories.length} categories (DB took ${dbTime}ms)`), console.error(`New lastSync saved: ${syncData2.time}`);
171
+ } else if (!silent)
172
+ console.error(`Synced ${syncData2.emails.length} emails, ${syncData2.mailboxes.length} mailboxes, ${syncData2.mailboxCategories.length} categories (DB took ${dbTime}ms)`), console.error("WARNING: API did not return 'time' field - lastSync not updated!");
173
+ } catch (error) {
174
+ if (!silent)
175
+ console.error("Sync failed:", error);
176
+ throw error;
177
+ }
178
+ }
179
+
180
+ export { EmailThingCLI, syncData };
package/dist/index.js ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bun
2
+ // @bun
3
+ import {
4
+ __require,
5
+ __toESM
6
+ } from "./index-afyvwmt5.js";
7
+
8
+ // src/index.ts
9
+ if (process.argv.includes("logout")) {
10
+ let { getDB, clearAuth, resetDB } = await import("./config-cfsae2jm.js"), db = getDB();
11
+ clearAuth(db), resetDB(db), console.log("Logged out successfully. Run again to login."), db.close();
12
+ } else if (process.argv.includes("agent") || (process.env.CLAUDECODE || process.env.OPENCODE || process.env.AGENT) === "1")
13
+ await import("./agent.js");
14
+ else if (!process.stdin.isTTY || !process.stdout.isTTY)
15
+ console.error("This application is meant to be run in an interactive terminal."), console.log("Use `bunx @emailthing/cli agent` flag to run in agent mode instead."), process.exit(1);
16
+ else
17
+ await import("./main.js");