gws-axi 0.8.4 → 0.9.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/README.md +39 -3
- package/dist/src/auth/scopes.d.ts +1 -0
- package/dist/src/auth/scopes.js +9 -1
- package/dist/src/auth/scopes.js.map +1 -1
- package/dist/src/commands/gmail/batch-modify.d.ts +2 -0
- package/dist/src/commands/gmail/batch-modify.js +158 -0
- package/dist/src/commands/gmail/batch-modify.js.map +1 -0
- package/dist/src/commands/gmail/compose.d.ts +24 -0
- package/dist/src/commands/gmail/compose.js +47 -0
- package/dist/src/commands/gmail/compose.js.map +1 -0
- package/dist/src/commands/gmail/compose.test.d.ts +1 -0
- package/dist/src/commands/gmail/compose.test.js +75 -0
- package/dist/src/commands/gmail/compose.test.js.map +1 -0
- package/dist/src/commands/gmail/draft.d.ts +3 -0
- package/dist/src/commands/gmail/draft.js +148 -0
- package/dist/src/commands/gmail/draft.js.map +1 -0
- package/dist/src/commands/gmail/filters.d.ts +6 -0
- package/dist/src/commands/gmail/filters.js +305 -0
- package/dist/src/commands/gmail/filters.js.map +1 -0
- package/dist/src/commands/gmail/label-ops.d.ts +6 -0
- package/dist/src/commands/gmail/label-ops.js +208 -0
- package/dist/src/commands/gmail/label-ops.js.map +1 -0
- package/dist/src/commands/gmail/labels-shared.d.ts +23 -0
- package/dist/src/commands/gmail/labels-shared.js +61 -0
- package/dist/src/commands/gmail/labels-shared.js.map +1 -0
- package/dist/src/commands/gmail/labels-shared.test.d.ts +1 -0
- package/dist/src/commands/gmail/labels-shared.test.js +59 -0
- package/dist/src/commands/gmail/labels-shared.test.js.map +1 -0
- package/dist/src/commands/gmail/modify.d.ts +2 -0
- package/dist/src/commands/gmail/modify.js +130 -0
- package/dist/src/commands/gmail/modify.js.map +1 -0
- package/dist/src/commands/gmail/search.js +2 -30
- package/dist/src/commands/gmail/search.js.map +1 -1
- package/dist/src/commands/gmail.js +39 -53
- package/dist/src/commands/gmail.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -4,7 +4,7 @@ Agent-ergonomic CLI for Google Workspace — Gmail, Calendar, Docs, Drive, and S
|
|
|
4
4
|
|
|
5
5
|
Designed for use by AI agents. Every response is structured, every error names a specific fix, and write operations lock to the explicit account when multiple are authenticated — so two agents in parallel sessions can't silently touch the wrong mailbox.
|
|
6
6
|
|
|
7
|
-
> **Status:** Read coverage is in across all five services — Calendar, Gmail, Docs, Drive, and Slides
|
|
7
|
+
> **Status:** Read coverage is in across all five services — Calendar, Gmail, Docs, Drive, and Slides. **Calendar** and **Gmail** both have full write support: Calendar does `create` / `update` / `delete` / `respond`, and Gmail does triage (`modify`, `batch-modify`), drafting (`draft`), label CRUD (`label-create` / `label-update` / `label-delete`), and server-side filters (`filter-create` / `filter-delete` / `filter-list`). Gmail **`send` is intentionally out of scope** — gws-axi drafts mail for human review but never sends it. Docs writes (`append`, `insert-text`, `delete-range`) are the next frontier. Auth (including a single-developer `auth publish` walkthrough that retires the 7-day Testing-token expiry), doctor with live per-account API probes, and multi-account write-protection are stable.
|
|
8
8
|
|
|
9
9
|
## Requirements
|
|
10
10
|
|
|
@@ -123,6 +123,8 @@ Writes default `--send-updates` to `none` so agent-created events don't spam att
|
|
|
123
123
|
|
|
124
124
|
### Gmail
|
|
125
125
|
|
|
126
|
+
**Reads:**
|
|
127
|
+
|
|
126
128
|
```bash
|
|
127
129
|
gws-axi gmail search # in:inbox by default
|
|
128
130
|
gws-axi gmail search --query "from:alice has:attachment"
|
|
@@ -134,8 +136,41 @@ gws-axi gmail labels # list labels with IDs
|
|
|
134
136
|
gws-axi gmail download <message-id> <attachment-id> # fetch attachment bytes
|
|
135
137
|
```
|
|
136
138
|
|
|
139
|
+
**Writes** (triage, drafting, labels, filters — but never *send*):
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
# Triage — Gmail state IS labels, so archive/read/star are label edits:
|
|
143
|
+
gws-axi gmail modify <message-id> --remove-label INBOX # archive
|
|
144
|
+
gws-axi gmail modify <message-id> --remove-label UNREAD # mark read
|
|
145
|
+
gws-axi gmail modify <message-id> --add-label STARRED # star
|
|
146
|
+
gws-axi gmail modify <message-id> --add-label "Work/Clients" --remove-label INBOX
|
|
147
|
+
gws-axi gmail modify <thread-id> --thread --remove-label UNREAD # whole thread
|
|
148
|
+
|
|
149
|
+
# Bulk triage by query:
|
|
150
|
+
gws-axi gmail batch-modify --query "from:news@x.com" --remove-label INBOX
|
|
151
|
+
gws-axi gmail batch-modify --query "is:unread older_than:30d" --remove-label UNREAD
|
|
152
|
+
|
|
153
|
+
# Drafts (composed for you; you send from Gmail):
|
|
154
|
+
gws-axi gmail draft --to alice@x.com --subject "Re: budget" --body "Approved."
|
|
155
|
+
gws-axi gmail draft --to a@x.com,b@x.com --subject Hi --body-file ./note.txt --thread <id>
|
|
156
|
+
|
|
157
|
+
# Label management:
|
|
158
|
+
gws-axi gmail label-create --name "Work/Clients"
|
|
159
|
+
gws-axi gmail label-update <label-id|name> --name "Archive"
|
|
160
|
+
gws-axi gmail label-delete <label-id|name>
|
|
161
|
+
|
|
162
|
+
# Server-side auto-sort filters:
|
|
163
|
+
gws-axi gmail filter-list
|
|
164
|
+
gws-axi gmail filter-create --from news@x.com --remove-label INBOX --add-label News
|
|
165
|
+
gws-axi gmail filter-delete <filter-id>
|
|
166
|
+
```
|
|
167
|
+
|
|
137
168
|
Bodies are decoded per declared `Content-Type` charset (UTF-8, latin-1, ascii, utf-16). HTML-only messages convert to markdown via turndown. Address headers parse with `addressparser` so display names with commas don't get split.
|
|
138
169
|
|
|
170
|
+
**On `send`:** gws-axi has no `send` command by design — an agent can triage, label, and draft replies, but a human reviews and sends from Gmail. This is a guardrail against *accidental* sends, not a hard security boundary (the OAuth token is send-capable; there is no Gmail scope that grants drafting + label edits while withholding send). `gws-axi gmail send` returns a `NOT_SUPPORTED` error pointing at `draft`.
|
|
171
|
+
|
|
172
|
+
**Scope note:** the filter commands need the `gmail.settings.basic` scope, which `gmail.modify` does not include. Accounts authenticated before this release will get a `SCOPE_MISSING` (403) on filter commands until you re-run `gws-axi auth login --account <email>` once to re-consent.
|
|
173
|
+
|
|
139
174
|
### Docs
|
|
140
175
|
|
|
141
176
|
```bash
|
|
@@ -198,8 +233,9 @@ help[2]:
|
|
|
198
233
|
|
|
199
234
|
## Known issues & roadmap
|
|
200
235
|
|
|
201
|
-
- **Gmail
|
|
202
|
-
- **Docs writes** (`append`, `insert-text`, `delete-range`, etc.): scaffolded as `NOT_IMPLEMENTED`.
|
|
236
|
+
- **Gmail `send`**: intentionally unsupported — gws-axi drafts mail but leaves sending to a human in the Gmail UI. See the Gmail section above.
|
|
237
|
+
- **Docs writes** (`append`, `insert-text`, `delete-range`, etc.): scaffolded as `NOT_IMPLEMENTED`. The next frontier.
|
|
238
|
+
- **Drive/Slides writes**: still scaffolded as `NOT_IMPLEMENTED` (reads are complete).
|
|
203
239
|
- **Testing-mode tokens** still expire every 7 days *if* you haven't published your OAuth app yet. Run `gws-axi auth publish` for the walkthrough — it covers the single-developer Production flow and removes the expiry once you've re-auth'd each account.
|
|
204
240
|
|
|
205
241
|
## Contributing
|
|
@@ -6,6 +6,7 @@ export declare const SERVICE_SCOPES: {
|
|
|
6
6
|
readonly drive: "https://www.googleapis.com/auth/drive";
|
|
7
7
|
readonly slides: "https://www.googleapis.com/auth/presentations";
|
|
8
8
|
};
|
|
9
|
+
export declare const ADDITIONAL_SCOPES: readonly ["https://www.googleapis.com/auth/gmail.settings.basic"];
|
|
9
10
|
export type ServiceName = keyof typeof SERVICE_SCOPES;
|
|
10
11
|
export declare const SERVICES: ServiceName[];
|
|
11
12
|
export declare const REQUIRED_APIS: Record<ServiceName, string>;
|
package/dist/src/auth/scopes.js
CHANGED
|
@@ -6,6 +6,14 @@ export const SERVICE_SCOPES = {
|
|
|
6
6
|
drive: "https://www.googleapis.com/auth/drive",
|
|
7
7
|
slides: "https://www.googleapis.com/auth/presentations",
|
|
8
8
|
};
|
|
9
|
+
// Scopes layered on top of the representative per-service scope above.
|
|
10
|
+
// gmail.settings.basic is required for Gmail filter management
|
|
11
|
+
// (users.settings.filters.*); it is NOT covered by gmail.modify. Kept
|
|
12
|
+
// separate so the per-service probe/health checks keep keying off the
|
|
13
|
+
// single representative scope in SERVICE_SCOPES.
|
|
14
|
+
export const ADDITIONAL_SCOPES = [
|
|
15
|
+
"https://www.googleapis.com/auth/gmail.settings.basic",
|
|
16
|
+
];
|
|
9
17
|
export const SERVICES = [
|
|
10
18
|
"gmail",
|
|
11
19
|
"calendar",
|
|
@@ -21,7 +29,7 @@ export const REQUIRED_APIS = {
|
|
|
21
29
|
slides: "slides.googleapis.com",
|
|
22
30
|
};
|
|
23
31
|
export function allScopes() {
|
|
24
|
-
return [...BASE_SCOPES, ...Object.values(SERVICE_SCOPES)];
|
|
32
|
+
return [...BASE_SCOPES, ...Object.values(SERVICE_SCOPES), ...ADDITIONAL_SCOPES];
|
|
25
33
|
}
|
|
26
34
|
export function allApis() {
|
|
27
35
|
return Object.values(REQUIRED_APIS);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../../src/auth/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAU,CAAC;AAEnE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,8CAA8C;IACrD,QAAQ,EAAE,0CAA0C;IACpD,IAAI,EAAE,2CAA2C;IACjD,KAAK,EAAE,uCAAuC;IAC9C,MAAM,EAAE,+CAA+C;CAC/C,CAAC;AAIX,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,OAAO;IACP,UAAU;IACV,MAAM;IACN,OAAO;IACP,QAAQ;CACT,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAgC;IACxD,KAAK,EAAE,sBAAsB;IAC7B,QAAQ,EAAE,8BAA8B;IACxC,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,sBAAsB;IAC7B,MAAM,EAAE,uBAAuB;CAChC,CAAC;AAEF,MAAM,UAAU,SAAS;IACvB,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"scopes.js","sourceRoot":"","sources":["../../../src/auth/scopes.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,QAAQ,EAAE,OAAO,EAAE,SAAS,CAAU,CAAC;AAEnE,MAAM,CAAC,MAAM,cAAc,GAAG;IAC5B,KAAK,EAAE,8CAA8C;IACrD,QAAQ,EAAE,0CAA0C;IACpD,IAAI,EAAE,2CAA2C;IACjD,KAAK,EAAE,uCAAuC;IAC9C,MAAM,EAAE,+CAA+C;CAC/C,CAAC;AAEX,uEAAuE;AACvE,+DAA+D;AAC/D,sEAAsE;AACtE,sEAAsE;AACtE,iDAAiD;AACjD,MAAM,CAAC,MAAM,iBAAiB,GAAG;IAC/B,sDAAsD;CAC9C,CAAC;AAIX,MAAM,CAAC,MAAM,QAAQ,GAAkB;IACrC,OAAO;IACP,UAAU;IACV,MAAM;IACN,OAAO;IACP,QAAQ;CACT,CAAC;AAEF,MAAM,CAAC,MAAM,aAAa,GAAgC;IACxD,KAAK,EAAE,sBAAsB;IAC7B,QAAQ,EAAE,8BAA8B;IACxC,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,sBAAsB;IAC7B,MAAM,EAAE,uBAAuB;CAChC,CAAC;AAEF,MAAM,UAAU,SAAS;IACvB,OAAO,CAAC,GAAG,WAAW,EAAE,GAAG,MAAM,CAAC,MAAM,CAAC,cAAc,CAAC,EAAE,GAAG,iBAAiB,CAAC,CAAC;AAClF,CAAC;AAED,MAAM,UAAU,OAAO;IACrB,OAAO,MAAM,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const BATCH_MODIFY_HELP = "usage: gws-axi gmail batch-modify --query <text> [--add-label <name>...] [--remove-label <name>...] [flags]\nflags[6]:\n --query <text> REQUIRED \u2014 Gmail search query selecting the messages\n to modify (full Gmail syntax: from:, is:unread, etc.)\n --add-label <name> Label to apply to every match. Repeatable / comma list.\n Accepts user + system labels (INBOX, UNREAD, STARRED\u2026)\n --remove-label <name> Label to remove from every match. Same name rules\n --limit <n> Max messages to affect (default: 100, max: 1000)\n --include-spam-trash Include spam/trash in the selection\n --account <email> REQUIRED when 2+ accounts are authenticated\nexamples:\n gws-axi gmail batch-modify --query \"from:newsletters@x.com\" --remove-label INBOX\n gws-axi gmail batch-modify --query \"is:unread older_than:30d\" --remove-label UNREAD\n gws-axi gmail batch-modify --query \"from:boss@x.com\" --add-label \"Important/Boss\"\nnotes:\n Operates on MESSAGES matching --query (not threads). At least one of\n --add-label / --remove-label is required. If more messages match than\n --limit, only the first --limit are modified and the response says so \u2014\n re-run to continue. Idempotent per message (see `gmail modify`).\n";
|
|
2
|
+
export declare function gmailBatchModifyCommand(account: string, args: string[]): Promise<string>;
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { AxiError } from "axi-sdk-js";
|
|
2
|
+
import { gmailClient, translateGoogleError } from "../../google/client.js";
|
|
3
|
+
import { joinBlocks, renderHelp, renderObject } from "../../output/index.js";
|
|
4
|
+
import { fetchLabels, labelNamesFor, resolveLabelIds, } from "./labels-shared.js";
|
|
5
|
+
const DEFAULT_LIMIT = 100;
|
|
6
|
+
const MAX_LIMIT = 1000; // messages.batchModify accepts up to 1000 ids per call.
|
|
7
|
+
export const BATCH_MODIFY_HELP = `usage: gws-axi gmail batch-modify --query <text> [--add-label <name>...] [--remove-label <name>...] [flags]
|
|
8
|
+
flags[6]:
|
|
9
|
+
--query <text> REQUIRED — Gmail search query selecting the messages
|
|
10
|
+
to modify (full Gmail syntax: from:, is:unread, etc.)
|
|
11
|
+
--add-label <name> Label to apply to every match. Repeatable / comma list.
|
|
12
|
+
Accepts user + system labels (INBOX, UNREAD, STARRED…)
|
|
13
|
+
--remove-label <name> Label to remove from every match. Same name rules
|
|
14
|
+
--limit <n> Max messages to affect (default: 100, max: 1000)
|
|
15
|
+
--include-spam-trash Include spam/trash in the selection
|
|
16
|
+
--account <email> REQUIRED when 2+ accounts are authenticated
|
|
17
|
+
examples:
|
|
18
|
+
gws-axi gmail batch-modify --query "from:newsletters@x.com" --remove-label INBOX
|
|
19
|
+
gws-axi gmail batch-modify --query "is:unread older_than:30d" --remove-label UNREAD
|
|
20
|
+
gws-axi gmail batch-modify --query "from:boss@x.com" --add-label "Important/Boss"
|
|
21
|
+
notes:
|
|
22
|
+
Operates on MESSAGES matching --query (not threads). At least one of
|
|
23
|
+
--add-label / --remove-label is required. If more messages match than
|
|
24
|
+
--limit, only the first --limit are modified and the response says so —
|
|
25
|
+
re-run to continue. Idempotent per message (see \`gmail modify\`).
|
|
26
|
+
`;
|
|
27
|
+
function collect(target, value) {
|
|
28
|
+
if (!value)
|
|
29
|
+
return;
|
|
30
|
+
for (const part of value.split(",")) {
|
|
31
|
+
const t = part.trim();
|
|
32
|
+
if (t)
|
|
33
|
+
target.push(t);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
function parseFlags(args) {
|
|
37
|
+
const flags = {
|
|
38
|
+
query: undefined,
|
|
39
|
+
add: [],
|
|
40
|
+
remove: [],
|
|
41
|
+
limit: DEFAULT_LIMIT,
|
|
42
|
+
includeSpamTrash: false,
|
|
43
|
+
};
|
|
44
|
+
for (let i = 0; i < args.length; i++) {
|
|
45
|
+
const arg = args[i];
|
|
46
|
+
const next = args[i + 1];
|
|
47
|
+
switch (arg) {
|
|
48
|
+
case "--query":
|
|
49
|
+
flags.query = next;
|
|
50
|
+
i++;
|
|
51
|
+
break;
|
|
52
|
+
case "--add-label":
|
|
53
|
+
collect(flags.add, next);
|
|
54
|
+
i++;
|
|
55
|
+
break;
|
|
56
|
+
case "--remove-label":
|
|
57
|
+
collect(flags.remove, next);
|
|
58
|
+
i++;
|
|
59
|
+
break;
|
|
60
|
+
case "--limit":
|
|
61
|
+
flags.limit = Math.max(1, Math.min(MAX_LIMIT, parseInt(next, 10) || DEFAULT_LIMIT));
|
|
62
|
+
i++;
|
|
63
|
+
break;
|
|
64
|
+
case "--include-spam-trash":
|
|
65
|
+
flags.includeSpamTrash = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return flags;
|
|
70
|
+
}
|
|
71
|
+
/** Page through messages.list until we hit `limit` ids or run out. */
|
|
72
|
+
async function collectMessageIds(api, account, query, limit, includeSpamTrash) {
|
|
73
|
+
const ids = [];
|
|
74
|
+
let pageToken;
|
|
75
|
+
try {
|
|
76
|
+
while (ids.length < limit) {
|
|
77
|
+
const res = await api.users.messages.list({
|
|
78
|
+
userId: "me",
|
|
79
|
+
q: query,
|
|
80
|
+
includeSpamTrash,
|
|
81
|
+
maxResults: Math.min(500, limit - ids.length),
|
|
82
|
+
pageToken,
|
|
83
|
+
});
|
|
84
|
+
for (const m of res.data.messages ?? []) {
|
|
85
|
+
if (m.id)
|
|
86
|
+
ids.push(m.id);
|
|
87
|
+
}
|
|
88
|
+
pageToken = res.data.nextPageToken ?? undefined;
|
|
89
|
+
if (!pageToken)
|
|
90
|
+
return { ids, more: false };
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
throw translateGoogleError(err, {
|
|
95
|
+
account,
|
|
96
|
+
operation: "gmail.messages.list",
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
// Reached the limit; `more` is true iff another page exists.
|
|
100
|
+
return { ids: ids.slice(0, limit), more: Boolean(pageToken) };
|
|
101
|
+
}
|
|
102
|
+
export async function gmailBatchModifyCommand(account, args) {
|
|
103
|
+
const flags = parseFlags(args);
|
|
104
|
+
if (!flags.query) {
|
|
105
|
+
throw new AxiError("--query is required", "VALIDATION_ERROR", [
|
|
106
|
+
"Usage: gws-axi gmail batch-modify --query <text> [--add-label <name>] [--remove-label <name>]",
|
|
107
|
+
]);
|
|
108
|
+
}
|
|
109
|
+
if (flags.add.length === 0 && flags.remove.length === 0) {
|
|
110
|
+
throw new AxiError("Nothing to do — pass --add-label and/or --remove-label", "VALIDATION_ERROR", ["Archive matches: --remove-label INBOX · Mark read: --remove-label UNREAD"]);
|
|
111
|
+
}
|
|
112
|
+
const api = await gmailClient(account);
|
|
113
|
+
const labels = await fetchLabels(api, account);
|
|
114
|
+
const addLabelIds = resolveLabelIds(flags.add, labels);
|
|
115
|
+
const removeLabelIds = resolveLabelIds(flags.remove, labels);
|
|
116
|
+
const { ids, more } = await collectMessageIds(api, account, flags.query, flags.limit, flags.includeSpamTrash);
|
|
117
|
+
if (ids.length === 0) {
|
|
118
|
+
return joinBlocks(renderObject({
|
|
119
|
+
action: "noop",
|
|
120
|
+
account,
|
|
121
|
+
query: flags.query,
|
|
122
|
+
matched: 0,
|
|
123
|
+
reason: "no messages matched the query",
|
|
124
|
+
}), renderHelp([
|
|
125
|
+
`Preview matches first with \`gws-axi gmail search --query ${JSON.stringify(flags.query)}\``,
|
|
126
|
+
]));
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await api.users.messages.batchModify({
|
|
130
|
+
userId: "me",
|
|
131
|
+
requestBody: { ids, addLabelIds, removeLabelIds },
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
throw translateGoogleError(err, {
|
|
136
|
+
account,
|
|
137
|
+
operation: "gmail.messages.batchModify",
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
const result = {
|
|
141
|
+
action: "batch_modified",
|
|
142
|
+
account,
|
|
143
|
+
query: flags.query,
|
|
144
|
+
matched: ids.length,
|
|
145
|
+
};
|
|
146
|
+
if (flags.add.length)
|
|
147
|
+
result.added = labelNamesFor(addLabelIds, labels).join(", ");
|
|
148
|
+
if (flags.remove.length) {
|
|
149
|
+
result.removed = labelNamesFor(removeLabelIds, labels).join(", ");
|
|
150
|
+
}
|
|
151
|
+
const suggestions = [];
|
|
152
|
+
if (more) {
|
|
153
|
+
suggestions.push(`More messages match than --limit ${flags.limit} — re-run the same command to modify the next batch`);
|
|
154
|
+
}
|
|
155
|
+
suggestions.push(`Verify with \`gws-axi gmail search --query ${JSON.stringify(flags.query)}\``);
|
|
156
|
+
return joinBlocks(renderObject(result), renderHelp(suggestions));
|
|
157
|
+
}
|
|
158
|
+
//# sourceMappingURL=batch-modify.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"batch-modify.js","sourceRoot":"","sources":["../../../../src/commands/gmail/batch-modify.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal RFC 5322 message builder for draft creation. We only need the
|
|
3
|
+
* plain-text single-part case: agents draft text bodies for human review,
|
|
4
|
+
* and Gmail's web composer handles any rich formatting on send. The raw
|
|
5
|
+
* message is base64url-encoded as the Gmail API's `raw` field expects.
|
|
6
|
+
*/
|
|
7
|
+
export interface ComposeFields {
|
|
8
|
+
/** Sender — the authenticated account's address. */
|
|
9
|
+
from: string;
|
|
10
|
+
to: string[];
|
|
11
|
+
cc?: string[];
|
|
12
|
+
bcc?: string[];
|
|
13
|
+
subject: string;
|
|
14
|
+
body: string;
|
|
15
|
+
}
|
|
16
|
+
/** Split a comma-separated recipient flag into trimmed, non-empty addresses. */
|
|
17
|
+
export declare function parseRecipients(value: string): string[];
|
|
18
|
+
/**
|
|
19
|
+
* Build a base64url-encoded RFC 5322 message suitable for
|
|
20
|
+
* `users.drafts.create` / `users.messages.send` `raw` fields. Body is sent
|
|
21
|
+
* as a single text/plain part with base64 transfer-encoding so any UTF-8
|
|
22
|
+
* content survives intact.
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildRawMessage(fields: ComposeFields): string;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Minimal RFC 5322 message builder for draft creation. We only need the
|
|
3
|
+
* plain-text single-part case: agents draft text bodies for human review,
|
|
4
|
+
* and Gmail's web composer handles any rich formatting on send. The raw
|
|
5
|
+
* message is base64url-encoded as the Gmail API's `raw` field expects.
|
|
6
|
+
*/
|
|
7
|
+
/** Split a comma-separated recipient flag into trimmed, non-empty addresses. */
|
|
8
|
+
export function parseRecipients(value) {
|
|
9
|
+
return value
|
|
10
|
+
.split(",")
|
|
11
|
+
.map((s) => s.trim())
|
|
12
|
+
.filter(Boolean);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* RFC 2047 "encoded-word" wrap a header value when it carries non-ASCII.
|
|
16
|
+
* Pure-ASCII values pass through untouched so common subjects stay readable
|
|
17
|
+
* on the wire.
|
|
18
|
+
*/
|
|
19
|
+
function encodeHeaderValue(value) {
|
|
20
|
+
if (/^[\x00-\x7F]*$/.test(value))
|
|
21
|
+
return value;
|
|
22
|
+
return `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=`;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Build a base64url-encoded RFC 5322 message suitable for
|
|
26
|
+
* `users.drafts.create` / `users.messages.send` `raw` fields. Body is sent
|
|
27
|
+
* as a single text/plain part with base64 transfer-encoding so any UTF-8
|
|
28
|
+
* content survives intact.
|
|
29
|
+
*/
|
|
30
|
+
export function buildRawMessage(fields) {
|
|
31
|
+
const headers = [`From: ${fields.from}`, `To: ${fields.to.join(", ")}`];
|
|
32
|
+
if (fields.cc?.length)
|
|
33
|
+
headers.push(`Cc: ${fields.cc.join(", ")}`);
|
|
34
|
+
if (fields.bcc?.length)
|
|
35
|
+
headers.push(`Bcc: ${fields.bcc.join(", ")}`);
|
|
36
|
+
headers.push(`Subject: ${encodeHeaderValue(fields.subject)}`);
|
|
37
|
+
headers.push("MIME-Version: 1.0");
|
|
38
|
+
headers.push('Content-Type: text/plain; charset="UTF-8"');
|
|
39
|
+
headers.push("Content-Transfer-Encoding: base64");
|
|
40
|
+
// Wrap the base64 body at 76 columns (RFC 2045) with CRLF line endings.
|
|
41
|
+
const encodedBody = Buffer.from(fields.body, "utf8")
|
|
42
|
+
.toString("base64")
|
|
43
|
+
.replace(/(.{76})/g, "$1\r\n");
|
|
44
|
+
const raw = `${headers.join("\r\n")}\r\n\r\n${encodedBody}`;
|
|
45
|
+
return Buffer.from(raw, "utf8").toString("base64url");
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=compose.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.js","sourceRoot":"","sources":["../../../../src/commands/gmail/compose.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAYH,gFAAgF;AAChF,MAAM,UAAU,eAAe,CAAC,KAAa;IAC3C,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,OAAO,CAAC,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,SAAS,iBAAiB,CAAC,KAAa;IACtC,IAAI,gBAAgB,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAC/C,OAAO,aAAa,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;AACxE,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,eAAe,CAAC,MAAqB;IACnD,MAAM,OAAO,GAAa,CAAC,SAAS,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAClF,IAAI,MAAM,CAAC,EAAE,EAAE,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnE,IAAI,MAAM,CAAC,GAAG,EAAE,MAAM;QAAE,OAAO,CAAC,IAAI,CAAC,QAAQ,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACtE,OAAO,CAAC,IAAI,CAAC,YAAY,iBAAiB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;IAC9D,OAAO,CAAC,IAAI,CAAC,mBAAmB,CAAC,CAAC;IAClC,OAAO,CAAC,IAAI,CAAC,2CAA2C,CAAC,CAAC;IAC1D,OAAO,CAAC,IAAI,CAAC,mCAAmC,CAAC,CAAC;IAElD,wEAAwE;IACxE,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC;SACjD,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;IAEjC,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,WAAW,EAAE,CAAC;IAC5D,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { buildRawMessage, parseRecipients } from "./compose.js";
|
|
3
|
+
/** Decode a base64url raw message back into its header block and body. */
|
|
4
|
+
function decodeRaw(raw) {
|
|
5
|
+
const text = Buffer.from(raw, "base64url").toString("utf8");
|
|
6
|
+
const idx = text.indexOf("\r\n\r\n");
|
|
7
|
+
const header = text.slice(0, idx);
|
|
8
|
+
const bodyB64 = text.slice(idx + 4).replace(/\r\n/g, "");
|
|
9
|
+
return { header, body: Buffer.from(bodyB64, "base64").toString("utf8") };
|
|
10
|
+
}
|
|
11
|
+
describe("parseRecipients", () => {
|
|
12
|
+
it("splits, trims, and drops empties", () => {
|
|
13
|
+
expect(parseRecipients("a@x.com, b@x.com ,, c@x.com")).toEqual([
|
|
14
|
+
"a@x.com",
|
|
15
|
+
"b@x.com",
|
|
16
|
+
"c@x.com",
|
|
17
|
+
]);
|
|
18
|
+
});
|
|
19
|
+
it("handles a single address", () => {
|
|
20
|
+
expect(parseRecipients("solo@x.com")).toEqual(["solo@x.com"]);
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("buildRawMessage", () => {
|
|
24
|
+
it("emits the core headers and round-trips the body", () => {
|
|
25
|
+
const raw = buildRawMessage({
|
|
26
|
+
from: "me@x.com",
|
|
27
|
+
to: ["alice@x.com", "bob@x.com"],
|
|
28
|
+
subject: "Hello",
|
|
29
|
+
body: "Line one\nLine two",
|
|
30
|
+
});
|
|
31
|
+
const { header, body } = decodeRaw(raw);
|
|
32
|
+
expect(header).toContain("From: me@x.com");
|
|
33
|
+
expect(header).toContain("To: alice@x.com, bob@x.com");
|
|
34
|
+
expect(header).toContain("Subject: Hello");
|
|
35
|
+
expect(header).toContain('Content-Type: text/plain; charset="UTF-8"');
|
|
36
|
+
expect(body).toBe("Line one\nLine two");
|
|
37
|
+
});
|
|
38
|
+
it("includes Cc/Bcc only when provided", () => {
|
|
39
|
+
const withCc = decodeRaw(buildRawMessage({
|
|
40
|
+
from: "me@x.com",
|
|
41
|
+
to: ["a@x.com"],
|
|
42
|
+
cc: ["c@x.com"],
|
|
43
|
+
bcc: ["d@x.com"],
|
|
44
|
+
subject: "s",
|
|
45
|
+
body: "b",
|
|
46
|
+
})).header;
|
|
47
|
+
expect(withCc).toContain("Cc: c@x.com");
|
|
48
|
+
expect(withCc).toContain("Bcc: d@x.com");
|
|
49
|
+
const without = decodeRaw(buildRawMessage({ from: "me@x.com", to: ["a@x.com"], subject: "s", body: "b" })).header;
|
|
50
|
+
expect(without).not.toContain("Cc:");
|
|
51
|
+
expect(without).not.toContain("Bcc:");
|
|
52
|
+
});
|
|
53
|
+
it("RFC 2047 encodes non-ASCII subjects but leaves the body decodable", () => {
|
|
54
|
+
const { header, body } = decodeRaw(buildRawMessage({
|
|
55
|
+
from: "me@x.com",
|
|
56
|
+
to: ["a@x.com"],
|
|
57
|
+
subject: "café ☕",
|
|
58
|
+
body: "naïve résumé — ☕",
|
|
59
|
+
}));
|
|
60
|
+
expect(header).toContain("Subject: =?UTF-8?B?");
|
|
61
|
+
expect(header).not.toContain("café");
|
|
62
|
+
expect(body).toBe("naïve résumé — ☕");
|
|
63
|
+
});
|
|
64
|
+
it("produces valid base64url (no +,/,= padding chars)", () => {
|
|
65
|
+
const raw = buildRawMessage({
|
|
66
|
+
from: "me@x.com",
|
|
67
|
+
to: ["a@x.com"],
|
|
68
|
+
subject: "s",
|
|
69
|
+
body: "x".repeat(200), // force base64 line wrapping
|
|
70
|
+
});
|
|
71
|
+
expect(raw).not.toMatch(/[+/=]/);
|
|
72
|
+
expect(decodeRaw(raw).body).toBe("x".repeat(200));
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
//# sourceMappingURL=compose.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose.test.js","sourceRoot":"","sources":["../../../../src/commands/gmail/compose.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export declare const DRAFT_HELP = "usage: gws-axi gmail draft --to <emails> --subject <text> --body <markdown> [flags]\nflags[8]:\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 --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. Body is sent as plain text (text/plain; UTF-8); any markdown is\n preserved verbatim, not rendered to HTML.\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
|
+
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
|
+
export declare function gmailDraftCommand(account: string, args: string[]): Promise<string>;
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { AxiError } from "axi-sdk-js";
|
|
3
|
+
import { gmailClient, translateGoogleError } from "../../google/client.js";
|
|
4
|
+
import { joinBlocks, renderHelp, renderObject } from "../../output/index.js";
|
|
5
|
+
import { buildRawMessage, parseRecipients } from "./compose.js";
|
|
6
|
+
export const DRAFT_HELP = `usage: gws-axi gmail draft --to <emails> --subject <text> --body <markdown> [flags]
|
|
7
|
+
flags[8]:
|
|
8
|
+
--to <emails> REQUIRED — comma-separated recipient addresses
|
|
9
|
+
--subject <text> Subject line (default: empty)
|
|
10
|
+
--body <text> Body text. Pass via quoted string or shell heredoc
|
|
11
|
+
--body-file <path> Read the body from a file instead of --body (mutually
|
|
12
|
+
exclusive with --body; use for long/multi-line bodies)
|
|
13
|
+
--cc <emails> Comma-separated Cc addresses
|
|
14
|
+
--bcc <emails> Comma-separated Bcc addresses
|
|
15
|
+
--thread <thread-id> Attach the draft to an existing thread (reply draft)
|
|
16
|
+
--account <email> REQUIRED when 2+ accounts are authenticated
|
|
17
|
+
examples:
|
|
18
|
+
gws-axi gmail draft --to alice@x.com --subject "Re: budget" --body "Looks good — approving."
|
|
19
|
+
gws-axi gmail draft --to a@x.com,b@x.com --subject Hi --body-file ./note.txt
|
|
20
|
+
gws-axi gmail draft --to alice@x.com --subject "Re: thread" --body "..." --thread 1899abcd
|
|
21
|
+
notes:
|
|
22
|
+
Creates a DRAFT only — gws-axi never sends mail. Review and send from the
|
|
23
|
+
Gmail UI. Body is sent as plain text (text/plain; UTF-8); any markdown is
|
|
24
|
+
preserved verbatim, not rendered to HTML.
|
|
25
|
+
output:
|
|
26
|
+
Returns \`action: drafted\` plus the new draft_id, message_id, recipients,
|
|
27
|
+
and subject. A help line links to where to review/send it.
|
|
28
|
+
`;
|
|
29
|
+
export const SEND_HELP = `usage: gws-axi gmail send — INTENTIONALLY OUT OF SCOPE
|
|
30
|
+
status: not supported by design
|
|
31
|
+
notes:
|
|
32
|
+
gws-axi deliberately does not send mail. It can draft messages for you, but
|
|
33
|
+
sending is left to a human in the Gmail UI so an automated agent can't email
|
|
34
|
+
people on your behalf. Use \`gws-axi gmail draft\` to compose, then review and
|
|
35
|
+
send the draft yourself in Gmail.
|
|
36
|
+
`;
|
|
37
|
+
function parseFlags(args) {
|
|
38
|
+
const flags = {
|
|
39
|
+
to: [],
|
|
40
|
+
cc: [],
|
|
41
|
+
bcc: [],
|
|
42
|
+
subject: "",
|
|
43
|
+
body: undefined,
|
|
44
|
+
bodyFile: undefined,
|
|
45
|
+
thread: undefined,
|
|
46
|
+
};
|
|
47
|
+
for (let i = 0; i < args.length; i++) {
|
|
48
|
+
const arg = args[i];
|
|
49
|
+
const next = args[i + 1];
|
|
50
|
+
switch (arg) {
|
|
51
|
+
case "--to":
|
|
52
|
+
flags.to = parseRecipients(next ?? "");
|
|
53
|
+
i++;
|
|
54
|
+
break;
|
|
55
|
+
case "--cc":
|
|
56
|
+
flags.cc = parseRecipients(next ?? "");
|
|
57
|
+
i++;
|
|
58
|
+
break;
|
|
59
|
+
case "--bcc":
|
|
60
|
+
flags.bcc = parseRecipients(next ?? "");
|
|
61
|
+
i++;
|
|
62
|
+
break;
|
|
63
|
+
case "--subject":
|
|
64
|
+
flags.subject = next ?? "";
|
|
65
|
+
i++;
|
|
66
|
+
break;
|
|
67
|
+
case "--body":
|
|
68
|
+
flags.body = next;
|
|
69
|
+
i++;
|
|
70
|
+
break;
|
|
71
|
+
case "--body-file":
|
|
72
|
+
flags.bodyFile = next;
|
|
73
|
+
i++;
|
|
74
|
+
break;
|
|
75
|
+
case "--thread":
|
|
76
|
+
flags.thread = next;
|
|
77
|
+
i++;
|
|
78
|
+
break;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return flags;
|
|
82
|
+
}
|
|
83
|
+
function resolveBody(flags) {
|
|
84
|
+
if (flags.body !== undefined && flags.bodyFile !== undefined) {
|
|
85
|
+
throw new AxiError("--body and --body-file are mutually exclusive", "VALIDATION_ERROR", ["Pass the body inline with --body OR from a file with --body-file, not both"]);
|
|
86
|
+
}
|
|
87
|
+
if (flags.bodyFile !== undefined) {
|
|
88
|
+
try {
|
|
89
|
+
return readFileSync(flags.bodyFile, "utf8");
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new AxiError(`Cannot read --body-file: ${flags.bodyFile}`, "VALIDATION_ERROR", ["Check the path exists and is readable"]);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return flags.body ?? "";
|
|
96
|
+
}
|
|
97
|
+
export async function gmailDraftCommand(account, args) {
|
|
98
|
+
const flags = parseFlags(args);
|
|
99
|
+
if (flags.to.length === 0) {
|
|
100
|
+
throw new AxiError("--to is required", "VALIDATION_ERROR", [
|
|
101
|
+
`Usage: gws-axi gmail draft --to <emails> --subject <text> --body <text>`,
|
|
102
|
+
]);
|
|
103
|
+
}
|
|
104
|
+
const body = resolveBody(flags);
|
|
105
|
+
const raw = buildRawMessage({
|
|
106
|
+
from: account,
|
|
107
|
+
to: flags.to,
|
|
108
|
+
cc: flags.cc.length ? flags.cc : undefined,
|
|
109
|
+
bcc: flags.bcc.length ? flags.bcc : undefined,
|
|
110
|
+
subject: flags.subject,
|
|
111
|
+
body,
|
|
112
|
+
});
|
|
113
|
+
const message = { raw };
|
|
114
|
+
if (flags.thread)
|
|
115
|
+
message.threadId = flags.thread;
|
|
116
|
+
const api = await gmailClient(account);
|
|
117
|
+
let draft;
|
|
118
|
+
try {
|
|
119
|
+
const res = await api.users.drafts.create({
|
|
120
|
+
userId: "me",
|
|
121
|
+
requestBody: { message },
|
|
122
|
+
});
|
|
123
|
+
draft = res.data;
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
throw translateGoogleError(err, {
|
|
127
|
+
account,
|
|
128
|
+
operation: "gmail.drafts.create",
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
const result = {
|
|
132
|
+
action: "drafted",
|
|
133
|
+
account,
|
|
134
|
+
draft_id: draft.id ?? "",
|
|
135
|
+
message_id: draft.message?.id ?? "",
|
|
136
|
+
to: flags.to.join(", "),
|
|
137
|
+
subject: flags.subject || "(no subject)",
|
|
138
|
+
};
|
|
139
|
+
if (flags.cc.length)
|
|
140
|
+
result.cc = flags.cc.join(", ");
|
|
141
|
+
if (flags.thread)
|
|
142
|
+
result.thread_id = flags.thread;
|
|
143
|
+
return joinBlocks(renderObject(result), renderHelp([
|
|
144
|
+
"Draft saved — NOT sent. Review and send it from the Gmail UI (Drafts folder)",
|
|
145
|
+
`Edit or delete it later via the draft_id (${draft.id ?? ""})`,
|
|
146
|
+
]));
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=draft.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"draft.js","sourceRoot":"","sources":["../../../../src/commands/gmail/draft.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const FILTER_LIST_HELP = "usage: gws-axi gmail filter-list [flags]\nflags[1]:\n --account <email> Account override when 2+ are configured\noutput:\n A `filters[N]{id,criteria,action}` table. Each row summarizes one\n server-side rule. Pass an `id` to `gws-axi gmail filter-delete <id>`.\nnotes:\n Filters are server-side auto-sort rules that run continuously on Google's\n side (separate from labels). Filters require the gmail.settings.basic scope. If this 403s, run `gws-axi auth login --account <email>` to re-consent.\n";
|
|
2
|
+
export declare const FILTER_CREATE_HELP = "usage: gws-axi gmail filter-create <criteria> <action> [flags]\ncriteria (>=1 required):\n --from <text> Match sender\n --to <text> Match recipient\n --subject <text> Match subject\n --query <text> Match an arbitrary Gmail search query\n --has-attachment Match only messages with attachments\naction (>=1 required):\n --add-label <name> Apply a label. Repeatable / comma list. Accepts user\n + system labels (e.g. STARRED, IMPORTANT)\n --remove-label <name> Remove a label. Use INBOX to skip-inbox (archive),\n UNREAD to mark read\n --forward <email> Forward matching mail to this address (must be a\n verified forwarding address in Gmail settings)\nflags:\n --account <email> REQUIRED when 2+ accounts are authenticated\nexamples:\n gws-axi gmail filter-create --from newsletters@x.com --remove-label INBOX --add-label News\n gws-axi gmail filter-create --subject \"[ALERT]\" --add-label STARRED --add-label IMPORTANT\nnotes:\n Idempotent: an existing filter with identical criteria + action returns\n `action: exists`. Filters require the gmail.settings.basic scope. If this 403s, run `gws-axi auth login --account <email>` to re-consent.\n";
|
|
3
|
+
export declare const FILTER_DELETE_HELP = "usage: gws-axi gmail filter-delete <filter-id> [flags]\nargs[1]:\n <filter-id> Filter ID (from `gws-axi gmail filter-list`)\nflags[1]:\n --account <email> REQUIRED when 2+ accounts are authenticated\nnotes:\n Idempotent: deleting an unknown filter id returns `action: noop`.\n Filters require the gmail.settings.basic scope. If this 403s, run `gws-axi auth login --account <email>` to re-consent.\n";
|
|
4
|
+
export declare function gmailFilterListCommand(account: string, _args: string[]): Promise<string>;
|
|
5
|
+
export declare function gmailFilterCreateCommand(account: string, args: string[]): Promise<string>;
|
|
6
|
+
export declare function gmailFilterDeleteCommand(account: string, args: string[]): Promise<string>;
|