surface-cli 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (60) hide show
  1. package/README.md +307 -0
  2. package/dist/cli.js +521 -0
  3. package/dist/cli.js.map +1 -0
  4. package/dist/config.js +156 -0
  5. package/dist/config.js.map +1 -0
  6. package/dist/contracts/account.js +9 -0
  7. package/dist/contracts/account.js.map +1 -0
  8. package/dist/contracts/mail.js +2 -0
  9. package/dist/contracts/mail.js.map +1 -0
  10. package/dist/e2e/gmail-v1.js +247 -0
  11. package/dist/e2e/gmail-v1.js.map +1 -0
  12. package/dist/e2e/outlook-v1.js +179 -0
  13. package/dist/e2e/outlook-v1.js.map +1 -0
  14. package/dist/lib/errors.js +47 -0
  15. package/dist/lib/errors.js.map +1 -0
  16. package/dist/lib/json.js +4 -0
  17. package/dist/lib/json.js.map +1 -0
  18. package/dist/lib/public-mail.js +29 -0
  19. package/dist/lib/public-mail.js.map +1 -0
  20. package/dist/lib/remote-auth.js +407 -0
  21. package/dist/lib/remote-auth.js.map +1 -0
  22. package/dist/lib/time.js +4 -0
  23. package/dist/lib/time.js.map +1 -0
  24. package/dist/lib/write-safety.js +34 -0
  25. package/dist/lib/write-safety.js.map +1 -0
  26. package/dist/paths.js +29 -0
  27. package/dist/paths.js.map +1 -0
  28. package/dist/providers/gmail/adapter.js +1102 -0
  29. package/dist/providers/gmail/adapter.js.map +1 -0
  30. package/dist/providers/gmail/api.js +99 -0
  31. package/dist/providers/gmail/api.js.map +1 -0
  32. package/dist/providers/gmail/normalize.js +336 -0
  33. package/dist/providers/gmail/normalize.js.map +1 -0
  34. package/dist/providers/gmail/oauth.js +328 -0
  35. package/dist/providers/gmail/oauth.js.map +1 -0
  36. package/dist/providers/index.js +12 -0
  37. package/dist/providers/index.js.map +1 -0
  38. package/dist/providers/outlook/adapter.js +1443 -0
  39. package/dist/providers/outlook/adapter.js.map +1 -0
  40. package/dist/providers/outlook/extract.js +416 -0
  41. package/dist/providers/outlook/extract.js.map +1 -0
  42. package/dist/providers/outlook/normalize.js +126 -0
  43. package/dist/providers/outlook/normalize.js.map +1 -0
  44. package/dist/providers/outlook/session.js +178 -0
  45. package/dist/providers/outlook/session.js.map +1 -0
  46. package/dist/providers/shared/html.js +88 -0
  47. package/dist/providers/shared/html.js.map +1 -0
  48. package/dist/providers/shared/types.js +2 -0
  49. package/dist/providers/shared/types.js.map +1 -0
  50. package/dist/providers/types.js +2 -0
  51. package/dist/providers/types.js.map +1 -0
  52. package/dist/refs.js +18 -0
  53. package/dist/refs.js.map +1 -0
  54. package/dist/runtime.js +23 -0
  55. package/dist/runtime.js.map +1 -0
  56. package/dist/state/database.js +731 -0
  57. package/dist/state/database.js.map +1 -0
  58. package/dist/summarizer.js +217 -0
  59. package/dist/summarizer.js.map +1 -0
  60. package/package.json +55 -0
package/README.md ADDED
@@ -0,0 +1,307 @@
1
+ # Surface CLI
2
+
3
+ A lean, local-first mail CLI for multi-provider, multi-account email.
4
+
5
+ Surface normalizes Gmail and Outlook behind one contract, keeps local state in SQLite,
6
+ stores auth/cache/downloads under `~/.surface-cli`, and prints machine-readable JSON to
7
+ stdout for automation.
8
+
9
+ ## Current V1 Shape
10
+
11
+ Top-level groups:
12
+
13
+ - `surface account`
14
+ - `surface auth`
15
+ - `surface mail`
16
+ - `surface attachment`
17
+ - `surface cache`
18
+
19
+ Current command surface:
20
+
21
+ ```bash
22
+ surface account add work --provider gmail --transport gmail-api --email me@company.com
23
+ surface account add school --provider outlook --transport outlook-web-playwright --email me@school.edu
24
+
25
+ surface auth login work
26
+ surface auth status
27
+ surface auth logout school
28
+
29
+ surface mail fetch-unread --account work --limit 25
30
+ surface mail search --account work --text invoice --limit 10
31
+ surface mail read msg_01...
32
+ surface mail send --account school --to me@example.com --subject "hello" --body "test"
33
+ surface mail send --account school --to me@example.com --subject "hello" --body "test" --draft
34
+ surface mail reply msg_01... --body "Thanks"
35
+ surface mail reply-all msg_01... --body "Thanks all"
36
+ surface mail forward msg_01... --to me@example.com --body "FYI"
37
+ surface mail archive msg_01...
38
+ surface mail rsvp msg_01... --response tentative
39
+
40
+ surface attachment list msg_01...
41
+ surface attachment download msg_01... att_01...
42
+
43
+ surface cache stats
44
+ surface cache prune
45
+ surface cache clear --account work
46
+ ```
47
+
48
+ ## Core Decisions
49
+
50
+ - `fetch-unread` is the public command name.
51
+ - Threads are the top-level result unit.
52
+ - Messages are elements within a thread.
53
+ - `read` takes a stable `message_ref`.
54
+ - Attachment download is separate from `read`.
55
+ - Machine-facing commands emit JSON on stdout.
56
+ - SQLite is the local source of truth for refs and cache metadata.
57
+
58
+ See the source-of-truth docs for the exact contracts:
59
+
60
+ - `docs/cli-contract.md`
61
+ - `docs/provider-contract.md`
62
+ - `docs/cache-and-db.md`
63
+ - `docs/config.md`
64
+
65
+ ## Current Implementation Status
66
+
67
+ The repo now contains a working TypeScript scaffold under `src/`:
68
+
69
+ - CLI entrypoint and command groups
70
+ - config loading from `~/.surface-cli/config.toml`
71
+ - SQLite-backed local account state
72
+ - adapter registry for `gmail-api` and `outlook-web-playwright`
73
+ - donor normalization utilities ported from the legacy Surface repo for Gmail and Outlook
74
+ - Gmail OAuth login wired to Google desktop-app OAuth with stored refresh tokens under `~/.surface-cli/auth/<account_id>/gmail-token.json`
75
+ - live Gmail `fetch-unread`, `search`, `read`, `attachment list`, `attachment download`, `send`, `reply`, `reply-all`, `forward`, `archive`, `mark-read`, `mark-unread`, and `--draft` on send-like actions
76
+ - Outlook Playwright auth lifecycle wired to persistent profiles under `~/.surface-cli/auth/<account_id>/profile`
77
+ - live Outlook `fetch-unread`, `search`, `read`, `attachment list`, `attachment download`, `send`, `reply`, `reply-all`, `forward`, `archive`, `mark-read`, `mark-unread`, `rsvp`, and `--draft` on send-like actions
78
+ - summary backends for `openrouter` and `openclaw`
79
+ - lean opt-in Outlook v1 and Gmail v1 live e2e coverage via `npm run e2e:outlook-v1` and `npm run e2e:gmail-v1`
80
+
81
+ What is still intentionally incomplete:
82
+
83
+ - Gmail RSVP
84
+ - draft lifecycle commands
85
+ - move / delete
86
+ - broader automated coverage beyond the opt-in provider v1 e2e scripts and cache-prune policy
87
+
88
+ ## Setup
89
+
90
+ Surface supports two setup modes:
91
+
92
+ - standard single-machine setup
93
+ Surface runs on the same machine where you can access the browser, localhost callback ports,
94
+ and any required GUI prompts
95
+ - headless remote setup
96
+ Surface runs on a remote machine such as a Mac mini, while a second local machine helps with
97
+ Gmail OAuth browser approval or Outlook browser-profile bootstrap
98
+
99
+ The correct split is:
100
+
101
+ - the machine that actually runs `surface` for day-to-day mail work is the canonical Surface host
102
+ - that host owns:
103
+ - `~/.surface-cli/state.db`
104
+ - `~/.surface-cli/auth/`
105
+ - `~/.surface-cli/cache/`
106
+ - `~/.surface-cli/downloads/`
107
+ - `~/.surface-cli/config.toml` is auto-created on first run and stores local policy only
108
+ such as summarizer and write-safety settings
109
+ - account registry and auth state do not live in `config.toml`
110
+
111
+ ### Install Surface
112
+
113
+ For development from a checkout:
114
+
115
+ ```bash
116
+ npm install
117
+ npm run build
118
+ npm link
119
+ ```
120
+
121
+ For a published install, use npm:
122
+
123
+ ```bash
124
+ npm install -g surface-cli
125
+ ```
126
+
127
+ ### Standard Single-Machine Setup
128
+
129
+ Use this when the same machine can:
130
+
131
+ - open Chrome locally
132
+ - receive loopback OAuth callbacks on `localhost`
133
+ - show any required Microsoft or Google auth UI
134
+
135
+ Typical flow:
136
+
137
+ 1. install Surface on that machine
138
+ 2. add accounts there
139
+ 3. run `surface auth login <account>` there
140
+ 4. use that same machine for normal `surface mail ...` commands
141
+
142
+ Gmail:
143
+
144
+ - place a Google desktop OAuth client secret at `./client_secret.json` or set
145
+ `SURFACE_GMAIL_CLIENT_SECRET_FILE`
146
+ - add the account first:
147
+ - `surface account add personal --provider gmail --transport gmail-api --email you@example.com`
148
+ - run:
149
+ - `surface auth login personal`
150
+
151
+ For Outlook auth:
152
+
153
+ - `surface auth login <account>` opens Chrome against the account profile directory
154
+ - `surface auth status [account]` probes Outlook headlessly and reports whether the profile lands in the mailbox or a sign-in flow
155
+ - `surface auth logout <account>` clears the stored Outlook profile for that account
156
+
157
+ ### Headless Remote Setup
158
+
159
+ Use this when your real Surface host is remote, for example a headless Mac mini, VM, or server.
160
+
161
+ In this mode:
162
+
163
+ - install Surface on the remote machine first
164
+ - add accounts on the remote machine first
165
+ - the remote machine is the source of truth for all Surface state
166
+ - install Surface locally too if you want to use `--remote-host` auth helpers
167
+ - `--remote-host` assumes the named account already exists on the remote machine
168
+ - remote auth only warns before replacement when the remote account already reports `authenticated`
169
+ - if the remote auth-state probe times out or fails, Surface proceeds without an overwrite warning
170
+ instead of blocking the remote auth flow
171
+
172
+ #### Gmail On A Headless Remote Host
173
+
174
+ The remote host is the real Surface runtime. Your local machine is only a browser helper.
175
+
176
+ 1. on the remote host, install Surface and add the Gmail account
177
+ 2. on the local machine, ensure `surface` is installed too
178
+ 3. on the local machine, run:
179
+
180
+ ```bash
181
+ surface auth login <gmail-account> --remote-host <ssh-host>
182
+ ```
183
+
184
+ What happens:
185
+
186
+ - Surface starts SSH port forwarding first
187
+ - Surface runs the Gmail OAuth listener on the remote host
188
+ - you open the Google auth URL locally
189
+ - the OAuth callback is forwarded back to the remote host
190
+ - the refresh token is stored on the remote host under `~/.surface-cli/auth/<account_id>/`
191
+
192
+ #### Outlook On A Headless Remote Host
193
+
194
+ The remote host is again the real Surface runtime. Your local machine is only an auth/bootstrap
195
+ helper.
196
+
197
+ 1. on the remote host, install Surface and add the Outlook account
198
+ 2. on the local machine, ensure `surface` is installed too
199
+ 3. on the local machine, run:
200
+
201
+ ```bash
202
+ surface auth login <outlook-account> --remote-host <ssh-host>
203
+ ```
204
+
205
+ What happens:
206
+
207
+ - Surface opens local Chrome in a dedicated Surface profile
208
+ - you complete the Microsoft sign-in locally
209
+ - Surface syncs that profile to the remote host
210
+ - Surface validates the copied profile on the remote host with `surface auth status <account>`
211
+
212
+ This is why headless remote auth currently requires `surface` to exist on both machines:
213
+
214
+ - local machine: helper for browser/UI work
215
+ - remote machine: canonical Surface runtime and state owner
216
+
217
+ If Chrome is installed in a non-default location, set:
218
+
219
+ ```bash
220
+ export SURFACE_CHROME_PATH="/absolute/path/to/Google Chrome"
221
+ ```
222
+
223
+ For the live Outlook v1 e2e script:
224
+
225
+ ```bash
226
+ export SURFACE_E2E_ENABLE=1
227
+ export SURFACE_TEST_RECIPIENTS='sender@example.com,recipient@example.com,observer@example.com'
228
+ export SURFACE_TEST_ACCOUNT_ALLOWLIST='uni'
229
+ npm run e2e:outlook-v1
230
+ ```
231
+
232
+ For the live Gmail v1 e2e script:
233
+
234
+ ```bash
235
+ export SURFACE_E2E_ENABLE=1
236
+ export SURFACE_E2E_ACCOUNT='personal_2'
237
+ npm run e2e:gmail-v1
238
+ ```
239
+
240
+ For live write-path testing, also set:
241
+
242
+ ```bash
243
+ export SURFACE_WRITES_ENABLED=1
244
+ export SURFACE_SEND_MODE=allow_send
245
+ export SURFACE_TEST_RECIPIENTS='sender@example.com,recipient@example.com,observer@example.com'
246
+ export SURFACE_TEST_ACCOUNT_ALLOWLIST='uni'
247
+ ```
248
+
249
+ ## Local State
250
+
251
+ ```text
252
+ ~/.surface-cli/
253
+ config.toml
254
+ state.db
255
+ auth/
256
+ <account_id>/
257
+ cache/
258
+ <account_id>/
259
+ messages/
260
+ <message_ref>/
261
+ downloads/
262
+ <account_id>/
263
+ <message_ref>/
264
+ ```
265
+
266
+ ## Development
267
+
268
+ ```bash
269
+ npm install
270
+ npm run check
271
+ npm run build
272
+ npm run surface -- --help
273
+ ```
274
+
275
+ ## Publish To ClawHub
276
+
277
+ ClawHub publishes the skill folder, not the whole repo. The publish unit is:
278
+
279
+ ```text
280
+ skills/surface-cli/
281
+ SKILL.md
282
+ ```
283
+
284
+ Recommended release order:
285
+
286
+ 1. publish the CLI package to npm so ClawHub can install `surface`
287
+ 2. log into ClawHub
288
+ 3. publish the skill folder
289
+
290
+ Example:
291
+
292
+ ```bash
293
+ npm publish
294
+ clawhub login
295
+ clawhub publish ./skills/surface-cli \
296
+ --slug surface-cli \
297
+ --name "Surface CLI" \
298
+ --version 0.1.0 \
299
+ --changelog "Initial Surface CLI skill release"
300
+ ```
301
+
302
+ Before publishing, verify the package payload:
303
+
304
+ ```bash
305
+ npm pack --dry-run
306
+ openclaw skills info surface-cli
307
+ ```