gws-axi 0.19.0 → 0.20.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/docs.js +22 -13
- package/dist/src/commands/docs.js.map +1 -1
- package/dist/src/commands/drive.js +16 -7
- package/dist/src/commands/drive.js.map +1 -1
- package/dist/src/commands/gmail/compose.d.ts +24 -7
- package/dist/src/commands/gmail/compose.js +45 -14
- package/dist/src/commands/gmail/compose.js.map +1 -1
- package/dist/src/commands/gmail/compose.test.js +85 -11
- 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 +8 -2
- package/dist/src/commands/gmail/draft.js.map +1 -1
- package/dist/src/commands/sheets.js +21 -10
- package/dist/src/commands/sheets.js.map +1 -1
- package/dist/src/commands/slides.js +21 -8
- package/dist/src/commands/slides.js.map +1 -1
- package/dist/src/commands/stub-signposts.d.ts +41 -0
- package/dist/src/commands/stub-signposts.js +51 -0
- package/dist/src/commands/stub-signposts.js.map +1 -0
- package/dist/src/commands/stub-signposts.test.d.ts +1 -0
- package/dist/src/commands/stub-signposts.test.js +54 -0
- package/dist/src/commands/stub-signposts.test.js.map +1 -0
- package/package.json +2 -1
|
@@ -1,11 +1,21 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
2
|
import { resolveAccount } from "../google/account.js";
|
|
3
|
+
import { notImplemented, renderAlternatives, withInstead } from "./stub-signposts.js";
|
|
3
4
|
import { docsCommentsCommand, COMMENTS_HELP } from "./docs/comments.js";
|
|
4
5
|
import { docsDiffCommand, DIFF_HELP } from "./docs/diff.js";
|
|
5
6
|
import { docsDownloadCommand, DOWNLOAD_HELP } from "./docs/download.js";
|
|
6
7
|
import { docsFindCommand, FIND_HELP } from "./docs/find.js";
|
|
7
8
|
import { docsReadCommand, READ_HELP } from "./docs/read.js";
|
|
8
9
|
import { driveRevisionsCommand, REVISIONS_HELP } from "./drive/revisions.js";
|
|
10
|
+
// Granular Docs edits need documents.batchUpdate, which isn't wired yet. The
|
|
11
|
+
// download → edit → upload round trip is the shipped substitute, and it
|
|
12
|
+
// replaces the whole document — so the lines say that rather than implying a
|
|
13
|
+
// targeted edit.
|
|
14
|
+
const ROUND_TRIP = [
|
|
15
|
+
"gws-axi docs download <documentId> --out ./doc.md — export the current content to edit locally",
|
|
16
|
+
"gws-axi drive upload ./doc.md --update <documentId> --convert --account <email> — write it back as a NEW REVISION, replacing the ENTIRE document",
|
|
17
|
+
"Multi-tab Docs are refused with MULTI_TAB_TARGET unless --replace-all-tabs; `gws-axi docs diff <documentId> <revA>` compares revisions afterward",
|
|
18
|
+
];
|
|
9
19
|
// Write subcommands are still stubs but we keep per-command --help text
|
|
10
20
|
// so agents can plan around the future surface.
|
|
11
21
|
const APPEND_HELP = `usage: gws-axi docs append <documentId> --text <markdown> [--tab <id>] [flags]
|
|
@@ -53,13 +63,15 @@ const SUBCOMMANDS = [
|
|
|
53
63
|
// model, but the implementation is Drive-wide (any file type).
|
|
54
64
|
{ name: "revisions", mutation: false, help: REVISIONS_HELP, handler: driveRevisionsCommand },
|
|
55
65
|
{ name: "diff", mutation: false, help: DIFF_HELP, handler: docsDiffCommand },
|
|
56
|
-
{ name: "append", mutation: true, help: APPEND_HELP },
|
|
57
|
-
{ name: "insert-text", mutation: true, help: INSERT_TEXT_HELP },
|
|
58
|
-
{ name: "delete-range", mutation: true, help: DELETE_RANGE_HELP },
|
|
59
|
-
{ name: "style-text", mutation: true, help: STYLE_TEXT_HELP },
|
|
60
|
-
{ name: "style-paragraph", mutation: true, help: STYLE_PARAGRAPH_HELP },
|
|
61
|
-
{ name: "insert-table", mutation: true, help: INSERT_TABLE_HELP },
|
|
62
|
-
{ name: "edit-cell", mutation: true, help: EDIT_CELL_HELP },
|
|
66
|
+
{ name: "append", mutation: true, help: APPEND_HELP, instead: ROUND_TRIP },
|
|
67
|
+
{ name: "insert-text", mutation: true, help: INSERT_TEXT_HELP, instead: ROUND_TRIP },
|
|
68
|
+
{ name: "delete-range", mutation: true, help: DELETE_RANGE_HELP, instead: ROUND_TRIP },
|
|
69
|
+
{ name: "style-text", mutation: true, help: STYLE_TEXT_HELP, instead: ROUND_TRIP },
|
|
70
|
+
{ name: "style-paragraph", mutation: true, help: STYLE_PARAGRAPH_HELP, instead: ROUND_TRIP },
|
|
71
|
+
{ name: "insert-table", mutation: true, help: INSERT_TABLE_HELP, instead: ROUND_TRIP },
|
|
72
|
+
{ name: "edit-cell", mutation: true, help: EDIT_CELL_HELP, instead: ROUND_TRIP },
|
|
73
|
+
// Comment writes need the Drive comments API, which no shipped command
|
|
74
|
+
// touches — the round trip below rewrites body content only.
|
|
63
75
|
{ name: "comment-add", mutation: true, help: COMMENT_ADD_HELP },
|
|
64
76
|
{ name: "comment-reply", mutation: true, help: COMMENT_REPLY_HELP },
|
|
65
77
|
{ name: "comment-resolve", mutation: true, help: COMMENT_RESOLVE_HELP },
|
|
@@ -91,7 +103,7 @@ notes:
|
|
|
91
103
|
Reads use the default account when --account is not provided.
|
|
92
104
|
Write subcommands are scaffolded for the next slice — all currently
|
|
93
105
|
throw NOT_IMPLEMENTED after account resolution runs.
|
|
94
|
-
subcommand help:
|
|
106
|
+
${renderAlternatives(SUBCOMMANDS)}subcommand help:
|
|
95
107
|
gws-axi docs read --help for documentId + tab handling
|
|
96
108
|
gws-axi docs find --help for text-match search
|
|
97
109
|
gws-axi docs comments --help for review comments + replies
|
|
@@ -118,7 +130,7 @@ export async function docsCommand(args) {
|
|
|
118
130
|
}
|
|
119
131
|
const rest = args.slice(1);
|
|
120
132
|
if (rest.includes("--help")) {
|
|
121
|
-
return def.help;
|
|
133
|
+
return def.handler ? def.help : withInstead(def.help, def.instead);
|
|
122
134
|
}
|
|
123
135
|
const { account: accountFlag, rest: remaining } = parseAccountFlag(rest);
|
|
124
136
|
const resolution = resolveAccount(accountFlag, {
|
|
@@ -126,10 +138,7 @@ export async function docsCommand(args) {
|
|
|
126
138
|
commandName: `docs ${sub}`,
|
|
127
139
|
});
|
|
128
140
|
if (!def.handler) {
|
|
129
|
-
throw
|
|
130
|
-
`Account resolution succeeded: would run as ${resolution.account}`,
|
|
131
|
-
`See \`gws-axi docs ${sub} --help\` for the planned surface`,
|
|
132
|
-
]);
|
|
141
|
+
throw notImplemented("docs", sub, resolution.account, def.instead);
|
|
133
142
|
}
|
|
134
143
|
return def.handler(resolution.account, remaining);
|
|
135
144
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs.js","sourceRoot":"","sources":["../../../src/commands/docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACxE,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"docs.js","sourceRoot":"","sources":["../../../src/commands/docs.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
2
|
import { resolveAccount } from "../google/account.js";
|
|
3
|
+
import { notImplemented, renderAlternatives, withInstead } from "./stub-signposts.js";
|
|
3
4
|
import { docsDownloadCommand } from "./docs/download.js";
|
|
4
5
|
import { driveGetCommand, GET_HELP } from "./drive/get.js";
|
|
5
6
|
import { driveLsCommand, LS_HELP } from "./drive/ls.js";
|
|
@@ -82,7 +83,18 @@ const SUBCOMMANDS = [
|
|
|
82
83
|
help: UPLOAD_HELP,
|
|
83
84
|
handler: driveUploadCommand,
|
|
84
85
|
},
|
|
85
|
-
{
|
|
86
|
+
{
|
|
87
|
+
name: "create",
|
|
88
|
+
mutation: true,
|
|
89
|
+
help: CREATE_HELP,
|
|
90
|
+
instead: [
|
|
91
|
+
"gws-axi drive upload <path|-|--content <string>> --name <name> --account <email> — creates a file from content (add --convert for a native Doc/Sheet/Slides)",
|
|
92
|
+
'gws-axi drive mkdir "<name>" --account <email> — creates a folder',
|
|
93
|
+
],
|
|
94
|
+
},
|
|
95
|
+
// copy / move / delete have no shipped equivalent. `rename` deliberately
|
|
96
|
+
// gets none either: `drive upload --update --name` does rename, but only
|
|
97
|
+
// while replacing the file's content, so advertising it here would mislead.
|
|
86
98
|
{ name: "copy", mutation: true, help: COPY_HELP },
|
|
87
99
|
{ name: "move", mutation: true, help: MOVE_HELP },
|
|
88
100
|
{ name: "rename", mutation: true, help: RENAME_HELP },
|
|
@@ -121,7 +133,7 @@ notes:
|
|
|
121
133
|
Reads use the default account when --account is not provided.
|
|
122
134
|
upload and mkdir are live; the remaining write subcommands are scaffolded
|
|
123
135
|
for the next slice and throw NOT_IMPLEMENTED after account resolution runs.
|
|
124
|
-
subcommand help:
|
|
136
|
+
${renderAlternatives(SUBCOMMANDS)}subcommand help:
|
|
125
137
|
gws-axi drive ls --help for folder listing (incl. --recursive)
|
|
126
138
|
gws-axi drive get --help for full file metadata
|
|
127
139
|
gws-axi drive search --help for full-text search
|
|
@@ -151,7 +163,7 @@ export async function driveCommand(args) {
|
|
|
151
163
|
}
|
|
152
164
|
const rest = args.slice(1);
|
|
153
165
|
if (rest.includes("--help")) {
|
|
154
|
-
return def.help;
|
|
166
|
+
return def.handler ? def.help : withInstead(def.help, def.instead);
|
|
155
167
|
}
|
|
156
168
|
const { account: accountFlag, rest: remaining } = parseAccountFlag(rest);
|
|
157
169
|
const resolution = resolveAccount(accountFlag, {
|
|
@@ -159,10 +171,7 @@ export async function driveCommand(args) {
|
|
|
159
171
|
commandName: `drive ${sub}`,
|
|
160
172
|
});
|
|
161
173
|
if (!def.handler) {
|
|
162
|
-
throw
|
|
163
|
-
`Account resolution succeeded: would run as ${resolution.account}`,
|
|
164
|
-
`See \`gws-axi drive ${sub} --help\` for the planned surface`,
|
|
165
|
-
]);
|
|
174
|
+
throw notImplemented("drive", sub, resolution.account, def.instead);
|
|
166
175
|
}
|
|
167
176
|
return def.handler(resolution.account, remaining);
|
|
168
177
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"drive.js","sourceRoot":"","sources":["../../../src/commands/drive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"drive.js","sourceRoot":"","sources":["../../../src/commands/drive.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AACtF,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,eAAe,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AACxD,OAAO,EAAE,uBAAuB,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AACnF,OAAO,EAAE,oBAAoB,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAC1E,OAAO,EAAE,qBAAqB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC7E,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AACpE,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAUjE,0EAA0E;AAC1E,sEAAsE;AACtE,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;;;;;CAmBrB,CAAC;AAEF,2EAA2E;AAC3E,MAAM,WAAW,GAAG;;CAEnB,CAAC;AACF,MAAM,SAAS,GAAG;;CAEjB,CAAC;AACF,MAAM,SAAS,GAAG;;CAEjB,CAAC;AACF,MAAM,WAAW,GAAG;;CAEnB,CAAC;AACF,MAAM,WAAW,GAAG;;CAEnB,CAAC;AAEF,MAAM,WAAW,GAAsB;IACrC,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,kBAAkB,EAAE;IACnF,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,eAAe,EAAE;IAC1E,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,OAAO,EAAE,OAAO,EAAE,cAAc,EAAE;IACvE;QACE,IAAI,EAAE,aAAa;QACnB,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,gBAAgB;QACtB,OAAO,EAAE,uBAAuB;KACjC;IACD;QACE,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,aAAa;QACnB,oEAAoE;QACpE,OAAO,EAAE,mBAAmB;KAC7B;IACD;QACE,IAAI,EAAE,WAAW;QACjB,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,cAAc;QACpB,OAAO,EAAE,qBAAqB;KAC/B;IACD;QACE,IAAI,EAAE,UAAU;QAChB,QAAQ,EAAE,KAAK;QACf,IAAI,EAAE,aAAa;QACnB,OAAO,EAAE,oBAAoB;KAC9B;IACD;QACE,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE,kBAAkB;KAC5B;IACD;QACE,IAAI,EAAE,QAAQ;QACd,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,WAAW;QACjB,OAAO,EAAE;YACP,8JAA8J;YAC9J,mEAAmE;SACpE;KACF;IACD,yEAAyE;IACzE,yEAAyE;IACzE,4EAA4E;IAC5E,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;IACjD,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,SAAS,EAAE;IACjD,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE;IACrD,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE;IACrD;QACE,IAAI,EAAE,OAAO;QACb,QAAQ,EAAE,IAAI;QACd,IAAI,EAAE,UAAU;QAChB,OAAO,EAAE,iBAAiB;KAC3B;CACF,CAAC;AAEF,MAAM,WAAW,GAAoC,MAAM,CAAC,WAAW,CACrE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CACpC,CAAC;AAEF,SAAS,gBAAgB,CAAC,IAAc;IAItC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,OAA2B,CAAC;IAChC,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,IAAI,GAAG,KAAK,WAAW,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACvC,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACtB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxE,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAExE,MAAM,CAAC,MAAM,UAAU,GAAG;QAClB,KAAK,CAAC,MAAM;IAChB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,MAAM;IAClB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;EAMnB,kBAAkB,CAAC,WAAW,CAAC;;;;;;;;;;;;;;;;CAgBhC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,IAAc;IAC/C,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC;QACrE,OAAO,UAAU,CAAC;IACpB,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,QAAQ,CAAC,6BAA6B,GAAG,EAAE,EAAE,kBAAkB,EAAE;YACzE,2DAA2D;SAC5D,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACzE,MAAM,UAAU,GAAG,cAAc,CAAC,WAAW,EAAE;QAC7C,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,WAAW,EAAE,SAAS,GAAG,EAAE;KAC5B,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,cAAc,CAAC,OAAO,EAAE,GAAG,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* RFC 5322 message builder for draft creation.
|
|
3
|
+
*
|
|
4
|
+
* Drafts go out as `multipart/alternative`: the `--body` markdown verbatim as
|
|
5
|
+
* `text/plain`, and its rendered form as `text/html`. That structure is what
|
|
6
|
+
* Gmail's own composer produces, and emitting it is load-bearing rather than
|
|
7
|
+
* cosmetic — a single-part `text/plain` draft drops Gmail's composer into
|
|
8
|
+
* plain-text mode, and on send Gmail hard-wraps the body at ~70 columns with
|
|
9
|
+
* real CRLFs and ships no HTML alternative. The compose window still shows the
|
|
10
|
+
* original unwrapped paragraphs, so the recipient's ragged columns are
|
|
11
|
+
* invisible until after the message is gone.
|
|
12
|
+
*
|
|
13
|
+
* See specs/commands/gmail-draft.md ("Message structure", "Body rendering").
|
|
6
14
|
*/
|
|
7
15
|
export interface ComposeFields {
|
|
8
16
|
/** Sender — the authenticated account's address. */
|
|
@@ -11,14 +19,23 @@ export interface ComposeFields {
|
|
|
11
19
|
cc?: string[];
|
|
12
20
|
bcc?: string[];
|
|
13
21
|
subject: string;
|
|
22
|
+
/** Markdown source. Rendered into the text/html part, kept verbatim as text/plain. */
|
|
14
23
|
body: string;
|
|
15
24
|
}
|
|
16
25
|
/** Split a comma-separated recipient flag into trimmed, non-empty addresses. */
|
|
17
26
|
export declare function parseRecipients(value: string): string[];
|
|
27
|
+
/**
|
|
28
|
+
* Render the body markdown to an HTML document.
|
|
29
|
+
*
|
|
30
|
+
* `breaks: true` is deliberate: email bodies carry meaningful single-line
|
|
31
|
+
* breaks (signature blocks, addresses, value lists), and markdown's default of
|
|
32
|
+
* collapsing them into spaces would silently reflow content the author laid
|
|
33
|
+
* out. The flip side — a pre-wrapped body keeps its wrapping — is why
|
|
34
|
+
* DRAFT_HELP tells callers not to hard-wrap.
|
|
35
|
+
*/
|
|
36
|
+
export declare function renderMarkdown(body: string): string;
|
|
18
37
|
/**
|
|
19
38
|
* Build a base64url-encoded RFC 5322 message suitable for
|
|
20
|
-
* `users.drafts.create` / `users.messages.send` `raw` fields.
|
|
21
|
-
* as a single text/plain part with base64 transfer-encoding so any UTF-8
|
|
22
|
-
* content survives intact.
|
|
39
|
+
* `users.drafts.create` / `users.messages.send` `raw` fields.
|
|
23
40
|
*/
|
|
24
41
|
export declare function buildRawMessage(fields: ComposeFields): string;
|
|
@@ -1,9 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* RFC 5322 message builder for draft creation.
|
|
3
|
+
*
|
|
4
|
+
* Drafts go out as `multipart/alternative`: the `--body` markdown verbatim as
|
|
5
|
+
* `text/plain`, and its rendered form as `text/html`. That structure is what
|
|
6
|
+
* Gmail's own composer produces, and emitting it is load-bearing rather than
|
|
7
|
+
* cosmetic — a single-part `text/plain` draft drops Gmail's composer into
|
|
8
|
+
* plain-text mode, and on send Gmail hard-wraps the body at ~70 columns with
|
|
9
|
+
* real CRLFs and ships no HTML alternative. The compose window still shows the
|
|
10
|
+
* original unwrapped paragraphs, so the recipient's ragged columns are
|
|
11
|
+
* invisible until after the message is gone.
|
|
12
|
+
*
|
|
13
|
+
* See specs/commands/gmail-draft.md ("Message structure", "Body rendering").
|
|
6
14
|
*/
|
|
15
|
+
import { randomUUID } from "node:crypto";
|
|
16
|
+
import { marked } from "marked";
|
|
7
17
|
/** Split a comma-separated recipient flag into trimmed, non-empty addresses. */
|
|
8
18
|
export function parseRecipients(value) {
|
|
9
19
|
return value
|
|
@@ -21,13 +31,33 @@ function encodeHeaderValue(value) {
|
|
|
21
31
|
return value;
|
|
22
32
|
return `=?UTF-8?B?${Buffer.from(value, "utf8").toString("base64")}?=`;
|
|
23
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Render the body markdown to an HTML document.
|
|
36
|
+
*
|
|
37
|
+
* `breaks: true` is deliberate: email bodies carry meaningful single-line
|
|
38
|
+
* breaks (signature blocks, addresses, value lists), and markdown's default of
|
|
39
|
+
* collapsing them into spaces would silently reflow content the author laid
|
|
40
|
+
* out. The flip side — a pre-wrapped body keeps its wrapping — is why
|
|
41
|
+
* DRAFT_HELP tells callers not to hard-wrap.
|
|
42
|
+
*/
|
|
43
|
+
export function renderMarkdown(body) {
|
|
44
|
+
const fragment = marked.parse(body, { gfm: true, breaks: true, async: false });
|
|
45
|
+
return `<html><body>${fragment}</body></html>`;
|
|
46
|
+
}
|
|
47
|
+
/** Base64 with CRLF line endings wrapped at 76 columns (RFC 2045). */
|
|
48
|
+
function encodePart(text) {
|
|
49
|
+
return Buffer.from(text, "utf8")
|
|
50
|
+
.toString("base64")
|
|
51
|
+
.replace(/(.{76})/g, "$1\r\n");
|
|
52
|
+
}
|
|
24
53
|
/**
|
|
25
54
|
* Build a base64url-encoded RFC 5322 message suitable for
|
|
26
|
-
* `users.drafts.create` / `users.messages.send` `raw` fields.
|
|
27
|
-
* as a single text/plain part with base64 transfer-encoding so any UTF-8
|
|
28
|
-
* content survives intact.
|
|
55
|
+
* `users.drafts.create` / `users.messages.send` `raw` fields.
|
|
29
56
|
*/
|
|
30
57
|
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()}`;
|
|
31
61
|
const headers = [`From: ${fields.from}`, `To: ${fields.to.join(", ")}`];
|
|
32
62
|
if (fields.cc?.length)
|
|
33
63
|
headers.push(`Cc: ${fields.cc.join(", ")}`);
|
|
@@ -35,13 +65,14 @@ export function buildRawMessage(fields) {
|
|
|
35
65
|
headers.push(`Bcc: ${fields.bcc.join(", ")}`);
|
|
36
66
|
headers.push(`Subject: ${encodeHeaderValue(fields.subject)}`);
|
|
37
67
|
headers.push("MIME-Version: 1.0");
|
|
38
|
-
headers.push(
|
|
39
|
-
|
|
40
|
-
//
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
68
|
+
headers.push(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
69
|
+
// RFC 2046 orders alternatives least- to most-faithful; clients render the
|
|
70
|
+
// last part they understand, so text/html goes second.
|
|
71
|
+
const parts = [
|
|
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}--`;
|
|
45
76
|
return Buffer.from(raw, "utf8").toString("base64url");
|
|
46
77
|
}
|
|
47
78
|
//# 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":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAahC,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,CAAC,GAAG,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,IAAI,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC7E,OAAO,aAAa,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,CAAC;AACxE,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,MAAM,QAAQ,GAAG,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,CAAC;IAC/E,OAAO,eAAe,QAAQ,gBAAgB,CAAC;AACjD,CAAC;AAED,sEAAsE;AACtE,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC;SAC7B,QAAQ,CAAC,QAAQ,CAAC;SAClB,OAAO,CAAC,UAAU,EAAE,QAAQ,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,eAAe,CAAC,MAAqB;IACnD,qEAAqE;IACrE,qDAAqD;IACrD,MAAM,QAAQ,GAAG,aAAa,UAAU,EAAE,EAAE,CAAC;IAE7C,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,kDAAkD,QAAQ,GAAG,CAAC,CAAC;IAE5E,2EAA2E;IAC3E,uDAAuD;IACvD,MAAM,KAAK,GAAG;QACZ,CAAC,2CAA2C,EAAE,MAAM,CAAC,IAAI,CAAC;QAC1D,CAAC,0CAA0C,EAAE,cAAc,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC;KAC1E,CAAC,GAAG,CACH,CAAC,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,EAAE,CACzB,KAAK,QAAQ,OAAO,WAAW,gDAAgD,UAAU,CAAC,OAAO,CAAC,EAAE,CACvG,CAAC;IAEF,MAAM,GAAG,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,WAAW,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,SAAS,QAAQ,IAAI,CAAC;IACtF,OAAO,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC;AACxD,CAAC"}
|
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
2
|
import { buildRawMessage, parseRecipients } from "./compose.js";
|
|
3
|
-
/** Decode a base64url raw message
|
|
3
|
+
/** Decode a base64url raw message into its header block and decoded MIME parts. */
|
|
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
7
|
const header = text.slice(0, idx);
|
|
8
|
-
const
|
|
9
|
-
|
|
8
|
+
const boundary = /boundary="([^"]+)"/.exec(header)?.[1] ?? "";
|
|
9
|
+
expect(boundary).not.toBe("");
|
|
10
|
+
// Drop the preamble (before the first boundary) and the closing `--<b>--`.
|
|
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") };
|
|
10
26
|
}
|
|
11
27
|
describe("parseRecipients", () => {
|
|
12
28
|
it("splits, trims, and drops empties", () => {
|
|
@@ -21,19 +37,76 @@ describe("parseRecipients", () => {
|
|
|
21
37
|
});
|
|
22
38
|
});
|
|
23
39
|
describe("buildRawMessage", () => {
|
|
24
|
-
it("emits the core headers and
|
|
40
|
+
it("emits the core headers and a multipart/alternative body", () => {
|
|
25
41
|
const raw = buildRawMessage({
|
|
26
42
|
from: "me@x.com",
|
|
27
43
|
to: ["alice@x.com", "bob@x.com"],
|
|
28
44
|
subject: "Hello",
|
|
29
45
|
body: "Line one\nLine two",
|
|
30
46
|
});
|
|
31
|
-
const { header,
|
|
47
|
+
const { header, boundary, parts } = decodeRaw(raw);
|
|
32
48
|
expect(header).toContain("From: me@x.com");
|
|
33
49
|
expect(header).toContain("To: alice@x.com, bob@x.com");
|
|
34
50
|
expect(header).toContain("Subject: Hello");
|
|
35
|
-
expect(header).toContain(
|
|
36
|
-
|
|
51
|
+
expect(header).toContain(`Content-Type: multipart/alternative; boundary="${boundary}"`);
|
|
52
|
+
// RFC 2046: least-faithful alternative first, so clients pick the HTML.
|
|
53
|
+
expect(parts.map((p) => p.contentType)).toEqual(["text/plain", "text/html"]);
|
|
54
|
+
});
|
|
55
|
+
it("delimits both parts and terminates the multipart body", () => {
|
|
56
|
+
const raw = buildRawMessage({
|
|
57
|
+
from: "me@x.com",
|
|
58
|
+
to: ["a@x.com"],
|
|
59
|
+
subject: "s",
|
|
60
|
+
body: "hello",
|
|
61
|
+
});
|
|
62
|
+
const { boundary } = decodeRaw(raw);
|
|
63
|
+
const text = Buffer.from(raw, "base64url").toString("utf8");
|
|
64
|
+
expect(text.split(`--${boundary}\r\n`)).toHaveLength(3); // two part delimiters
|
|
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({
|
|
78
|
+
from: "me@x.com",
|
|
79
|
+
to: ["a@x.com"],
|
|
80
|
+
subject: "s",
|
|
81
|
+
body: "Some **bold** text.\n\n- one\n- two",
|
|
82
|
+
}));
|
|
83
|
+
expect(html).toContain("<strong>bold</strong>");
|
|
84
|
+
expect(html).toContain("<li>one</li>");
|
|
85
|
+
expect(html).toMatch(/^<html><body>/);
|
|
86
|
+
expect(html).toMatch(/<\/body><\/html>$/);
|
|
87
|
+
});
|
|
88
|
+
it("maps a blank line to a new paragraph and a single newline to <br>", () => {
|
|
89
|
+
const { html } = decodeRaw(buildRawMessage({
|
|
90
|
+
from: "me@x.com",
|
|
91
|
+
to: ["a@x.com"],
|
|
92
|
+
subject: "s",
|
|
93
|
+
body: "First para.\n\nSecond line one\nsecond line two",
|
|
94
|
+
}));
|
|
95
|
+
// Two paragraphs...
|
|
96
|
+
expect(html.match(/<p>/g)).toHaveLength(2);
|
|
97
|
+
// ...and the intra-paragraph newline survives as a break, not a space.
|
|
98
|
+
expect(html).toContain("Second line one<br>second line two");
|
|
99
|
+
});
|
|
100
|
+
it("never breaks a long paragraph inside the decoded content of either part", () => {
|
|
101
|
+
// The 76-column wrapping is base64 transport only — it must not survive
|
|
102
|
+
// decoding, or the recipient sees the ragged columns this format exists
|
|
103
|
+
// to prevent.
|
|
104
|
+
const body = `${"word ".repeat(60).trim()}.`;
|
|
105
|
+
const { plain, html } = decodeRaw(buildRawMessage({ from: "me@x.com", to: ["a@x.com"], subject: "s", body }));
|
|
106
|
+
expect(plain).toBe(body);
|
|
107
|
+
expect(plain).not.toContain("\r\n");
|
|
108
|
+
expect(html).not.toContain("\r\n");
|
|
109
|
+
expect(html).toContain(body);
|
|
37
110
|
});
|
|
38
111
|
it("includes Cc/Bcc only when provided", () => {
|
|
39
112
|
const withCc = decodeRaw(buildRawMessage({
|
|
@@ -50,8 +123,8 @@ describe("buildRawMessage", () => {
|
|
|
50
123
|
expect(without).not.toContain("Cc:");
|
|
51
124
|
expect(without).not.toContain("Bcc:");
|
|
52
125
|
});
|
|
53
|
-
it("RFC 2047 encodes non-ASCII subjects but leaves
|
|
54
|
-
const { header,
|
|
126
|
+
it("RFC 2047 encodes non-ASCII subjects but leaves both parts decodable", () => {
|
|
127
|
+
const { header, plain, html } = decodeRaw(buildRawMessage({
|
|
55
128
|
from: "me@x.com",
|
|
56
129
|
to: ["a@x.com"],
|
|
57
130
|
subject: "café ☕",
|
|
@@ -59,7 +132,8 @@ describe("buildRawMessage", () => {
|
|
|
59
132
|
}));
|
|
60
133
|
expect(header).toContain("Subject: =?UTF-8?B?");
|
|
61
134
|
expect(header).not.toContain("café");
|
|
62
|
-
expect(
|
|
135
|
+
expect(plain).toBe("naïve résumé — ☕");
|
|
136
|
+
expect(html).toContain("naïve résumé — ☕");
|
|
63
137
|
});
|
|
64
138
|
it("produces valid base64url (no +,/,= padding chars)", () => {
|
|
65
139
|
const raw = buildRawMessage({
|
|
@@ -69,7 +143,7 @@ describe("buildRawMessage", () => {
|
|
|
69
143
|
body: "x".repeat(200), // force base64 line wrapping
|
|
70
144
|
});
|
|
71
145
|
expect(raw).not.toMatch(/[+/=]/);
|
|
72
|
-
expect(decodeRaw(raw).
|
|
146
|
+
expect(decodeRaw(raw).plain).toBe("x".repeat(200));
|
|
73
147
|
});
|
|
74
148
|
});
|
|
75
149
|
//# 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[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.
|
|
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. The body is markdown, rendered to HTML: the draft goes out as\n multipart/alternative (markdown source as text/plain, rendered GFM as\n text/html) so recipients get reflowable text.\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).\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>;
|
|
@@ -20,8 +20,14 @@ examples:
|
|
|
20
20
|
gws-axi gmail draft --to alice@x.com --subject "Re: thread" --body "..." --thread 1899abcd
|
|
21
21
|
notes:
|
|
22
22
|
Creates a DRAFT only — gws-axi never sends mail. Review and send from the
|
|
23
|
-
Gmail UI.
|
|
24
|
-
|
|
23
|
+
Gmail UI. The body is markdown, rendered to HTML: the draft goes out as
|
|
24
|
+
multipart/alternative (markdown source as text/plain, rendered GFM as
|
|
25
|
+
text/html) so recipients get reflowable text.
|
|
26
|
+
DO NOT hard-wrap the body. Write each paragraph as one long line — every
|
|
27
|
+
newline you write becomes a line break in the sent message, so a body
|
|
28
|
+
pre-wrapped at 72/80 columns arrives locked to that width on every screen.
|
|
29
|
+
Blank line = new paragraph. GFM applies: **bold**, lists, tables, fenced
|
|
30
|
+
code blocks (use those for text meant literally).
|
|
25
31
|
output:
|
|
26
32
|
Returns \`action: drafted\` plus the new draft_id, message_id, recipients,
|
|
27
33
|
and subject. A help line links to where to review/send it.
|
|
@@ -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":"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"}
|
|
@@ -2,6 +2,7 @@ import { AxiError } from "axi-sdk-js";
|
|
|
2
2
|
import { resolveAccount } from "../google/account.js";
|
|
3
3
|
import { docsCommentsCommand } from "./docs/comments.js";
|
|
4
4
|
import { READ_HELP, sheetsReadCommand } from "./sheets/read.js";
|
|
5
|
+
import { notImplemented, renderAlternatives, withInstead } from "./stub-signposts.js";
|
|
5
6
|
// Google Sheets comments are Drive comments — the exact same file-agnostic API
|
|
6
7
|
// `docs comments` uses. Alias the handler (cf. `docs revisions` → `drive
|
|
7
8
|
// revisions`) and give it a Sheets-worded help block.
|
|
@@ -21,6 +22,17 @@ notes:
|
|
|
21
22
|
data as \`gws-axi docs comments\`. Cell *notes* are distinct — those come
|
|
22
23
|
back in the notes[] block of \`gws-axi sheets read\`.
|
|
23
24
|
`;
|
|
25
|
+
// Until the Sheets write surface lands, `drive upload --convert` is the only
|
|
26
|
+
// way to put content into a spreadsheet. It replaces the file wholesale, so
|
|
27
|
+
// each line says so — an agent that reads "use drive upload" and nothing else
|
|
28
|
+
// would silently destroy the sheets it never looked at.
|
|
29
|
+
const REPLACE_WHOLESALE = [
|
|
30
|
+
"gws-axi drive upload <file.csv> --update <spreadsheetId> --convert --account <email> — replaces the ENTIRE spreadsheet from CSV/TSV (or --content <string>); NOT a per-range write",
|
|
31
|
+
"Multi-sheet targets are refused with MULTI_TAB_TARGET unless --replace-all-tabs; read them first with `gws-axi sheets read <spreadsheetId>`",
|
|
32
|
+
];
|
|
33
|
+
const CREATE_VIA_UPLOAD = [
|
|
34
|
+
'gws-axi drive upload <file.csv> --convert --name "<title>" --account <email> — creates a new native Spreadsheet and returns its id',
|
|
35
|
+
];
|
|
24
36
|
// Write subcommands are stubs for the next slice — kept with per-command --help
|
|
25
37
|
// so agents can plan around the future surface. They throw NOT_IMPLEMENTED after
|
|
26
38
|
// account resolution runs.
|
|
@@ -56,10 +68,12 @@ const sheetsCommentsCommand = (account, args) => docsCommentsCommand(account, ar
|
|
|
56
68
|
const SUBCOMMANDS = [
|
|
57
69
|
{ name: "read", mutation: false, help: READ_HELP, handler: sheetsReadCommand },
|
|
58
70
|
{ name: "comments", mutation: false, help: COMMENTS_HELP, handler: sheetsCommentsCommand },
|
|
59
|
-
{ name: "update", mutation: true, help: UPDATE_HELP },
|
|
60
|
-
{ name: "append", mutation: true, help: APPEND_HELP },
|
|
61
|
-
{ name: "clear", mutation: true, help: CLEAR_HELP },
|
|
62
|
-
{ name: "create", mutation: true, help: CREATE_HELP },
|
|
71
|
+
{ name: "update", mutation: true, help: UPDATE_HELP, instead: REPLACE_WHOLESALE },
|
|
72
|
+
{ name: "append", mutation: true, help: APPEND_HELP, instead: REPLACE_WHOLESALE },
|
|
73
|
+
{ name: "clear", mutation: true, help: CLEAR_HELP, instead: REPLACE_WHOLESALE },
|
|
74
|
+
{ name: "create", mutation: true, help: CREATE_HELP, instead: CREATE_VIA_UPLOAD },
|
|
75
|
+
// Adding a tab needs spreadsheets.batchUpdate — drive upload can only
|
|
76
|
+
// collapse a spreadsheet to one sheet, never grow it one.
|
|
63
77
|
{ name: "add-tab", mutation: true, help: ADD_TAB_HELP },
|
|
64
78
|
];
|
|
65
79
|
const SUB_BY_NAME = Object.fromEntries(SUBCOMMANDS.map((s) => [s.name, s]));
|
|
@@ -89,7 +103,7 @@ notes:
|
|
|
89
103
|
Reads use the default account when --account is not provided.
|
|
90
104
|
Write subcommands are scaffolded for the next slice — all currently
|
|
91
105
|
throw NOT_IMPLEMENTED after account resolution runs.
|
|
92
|
-
subcommand help:
|
|
106
|
+
${renderAlternatives(SUBCOMMANDS)}subcommand help:
|
|
93
107
|
gws-axi sheets read --help for spreadsheetId + tab/range handling
|
|
94
108
|
gws-axi sheets comments --help for review comments (Drive comments)
|
|
95
109
|
examples:
|
|
@@ -111,7 +125,7 @@ export async function sheetsCommand(args) {
|
|
|
111
125
|
}
|
|
112
126
|
const rest = args.slice(1);
|
|
113
127
|
if (rest.includes("--help")) {
|
|
114
|
-
return def.help;
|
|
128
|
+
return def.handler ? def.help : withInstead(def.help, def.instead);
|
|
115
129
|
}
|
|
116
130
|
const { account: accountFlag, rest: remaining } = parseAccountFlag(rest);
|
|
117
131
|
const resolution = resolveAccount(accountFlag, {
|
|
@@ -119,10 +133,7 @@ export async function sheetsCommand(args) {
|
|
|
119
133
|
commandName: `sheets ${sub}`,
|
|
120
134
|
});
|
|
121
135
|
if (!def.handler) {
|
|
122
|
-
throw
|
|
123
|
-
`Account resolution succeeded: would run as ${resolution.account}`,
|
|
124
|
-
`See \`gws-axi sheets ${sub} --help\` for the planned surface`,
|
|
125
|
-
]);
|
|
136
|
+
throw notImplemented("sheets", sub, resolution.account, def.instead);
|
|
126
137
|
}
|
|
127
138
|
return def.handler(resolution.account, remaining);
|
|
128
139
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sheets.js","sourceRoot":"","sources":["../../../src/commands/sheets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"sheets.js","sourceRoot":"","sources":["../../../src/commands/sheets.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,kBAAkB,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEtF,+EAA+E;AAC/E,yEAAyE;AACzE,sDAAsD;AACtD,MAAM,aAAa,GAAG;;;;;;;;;;;;;;;CAerB,CAAC;AAUF,6EAA6E;AAC7E,4EAA4E;AAC5E,8EAA8E;AAC9E,wDAAwD;AACxD,MAAM,iBAAiB,GAAG;IACxB,oLAAoL;IACpL,6IAA6I;CAC9I,CAAC;AACF,MAAM,iBAAiB,GAAG;IACxB,oIAAoI;CACrI,CAAC;AAEF,gFAAgF;AAChF,iFAAiF;AACjF,2BAA2B;AAC3B,MAAM,WAAW,GAAG;;;;;CAKnB,CAAC;AACF,MAAM,WAAW,GAAG;;;;CAInB,CAAC;AACF,MAAM,UAAU,GAAG;;CAElB,CAAC;AACF,MAAM,WAAW,GAAG;;;;;CAKnB,CAAC;AACF,MAAM,YAAY,GAAG;;;;CAIpB,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,OAAe,EAAE,IAAc,EAAmB,EAAE,CACjF,mBAAmB,CAAC,OAAO,EAAE,IAAI,EAAE;IACjC,QAAQ,EAAE,aAAa;IACvB,YAAY,EAAE,uBAAuB;CACtC,CAAC,CAAC;AAEL,MAAM,WAAW,GAAuB;IACtC,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,SAAS,EAAE,OAAO,EAAE,iBAAiB,EAAE;IAC9E,EAAE,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,OAAO,EAAE,qBAAqB,EAAE;IAC1F,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE;IACjF,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE;IACjF,EAAE,IAAI,EAAE,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,OAAO,EAAE,iBAAiB,EAAE;IAC/E,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,iBAAiB,EAAE;IACjF,sEAAsE;IACtE,0DAA0D;IAC1D,EAAE,IAAI,EAAE,SAAS,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE;CACxD,CAAC;AAEF,MAAM,WAAW,GAAqC,MAAM,CAAC,WAAW,CACtE,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CACpC,CAAC;AAEF,SAAS,gBAAgB,CAAC,IAAc;IAItC,MAAM,IAAI,GAAa,EAAE,CAAC;IAC1B,IAAI,OAA2B,CAAC;IAChC,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,IAAI,GAAG,KAAK,WAAW,IAAI,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC;YACvC,OAAO,GAAG,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;YACtB,CAAC,EAAE,CAAC;YACJ,SAAS;QACX,CAAC;QACD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjB,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;AAC3B,CAAC;AAED,MAAM,KAAK,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AACxE,MAAM,MAAM,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;AAExE,MAAM,CAAC,MAAM,WAAW,GAAG;QACnB,KAAK,CAAC,MAAM;IAChB,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,MAAM;IAClB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC;;;;;;EAMnB,kBAAkB,CAAC,WAAW,CAAC;;;;;;;;CAQhC,CAAC;AAEF,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,IAAc;IAChD,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ,CAAC,EAAE,CAAC;QACrE,OAAO,WAAW,CAAC;IACrB,CAAC;IAED,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,MAAM,GAAG,GAAG,WAAW,CAAC,GAAG,CAAC,CAAC;IAC7B,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,MAAM,IAAI,QAAQ,CAAC,8BAA8B,GAAG,EAAE,EAAE,kBAAkB,EAAE;YAC1E,4DAA4D;SAC7D,CAAC,CAAC;IACL,CAAC;IAED,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3B,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC5B,OAAO,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACrE,CAAC;IAED,MAAM,EAAE,OAAO,EAAE,WAAW,EAAE,IAAI,EAAE,SAAS,EAAE,GAAG,gBAAgB,CAAC,IAAI,CAAC,CAAC;IACzE,MAAM,UAAU,GAAG,cAAc,CAAC,WAAW,EAAE;QAC7C,QAAQ,EAAE,GAAG,CAAC,QAAQ;QACtB,WAAW,EAAE,UAAU,GAAG,EAAE;KAC7B,CAAC,CAAC;IAEH,IAAI,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC;QACjB,MAAM,cAAc,CAAC,QAAQ,EAAE,GAAG,EAAE,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,OAAO,CAAC,CAAC;IACvE,CAAC;IAED,OAAO,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;AACpD,CAAC"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { AxiError } from "axi-sdk-js";
|
|
2
2
|
import { resolveAccount } from "../google/account.js";
|
|
3
|
+
import { notImplemented, renderAlternatives, withInstead } from "./stub-signposts.js";
|
|
3
4
|
import { docsCommentsCommand } from "./docs/comments.js";
|
|
4
5
|
import { GET_HELP, slidesGetCommand } from "./slides/get.js";
|
|
5
6
|
import { PAGE_HELP, slidesPageCommand } from "./slides/page.js";
|
|
@@ -50,8 +51,23 @@ const SUBCOMMANDS = [
|
|
|
50
51
|
handler: slidesSummarizeCommand,
|
|
51
52
|
},
|
|
52
53
|
{ name: "comments", mutation: false, help: COMMENTS_HELP, handler: slidesCommentsCommand },
|
|
53
|
-
{
|
|
54
|
-
|
|
54
|
+
{
|
|
55
|
+
name: "create",
|
|
56
|
+
mutation: true,
|
|
57
|
+
help: CREATE_HELP,
|
|
58
|
+
instead: [
|
|
59
|
+
'gws-axi drive upload <deck.pptx> --convert --name "<title>" --account <email> — creates a new native Presentation and returns its id',
|
|
60
|
+
],
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "update",
|
|
64
|
+
mutation: true,
|
|
65
|
+
help: UPDATE_HELP,
|
|
66
|
+
instead: [
|
|
67
|
+
"gws-axi drive upload <deck.pptx> --update <presentationId> --convert --account <email> — replaces the ENTIRE deck as a new revision; NOT a per-slide or per-element edit",
|
|
68
|
+
"gws-axi slides summarize <presentationId> — read the current deck first; there is no export-to-pptx round trip",
|
|
69
|
+
],
|
|
70
|
+
},
|
|
55
71
|
];
|
|
56
72
|
const SUB_BY_NAME = Object.fromEntries(SUBCOMMANDS.map((s) => [s.name, s]));
|
|
57
73
|
function parseAccountFlag(args) {
|
|
@@ -80,7 +96,7 @@ notes:
|
|
|
80
96
|
Reads use the default account when --account is not provided.
|
|
81
97
|
Write subcommands are scaffolded for the next slice; they throw
|
|
82
98
|
NOT_IMPLEMENTED after account resolution runs.
|
|
83
|
-
subcommand help:
|
|
99
|
+
${renderAlternatives(SUBCOMMANDS)}subcommand help:
|
|
84
100
|
gws-axi slides get --help for metadata + slide list
|
|
85
101
|
gws-axi slides page --help for a single slide's content
|
|
86
102
|
gws-axi slides summarize --help for the whole deck as markdown
|
|
@@ -104,7 +120,7 @@ export async function slidesCommand(args) {
|
|
|
104
120
|
}
|
|
105
121
|
const rest = args.slice(1);
|
|
106
122
|
if (rest.includes("--help")) {
|
|
107
|
-
return def.help;
|
|
123
|
+
return def.handler ? def.help : withInstead(def.help, def.instead);
|
|
108
124
|
}
|
|
109
125
|
const { account: accountFlag, rest: remaining } = parseAccountFlag(rest);
|
|
110
126
|
const resolution = resolveAccount(accountFlag, {
|
|
@@ -112,10 +128,7 @@ export async function slidesCommand(args) {
|
|
|
112
128
|
commandName: `slides ${sub}`,
|
|
113
129
|
});
|
|
114
130
|
if (!def.handler) {
|
|
115
|
-
throw
|
|
116
|
-
`Account resolution succeeded: would run as ${resolution.account}`,
|
|
117
|
-
`See \`gws-axi slides ${sub} --help\` for the planned surface`,
|
|
118
|
-
]);
|
|
131
|
+
throw notImplemented("slides", sub, resolution.account, def.instead);
|
|
119
132
|
}
|
|
120
133
|
return def.handler(resolution.account, remaining);
|
|
121
134
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"slides.js","sourceRoot":"","sources":["../../../src/commands/slides.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAC7D,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAC;AAChE,OAAO,EAAE,cAAc,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAE/E,wEAAwE;AACxE,qEAAqE;AACrE,uDAAuD;AACvD,MAAM,aAAa,GAAG;;;;;;;;;;;;;;CAcrB,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,OAAe,EAAE,IAAc,EAAmB,EAAE,CACjF,mBAAmB,CAAC,OAAO,EAAE,IAAI,EAAE;IACjC,QAAQ,EAAE,cAAc;IACxB,YAAY,EAAE,wBAAwB;CACvC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"slides.js","sourceRoot":"","sources":["../../../src/commands/slides.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signposts for scaffolded subcommands.
|
|
3
|
+
*
|
|
4
|
+
* A stub that refuses with nothing but "not implemented; see --help" — where
|
|
5
|
+
* `--help` lists the same unimplemented command — is a closed circuit, and an
|
|
6
|
+
* agent's way out of a closed circuit is to leave gws-axi entirely and drive
|
|
7
|
+
* the Google APIs with the raw token, discarding write-protection and error
|
|
8
|
+
* translation with it. So every stub declares the working commands that do
|
|
9
|
+
* some or all of its job, and the same list feeds all three surfaces a caller
|
|
10
|
+
* can hit: the NOT_IMPLEMENTED error, the stub's own --help, and the service
|
|
11
|
+
* help screen.
|
|
12
|
+
*
|
|
13
|
+
* See specs/principles.md#no-dead-end-surfaces and specs/api/conventions.md
|
|
14
|
+
* ("Unimplemented and unsupported surfaces").
|
|
15
|
+
*/
|
|
16
|
+
import { AxiError } from "axi-sdk-js";
|
|
17
|
+
export interface StubbableSubcommand {
|
|
18
|
+
name: string;
|
|
19
|
+
handler?: unknown;
|
|
20
|
+
/**
|
|
21
|
+
* Runnable gws-axi commands that accomplish some or all of this subcommand's
|
|
22
|
+
* job. Each line names the command AND where it falls short — these are
|
|
23
|
+
* mostly wholesale-replace substitutes for a granular edit, and a signpost
|
|
24
|
+
* that oversells is worse than none. Omit when nothing comes close.
|
|
25
|
+
*/
|
|
26
|
+
instead?: string[];
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* The refusal for a scaffolded subcommand. Alternatives lead; the account
|
|
30
|
+
* diagnostic and the pointer to the planned surface follow, because they are
|
|
31
|
+
* context rather than a next step.
|
|
32
|
+
*/
|
|
33
|
+
export declare function notImplemented(service: string, sub: string, account: string, instead?: string[]): AxiError;
|
|
34
|
+
/** Append the `instead[N]:` block to a stubbed subcommand's own --help. */
|
|
35
|
+
export declare function withInstead(help: string, instead?: string[]): string;
|
|
36
|
+
/**
|
|
37
|
+
* The service-level `alternatives[N]:` block, deduped across every stub.
|
|
38
|
+
* Returns "" when the service has no stubs, so fully-implemented services
|
|
39
|
+
* (calendar) gain nothing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function renderAlternatives(subs: StubbableSubcommand[]): string;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Signposts for scaffolded subcommands.
|
|
3
|
+
*
|
|
4
|
+
* A stub that refuses with nothing but "not implemented; see --help" — where
|
|
5
|
+
* `--help` lists the same unimplemented command — is a closed circuit, and an
|
|
6
|
+
* agent's way out of a closed circuit is to leave gws-axi entirely and drive
|
|
7
|
+
* the Google APIs with the raw token, discarding write-protection and error
|
|
8
|
+
* translation with it. So every stub declares the working commands that do
|
|
9
|
+
* some or all of its job, and the same list feeds all three surfaces a caller
|
|
10
|
+
* can hit: the NOT_IMPLEMENTED error, the stub's own --help, and the service
|
|
11
|
+
* help screen.
|
|
12
|
+
*
|
|
13
|
+
* See specs/principles.md#no-dead-end-surfaces and specs/api/conventions.md
|
|
14
|
+
* ("Unimplemented and unsupported surfaces").
|
|
15
|
+
*/
|
|
16
|
+
import { AxiError } from "axi-sdk-js";
|
|
17
|
+
// Stated explicitly rather than omitted: "no alternative" is itself the answer
|
|
18
|
+
// an agent needs, and silence reads as an oversight worth working around.
|
|
19
|
+
const NO_ALTERNATIVE = "No gws-axi command does this yet — it needs the planned write surface, so there is nothing to fall back to";
|
|
20
|
+
/** Stubs are the entries with no handler. */
|
|
21
|
+
const stubsOf = (subs) => subs.filter((s) => !s.handler);
|
|
22
|
+
/**
|
|
23
|
+
* The refusal for a scaffolded subcommand. Alternatives lead; the account
|
|
24
|
+
* diagnostic and the pointer to the planned surface follow, because they are
|
|
25
|
+
* context rather than a next step.
|
|
26
|
+
*/
|
|
27
|
+
export function notImplemented(service, sub, account, instead) {
|
|
28
|
+
const alternatives = instead?.length ? instead : [NO_ALTERNATIVE];
|
|
29
|
+
return new AxiError(`gws-axi ${service} ${sub} is not yet implemented`, "NOT_IMPLEMENTED", [
|
|
30
|
+
...alternatives,
|
|
31
|
+
`See \`gws-axi ${service} ${sub} --help\` for the planned surface`,
|
|
32
|
+
`Account resolution succeeded: would run as ${account}`,
|
|
33
|
+
]);
|
|
34
|
+
}
|
|
35
|
+
/** Append the `instead[N]:` block to a stubbed subcommand's own --help. */
|
|
36
|
+
export function withInstead(help, instead) {
|
|
37
|
+
const lines = instead?.length ? instead : [NO_ALTERNATIVE];
|
|
38
|
+
return `${help.trimEnd()}\ninstead[${lines.length}]:\n${lines.map((l) => ` ${l}`).join("\n")}\n`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The service-level `alternatives[N]:` block, deduped across every stub.
|
|
42
|
+
* Returns "" when the service has no stubs, so fully-implemented services
|
|
43
|
+
* (calendar) gain nothing.
|
|
44
|
+
*/
|
|
45
|
+
export function renderAlternatives(subs) {
|
|
46
|
+
const lines = [...new Set(stubsOf(subs).flatMap((s) => s.instead ?? []))];
|
|
47
|
+
if (lines.length === 0)
|
|
48
|
+
return "";
|
|
49
|
+
return `alternatives[${lines.length}]:\n${lines.map((l) => ` ${l}`).join("\n")}\n`;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=stub-signposts.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub-signposts.js","sourceRoot":"","sources":["../../../src/commands/stub-signposts.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AActC,+EAA+E;AAC/E,0EAA0E;AAC1E,MAAM,cAAc,GAClB,4GAA4G,CAAC;AAE/G,6CAA6C;AAC7C,MAAM,OAAO,GAAG,CAAC,IAA2B,EAAyB,EAAE,CACrE,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC;AAEjC;;;;GAIG;AACH,MAAM,UAAU,cAAc,CAC5B,OAAe,EACf,GAAW,EACX,OAAe,EACf,OAAkB;IAElB,MAAM,YAAY,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IAClE,OAAO,IAAI,QAAQ,CAAC,WAAW,OAAO,IAAI,GAAG,yBAAyB,EAAE,iBAAiB,EAAE;QACzF,GAAG,YAAY;QACf,iBAAiB,OAAO,IAAI,GAAG,mCAAmC;QAClE,8CAA8C,OAAO,EAAE;KACxD,CAAC,CAAC;AACL,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,WAAW,CAAC,IAAY,EAAE,OAAkB;IAC1D,MAAM,KAAK,GAAG,OAAO,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;IAC3D,OAAO,GAAG,IAAI,CAAC,OAAO,EAAE,aAAa,KAAK,CAAC,MAAM,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACpG,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,kBAAkB,CAAC,IAA2B;IAC5D,MAAM,KAAK,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,OAAO,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IAC1E,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,EAAE,CAAC;IAClC,OAAO,gBAAgB,KAAK,CAAC,MAAM,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC;AACtF,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { notImplemented, renderAlternatives, withInstead } from "./stub-signposts.js";
|
|
3
|
+
const noop = async () => "";
|
|
4
|
+
describe("notImplemented", () => {
|
|
5
|
+
it("leads with the alternatives, not the account diagnostic", () => {
|
|
6
|
+
const err = notImplemented("sheets", "update", "me@x.com", [
|
|
7
|
+
"gws-axi drive upload … --convert — replaces the ENTIRE spreadsheet",
|
|
8
|
+
]);
|
|
9
|
+
expect(err.code).toBe("NOT_IMPLEMENTED");
|
|
10
|
+
expect(err.suggestions[0]).toContain("drive upload");
|
|
11
|
+
// Diagnostics are context, so they come after the next step.
|
|
12
|
+
expect(err.suggestions[err.suggestions.length - 1]).toContain("me@x.com");
|
|
13
|
+
});
|
|
14
|
+
it("still points at the subcommand's planned surface", () => {
|
|
15
|
+
const err = notImplemented("docs", "append", "me@x.com", ["do the other thing"]);
|
|
16
|
+
expect(err.suggestions.some((s) => s.includes("gws-axi docs append --help"))).toBe(true);
|
|
17
|
+
});
|
|
18
|
+
it("says so explicitly when nothing comes close", () => {
|
|
19
|
+
const err = notImplemented("drive", "move", "me@x.com");
|
|
20
|
+
expect(err.suggestions[0]).toContain("No gws-axi command does this yet");
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
describe("withInstead", () => {
|
|
24
|
+
it("appends an instead[N] block sized to the lines", () => {
|
|
25
|
+
const out = withInstead("usage: gws-axi sheets update …\nstatus: planned\n", ["a", "b"]);
|
|
26
|
+
expect(out).toContain("instead[2]:\n a\n b");
|
|
27
|
+
});
|
|
28
|
+
it("falls back to the no-alternative line", () => {
|
|
29
|
+
expect(withInstead("usage: …\n")).toContain("instead[1]:");
|
|
30
|
+
expect(withInstead("usage: …\n")).toContain("No gws-axi command does this yet");
|
|
31
|
+
});
|
|
32
|
+
});
|
|
33
|
+
describe("renderAlternatives", () => {
|
|
34
|
+
it("dedupes lines shared across stubs", () => {
|
|
35
|
+
const block = renderAlternatives([
|
|
36
|
+
{ name: "read", handler: noop },
|
|
37
|
+
{ name: "update", instead: ["shared line", "update only"] },
|
|
38
|
+
{ name: "append", instead: ["shared line"] },
|
|
39
|
+
]);
|
|
40
|
+
expect(block).toContain("alternatives[2]:");
|
|
41
|
+
expect(block.match(/shared line/g)).toHaveLength(1);
|
|
42
|
+
});
|
|
43
|
+
it("ignores alternatives on implemented subcommands", () => {
|
|
44
|
+
const block = renderAlternatives([{ name: "read", handler: noop, instead: ["ignored"] }]);
|
|
45
|
+
expect(block).toBe("");
|
|
46
|
+
});
|
|
47
|
+
it("returns empty for a service with nothing stubbed", () => {
|
|
48
|
+
expect(renderAlternatives([{ name: "events", handler: noop }])).toBe("");
|
|
49
|
+
});
|
|
50
|
+
it("skips stubs that declare no alternative", () => {
|
|
51
|
+
expect(renderAlternatives([{ name: "move" }])).toBe("");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
//# sourceMappingURL=stub-signposts.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stub-signposts.test.js","sourceRoot":"","sources":["../../../src/commands/stub-signposts.test.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "gws-axi",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.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": {
|
|
@@ -53,6 +53,7 @@
|
|
|
53
53
|
"diff": "^9.0.0",
|
|
54
54
|
"google-auth-library": "^10.7.0",
|
|
55
55
|
"googleapis": "^173.0.0",
|
|
56
|
+
"marked": "^18.0.9",
|
|
56
57
|
"turndown": "^7.2.4"
|
|
57
58
|
},
|
|
58
59
|
"overrides": {
|