surface-cli 0.3.3 → 0.3.9

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,138 @@
1
+ import { Buffer } from "node:buffer";
2
+ import { readFileSync, statSync } from "node:fs";
3
+ import { homedir } from "node:os";
4
+ import { basename, extname, join, resolve } from "node:path";
5
+ import { SurfaceError } from "./errors.js";
6
+ const MIME_TYPES_BY_EXTENSION = {
7
+ ".csv": "text/csv",
8
+ ".gif": "image/gif",
9
+ ".htm": "text/html",
10
+ ".html": "text/html",
11
+ ".ics": "text/calendar",
12
+ ".jpeg": "image/jpeg",
13
+ ".jpg": "image/jpeg",
14
+ ".json": "application/json",
15
+ ".pdf": "application/pdf",
16
+ ".png": "image/png",
17
+ ".text": "text/plain",
18
+ ".txt": "text/plain",
19
+ };
20
+ function expandUserPath(inputPath) {
21
+ const trimmed = inputPath.trim();
22
+ if (trimmed === "~") {
23
+ return homedir();
24
+ }
25
+ if (trimmed.startsWith("~/")) {
26
+ return join(homedir(), trimmed.slice(2));
27
+ }
28
+ return trimmed;
29
+ }
30
+ function mimeTypeForPath(path) {
31
+ return MIME_TYPES_BY_EXTENSION[extname(path).toLowerCase()] ?? "application/octet-stream";
32
+ }
33
+ function resolveLocalComposeAttachment(inputPath) {
34
+ if (!inputPath.trim()) {
35
+ throw new SurfaceError("invalid_argument", "Attachment path must be non-empty.");
36
+ }
37
+ const resolvedPath = resolve(expandUserPath(inputPath));
38
+ let stats;
39
+ try {
40
+ stats = statSync(resolvedPath);
41
+ }
42
+ catch {
43
+ throw new SurfaceError("invalid_argument", `Attachment '${inputPath}' was not found.`);
44
+ }
45
+ if (!stats.isFile()) {
46
+ throw new SurfaceError("invalid_argument", `Attachment '${inputPath}' is not a regular file.`);
47
+ }
48
+ const content = readFileSync(resolvedPath);
49
+ const filename = basename(resolvedPath).trim() || "attachment";
50
+ return {
51
+ path: resolvedPath,
52
+ filename,
53
+ mime_type: mimeTypeForPath(resolvedPath),
54
+ size_bytes: content.length,
55
+ content_base64: content.toString("base64"),
56
+ };
57
+ }
58
+ export function resolveLocalComposeAttachments(paths) {
59
+ return (paths ?? []).map(resolveLocalComposeAttachment);
60
+ }
61
+ export function composeAttachmentMetas(attachments) {
62
+ return (attachments ?? []).map((attachment) => ({
63
+ filename: attachment.filename,
64
+ mime_type: attachment.mime_type,
65
+ size_bytes: attachment.size_bytes,
66
+ }));
67
+ }
68
+ function sanitizeHeaderValue(value) {
69
+ return value.replace(/[\r\n]+/g, " ").trim();
70
+ }
71
+ function sanitizeMimeType(value) {
72
+ const normalized = value.trim();
73
+ return /^[A-Za-z0-9][A-Za-z0-9.+-]*\/[A-Za-z0-9][A-Za-z0-9.+-]*$/u.test(normalized)
74
+ ? normalized
75
+ : "application/octet-stream";
76
+ }
77
+ function sanitizeMimeParameter(value) {
78
+ const normalized = sanitizeHeaderValue(value).replace(/[\\"]/g, "_");
79
+ return normalized || "attachment";
80
+ }
81
+ function wrapBase64(value) {
82
+ return value.match(/.{1,76}/gu)?.join("\r\n") ?? "";
83
+ }
84
+ function makeMimeBoundary() {
85
+ return `surface-${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
86
+ }
87
+ export function encodeRawMimeBase64Url(mime) {
88
+ return Buffer.from(mime, "utf8").toString("base64url");
89
+ }
90
+ export function rfc2822Date(value = new Date()) {
91
+ return value.toUTCString().replace("GMT", "+0000");
92
+ }
93
+ export function buildRawMimeMessage(input) {
94
+ const attachments = input.attachments ?? [];
95
+ const includeBccHeader = input.includeBccHeader ?? true;
96
+ const headers = [
97
+ `From: ${sanitizeHeaderValue(input.from)}`,
98
+ ...(input.to.length > 0 ? [`To: ${input.to.map(sanitizeHeaderValue).join(", ")}`] : []),
99
+ ...(input.cc.length > 0 ? [`Cc: ${input.cc.map(sanitizeHeaderValue).join(", ")}`] : []),
100
+ ...(includeBccHeader && input.bcc.length > 0 ? [`Bcc: ${input.bcc.map(sanitizeHeaderValue).join(", ")}`] : []),
101
+ `Subject: ${sanitizeHeaderValue(input.subject)}`,
102
+ ...(input.messageId ? [`Message-ID: ${sanitizeHeaderValue(input.messageId)}`] : []),
103
+ ...(input.date || input.messageId ? [`Date: ${sanitizeHeaderValue(input.date ?? rfc2822Date())}`] : []),
104
+ ...(input.inReplyTo ? [`In-Reply-To: ${sanitizeHeaderValue(input.inReplyTo)}`] : []),
105
+ ...(input.references ? [`References: ${sanitizeHeaderValue(input.references)}`] : []),
106
+ "MIME-Version: 1.0",
107
+ ];
108
+ const body = input.body.replace(/\r\n/g, "\n");
109
+ if (attachments.length === 0) {
110
+ return [
111
+ ...headers,
112
+ 'Content-Type: text/plain; charset="UTF-8"',
113
+ "Content-Transfer-Encoding: 8bit",
114
+ "",
115
+ body,
116
+ "",
117
+ ].join("\r\n");
118
+ }
119
+ const boundary = makeMimeBoundary();
120
+ const lines = [
121
+ ...headers,
122
+ `Content-Type: multipart/mixed; boundary="${boundary}"`,
123
+ "",
124
+ `--${boundary}`,
125
+ 'Content-Type: text/plain; charset="UTF-8"',
126
+ "Content-Transfer-Encoding: 8bit",
127
+ "",
128
+ body,
129
+ "",
130
+ ];
131
+ for (const attachment of attachments) {
132
+ const filename = sanitizeMimeParameter(attachment.filename);
133
+ lines.push(`--${boundary}`, `Content-Type: ${sanitizeMimeType(attachment.mime_type)}; name="${filename}"`, `Content-Disposition: attachment; filename="${filename}"`, "Content-Transfer-Encoding: base64", "", wrapBase64(attachment.content_base64), "");
134
+ }
135
+ lines.push(`--${boundary}--`, "");
136
+ return lines.join("\r\n");
137
+ }
138
+ //# sourceMappingURL=compose-attachments.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-attachments.js","sourceRoot":"","sources":["../../src/lib/compose-attachments.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,47 @@
1
+ import assert from "node:assert/strict";
2
+ import { Buffer } from "node:buffer";
3
+ import test from "node:test";
4
+ import { buildRawMimeMessage, composeAttachmentMetas, resolveLocalComposeAttachments, } from "./compose-attachments.js";
5
+ test("resolveLocalComposeAttachments reads file metadata and bytes without exposing paths in public metadata", () => {
6
+ const [attachment] = resolveLocalComposeAttachments(["tsconfig.json"]);
7
+ assert.equal(attachment?.filename, "tsconfig.json");
8
+ assert.equal(attachment?.mime_type, "application/json");
9
+ assert.equal(typeof attachment?.size_bytes, "number");
10
+ assert.ok(attachment?.content_base64);
11
+ assert.deepEqual(composeAttachmentMetas([attachment]), [
12
+ {
13
+ filename: "tsconfig.json",
14
+ mime_type: "application/json",
15
+ size_bytes: attachment.size_bytes,
16
+ },
17
+ ]);
18
+ });
19
+ test("buildRawMimeMessage emits multipart MIME attachments and can hide Bcc for delivery copies", () => {
20
+ const raw = buildRawMimeMessage({
21
+ from: "surface@example.com",
22
+ to: ["to@example.com"],
23
+ cc: ["cc@example.com"],
24
+ bcc: ["hidden@example.com"],
25
+ subject: "Surface attachment probe",
26
+ body: "Hello with file",
27
+ messageId: "<surface-test@example.com>",
28
+ includeBccHeader: false,
29
+ attachments: [
30
+ {
31
+ path: "/tmp/report.txt",
32
+ filename: "report.txt",
33
+ mime_type: "text/plain",
34
+ size_bytes: 11,
35
+ content_base64: Buffer.from("hello file").toString("base64"),
36
+ },
37
+ ],
38
+ });
39
+ assert.match(raw, /^To: to@example\.com/m);
40
+ assert.match(raw, /^Cc: cc@example\.com/m);
41
+ assert.doesNotMatch(raw, /^Bcc:/m);
42
+ assert.match(raw, /^Content-Type: multipart\/mixed; boundary="surface-/m);
43
+ assert.match(raw, /Content-Type: text\/plain; name="report\.txt"/);
44
+ assert.match(raw, /Content-Disposition: attachment; filename="report\.txt"/);
45
+ assert.match(raw, new RegExp(Buffer.from("hello file").toString("base64")));
46
+ });
47
+ //# sourceMappingURL=compose-attachments.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compose-attachments.test.js","sourceRoot":"","sources":["../../src/lib/compose-attachments.test.ts"],"names":[],"mappings":"AAAA,OAAO,MAAM,MAAM,oBAAoB,CAAC;AACxC,OAAO,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AACrC,OAAO,IAAI,MAAM,WAAW,CAAC;AAE7B,OAAO,EACL,mBAAmB,EACnB,sBAAsB,EACtB,8BAA8B,GAC/B,MAAM,0BAA0B,CAAC;AAElC,IAAI,CAAC,wGAAwG,EAAE,GAAG,EAAE;IAClH,MAAM,CAAC,UAAU,CAAC,GAAG,8BAA8B,CAAC,CAAC,eAAe,CAAC,CAAC,CAAC;IAEvE,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,QAAQ,EAAE,eAAe,CAAC,CAAC;IACpD,MAAM,CAAC,KAAK,CAAC,UAAU,EAAE,SAAS,EAAE,kBAAkB,CAAC,CAAC;IACxD,MAAM,CAAC,KAAK,CAAC,OAAO,UAAU,EAAE,UAAU,EAAE,QAAQ,CAAC,CAAC;IACtD,MAAM,CAAC,EAAE,CAAC,UAAU,EAAE,cAAc,CAAC,CAAC;IACtC,MAAM,CAAC,SAAS,CAAC,sBAAsB,CAAC,CAAC,UAAW,CAAC,CAAC,EAAE;QACtD;YACE,QAAQ,EAAE,eAAe;YACzB,SAAS,EAAE,kBAAkB;YAC7B,UAAU,EAAE,UAAW,CAAC,UAAU;SACnC;KACF,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,IAAI,CAAC,2FAA2F,EAAE,GAAG,EAAE;IACrG,MAAM,GAAG,GAAG,mBAAmB,CAAC;QAC9B,IAAI,EAAE,qBAAqB;QAC3B,EAAE,EAAE,CAAC,gBAAgB,CAAC;QACtB,EAAE,EAAE,CAAC,gBAAgB,CAAC;QACtB,GAAG,EAAE,CAAC,oBAAoB,CAAC;QAC3B,OAAO,EAAE,0BAA0B;QACnC,IAAI,EAAE,iBAAiB;QACvB,SAAS,EAAE,4BAA4B;QACvC,gBAAgB,EAAE,KAAK;QACvB,WAAW,EAAE;YACX;gBACE,IAAI,EAAE,iBAAiB;gBACvB,QAAQ,EAAE,YAAY;gBACtB,SAAS,EAAE,YAAY;gBACvB,UAAU,EAAE,EAAE;gBACd,cAAc,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC;aAC7D;SACF;KACF,CAAC,CAAC;IAEH,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;IAC3C,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,uBAAuB,CAAC,CAAC;IAC3C,MAAM,CAAC,YAAY,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC;IACnC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,sDAAsD,CAAC,CAAC;IAC1E,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,+CAA+C,CAAC,CAAC;IACnE,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,yDAAyD,CAAC,CAAC;IAC7E,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;AAC9E,CAAC,CAAC,CAAC"}
@@ -0,0 +1,64 @@
1
+ import { SurfaceError } from "./errors.js";
2
+ import { loadStoredThread } from "./stored-mail.js";
3
+ export function normalizeComparableEmail(value) {
4
+ return (value ?? "").trim().toLowerCase();
5
+ }
6
+ export function accountIdentityEmails(account, context) {
7
+ const identity = context.db.getAccountIdentity(account);
8
+ return new Set([identity.primary_email, account.email, ...identity.email_aliases]
9
+ .map(normalizeComparableEmail)
10
+ .filter(Boolean));
11
+ }
12
+ export function messageMatchesRecipient(message, recipient) {
13
+ if (!recipient) {
14
+ return true;
15
+ }
16
+ const normalizedRecipient = normalizeComparableEmail(recipient);
17
+ return [...message.envelope.to, ...message.envelope.cc]
18
+ .some((participant) => normalizeComparableEmail(participant.email) === normalizedRecipient);
19
+ }
20
+ function messageWasSentByAccount(message, emails) {
21
+ const fromEmail = normalizeComparableEmail(message.envelope.from?.email);
22
+ return Boolean(fromEmail && emails.has(fromEmail));
23
+ }
24
+ export function sentTimestamp(message) {
25
+ const parsed = Date.parse(message.envelope.sent_at ?? message.envelope.received_at ?? "");
26
+ return Number.isFinite(parsed) ? parsed : 0;
27
+ }
28
+ export function sentMessagesFromStoredThread(account, context, query) {
29
+ if (!query.thread_ref) {
30
+ return [];
31
+ }
32
+ const resolved = context.db.findThreadByRef(query.thread_ref);
33
+ if (!resolved) {
34
+ throw new SurfaceError("not_found", `Thread '${query.thread_ref}' was not found.`, {
35
+ account: account.name,
36
+ threadRef: query.thread_ref,
37
+ });
38
+ }
39
+ if (resolved.account_id !== account.account_id) {
40
+ throw new SurfaceError("invalid_argument", `Thread '${query.thread_ref}' does not belong to account '${account.name}'.`, {
41
+ account: account.name,
42
+ threadRef: query.thread_ref,
43
+ });
44
+ }
45
+ const thread = loadStoredThread(context.db, account, query.thread_ref);
46
+ if (!thread) {
47
+ throw new SurfaceError("not_found", `Thread '${query.thread_ref}' was not found.`, {
48
+ account: account.name,
49
+ threadRef: query.thread_ref,
50
+ });
51
+ }
52
+ const emails = accountIdentityEmails(account, context);
53
+ return thread.messages
54
+ .filter((message) => messageWasSentByAccount(message, emails))
55
+ .filter((message) => messageMatchesRecipient(message, query.recipient))
56
+ .sort((left, right) => sentTimestamp(right) - sentTimestamp(left))
57
+ .slice(0, query.limit)
58
+ .map((message) => ({
59
+ ...message,
60
+ thread_ref: thread.thread_ref,
61
+ source: thread.source,
62
+ }));
63
+ }
64
+ //# sourceMappingURL=sent-mail.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sent-mail.js","sourceRoot":"","sources":["../../src/lib/sent-mail.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,55 @@
1
+ import { copyFile, mkdir, stat } from "node:fs/promises";
2
+ import { homedir } from "node:os";
3
+ import { dirname, join, resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+ import { SurfaceError } from "./errors.js";
6
+ const SKILL_NAME = "surface-cli";
7
+ function packageRoot() {
8
+ return resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
9
+ }
10
+ export function bundledSkillPath() {
11
+ return join(packageRoot(), "skills", SKILL_NAME, "SKILL.md");
12
+ }
13
+ export function defaultSkillDestination(target) {
14
+ switch (target) {
15
+ case "codex":
16
+ return join(homedir(), ".codex", "skills", SKILL_NAME, "SKILL.md");
17
+ case "claude-code":
18
+ return join(homedir(), ".claude", "skills", SKILL_NAME, "SKILL.md");
19
+ }
20
+ }
21
+ export function parseSkillInstallTarget(value) {
22
+ switch (value) {
23
+ case "codex":
24
+ return "codex";
25
+ case "claude":
26
+ case "claude-code":
27
+ return "claude-code";
28
+ case "all":
29
+ return "all";
30
+ default:
31
+ throw new SurfaceError("invalid_argument", `Expected skill install target to be one of: codex, claude-code, all. Received '${value}'.`);
32
+ }
33
+ }
34
+ export function expandSkillInstallTargets(target) {
35
+ return target === "all" ? ["codex", "claude-code"] : [target];
36
+ }
37
+ export async function installSurfaceSkill(target) {
38
+ const source = bundledSkillPath();
39
+ try {
40
+ await stat(source);
41
+ }
42
+ catch {
43
+ throw new SurfaceError("not_found", `Bundled Surface skill was not found at '${source}'. Reinstall surface-cli or install the skill from GitHub.`);
44
+ }
45
+ const destination = defaultSkillDestination(target);
46
+ await mkdir(dirname(destination), { recursive: true });
47
+ await copyFile(source, destination);
48
+ return {
49
+ agent: target,
50
+ skill: SKILL_NAME,
51
+ source,
52
+ path: destination,
53
+ };
54
+ }
55
+ //# sourceMappingURL=skill-install.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"skill-install.js","sourceRoot":"","sources":["../../src/lib/skill-install.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACzD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAW3C,MAAM,UAAU,GAAG,aAAa,CAAC;AAEjC,SAAS,WAAW;IAClB,OAAO,OAAO,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;AACtE,CAAC;AAED,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,WAAW,EAAE,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;AAC/D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAA0B;IAChE,QAAQ,MAAM,EAAE,CAAC;QACf,KAAK,OAAO;YACV,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;QACrE,KAAK,aAAa;YAChB,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,EAAE,UAAU,CAAC,CAAC;IACxE,CAAC;AACH,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,KAAa;IACnD,QAAQ,KAAK,EAAE,CAAC;QACd,KAAK,OAAO;YACV,OAAO,OAAO,CAAC;QACjB,KAAK,QAAQ,CAAC;QACd,KAAK,aAAa;YAChB,OAAO,aAAa,CAAC;QACvB,KAAK,KAAK;YACR,OAAO,KAAK,CAAC;QACf;YACE,MAAM,IAAI,YAAY,CACpB,kBAAkB,EAClB,kFAAkF,KAAK,IAAI,CAC5F,CAAC;IACN,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,MAAkC;IAC1E,OAAO,MAAM,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;AAChE,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,mBAAmB,CAAC,MAA0B;IAClE,MAAM,MAAM,GAAG,gBAAgB,EAAE,CAAC;IAClC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,MAAM,CAAC,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,YAAY,CACpB,WAAW,EACX,2CAA2C,MAAM,4DAA4D,CAC9G,CAAC;IACJ,CAAC;IAED,MAAM,WAAW,GAAG,uBAAuB,CAAC,MAAM,CAAC,CAAC;IACpD,MAAM,KAAK,CAAC,OAAO,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACvD,MAAM,QAAQ,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC;IAEpC,OAAO;QACL,KAAK,EAAE,MAAM;QACb,KAAK,EAAE,UAAU;QACjB,MAAM;QACN,IAAI,EAAE,WAAW;KAClB,CAAC;AACJ,CAAC"}
@@ -1,8 +1,10 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
3
3
  import { join } from "node:path";
4
+ import { buildRawMimeMessage, composeAttachmentMetas, encodeRawMimeBase64Url, } from "../../lib/compose-attachments.js";
4
5
  import { SurfaceError } from "../../lib/errors.js";
5
6
  import { toPublicSentMessage } from "../../lib/public-mail.js";
7
+ import { sentMessagesFromStoredThread } from "../../lib/sent-mail.js";
6
8
  import { assertWriteAllowed } from "../../lib/write-safety.js";
7
9
  import { makeAttachmentId, makeMessageRef, makeThreadRef } from "../../refs.js";
8
10
  import { summarizeAndPersistThreads } from "../../summarizer.js";
@@ -505,9 +507,6 @@ function normalizeEmailList(values) {
505
507
  }
506
508
  return [...deduped];
507
509
  }
508
- function sanitizeHeaderValue(value) {
509
- return value.replace(/\r?\n/g, " ").trim();
510
- }
511
510
  function prefixSubject(subject, prefix) {
512
511
  const normalized = subject.trim();
513
512
  if (!normalized) {
@@ -551,27 +550,6 @@ function buildForwardBody(inputBody, stored) {
551
550
  lines.push("", originalBody);
552
551
  return lines.join("\n").trim();
553
552
  }
554
- function encodeMimeBase64Url(mime) {
555
- return Buffer.from(mime, "utf8").toString("base64url");
556
- }
557
- function buildRawMimeMessage(input) {
558
- const lines = [
559
- `From: ${sanitizeHeaderValue(input.from)}`,
560
- ...(input.to.length > 0 ? [`To: ${input.to.map(sanitizeHeaderValue).join(", ")}`] : []),
561
- ...(input.cc.length > 0 ? [`Cc: ${input.cc.map(sanitizeHeaderValue).join(", ")}`] : []),
562
- ...(input.bcc.length > 0 ? [`Bcc: ${input.bcc.map(sanitizeHeaderValue).join(", ")}`] : []),
563
- `Subject: ${sanitizeHeaderValue(input.subject)}`,
564
- ...(input.inReplyTo ? [`In-Reply-To: ${sanitizeHeaderValue(input.inReplyTo)}`] : []),
565
- ...(input.references ? [`References: ${sanitizeHeaderValue(input.references)}`] : []),
566
- "MIME-Version: 1.0",
567
- 'Content-Type: text/plain; charset="UTF-8"',
568
- "Content-Transfer-Encoding: 8bit",
569
- "",
570
- input.body.replace(/\r\n/g, "\n"),
571
- "",
572
- ];
573
- return lines.join("\r\n");
574
- }
575
553
  function parseMessageLocator(locatorJson) {
576
554
  const parsed = JSON.parse(locatorJson);
577
555
  return {
@@ -589,7 +567,7 @@ function latestStoredThreadMessage(threadRef, context) {
589
567
  stored: messageRef ? context.db.getStoredMessage(messageRef) ?? null : null,
590
568
  };
591
569
  }
592
- function buildSendEnvelope(account, command, status, subject, recipients, result, inReplyToMessageRef) {
570
+ function buildSendEnvelope(account, command, status, subject, recipients, result, inReplyToMessageRef, attachments = []) {
593
571
  return {
594
572
  schema_version: "1",
595
573
  command,
@@ -598,6 +576,7 @@ function buildSendEnvelope(account, command, status, subject, recipients, result
598
576
  status,
599
577
  subject,
600
578
  recipients,
579
+ attachments,
601
580
  thread_ref: result.thread_ref,
602
581
  message_ref: result.message_ref,
603
582
  in_reply_to_message_ref: inReplyToMessageRef,
@@ -975,6 +954,10 @@ async function fetchGmailThreads(account, context, options) {
975
954
  return summarizeAndPersistThreads(persisted, context.config, context.db, context.db.getAccountIdentity(account));
976
955
  }
977
956
  async function fetchGmailSentMessages(account, context, query) {
957
+ if (query.thread_ref) {
958
+ await refreshGmailThread(account, query.thread_ref, context);
959
+ return sentMessagesFromStoredThread(account, context, query);
960
+ }
978
961
  const queryParts = ["in:sent"];
979
962
  if (query.recipient?.trim()) {
980
963
  queryParts.push(buildGmailRecipientQuery(query.recipient));
@@ -1013,6 +996,23 @@ async function fetchGmailSentMessages(account, context, query) {
1013
996
  }
1014
997
  return results;
1015
998
  }
999
+ async function refreshGmailThread(account, threadRef, context) {
1000
+ const locatorRow = context.db.findProviderLocator("thread", threadRef);
1001
+ if (!locatorRow) {
1002
+ throw new SurfaceError("cache_miss", `No provider locator exists for thread '${threadRef}'.`, {
1003
+ account: account.name,
1004
+ threadRef,
1005
+ });
1006
+ }
1007
+ const locator = JSON.parse(locatorRow.locator_json);
1008
+ if (!locator.thread_id) {
1009
+ throw new SurfaceError("transport_error", `Thread '${threadRef}' is missing a Gmail thread id.`, {
1010
+ account: account.name,
1011
+ threadRef,
1012
+ });
1013
+ }
1014
+ await fetchAndPersistGmailThread(account, context, locator.thread_id);
1015
+ }
1016
1016
  async function sendOrDraftGmailMessage(account, context, payload) {
1017
1017
  if (payload.draft) {
1018
1018
  const draft = await createGmailDraft(account, context, {
@@ -1083,21 +1083,7 @@ export class GmailApiAdapter {
1083
1083
  return fetchGmailSentMessages(account, context, query);
1084
1084
  }
1085
1085
  async refreshThread(account, threadRef, context) {
1086
- const locatorRow = context.db.findProviderLocator("thread", threadRef);
1087
- if (!locatorRow) {
1088
- throw new SurfaceError("cache_miss", `No provider locator exists for thread '${threadRef}'.`, {
1089
- account: account.name,
1090
- threadRef,
1091
- });
1092
- }
1093
- const locator = JSON.parse(locatorRow.locator_json);
1094
- if (!locator.thread_id) {
1095
- throw new SurfaceError("transport_error", `Thread '${threadRef}' is missing a Gmail thread id.`, {
1096
- account: account.name,
1097
- threadRef,
1098
- });
1099
- }
1100
- await fetchAndPersistGmailThread(account, context, locator.thread_id);
1086
+ await refreshGmailThread(account, threadRef, context);
1101
1087
  }
1102
1088
  async readMessage(account, messageRef, refresh, context) {
1103
1089
  const stored = context.db.getStoredMessage(messageRef);
@@ -1242,19 +1228,20 @@ export class GmailApiAdapter {
1242
1228
  assertWriteAllowed(context.config, account, recipients, {
1243
1229
  disposition: input.draft ? "draft" : "send",
1244
1230
  });
1245
- const raw = encodeMimeBase64Url(buildRawMimeMessage({
1231
+ const raw = encodeRawMimeBase64Url(buildRawMimeMessage({
1246
1232
  from: account.email,
1247
1233
  to: recipients.to,
1248
1234
  cc: recipients.cc,
1249
1235
  bcc: recipients.bcc,
1250
1236
  subject: input.subject,
1251
1237
  body: input.body,
1238
+ attachments: input.attachments,
1252
1239
  }));
1253
1240
  const result = await sendOrDraftGmailMessage(account, context, {
1254
1241
  raw,
1255
1242
  draft: input.draft,
1256
1243
  });
1257
- return buildSendEnvelope(account, "send", result.status, input.subject, recipientsFromInput(recipients), result.refs, null);
1244
+ return buildSendEnvelope(account, "send", result.status, input.subject, recipientsFromInput(recipients), result.refs, null, composeAttachmentMetas(input.attachments));
1258
1245
  }
1259
1246
  async reply(account, messageRef, input, context) {
1260
1247
  const target = await resolveGmailMessageContext(account, messageRef, context);
@@ -1286,7 +1273,7 @@ export class GmailApiAdapter {
1286
1273
  ? `${target.headers.references}${originalMessageId ? ` ${originalMessageId}` : ""}`.trim()
1287
1274
  : originalMessageId;
1288
1275
  const subject = prefixSubject(target.stored.subject ?? target.headers.subject ?? "", "Re");
1289
- const raw = encodeMimeBase64Url(buildRawMimeMessage({
1276
+ const raw = encodeRawMimeBase64Url(buildRawMimeMessage({
1290
1277
  from: account.email,
1291
1278
  to: recipients.to,
1292
1279
  cc: recipients.cc,
@@ -1341,7 +1328,7 @@ export class GmailApiAdapter {
1341
1328
  ? `${target.headers.references}${originalMessageId ? ` ${originalMessageId}` : ""}`.trim()
1342
1329
  : originalMessageId;
1343
1330
  const subject = prefixSubject(target.stored.subject ?? target.headers.subject ?? "", "Re");
1344
- const raw = encodeMimeBase64Url(buildRawMimeMessage({
1331
+ const raw = encodeRawMimeBase64Url(buildRawMimeMessage({
1345
1332
  from: account.email,
1346
1333
  to: recipients.to,
1347
1334
  cc: recipients.cc,
@@ -1374,7 +1361,7 @@ export class GmailApiAdapter {
1374
1361
  disposition: input.draft ? "draft" : "send",
1375
1362
  });
1376
1363
  const subject = prefixSubject(target.stored.subject ?? target.headers.subject ?? "", "Fwd");
1377
- const raw = encodeMimeBase64Url(buildRawMimeMessage({
1364
+ const raw = encodeRawMimeBase64Url(buildRawMimeMessage({
1378
1365
  from: account.email,
1379
1366
  to: recipients.to,
1380
1367
  cc: recipients.cc,