surface-cli 0.1.0

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.
Files changed (60) hide show
  1. package/README.md +307 -0
  2. package/dist/cli.js +521 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/config.js +156 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/contracts/account.js +9 -0
  7. package/dist/contracts/account.js.map +1 -0
  8. package/dist/contracts/mail.js +2 -0
  9. package/dist/contracts/mail.js.map +1 -0
  10. package/dist/e2e/gmail-v1.js +247 -0
  11. package/dist/e2e/gmail-v1.js.map +1 -0
  12. package/dist/e2e/outlook-v1.js +179 -0
  13. package/dist/e2e/outlook-v1.js.map +1 -0
  14. package/dist/lib/errors.js +47 -0
  15. package/dist/lib/errors.js.map +1 -0
  16. package/dist/lib/json.js +4 -0
  17. package/dist/lib/json.js.map +1 -0
  18. package/dist/lib/public-mail.js +29 -0
  19. package/dist/lib/public-mail.js.map +1 -0
  20. package/dist/lib/remote-auth.js +407 -0
  21. package/dist/lib/remote-auth.js.map +1 -0
  22. package/dist/lib/time.js +4 -0
  23. package/dist/lib/time.js.map +1 -0
  24. package/dist/lib/write-safety.js +34 -0
  25. package/dist/lib/write-safety.js.map +1 -0
  26. package/dist/paths.js +29 -0
  27. package/dist/paths.js.map +1 -0
  28. package/dist/providers/gmail/adapter.js +1102 -0
  29. package/dist/providers/gmail/adapter.js.map +1 -0
  30. package/dist/providers/gmail/api.js +99 -0
  31. package/dist/providers/gmail/api.js.map +1 -0
  32. package/dist/providers/gmail/normalize.js +336 -0
  33. package/dist/providers/gmail/normalize.js.map +1 -0
  34. package/dist/providers/gmail/oauth.js +328 -0
  35. package/dist/providers/gmail/oauth.js.map +1 -0
  36. package/dist/providers/index.js +12 -0
  37. package/dist/providers/index.js.map +1 -0
  38. package/dist/providers/outlook/adapter.js +1443 -0
  39. package/dist/providers/outlook/adapter.js.map +1 -0
  40. package/dist/providers/outlook/extract.js +416 -0
  41. package/dist/providers/outlook/extract.js.map +1 -0
  42. package/dist/providers/outlook/normalize.js +126 -0
  43. package/dist/providers/outlook/normalize.js.map +1 -0
  44. package/dist/providers/outlook/session.js +178 -0
  45. package/dist/providers/outlook/session.js.map +1 -0
  46. package/dist/providers/shared/html.js +88 -0
  47. package/dist/providers/shared/html.js.map +1 -0
  48. package/dist/providers/shared/types.js +2 -0
  49. package/dist/providers/shared/types.js.map +1 -0
  50. package/dist/providers/types.js +2 -0
  51. package/dist/providers/types.js.map +1 -0
  52. package/dist/refs.js +18 -0
  53. package/dist/refs.js.map +1 -0
  54. package/dist/runtime.js +23 -0
  55. package/dist/runtime.js.map +1 -0
  56. package/dist/state/database.js +731 -0
  57. package/dist/state/database.js.map +1 -0
  58. package/dist/summarizer.js +217 -0
  59. package/dist/summarizer.js.map +1 -0
  60. package/package.json +55 -0
package/dist/config.js ADDED
@@ -0,0 +1,156 @@
1
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { dirname, resolve } from "node:path";
4
+ import { parse as parseToml } from "smol-toml";
5
+ import { z } from "zod";
6
+ const summarizerBackendSchema = z.enum(["openrouter", "openclaw", "none"]);
7
+ const sendModeSchema = z.enum(["draft_only", "allow_send"]);
8
+ const stringListSchema = z.union([z.string(), z.array(z.string())]);
9
+ const fileConfigSchema = z.object({
10
+ cache_dir: z.string().optional(),
11
+ default_result_limit: z.number().int().positive().optional(),
12
+ provider_timeout_ms: z.number().int().positive().optional(),
13
+ summarizer: z
14
+ .object({
15
+ backend: summarizerBackendSchema.optional(),
16
+ model: z.string().min(1).optional(),
17
+ timeout_ms: z.number().int().positive().optional(),
18
+ })
19
+ .optional(),
20
+ summary_input_max_bytes: z.number().int().positive().optional(),
21
+ summarizer_backend: summarizerBackendSchema.optional(),
22
+ summarizer_model: z.string().min(1).optional(),
23
+ summarizer_timeout_ms: z.number().int().positive().optional(),
24
+ writes_enabled: z.boolean().optional(),
25
+ send_mode: sendModeSchema.optional(),
26
+ test_recipients: stringListSchema.optional(),
27
+ test_account_allowlist: stringListSchema.optional(),
28
+ });
29
+ export function defaultConfigPath() {
30
+ return resolve(homedir(), ".surface-cli", "config.toml");
31
+ }
32
+ function defaultConfigTemplate() {
33
+ return [
34
+ "# Surface CLI local config",
35
+ "#",
36
+ "# This file stores local policy and preference knobs only.",
37
+ "# Account registry and auth state live in SQLite plus ~/.surface-cli/auth/.",
38
+ "",
39
+ "cache_dir = \"~/.surface-cli\"",
40
+ "default_result_limit = 50",
41
+ "provider_timeout_ms = 30000",
42
+ "summary_input_max_bytes = 16384",
43
+ "summarizer_backend = \"none\"",
44
+ "summarizer_model = \"openai/gpt-4o-mini\"",
45
+ "summarizer_timeout_ms = 20000",
46
+ "writes_enabled = false",
47
+ "send_mode = \"draft_only\"",
48
+ "test_recipients = []",
49
+ "test_account_allowlist = []",
50
+ "",
51
+ ].join("\n");
52
+ }
53
+ function ensureConfigFileExists(configPath) {
54
+ if (existsSync(configPath)) {
55
+ return;
56
+ }
57
+ mkdirSync(dirname(configPath), { recursive: true });
58
+ writeFileSync(configPath, defaultConfigTemplate(), "utf8");
59
+ }
60
+ function expandHomePath(value) {
61
+ if (value === "~") {
62
+ return homedir();
63
+ }
64
+ if (value.startsWith("~/")) {
65
+ return resolve(homedir(), value.slice(2));
66
+ }
67
+ return resolve(value);
68
+ }
69
+ function parseConfigFile(rawText) {
70
+ const parsed = parseToml(rawText);
71
+ return fileConfigSchema.parse(parsed);
72
+ }
73
+ function envInt(name) {
74
+ const rawValue = process.env[name];
75
+ if (rawValue === undefined || rawValue.trim() === "") {
76
+ return undefined;
77
+ }
78
+ const parsed = Number.parseInt(rawValue, 10);
79
+ if (Number.isNaN(parsed) || parsed <= 0) {
80
+ throw new Error(`Environment variable ${name} must be a positive integer.`);
81
+ }
82
+ return parsed;
83
+ }
84
+ function envBoolean(name) {
85
+ const rawValue = process.env[name];
86
+ if (rawValue === undefined || rawValue.trim() === "") {
87
+ return undefined;
88
+ }
89
+ const normalized = rawValue.trim().toLowerCase();
90
+ if (["1", "true", "yes", "on"].includes(normalized)) {
91
+ return true;
92
+ }
93
+ if (["0", "false", "no", "off"].includes(normalized)) {
94
+ return false;
95
+ }
96
+ throw new Error(`Environment variable ${name} must be a boolean.`);
97
+ }
98
+ function parseStringList(value) {
99
+ if (value === undefined) {
100
+ return undefined;
101
+ }
102
+ if (Array.isArray(value)) {
103
+ return value.map((entry) => entry.trim()).filter((entry) => entry.length > 0);
104
+ }
105
+ return value
106
+ .split(",")
107
+ .map((entry) => entry.trim())
108
+ .filter((entry) => entry.length > 0);
109
+ }
110
+ export function loadConfig(options = {}) {
111
+ const configPath = expandHomePath(options.configPath ?? process.env.SURFACE_CONFIG_PATH ?? defaultConfigPath());
112
+ ensureConfigFileExists(configPath);
113
+ let fileConfig = {};
114
+ try {
115
+ fileConfig = parseConfigFile(readFileSync(configPath, "utf8"));
116
+ }
117
+ catch (error) {
118
+ if (error.code !== "ENOENT") {
119
+ throw error;
120
+ }
121
+ }
122
+ const config = {
123
+ cacheDir: expandHomePath(process.env.SURFACE_CACHE_DIR ?? fileConfig.cache_dir ?? resolve(homedir(), ".surface-cli")),
124
+ defaultResultLimit: envInt("SURFACE_DEFAULT_RESULT_LIMIT") ?? fileConfig.default_result_limit ?? 50,
125
+ providerTimeoutMs: envInt("SURFACE_PROVIDER_TIMEOUT_MS") ?? fileConfig.provider_timeout_ms ?? 30_000,
126
+ summarizerBackend: process.env.SURFACE_SUMMARIZER_BACKEND ??
127
+ fileConfig.summarizer?.backend ??
128
+ fileConfig.summarizer_backend ??
129
+ "none",
130
+ summarizerModel: process.env.SURFACE_SUMMARIZER_MODEL ??
131
+ fileConfig.summarizer?.model ??
132
+ fileConfig.summarizer_model ??
133
+ "openai/gpt-4o-mini",
134
+ summaryInputMaxBytes: envInt("SURFACE_SUMMARY_INPUT_MAX_BYTES") ??
135
+ fileConfig.summary_input_max_bytes ??
136
+ 16_384,
137
+ summarizerTimeoutMs: envInt("SURFACE_SUMMARIZER_TIMEOUT_MS") ??
138
+ fileConfig.summarizer?.timeout_ms ??
139
+ fileConfig.summarizer_timeout_ms ??
140
+ 20_000,
141
+ writesEnabled: envBoolean("SURFACE_WRITES_ENABLED") ??
142
+ fileConfig.writes_enabled ??
143
+ false,
144
+ sendMode: process.env.SURFACE_SEND_MODE ??
145
+ fileConfig.send_mode ??
146
+ "draft_only",
147
+ testRecipients: parseStringList(process.env.SURFACE_TEST_RECIPIENTS) ??
148
+ parseStringList(fileConfig.test_recipients) ??
149
+ [],
150
+ testAccountAllowlist: parseStringList(process.env.SURFACE_TEST_ACCOUNT_ALLOWLIST) ??
151
+ parseStringList(fileConfig.test_account_allowlist) ??
152
+ [],
153
+ };
154
+ return { config, configPath };
155
+ }
156
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ import { z } from "zod";
2
+ export const providerSchema = z.enum(["gmail", "outlook"]);
3
+ export const accountInputSchema = z.object({
4
+ name: z.string().trim().min(1),
5
+ provider: providerSchema,
6
+ transport: z.string().trim().min(1),
7
+ email: z.email(),
8
+ });
9
+ //# sourceMappingURL=account.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"account.js","sourceRoot":"","sources":["../../src/contracts/account.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC,CAAC;AAG3D,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,CAAC,MAAM,CAAC;IACzC,IAAI,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IAC9B,QAAQ,EAAE,cAAc;IACxB,SAAS,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,IAAI,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;IACnC,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE;CACjB,CAAC,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=mail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mail.js","sourceRoot":"","sources":["../../src/contracts/mail.ts"],"names":[],"mappings":""}
@@ -0,0 +1,247 @@
1
+ import { existsSync } from "node:fs";
2
+ import { spawn } from "node:child_process";
3
+ import { fileURLToPath } from "node:url";
4
+ import { dirname, resolve } from "node:path";
5
+ import { createRuntimeContext } from "../runtime.js";
6
+ const __filename = fileURLToPath(import.meta.url);
7
+ const __dirname = dirname(__filename);
8
+ const repoRoot = resolve(__dirname, "..", "..");
9
+ function fail(message) {
10
+ throw new Error(message);
11
+ }
12
+ function assert(condition, message) {
13
+ if (!condition) {
14
+ fail(message);
15
+ }
16
+ }
17
+ function sleep(ms) {
18
+ return new Promise((resolvePromise) => {
19
+ setTimeout(resolvePromise, ms);
20
+ });
21
+ }
22
+ function uniqueSubject(prefix) {
23
+ return `[surface-e2e] ${prefix} ${Date.now()}`;
24
+ }
25
+ function parseCsv(value) {
26
+ return value
27
+ .split(",")
28
+ .map((entry) => entry.trim())
29
+ .filter((entry) => entry.length > 0);
30
+ }
31
+ function normalizeAssertionText(value) {
32
+ return value.replace(/\s+/g, " ").trim().toLowerCase();
33
+ }
34
+ function resolveTestRecipients() {
35
+ const fromEnv = process.env.SURFACE_TEST_RECIPIENTS?.trim();
36
+ if (fromEnv) {
37
+ return parseCsv(fromEnv);
38
+ }
39
+ const runtime = createRuntimeContext(process.env.SURFACE_CONFIG_PATH ? { configPath: process.env.SURFACE_CONFIG_PATH } : {});
40
+ try {
41
+ return runtime.config.testRecipients;
42
+ }
43
+ finally {
44
+ runtime.db.close();
45
+ }
46
+ }
47
+ async function runSurfaceJson(args, env) {
48
+ return await new Promise((resolvePromise, rejectPromise) => {
49
+ const child = spawn(process.execPath, ["dist/cli.js", ...args], {
50
+ cwd: repoRoot,
51
+ env,
52
+ stdio: ["ignore", "pipe", "pipe"],
53
+ });
54
+ let stdout = "";
55
+ let stderr = "";
56
+ child.stdout.on("data", (chunk) => {
57
+ stdout += chunk.toString("utf8");
58
+ });
59
+ child.stderr.on("data", (chunk) => {
60
+ stderr += chunk.toString("utf8");
61
+ });
62
+ child.on("error", rejectPromise);
63
+ child.on("close", (code) => {
64
+ let parsed = null;
65
+ try {
66
+ parsed = stdout.trim() ? JSON.parse(stdout) : null;
67
+ }
68
+ catch (error) {
69
+ rejectPromise(new Error(`surface ${args.join(" ")} returned non-JSON stdout.\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}\n${String(error)}`));
70
+ return;
71
+ }
72
+ if (code !== 0) {
73
+ rejectPromise(new Error(`surface ${args.join(" ")} failed with exit code ${code}.\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`));
74
+ return;
75
+ }
76
+ resolvePromise(parsed);
77
+ });
78
+ });
79
+ }
80
+ async function pollThreadBySubject(env, account, subject, predicate) {
81
+ for (let attempt = 0; attempt < 12; attempt += 1) {
82
+ const search = await runSurfaceJson(["mail", "search", "--account", account, "--text", subject, "--limit", "10"], env);
83
+ const thread = search.threads.find((candidate) => candidate.envelope.subject.includes(subject));
84
+ if (thread && predicate(thread)) {
85
+ return thread;
86
+ }
87
+ await sleep(2_500);
88
+ }
89
+ fail(`Timed out waiting for a Gmail thread matching subject '${subject}'.`);
90
+ }
91
+ async function findAttachmentMessage(env, account) {
92
+ const search = await runSurfaceJson(["mail", "search", "--account", account, "--text", "has:attachment newer_than:3650d", "--limit", "10"], env);
93
+ for (const thread of search.threads) {
94
+ for (const message of thread.messages) {
95
+ const attachment = message.attachments[0];
96
+ if (attachment) {
97
+ return {
98
+ message_ref: message.message_ref,
99
+ attachment_id: attachment.attachment_id,
100
+ };
101
+ }
102
+ }
103
+ }
104
+ fail("Could not find a Gmail message with an attachment for live download verification.");
105
+ }
106
+ async function findInboxMessage(env, account) {
107
+ const search = await runSurfaceJson(["mail", "search", "--account", account, "--text", "in:inbox newer_than:3650d", "--limit", "10"], env);
108
+ for (const thread of search.threads) {
109
+ const message = thread.messages[0];
110
+ if (message) {
111
+ return {
112
+ thread_ref: thread.thread_ref,
113
+ message_ref: message.message_ref,
114
+ };
115
+ }
116
+ }
117
+ fail("Could not find a Gmail inbox message for archive/read-state verification.");
118
+ }
119
+ async function main() {
120
+ assert(process.env.SURFACE_E2E_ENABLE === "1", "Refusing to run live Gmail e2e without SURFACE_E2E_ENABLE=1.");
121
+ const account = process.env.SURFACE_E2E_ACCOUNT?.trim() || "personal_2";
122
+ const recipients = resolveTestRecipients();
123
+ assert(recipients.length >= 1, "Need at least one address in SURFACE_TEST_RECIPIENTS for Gmail e2e.");
124
+ const primaryRecipient = recipients[0];
125
+ const childEnv = {
126
+ ...process.env,
127
+ SURFACE_WRITES_ENABLED: "1",
128
+ SURFACE_SEND_MODE: "allow_send",
129
+ SURFACE_SUMMARIZER_BACKEND: "none",
130
+ };
131
+ const baseSubject = uniqueSubject("gmail-v1");
132
+ const sendBody = `surface gmail e2e send body ${Date.now()}`;
133
+ const replyBody = `surface gmail e2e reply body ${Date.now()}`;
134
+ const replyAllBody = `surface gmail e2e reply-all body ${Date.now()}`;
135
+ const forwardBody = `surface gmail e2e forward body ${Date.now()}`;
136
+ const draftSubject = uniqueSubject("gmail-v1-draft");
137
+ console.log(`Running live Gmail v1 e2e on account '${account}' with subject '${baseSubject}'.`);
138
+ const sendResult = await runSurfaceJson([
139
+ "mail",
140
+ "send",
141
+ "--account",
142
+ account,
143
+ "--to",
144
+ primaryRecipient,
145
+ "--subject",
146
+ baseSubject,
147
+ "--body",
148
+ sendBody,
149
+ ], childEnv);
150
+ assert(sendResult.status === "sent", "send did not report status=sent");
151
+ assert(sendResult.thread_ref, "send did not resolve a thread_ref");
152
+ assert(sendResult.message_ref, "send did not resolve a message_ref");
153
+ console.log(`send: ${sendResult.message_ref} in ${sendResult.thread_ref}`);
154
+ const sentThread = await pollThreadBySubject(childEnv, account, baseSubject, (thread) => thread.messages.length >= 1);
155
+ const baseMessageCount = sentThread.messages.length;
156
+ console.log(`search: found thread ${sentThread.thread_ref} with ${baseMessageCount} message(s)`);
157
+ const readResult = await runSurfaceJson(["mail", "read", sendResult.message_ref], childEnv);
158
+ assert(readResult.message.envelope.subject === baseSubject, "read returned the wrong subject after send");
159
+ assert(normalizeAssertionText(readResult.message.body.text).includes(normalizeAssertionText(sendBody)), "read did not expose the sent body text in the cached Gmail message");
160
+ console.log("read: verified sent body and subject");
161
+ const replyResult = await runSurfaceJson(["mail", "reply", sendResult.message_ref, "--body", replyBody], childEnv);
162
+ assert(replyResult.status === "sent", "reply did not report status=sent");
163
+ assert(replyResult.thread_ref === sendResult.thread_ref, "reply resolved a different thread_ref");
164
+ const repliedThread = await pollThreadBySubject(childEnv, account, baseSubject, (thread) => thread.messages.length > baseMessageCount);
165
+ console.log(`reply: thread now has ${repliedThread.messages.length} message(s)`);
166
+ const replyAllResult = await runSurfaceJson(["mail", "reply-all", sendResult.message_ref, "--body", replyAllBody], childEnv);
167
+ assert(replyAllResult.status === "sent", "reply-all did not report status=sent");
168
+ assert(replyAllResult.thread_ref === sendResult.thread_ref, "reply-all resolved a different thread_ref");
169
+ const replyAllThread = await pollThreadBySubject(childEnv, account, baseSubject, (thread) => thread.messages.length > repliedThread.messages.length);
170
+ console.log(`reply-all: thread now has ${replyAllThread.messages.length} message(s)`);
171
+ const forwardResult = await runSurfaceJson([
172
+ "mail",
173
+ "forward",
174
+ sendResult.message_ref,
175
+ "--to",
176
+ primaryRecipient,
177
+ "--body",
178
+ forwardBody,
179
+ ], childEnv);
180
+ assert(forwardResult.status === "sent", "forward did not report status=sent");
181
+ assert(forwardResult.subject.startsWith("Fwd:"), "forward subject did not start with 'Fwd:'");
182
+ await pollThreadBySubject(childEnv, account, forwardResult.subject, (thread) => thread.messages.length >= 1);
183
+ console.log(`forward: verified search visibility for '${forwardResult.subject}'`);
184
+ const sendDraftResult = await runSurfaceJson([
185
+ "mail",
186
+ "send",
187
+ "--account",
188
+ account,
189
+ "--to",
190
+ primaryRecipient,
191
+ "--subject",
192
+ draftSubject,
193
+ "--body",
194
+ `surface gmail e2e draft body ${Date.now()}`,
195
+ "--draft",
196
+ ], {
197
+ ...childEnv,
198
+ SURFACE_SEND_MODE: "draft_only",
199
+ });
200
+ assert(sendDraftResult.status === "drafted", "send --draft did not report status=drafted");
201
+ assert(sendDraftResult.message_ref, "send --draft did not resolve a message_ref");
202
+ console.log(`send --draft: ${sendDraftResult.message_ref}`);
203
+ const replyDraftResult = await runSurfaceJson(["mail", "reply", sendResult.message_ref, "--body", `surface gmail e2e reply draft ${Date.now()}`, "--draft"], {
204
+ ...childEnv,
205
+ SURFACE_SEND_MODE: "draft_only",
206
+ });
207
+ assert(replyDraftResult.status === "drafted", "reply --draft did not report status=drafted");
208
+ console.log(`reply --draft: ${replyDraftResult.message_ref}`);
209
+ const attachmentTarget = await findAttachmentMessage(childEnv, account);
210
+ const attachmentList = await runSurfaceJson(["attachment", "list", attachmentTarget.message_ref], childEnv);
211
+ assert(attachmentList.attachments.length > 0, "attachment list returned no attachments for the chosen Gmail message");
212
+ const attachmentDownload = await runSurfaceJson(["attachment", "download", attachmentTarget.message_ref, attachmentTarget.attachment_id], childEnv);
213
+ assert(existsSync(attachmentDownload.attachment.saved_to), "attachment download did not create the expected file");
214
+ console.log(`attachment download: ${attachmentDownload.attachment.saved_to}`);
215
+ const inboxTarget = await findInboxMessage(childEnv, account);
216
+ const markReadResult = await runSurfaceJson(["mail", "mark-read", inboxTarget.message_ref], childEnv);
217
+ assert(markReadResult.updated[0]?.unread === false, "mark-read did not return unread=false");
218
+ const readAfterMarkRead = await runSurfaceJson(["mail", "read", inboxTarget.message_ref, "--refresh"], childEnv);
219
+ assert(readAfterMarkRead.message.envelope.unread === false, "mark-read was not reflected by a refreshed read");
220
+ const markUnreadResult = await runSurfaceJson(["mail", "mark-unread", inboxTarget.message_ref], childEnv);
221
+ assert(markUnreadResult.updated[0]?.unread === true, "mark-unread did not return unread=true");
222
+ const readAfterMarkUnread = await runSurfaceJson(["mail", "read", inboxTarget.message_ref, "--refresh"], childEnv);
223
+ assert(readAfterMarkUnread.message.envelope.unread === true, "mark-unread was not reflected by a refreshed read");
224
+ const readMarked = await runSurfaceJson(["mail", "read", inboxTarget.message_ref, "--mark-read"], childEnv);
225
+ assert(readMarked.message.envelope.unread === false, "read --mark-read did not return unread=false");
226
+ console.log("mark-read/mark-unread/read --mark-read: verified read-state mutation");
227
+ const archiveResult = await runSurfaceJson(["mail", "archive", inboxTarget.message_ref], childEnv);
228
+ assert(archiveResult.status === "archived", "archive did not report status=archived");
229
+ const runtime = createRuntimeContext(process.env.SURFACE_CONFIG_PATH ? { configPath: process.env.SURFACE_CONFIG_PATH } : {});
230
+ try {
231
+ const archived = runtime.db.connection
232
+ .prepare("SELECT mailbox FROM threads WHERE thread_ref = ? LIMIT 1")
233
+ .get(archiveResult.thread_ref);
234
+ assert(archived?.mailbox === "archive", "archive did not persist mailbox=archive in local state");
235
+ }
236
+ finally {
237
+ runtime.db.close();
238
+ }
239
+ console.log("archive: verified local thread mailbox state");
240
+ console.log("Gmail v1 e2e completed successfully.");
241
+ }
242
+ main().catch((error) => {
243
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
244
+ console.error(message);
245
+ process.exitCode = 1;
246
+ });
247
+ //# sourceMappingURL=gmail-v1.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"gmail-v1.js","sourceRoot":"","sources":["../../src/e2e/gmail-v1.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,179 @@
1
+ import { spawn } from "node:child_process";
2
+ import { fileURLToPath } from "node:url";
3
+ import { dirname, resolve } from "node:path";
4
+ import { createRuntimeContext } from "../runtime.js";
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ const repoRoot = resolve(__dirname, "..", "..");
8
+ function fail(message) {
9
+ throw new Error(message);
10
+ }
11
+ function assert(condition, message) {
12
+ if (!condition) {
13
+ fail(message);
14
+ }
15
+ }
16
+ function sleep(ms) {
17
+ return new Promise((resolvePromise) => {
18
+ setTimeout(resolvePromise, ms);
19
+ });
20
+ }
21
+ function uniqueSubject(prefix) {
22
+ return `[surface-e2e] ${prefix} ${Date.now()}`;
23
+ }
24
+ function requireEnv(name) {
25
+ const value = process.env[name]?.trim();
26
+ if (!value) {
27
+ fail(`Missing required environment variable ${name}.`);
28
+ }
29
+ return value;
30
+ }
31
+ function parseCsv(value) {
32
+ return value
33
+ .split(",")
34
+ .map((entry) => entry.trim())
35
+ .filter((entry) => entry.length > 0);
36
+ }
37
+ function normalizeAssertionText(value) {
38
+ return value.replace(/\s+/g, " ").trim().toLowerCase();
39
+ }
40
+ async function runSurfaceJson(args, env) {
41
+ return await new Promise((resolvePromise, rejectPromise) => {
42
+ const child = spawn(process.execPath, ["dist/cli.js", ...args], {
43
+ cwd: repoRoot,
44
+ env,
45
+ stdio: ["ignore", "pipe", "pipe"],
46
+ });
47
+ let stdout = "";
48
+ let stderr = "";
49
+ child.stdout.on("data", (chunk) => {
50
+ stdout += chunk.toString("utf8");
51
+ });
52
+ child.stderr.on("data", (chunk) => {
53
+ stderr += chunk.toString("utf8");
54
+ });
55
+ child.on("error", rejectPromise);
56
+ child.on("close", (code) => {
57
+ let parsed = null;
58
+ try {
59
+ parsed = stdout.trim() ? JSON.parse(stdout) : null;
60
+ }
61
+ catch (error) {
62
+ rejectPromise(new Error(`surface ${args.join(" ")} returned non-JSON stdout.\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}\n${String(error)}`));
63
+ return;
64
+ }
65
+ if (code !== 0) {
66
+ rejectPromise(new Error(`surface ${args.join(" ")} failed with exit code ${code}.\nSTDOUT:\n${stdout}\nSTDERR:\n${stderr}`));
67
+ return;
68
+ }
69
+ resolvePromise(parsed);
70
+ });
71
+ });
72
+ }
73
+ async function pollThreadBySubject(env, account, subject, predicate) {
74
+ for (let attempt = 0; attempt < 12; attempt += 1) {
75
+ const search = await runSurfaceJson(["mail", "search", "--account", account, "--text", subject, "--limit", "10"], env);
76
+ const thread = search.threads.find((candidate) => candidate.envelope.subject.includes(subject));
77
+ if (thread && predicate(thread)) {
78
+ return thread;
79
+ }
80
+ await sleep(2_500);
81
+ }
82
+ fail(`Timed out waiting for a search result thread matching subject '${subject}'.`);
83
+ }
84
+ async function main() {
85
+ assert(process.env.SURFACE_E2E_ENABLE === "1", "Refusing to run live Outlook e2e without SURFACE_E2E_ENABLE=1.");
86
+ const account = process.env.SURFACE_E2E_ACCOUNT?.trim() || "uni";
87
+ const recipients = parseCsv(requireEnv("SURFACE_TEST_RECIPIENTS"));
88
+ assert(recipients.length >= 2, "Need at least two addresses in SURFACE_TEST_RECIPIENTS for reply-all coverage.");
89
+ const [primaryRecipient, ccRecipient, bccRecipient] = recipients;
90
+ const childEnv = {
91
+ ...process.env,
92
+ SURFACE_WRITES_ENABLED: "1",
93
+ SURFACE_SEND_MODE: "allow_send",
94
+ };
95
+ const baseSubject = uniqueSubject("outlook-v1");
96
+ const sendBody = `surface e2e send body ${Date.now()}`;
97
+ const replyBody = `surface e2e reply body ${Date.now()}`;
98
+ const replyAllBody = `surface e2e reply-all body ${Date.now()}`;
99
+ const forwardBody = `surface e2e forward body ${Date.now()}`;
100
+ console.log(`Running live Outlook v1 e2e on account '${account}' with subject '${baseSubject}'.`);
101
+ const sendResult = await runSurfaceJson([
102
+ "mail",
103
+ "send",
104
+ "--account",
105
+ account,
106
+ "--to",
107
+ primaryRecipient,
108
+ "--cc",
109
+ ccRecipient,
110
+ ...(bccRecipient ? ["--bcc", bccRecipient] : []),
111
+ "--subject",
112
+ baseSubject,
113
+ "--body",
114
+ sendBody,
115
+ ], childEnv);
116
+ assert(sendResult.status === "sent", "send did not report status=sent");
117
+ assert(sendResult.thread_ref, "send did not resolve a thread_ref");
118
+ assert(sendResult.message_ref, "send did not resolve a message_ref");
119
+ console.log(`send: ${sendResult.message_ref} in ${sendResult.thread_ref}`);
120
+ const sentThread = await pollThreadBySubject(childEnv, account, baseSubject, (thread) => thread.messages.length >= 1);
121
+ const baseMessageCount = sentThread.messages.length;
122
+ console.log(`search: found thread ${sentThread.thread_ref} with ${baseMessageCount} message(s)`);
123
+ const readResult = await runSurfaceJson(["mail", "read", sendResult.message_ref], childEnv);
124
+ assert(readResult.message.envelope.subject === baseSubject, "read returned the wrong subject after send");
125
+ assert(normalizeAssertionText(readResult.message.body.text).includes(normalizeAssertionText(sendBody)), "read did not expose the sent body text in the cached message");
126
+ console.log("read: verified sent body and subject");
127
+ const markUnreadResult = await runSurfaceJson(["mail", "mark-unread", sendResult.message_ref], childEnv);
128
+ assert(markUnreadResult.updated[0]?.unread === true, "mark-unread did not return unread=true");
129
+ const markReadViaRead = await runSurfaceJson(["mail", "read", sendResult.message_ref, "--mark-read"], childEnv);
130
+ assert(markReadViaRead.message.envelope.unread === false, "read --mark-read did not return unread=false");
131
+ console.log("mark-unread/read --mark-read: verified read-state mutation");
132
+ const replyResult = await runSurfaceJson(["mail", "reply", sendResult.message_ref, "--body", replyBody], childEnv);
133
+ assert(replyResult.status === "sent", "reply did not report status=sent");
134
+ assert(replyResult.thread_ref === sendResult.thread_ref, "reply resolved a different thread_ref");
135
+ const repliedThread = await pollThreadBySubject(childEnv, account, baseSubject, (thread) => thread.messages.length > baseMessageCount);
136
+ console.log(`reply: thread now has ${repliedThread.messages.length} message(s)`);
137
+ const replyAllResult = await runSurfaceJson(["mail", "reply-all", sendResult.message_ref, "--body", replyAllBody], childEnv);
138
+ assert(replyAllResult.status === "sent", "reply-all did not report status=sent");
139
+ assert(replyAllResult.thread_ref === sendResult.thread_ref, "reply-all resolved a different thread_ref");
140
+ assert(replyAllResult.recipients.cc.length > 0, "reply-all did not preserve any non-primary recipients");
141
+ const replyAllThread = await pollThreadBySubject(childEnv, account, baseSubject, (thread) => thread.messages.length > repliedThread.messages.length);
142
+ console.log(`reply-all: thread now has ${replyAllThread.messages.length} message(s)`);
143
+ const forwardResult = await runSurfaceJson([
144
+ "mail",
145
+ "forward",
146
+ sendResult.message_ref,
147
+ "--to",
148
+ primaryRecipient,
149
+ "--cc",
150
+ ccRecipient,
151
+ ...(bccRecipient ? ["--bcc", bccRecipient] : []),
152
+ "--body",
153
+ forwardBody,
154
+ ], childEnv);
155
+ assert(forwardResult.status === "sent", "forward did not report status=sent");
156
+ assert(forwardResult.subject.startsWith("Fw:"), "forward subject did not start with 'Fw:'");
157
+ await pollThreadBySubject(childEnv, account, forwardResult.subject, (thread) => thread.messages.length >= 1);
158
+ console.log(`forward: verified search visibility for '${forwardResult.subject}'`);
159
+ const archiveResult = await runSurfaceJson(["mail", "archive", sendResult.message_ref], childEnv);
160
+ assert(archiveResult.status === "archived", "archive did not report status=archived");
161
+ const runtime = createRuntimeContext(process.env.SURFACE_CONFIG_PATH ? { configPath: process.env.SURFACE_CONFIG_PATH } : {});
162
+ try {
163
+ const archived = runtime.db.connection
164
+ .prepare("SELECT mailbox FROM threads WHERE thread_ref = ? LIMIT 1")
165
+ .get(archiveResult.thread_ref);
166
+ assert(archived?.mailbox === "archive", "archive did not persist mailbox=archive in local state");
167
+ }
168
+ finally {
169
+ runtime.db.close();
170
+ }
171
+ console.log("archive: verified local thread mailbox state");
172
+ console.log("Outlook v1 e2e completed successfully.");
173
+ }
174
+ main().catch((error) => {
175
+ const message = error instanceof Error ? error.stack ?? error.message : String(error);
176
+ console.error(message);
177
+ process.exitCode = 1;
178
+ });
179
+ //# sourceMappingURL=outlook-v1.js.map