gws-axi 0.20.0 → 0.21.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.
- package/dist/src/commands/gmail/compose.d.ts +31 -9
- package/dist/src/commands/gmail/compose.js +56 -21
- package/dist/src/commands/gmail/compose.js.map +1 -1
- package/dist/src/commands/gmail/compose.test.js +87 -70
- package/dist/src/commands/gmail/compose.test.js.map +1 -1
- package/dist/src/commands/gmail/draft.d.ts +1 -1
- package/dist/src/commands/gmail/draft.js +13 -5
- package/dist/src/commands/gmail/draft.js.map +1 -1
- package/package.json +1 -1
- package/dist/src/commands/service-stub.d.ts +0 -8
- package/dist/src/commands/service-stub.js +0 -69
- package/dist/src/commands/service-stub.js.map +0 -1
|
@@ -1,14 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RFC 5322 message builder for draft creation.
|
|
3
3
|
*
|
|
4
|
-
* Drafts go out as `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Drafts go out as a single `text/html` part carrying the rendered `--body`
|
|
5
|
+
* markdown. Gmail's composer adopts exactly one part of a draft as its editing
|
|
6
|
+
* surface and discards the rest, so a message offering only HTML leaves it no
|
|
7
|
+
* choice — the composer opens in rich text, our markup survives the human's
|
|
8
|
+
* edits, and Gmail generates the `text/plain` alternative itself on send.
|
|
9
|
+
*
|
|
10
|
+
* Both alternatives were tried and rejected against live sends:
|
|
11
|
+
*
|
|
12
|
+
* - Single-part `text/plain` (the original bug) drops the composer into
|
|
13
|
+
* plain-text mode. On send Gmail hard-wraps at ~70 columns with real CRLFs
|
|
14
|
+
* and ships no HTML alternative, so recipients get ragged columns nothing
|
|
15
|
+
* can reflow — while the compose window still shows the original unwrapped
|
|
16
|
+
* paragraphs, hiding the damage until the message is gone.
|
|
17
|
+
* - `multipart/alternative` (plain + HTML) fixes the wrapping but is a coin
|
|
18
|
+
* flip: two identically-built drafts behaved differently, one keeping our
|
|
19
|
+
* rendered markup and one adopting the plain part and delivering the raw
|
|
20
|
+
* markdown source to the reader — `**bold**` and `[text](url)` and all.
|
|
12
21
|
*
|
|
13
22
|
* See specs/commands/gmail-draft.md ("Message structure", "Body rendering").
|
|
14
23
|
*/
|
|
@@ -19,8 +28,14 @@ export interface ComposeFields {
|
|
|
19
28
|
cc?: string[];
|
|
20
29
|
bcc?: string[];
|
|
21
30
|
subject: string;
|
|
22
|
-
/** Markdown source
|
|
31
|
+
/** Markdown source, rendered to the HTML body — unless `plain` is set. */
|
|
23
32
|
body: string;
|
|
33
|
+
/**
|
|
34
|
+
* Treat `body` as literal text rather than markdown. Still sends a single
|
|
35
|
+
* `text/html` part (reverting to `text/plain` is the original defect); the
|
|
36
|
+
* HTML is built structurally instead of through the markdown renderer.
|
|
37
|
+
*/
|
|
38
|
+
plain?: boolean;
|
|
24
39
|
}
|
|
25
40
|
/** Split a comma-separated recipient flag into trimmed, non-empty addresses. */
|
|
26
41
|
export declare function parseRecipients(value: string): string[];
|
|
@@ -34,6 +49,13 @@ export declare function parseRecipients(value: string): string[];
|
|
|
34
49
|
* DRAFT_HELP tells callers not to hard-wrap.
|
|
35
50
|
*/
|
|
36
51
|
export declare function renderMarkdown(body: string): string;
|
|
52
|
+
/**
|
|
53
|
+
* Render the body as literal text: no markdown, so `*`, `_`, `#` and friends
|
|
54
|
+
* reach the reader exactly as written. Blank line starts a paragraph, a single
|
|
55
|
+
* newline is a line break — the same block semantics as the markdown path, so
|
|
56
|
+
* `--plain` changes how the body is interpreted, not how it is laid out.
|
|
57
|
+
*/
|
|
58
|
+
export declare function renderPlainText(body: string): string;
|
|
37
59
|
/**
|
|
38
60
|
* Build a base64url-encoded RFC 5322 message suitable for
|
|
39
61
|
* `users.drafts.create` / `users.messages.send` `raw` fields.
|
|
@@ -1,18 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* RFC 5322 message builder for draft creation.
|
|
3
3
|
*
|
|
4
|
-
* Drafts go out as `
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
4
|
+
* Drafts go out as a single `text/html` part carrying the rendered `--body`
|
|
5
|
+
* markdown. Gmail's composer adopts exactly one part of a draft as its editing
|
|
6
|
+
* surface and discards the rest, so a message offering only HTML leaves it no
|
|
7
|
+
* choice — the composer opens in rich text, our markup survives the human's
|
|
8
|
+
* edits, and Gmail generates the `text/plain` alternative itself on send.
|
|
9
|
+
*
|
|
10
|
+
* Both alternatives were tried and rejected against live sends:
|
|
11
|
+
*
|
|
12
|
+
* - Single-part `text/plain` (the original bug) drops the composer into
|
|
13
|
+
* plain-text mode. On send Gmail hard-wraps at ~70 columns with real CRLFs
|
|
14
|
+
* and ships no HTML alternative, so recipients get ragged columns nothing
|
|
15
|
+
* can reflow — while the compose window still shows the original unwrapped
|
|
16
|
+
* paragraphs, hiding the damage until the message is gone.
|
|
17
|
+
* - `multipart/alternative` (plain + HTML) fixes the wrapping but is a coin
|
|
18
|
+
* flip: two identically-built drafts behaved differently, one keeping our
|
|
19
|
+
* rendered markup and one adopting the plain part and delivering the raw
|
|
20
|
+
* markdown source to the reader — `**bold**` and `[text](url)` and all.
|
|
12
21
|
*
|
|
13
22
|
* See specs/commands/gmail-draft.md ("Message structure", "Body rendering").
|
|
14
23
|
*/
|
|
15
|
-
import { randomUUID } from "node:crypto";
|
|
16
24
|
import { marked } from "marked";
|
|
17
25
|
/** Split a comma-separated recipient flag into trimmed, non-empty addresses. */
|
|
18
26
|
export function parseRecipients(value) {
|
|
@@ -44,8 +52,42 @@ export function renderMarkdown(body) {
|
|
|
44
52
|
const fragment = marked.parse(body, { gfm: true, breaks: true, async: false });
|
|
45
53
|
return `<html><body>${fragment}</body></html>`;
|
|
46
54
|
}
|
|
55
|
+
function escapeHtml(text) {
|
|
56
|
+
return text.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Keep indentation and column alignment that HTML would otherwise collapse.
|
|
60
|
+
*
|
|
61
|
+
* Leading spaces become ` ` outright; an internal run keeps one real
|
|
62
|
+
* space — a wrap point, so the line still reflows — and pads the rest. This is
|
|
63
|
+
* what Gmail itself does when it converts plain text to HTML, so it is known to
|
|
64
|
+
* survive the composer.
|
|
65
|
+
*/
|
|
66
|
+
function preserveSpacing(line) {
|
|
67
|
+
return line
|
|
68
|
+
.replace(/\t/g, " ")
|
|
69
|
+
.replace(/^ +/, (run) => " ".repeat(run.length))
|
|
70
|
+
.replace(/ {2,}/g, (run) => ` ${" ".repeat(run.length - 1)}`);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Render the body as literal text: no markdown, so `*`, `_`, `#` and friends
|
|
74
|
+
* reach the reader exactly as written. Blank line starts a paragraph, a single
|
|
75
|
+
* newline is a line break — the same block semantics as the markdown path, so
|
|
76
|
+
* `--plain` changes how the body is interpreted, not how it is laid out.
|
|
77
|
+
*/
|
|
78
|
+
export function renderPlainText(body) {
|
|
79
|
+
const blocks = body
|
|
80
|
+
.replace(/\r\n/g, "\n")
|
|
81
|
+
.split(/\n{2,}/)
|
|
82
|
+
.filter((block) => block.trim() !== "")
|
|
83
|
+
.map((block) => {
|
|
84
|
+
const lines = escapeHtml(block).split("\n").map(preserveSpacing);
|
|
85
|
+
return `<p>${lines.join("<br>")}</p>`;
|
|
86
|
+
});
|
|
87
|
+
return `<html><body>${blocks.join("\n")}</body></html>`;
|
|
88
|
+
}
|
|
47
89
|
/** Base64 with CRLF line endings wrapped at 76 columns (RFC 2045). */
|
|
48
|
-
function
|
|
90
|
+
function encodeBody(text) {
|
|
49
91
|
return Buffer.from(text, "utf8")
|
|
50
92
|
.toString("base64")
|
|
51
93
|
.replace(/(.{76})/g, "$1\r\n");
|
|
@@ -55,9 +97,6 @@ function encodePart(text) {
|
|
|
55
97
|
* `users.drafts.create` / `users.messages.send` `raw` fields.
|
|
56
98
|
*/
|
|
57
99
|
export function buildRawMessage(fields) {
|
|
58
|
-
// Unique per message, so it can never collide with body content — no
|
|
59
|
-
// scanning the body for the boundary, no retry loop.
|
|
60
|
-
const boundary = `=_gws-axi_${randomUUID()}`;
|
|
61
100
|
const headers = [`From: ${fields.from}`, `To: ${fields.to.join(", ")}`];
|
|
62
101
|
if (fields.cc?.length)
|
|
63
102
|
headers.push(`Cc: ${fields.cc.join(", ")}`);
|
|
@@ -65,14 +104,10 @@ export function buildRawMessage(fields) {
|
|
|
65
104
|
headers.push(`Bcc: ${fields.bcc.join(", ")}`);
|
|
66
105
|
headers.push(`Subject: ${encodeHeaderValue(fields.subject)}`);
|
|
67
106
|
headers.push("MIME-Version: 1.0");
|
|
68
|
-
headers.push(
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
const
|
|
72
|
-
['Content-Type: text/plain; charset="UTF-8"', fields.body],
|
|
73
|
-
['Content-Type: text/html; charset="UTF-8"', renderMarkdown(fields.body)],
|
|
74
|
-
].map(([contentType, content]) => `--${boundary}\r\n${contentType}\r\nContent-Transfer-Encoding: base64\r\n\r\n${encodePart(content)}`);
|
|
75
|
-
const raw = `${headers.join("\r\n")}\r\n\r\n${parts.join("\r\n")}\r\n--${boundary}--`;
|
|
107
|
+
headers.push('Content-Type: text/html; charset="UTF-8"');
|
|
108
|
+
headers.push("Content-Transfer-Encoding: base64");
|
|
109
|
+
const html = fields.plain ? renderPlainText(fields.body) : renderMarkdown(fields.body);
|
|
110
|
+
const raw = `${headers.join("\r\n")}\r\n\r\n${encodeBody(html)}`;
|
|
76
111
|
return Buffer.from(raw, "utf8").toString("base64url");
|
|
77
112
|
}
|
|
78
113
|
//# sourceMappingURL=compose.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../../src/commands/gmail/compose.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../../src/commands/gmail/compose.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,28 +1,13 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { buildRawMessage, parseRecipients } from "./compose.js";
|
|
3
|
-
/** Decode a base64url raw message into its header block and decoded
|
|
3
|
+
/** Decode a base64url raw message into its header block and decoded HTML body. */
|
|
4
4
|
function decodeRaw(raw) {
|
|
5
5
|
const text = Buffer.from(raw, "base64url").toString("utf8");
|
|
6
6
|
const idx = text.indexOf("\r\n\r\n");
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const parts = text
|
|
12
|
-
.slice(idx + 4)
|
|
13
|
-
.split(`--${boundary}`)
|
|
14
|
-
.slice(1, -1)
|
|
15
|
-
.map((chunk) => {
|
|
16
|
-
const split = chunk.indexOf("\r\n\r\n");
|
|
17
|
-
const partHeaders = chunk.slice(0, split);
|
|
18
|
-
const b64 = chunk.slice(split + 4).replace(/\r\n/g, "");
|
|
19
|
-
return {
|
|
20
|
-
contentType: /Content-Type: ([^;\r\n]+)/.exec(partHeaders)?.[1] ?? "",
|
|
21
|
-
content: Buffer.from(b64, "base64").toString("utf8"),
|
|
22
|
-
};
|
|
23
|
-
});
|
|
24
|
-
const find = (type) => parts.find((p) => p.contentType === type)?.content ?? "";
|
|
25
|
-
return { header, boundary, parts, plain: find("text/plain"), html: find("text/html") };
|
|
7
|
+
return {
|
|
8
|
+
header: text.slice(0, idx),
|
|
9
|
+
body: Buffer.from(text.slice(idx + 4).replace(/\r\n/g, ""), "base64").toString("utf8"),
|
|
10
|
+
};
|
|
26
11
|
}
|
|
27
12
|
describe("parseRecipients", () => {
|
|
28
13
|
it("splits, trims, and drops empties", () => {
|
|
@@ -37,76 +22,58 @@ describe("parseRecipients", () => {
|
|
|
37
22
|
});
|
|
38
23
|
});
|
|
39
24
|
describe("buildRawMessage", () => {
|
|
40
|
-
it("emits the core headers and a
|
|
25
|
+
it("emits the core headers and a single text/html body", () => {
|
|
41
26
|
const raw = buildRawMessage({
|
|
42
27
|
from: "me@x.com",
|
|
43
28
|
to: ["alice@x.com", "bob@x.com"],
|
|
44
29
|
subject: "Hello",
|
|
45
30
|
body: "Line one\nLine two",
|
|
46
31
|
});
|
|
47
|
-
const { header,
|
|
32
|
+
const { header, body } = decodeRaw(raw);
|
|
48
33
|
expect(header).toContain("From: me@x.com");
|
|
49
34
|
expect(header).toContain("To: alice@x.com, bob@x.com");
|
|
50
35
|
expect(header).toContain("Subject: Hello");
|
|
51
|
-
expect(header).toContain(
|
|
52
|
-
|
|
53
|
-
expect(
|
|
36
|
+
expect(header).toContain('Content-Type: text/html; charset="UTF-8"');
|
|
37
|
+
expect(body).toMatch(/^<html><body>/);
|
|
38
|
+
expect(body).toMatch(/<\/body><\/html>$/);
|
|
54
39
|
});
|
|
55
|
-
it("
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
expect(text.endsWith(`--${boundary}--`)).toBe(true);
|
|
66
|
-
});
|
|
67
|
-
it("uses a fresh boundary per message", () => {
|
|
68
|
-
const fields = { from: "me@x.com", to: ["a@x.com"], subject: "s", body: "b" };
|
|
69
|
-
expect(decodeRaw(buildRawMessage(fields)).boundary).not.toBe(decodeRaw(buildRawMessage(fields)).boundary);
|
|
70
|
-
});
|
|
71
|
-
it("keeps the markdown source verbatim in the text/plain part", () => {
|
|
72
|
-
const body = "# Heading\n\nSome **bold** text.\n\n- one\n- two";
|
|
73
|
-
const { plain } = decodeRaw(buildRawMessage({ from: "me@x.com", to: ["a@x.com"], subject: "s", body }));
|
|
74
|
-
expect(plain).toBe(body);
|
|
75
|
-
});
|
|
76
|
-
it("renders markdown into the text/html part", () => {
|
|
77
|
-
const { html } = decodeRaw(buildRawMessage({
|
|
40
|
+
it("offers no text/plain alternative for the composer to adopt", () => {
|
|
41
|
+
// A plain part reopens the coin flip that delivered raw markdown to the
|
|
42
|
+
// reader — see specs/commands/gmail-draft.md.
|
|
43
|
+
const { header } = decodeRaw(buildRawMessage({ from: "me@x.com", to: ["a@x.com"], subject: "s", body: "b" }));
|
|
44
|
+
expect(header).not.toContain("multipart");
|
|
45
|
+
expect(header).not.toContain("text/plain");
|
|
46
|
+
expect(header).not.toContain("boundary");
|
|
47
|
+
});
|
|
48
|
+
it("renders markdown to HTML", () => {
|
|
49
|
+
const { body } = decodeRaw(buildRawMessage({
|
|
78
50
|
from: "me@x.com",
|
|
79
51
|
to: ["a@x.com"],
|
|
80
52
|
subject: "s",
|
|
81
|
-
body: "Some **bold** text.\n\n- one\n- two",
|
|
53
|
+
body: "Some **bold** text and a [link](https://axi.md).\n\n- one\n- two",
|
|
82
54
|
}));
|
|
83
|
-
expect(
|
|
84
|
-
expect(
|
|
85
|
-
expect(
|
|
86
|
-
expect(html).toMatch(/<\/body><\/html>$/);
|
|
55
|
+
expect(body).toContain("<strong>bold</strong>");
|
|
56
|
+
expect(body).toContain('<a href="https://axi.md">link</a>');
|
|
57
|
+
expect(body).toContain("<li>one</li>");
|
|
87
58
|
});
|
|
88
59
|
it("maps a blank line to a new paragraph and a single newline to <br>", () => {
|
|
89
|
-
const {
|
|
60
|
+
const { body } = decodeRaw(buildRawMessage({
|
|
90
61
|
from: "me@x.com",
|
|
91
62
|
to: ["a@x.com"],
|
|
92
63
|
subject: "s",
|
|
93
64
|
body: "First para.\n\nSecond line one\nsecond line two",
|
|
94
65
|
}));
|
|
95
|
-
|
|
96
|
-
expect(
|
|
97
|
-
// ...and the intra-paragraph newline survives as a break, not a space.
|
|
98
|
-
expect(html).toContain("Second line one<br>second line two");
|
|
66
|
+
expect(body.match(/<p>/g)).toHaveLength(2);
|
|
67
|
+
expect(body).toContain("Second line one<br>second line two");
|
|
99
68
|
});
|
|
100
|
-
it("never breaks a long paragraph inside the decoded
|
|
69
|
+
it("never breaks a long paragraph inside the decoded body", () => {
|
|
101
70
|
// The 76-column wrapping is base64 transport only — it must not survive
|
|
102
71
|
// decoding, or the recipient sees the ragged columns this format exists
|
|
103
72
|
// to prevent.
|
|
104
|
-
const
|
|
105
|
-
const {
|
|
106
|
-
expect(
|
|
107
|
-
expect(
|
|
108
|
-
expect(html).not.toContain("\r\n");
|
|
109
|
-
expect(html).toContain(body);
|
|
73
|
+
const text = `${"word ".repeat(60).trim()}.`;
|
|
74
|
+
const { body } = decodeRaw(buildRawMessage({ from: "me@x.com", to: ["a@x.com"], subject: "s", body: text }));
|
|
75
|
+
expect(body).not.toContain("\r\n");
|
|
76
|
+
expect(body).toContain(text);
|
|
110
77
|
});
|
|
111
78
|
it("includes Cc/Bcc only when provided", () => {
|
|
112
79
|
const withCc = decodeRaw(buildRawMessage({
|
|
@@ -123,8 +90,8 @@ describe("buildRawMessage", () => {
|
|
|
123
90
|
expect(without).not.toContain("Cc:");
|
|
124
91
|
expect(without).not.toContain("Bcc:");
|
|
125
92
|
});
|
|
126
|
-
it("RFC 2047 encodes non-ASCII subjects but leaves
|
|
127
|
-
const { header,
|
|
93
|
+
it("RFC 2047 encodes non-ASCII subjects but leaves the body decodable", () => {
|
|
94
|
+
const { header, body } = decodeRaw(buildRawMessage({
|
|
128
95
|
from: "me@x.com",
|
|
129
96
|
to: ["a@x.com"],
|
|
130
97
|
subject: "café ☕",
|
|
@@ -132,8 +99,7 @@ describe("buildRawMessage", () => {
|
|
|
132
99
|
}));
|
|
133
100
|
expect(header).toContain("Subject: =?UTF-8?B?");
|
|
134
101
|
expect(header).not.toContain("café");
|
|
135
|
-
expect(
|
|
136
|
-
expect(html).toContain("naïve résumé — ☕");
|
|
102
|
+
expect(body).toContain("naïve résumé — ☕");
|
|
137
103
|
});
|
|
138
104
|
it("produces valid base64url (no +,/,= padding chars)", () => {
|
|
139
105
|
const raw = buildRawMessage({
|
|
@@ -143,7 +109,58 @@ describe("buildRawMessage", () => {
|
|
|
143
109
|
body: "x".repeat(200), // force base64 line wrapping
|
|
144
110
|
});
|
|
145
111
|
expect(raw).not.toMatch(/[+/=]/);
|
|
146
|
-
expect(decodeRaw(raw).
|
|
112
|
+
expect(decodeRaw(raw).body).toContain("x".repeat(200));
|
|
113
|
+
});
|
|
114
|
+
});
|
|
115
|
+
describe("buildRawMessage --plain", () => {
|
|
116
|
+
const plainBody = (body) => decodeRaw(buildRawMessage({ from: "me@x.com", to: ["a@x.com"], subject: "s", body, plain: true })).body;
|
|
117
|
+
it("still sends a single text/html part", () => {
|
|
118
|
+
// Reverting to text/plain is the original defect; --plain changes how the
|
|
119
|
+
// body is interpreted, not the message structure.
|
|
120
|
+
const { header } = decodeRaw(buildRawMessage({ from: "me@x.com", to: ["a@x.com"], subject: "s", body: "b", plain: true }));
|
|
121
|
+
expect(header).toContain('Content-Type: text/html; charset="UTF-8"');
|
|
122
|
+
expect(header).not.toContain("multipart");
|
|
123
|
+
expect(header).not.toContain("text/plain");
|
|
124
|
+
});
|
|
125
|
+
it("leaves markdown syntax literal", () => {
|
|
126
|
+
const body = plainBody("Some **bold** text, a [link](https://axi.md), and _underscores_.");
|
|
127
|
+
expect(body).toContain("**bold**");
|
|
128
|
+
expect(body).toContain("[link](https://axi.md)");
|
|
129
|
+
expect(body).toContain("_underscores_");
|
|
130
|
+
expect(body).not.toContain("<strong>");
|
|
131
|
+
expect(body).not.toContain("<a href");
|
|
132
|
+
});
|
|
133
|
+
it("does not turn a leading # or - into a heading or list", () => {
|
|
134
|
+
const body = plainBody("# not a heading\n- not a list item");
|
|
135
|
+
expect(body).toContain("# not a heading");
|
|
136
|
+
expect(body).toContain("- not a list item");
|
|
137
|
+
expect(body).not.toContain("<h1>");
|
|
138
|
+
expect(body).not.toContain("<li>");
|
|
139
|
+
});
|
|
140
|
+
it("escapes HTML so tags reach the reader as text", () => {
|
|
141
|
+
const body = plainBody("Use <script>alert(1)</script> & <b>tags</b> literally");
|
|
142
|
+
expect(body).toContain("<script>alert(1)</script>");
|
|
143
|
+
expect(body).toContain("&");
|
|
144
|
+
expect(body).toContain("<b>tags</b>");
|
|
145
|
+
});
|
|
146
|
+
it("keeps the same block semantics as the markdown path", () => {
|
|
147
|
+
const body = plainBody("First para.\n\nSecond line one\nsecond line two");
|
|
148
|
+
expect(body.match(/<p>/g)).toHaveLength(2);
|
|
149
|
+
expect(body).toContain("Second line one<br>second line two");
|
|
150
|
+
});
|
|
151
|
+
it("preserves indentation and column alignment", () => {
|
|
152
|
+
const body = plainBody(" calories 1,895\n protein 100 g");
|
|
153
|
+
// Leading indent is fully non-breaking; an internal run keeps one real
|
|
154
|
+
// space as a wrap point.
|
|
155
|
+
expect(body).toContain(" calories");
|
|
156
|
+
expect(body).toContain("calories 1,895");
|
|
157
|
+
});
|
|
158
|
+
it("expands tabs and drops blank-only blocks", () => {
|
|
159
|
+
expect(plainBody("\tindented")).toContain(" indented");
|
|
160
|
+
expect(plainBody("one\n\n \n\ntwo").match(/<p>/g)).toHaveLength(2);
|
|
161
|
+
});
|
|
162
|
+
it("renders an empty body as an empty document", () => {
|
|
163
|
+
expect(plainBody("")).toBe("<html><body></body></html>");
|
|
147
164
|
});
|
|
148
165
|
});
|
|
149
166
|
//# sourceMappingURL=compose.test.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"compose.test.js","sourceRoot":"","sources":["../../../../src/commands/gmail/compose.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;
|
|
1
|
+
{"version":3,"file":"compose.test.js","sourceRoot":"","sources":["../../../../src/commands/gmail/compose.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export declare const DRAFT_HELP = "usage: gws-axi gmail draft --to <emails> --subject <text> --body <markdown> [flags]\nflags[
|
|
1
|
+
export declare const DRAFT_HELP = "usage: gws-axi gmail draft --to <emails> --subject <text> --body <markdown> [flags]\nflags[9]:\n --to <emails> REQUIRED \u2014 comma-separated recipient addresses\n --subject <text> Subject line (default: empty)\n --body <text> Body text. Pass via quoted string or shell heredoc\n --body-file <path> Read the body from a file instead of --body (mutually\n exclusive with --body; use for long/multi-line bodies)\n --cc <emails> Comma-separated Cc addresses\n --bcc <emails> Comma-separated Bcc addresses\n --thread <thread-id> Attach the draft to an existing thread (reply draft)\n --plain Treat the body as LITERAL text, not markdown. Use when\n *, _, # or backticks must reach the reader as typed\n --account <email> REQUIRED when 2+ accounts are authenticated\nexamples:\n gws-axi gmail draft --to alice@x.com --subject \"Re: budget\" --body \"Looks good \u2014 approving.\"\n gws-axi gmail draft --to a@x.com,b@x.com --subject Hi --body-file ./note.txt\n gws-axi gmail draft --to alice@x.com --subject \"Re: thread\" --body \"...\" --thread 1899abcd\nnotes:\n Creates a DRAFT only \u2014 gws-axi never sends mail. Review and send from the\n Gmail UI. The body is markdown, rendered to a single text/html part so\n recipients get reflowable, formatted text (Gmail adds the plain-text\n alternative itself on send).\n DO NOT hard-wrap the body. Write each paragraph as one long line \u2014 every\n newline you write becomes a line break in the sent message, so a body\n pre-wrapped at 72/80 columns arrives locked to that width on every screen.\n Blank line = new paragraph. GFM applies: **bold**, lists, tables, fenced\n code blocks (use those for text meant literally) \u2014 or pass --plain to turn\n markdown off entirely and keep indentation as typed.\noutput:\n Returns `action: drafted` plus the new draft_id, message_id, recipients,\n and subject. A help line links to where to review/send it.\n";
|
|
2
2
|
export declare const SEND_HELP = "usage: gws-axi gmail send \u2014 INTENTIONALLY OUT OF SCOPE\nstatus: not supported by design\nnotes:\n gws-axi deliberately does not send mail. It can draft messages for you, but\n sending is left to a human in the Gmail UI so an automated agent can't email\n people on your behalf. Use `gws-axi gmail draft` to compose, then review and\n send the draft yourself in Gmail.\n";
|
|
3
3
|
export declare function gmailDraftCommand(account: string, args: string[]): Promise<string>;
|
|
@@ -4,7 +4,7 @@ import { gmailClient, translateGoogleError } from "../../google/client.js";
|
|
|
4
4
|
import { joinBlocks, renderHelp, renderObject } from "../../output/index.js";
|
|
5
5
|
import { buildRawMessage, parseRecipients } from "./compose.js";
|
|
6
6
|
export const DRAFT_HELP = `usage: gws-axi gmail draft --to <emails> --subject <text> --body <markdown> [flags]
|
|
7
|
-
flags[
|
|
7
|
+
flags[9]:
|
|
8
8
|
--to <emails> REQUIRED — comma-separated recipient addresses
|
|
9
9
|
--subject <text> Subject line (default: empty)
|
|
10
10
|
--body <text> Body text. Pass via quoted string or shell heredoc
|
|
@@ -13,6 +13,8 @@ flags[8]:
|
|
|
13
13
|
--cc <emails> Comma-separated Cc addresses
|
|
14
14
|
--bcc <emails> Comma-separated Bcc addresses
|
|
15
15
|
--thread <thread-id> Attach the draft to an existing thread (reply draft)
|
|
16
|
+
--plain Treat the body as LITERAL text, not markdown. Use when
|
|
17
|
+
*, _, # or backticks must reach the reader as typed
|
|
16
18
|
--account <email> REQUIRED when 2+ accounts are authenticated
|
|
17
19
|
examples:
|
|
18
20
|
gws-axi gmail draft --to alice@x.com --subject "Re: budget" --body "Looks good — approving."
|
|
@@ -20,14 +22,15 @@ examples:
|
|
|
20
22
|
gws-axi gmail draft --to alice@x.com --subject "Re: thread" --body "..." --thread 1899abcd
|
|
21
23
|
notes:
|
|
22
24
|
Creates a DRAFT only — gws-axi never sends mail. Review and send from the
|
|
23
|
-
Gmail UI. The body is markdown, rendered to
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
Gmail UI. The body is markdown, rendered to a single text/html part so
|
|
26
|
+
recipients get reflowable, formatted text (Gmail adds the plain-text
|
|
27
|
+
alternative itself on send).
|
|
26
28
|
DO NOT hard-wrap the body. Write each paragraph as one long line — every
|
|
27
29
|
newline you write becomes a line break in the sent message, so a body
|
|
28
30
|
pre-wrapped at 72/80 columns arrives locked to that width on every screen.
|
|
29
31
|
Blank line = new paragraph. GFM applies: **bold**, lists, tables, fenced
|
|
30
|
-
code blocks (use those for text meant literally)
|
|
32
|
+
code blocks (use those for text meant literally) — or pass --plain to turn
|
|
33
|
+
markdown off entirely and keep indentation as typed.
|
|
31
34
|
output:
|
|
32
35
|
Returns \`action: drafted\` plus the new draft_id, message_id, recipients,
|
|
33
36
|
and subject. A help line links to where to review/send it.
|
|
@@ -49,6 +52,7 @@ function parseFlags(args) {
|
|
|
49
52
|
body: undefined,
|
|
50
53
|
bodyFile: undefined,
|
|
51
54
|
thread: undefined,
|
|
55
|
+
plain: false,
|
|
52
56
|
};
|
|
53
57
|
for (let i = 0; i < args.length; i++) {
|
|
54
58
|
const arg = args[i];
|
|
@@ -82,6 +86,9 @@ function parseFlags(args) {
|
|
|
82
86
|
flags.thread = next;
|
|
83
87
|
i++;
|
|
84
88
|
break;
|
|
89
|
+
case "--plain":
|
|
90
|
+
flags.plain = true;
|
|
91
|
+
break;
|
|
85
92
|
}
|
|
86
93
|
}
|
|
87
94
|
return flags;
|
|
@@ -119,6 +126,7 @@ export async function gmailDraftCommand(account, args) {
|
|
|
119
126
|
bcc: flags.bcc.length ? flags.bcc : undefined,
|
|
120
127
|
subject: flags.subject,
|
|
121
128
|
body,
|
|
129
|
+
plain: flags.plain,
|
|
122
130
|
});
|
|
123
131
|
const message = { raw };
|
|
124
132
|
if (flags.thread)
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../../src/commands/gmail/draft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,CAAC,MAAM,UAAU,GAAG
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../../src/commands/gmail/draft.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,OAAO,EAAE,WAAW,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC3E,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAC7E,OAAO,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,cAAc,CAAC;AAEhE,MAAM,CAAC,MAAM,UAAU,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CA+BzB,CAAC;AAEF,MAAM,CAAC,MAAM,SAAS,GAAG;;;;;;;CAOxB,CAAC;AAaF,SAAS,UAAU,CAAC,IAAc;IAChC,MAAM,KAAK,GAAgB;QACzB,EAAE,EAAE,EAAE;QACN,EAAE,EAAE,EAAE;QACN,GAAG,EAAE,EAAE;QACP,OAAO,EAAE,EAAE;QACX,IAAI,EAAE,SAAS;QACf,QAAQ,EAAE,SAAS;QACnB,MAAM,EAAE,SAAS;QACjB,KAAK,EAAE,KAAK;KACb,CAAC;IACF,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACrC,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;QACpB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;QACzB,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,MAAM;gBACT,KAAK,CAAC,EAAE,GAAG,eAAe,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBACvC,CAAC,EAAE,CAAC;gBACJ,MAAM;YACR,KAAK,MAAM;gBACT,KAAK,CAAC,EAAE,GAAG,eAAe,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBACvC,CAAC,EAAE,CAAC;gBACJ,MAAM;YACR,KAAK,OAAO;gBACV,KAAK,CAAC,GAAG,GAAG,eAAe,CAAC,IAAI,IAAI,EAAE,CAAC,CAAC;gBACxC,CAAC,EAAE,CAAC;gBACJ,MAAM;YACR,KAAK,WAAW;gBACd,KAAK,CAAC,OAAO,GAAG,IAAI,IAAI,EAAE,CAAC;gBAC3B,CAAC,EAAE,CAAC;gBACJ,MAAM;YACR,KAAK,QAAQ;gBACX,KAAK,CAAC,IAAI,GAAG,IAAI,CAAC;gBAClB,CAAC,EAAE,CAAC;gBACJ,MAAM;YACR,KAAK,aAAa;gBAChB,KAAK,CAAC,QAAQ,GAAG,IAAI,CAAC;gBACtB,CAAC,EAAE,CAAC;gBACJ,MAAM;YACR,KAAK,UAAU;gBACb,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;gBACpB,CAAC,EAAE,CAAC;gBACJ,MAAM;YACR,KAAK,SAAS;gBACZ,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC;gBACnB,MAAM;QACV,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,WAAW,CAAC,KAAkB;IACrC,IAAI,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAC7D,MAAM,IAAI,QAAQ,CAAC,+CAA+C,EAAE,kBAAkB,EAAE;YACtF,4EAA4E;SAC7E,CAAC,CAAC;IACL,CAAC;IACD,IAAI,KAAK,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,CAAC;YACH,OAAO,YAAY,CAAC,KAAK,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC9C,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,QAAQ,CAAC,4BAA4B,KAAK,CAAC,QAAQ,EAAE,EAAE,kBAAkB,EAAE;gBACnF,uCAAuC;aACxC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;AAC1B,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,OAAe,EAAE,IAAc;IACrE,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;IAC/B,IAAI,KAAK,CAAC,EAAE,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,QAAQ,CAAC,kBAAkB,EAAE,kBAAkB,EAAE;YACzD,yEAAyE;SAC1E,CAAC,CAAC;IACL,CAAC;IACD,MAAM,IAAI,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;IAEhC,MAAM,GAAG,GAAG,eAAe,CAAC;QAC1B,IAAI,EAAE,OAAO;QACb,EAAE,EAAE,KAAK,CAAC,EAAE;QACZ,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS;QAC1C,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS;QAC7C,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,IAAI;QACJ,KAAK,EAAE,KAAK,CAAC,KAAK;KACnB,CAAC,CAAC;IAEH,MAAM,OAAO,GAA4B,EAAE,GAAG,EAAE,CAAC;IACjD,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,CAAC,QAAQ,GAAG,KAAK,CAAC,MAAM,CAAC;IAElD,MAAM,GAAG,GAAG,MAAM,WAAW,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,KAA4B,CAAC;IACjC,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC;YACxC,MAAM,EAAE,IAAI;YACZ,WAAW,EAAE,EAAE,OAAO,EAAE;SACzB,CAAC,CAAC;QACH,KAAK,GAAG,GAAG,CAAC,IAAI,CAAC;IACnB,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,oBAAoB,CAAC,GAAG,EAAE;YAC9B,OAAO;YACP,SAAS,EAAE,qBAAqB;SACjC,CAAC,CAAC;IACL,CAAC;IAED,MAAM,MAAM,GAA4B;QACtC,MAAM,EAAE,SAAS;QACjB,OAAO;QACP,QAAQ,EAAE,KAAK,CAAC,EAAE,IAAI,EAAE;QACxB,UAAU,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE;QACnC,EAAE,EAAE,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC;QACvB,OAAO,EAAE,KAAK,CAAC,OAAO,IAAI,cAAc;KACzC,CAAC;IACF,IAAI,KAAK,CAAC,EAAE,CAAC,MAAM;QAAE,MAAM,CAAC,EAAE,GAAG,KAAK,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IACrD,IAAI,KAAK,CAAC,MAAM;QAAE,MAAM,CAAC,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC;IAElD,OAAO,UAAU,CACf,YAAY,CAAC,MAAM,CAAC,EACpB,UAAU,CAAC;QACT,8EAA8E;QAC9E,6CAA6C,KAAK,CAAC,EAAE,IAAI,EAAE,GAAG;KAC/D,CAAC,CACH,CAAC;AACJ,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gws-axi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "AXI-compliant Google Workspace CLI — token-efficient TOON output, agent-guided auth setup, unified interface for Gmail, Calendar, Docs, Drive, Slides",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"repository": {
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { AxiError } from "axi-sdk-js";
|
|
2
|
-
import { resolveAccount } from "../google/account.js";
|
|
3
|
-
function parseAccountFlag(args) {
|
|
4
|
-
const rest = [];
|
|
5
|
-
let account;
|
|
6
|
-
for (let i = 0; i < args.length; i++) {
|
|
7
|
-
const arg = args[i];
|
|
8
|
-
if (arg === "--account" && args[i + 1]) {
|
|
9
|
-
account = args[i + 1];
|
|
10
|
-
i++;
|
|
11
|
-
continue;
|
|
12
|
-
}
|
|
13
|
-
rest.push(arg);
|
|
14
|
-
}
|
|
15
|
-
return { account, rest };
|
|
16
|
-
}
|
|
17
|
-
export function buildServiceStub(service, subcommands) {
|
|
18
|
-
const readOps = subcommands.filter((s) => !s.mutation).map((s) => s.name);
|
|
19
|
-
const writeOps = subcommands.filter((s) => s.mutation).map((s) => s.name);
|
|
20
|
-
const help = `usage: gws-axi ${service} <subcommand> [args] [--account <email>] [flags]
|
|
21
|
-
reads[${readOps.length}]:
|
|
22
|
-
${readOps.join(", ") || "(none)"}
|
|
23
|
-
writes[${writeOps.length}]:
|
|
24
|
-
${writeOps.join(", ") || "(none)"}
|
|
25
|
-
status: ${service} is not yet implemented in v1-dev
|
|
26
|
-
see: docs/design.md for the full command surface plan
|
|
27
|
-
notes:
|
|
28
|
-
Writes require --account <email> when 2+ accounts are authenticated.
|
|
29
|
-
Reads use the default account when --account is not provided.
|
|
30
|
-
`;
|
|
31
|
-
const command = async (args) => {
|
|
32
|
-
if (args.length === 0) {
|
|
33
|
-
// Return help as structured output so it integrates with TOON
|
|
34
|
-
return {
|
|
35
|
-
service,
|
|
36
|
-
status: "not_implemented",
|
|
37
|
-
reads: readOps,
|
|
38
|
-
writes: writeOps,
|
|
39
|
-
help: [
|
|
40
|
-
`Run \`gws-axi ${service} --help\` for details`,
|
|
41
|
-
"See docs/design.md for implementation roadmap",
|
|
42
|
-
],
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
const sub = args[0];
|
|
46
|
-
const def = subcommands.find((s) => s.name === sub);
|
|
47
|
-
if (!def) {
|
|
48
|
-
throw new AxiError(`Unknown ${service} subcommand: ${sub}`, "VALIDATION_ERROR", [
|
|
49
|
-
`Run \`gws-axi ${service} --help\` to see available subcommands`,
|
|
50
|
-
]);
|
|
51
|
-
}
|
|
52
|
-
// Account resolution runs even though the operation is stubbed — this
|
|
53
|
-
// exercises write-protection and error paths without needing real API
|
|
54
|
-
// plumbing, and gives users an accurate preview of the multi-account
|
|
55
|
-
// error messages they'd see in production.
|
|
56
|
-
const { account } = parseAccountFlag(args.slice(1));
|
|
57
|
-
const resolution = resolveAccount(account, {
|
|
58
|
-
mutation: def.mutation,
|
|
59
|
-
commandName: `${service} ${sub}`,
|
|
60
|
-
});
|
|
61
|
-
throw new AxiError(`gws-axi ${service} ${sub} is not yet implemented in v1-dev`, "NOT_IMPLEMENTED", [
|
|
62
|
-
`Account resolution succeeded: would run as ${resolution.account}`,
|
|
63
|
-
`Run \`gws-axi ${service} --help\` for the planned command surface`,
|
|
64
|
-
"See docs/design.md for implementation roadmap",
|
|
65
|
-
]);
|
|
66
|
-
};
|
|
67
|
-
return { help, command };
|
|
68
|
-
}
|
|
69
|
-
//# sourceMappingURL=service-stub.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"service-stub.js","sourceRoot":"","sources":["../../../src/commands/service-stub.ts"],"names":[],"mappings":"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"}
|