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.
- package/README.md +307 -0
- package/dist/cli.js +521 -0
- package/dist/cli.js.map +1 -0
- package/dist/config.js +156 -0
- package/dist/config.js.map +1 -0
- package/dist/contracts/account.js +9 -0
- package/dist/contracts/account.js.map +1 -0
- package/dist/contracts/mail.js +2 -0
- package/dist/contracts/mail.js.map +1 -0
- package/dist/e2e/gmail-v1.js +247 -0
- package/dist/e2e/gmail-v1.js.map +1 -0
- package/dist/e2e/outlook-v1.js +179 -0
- package/dist/e2e/outlook-v1.js.map +1 -0
- package/dist/lib/errors.js +47 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/json.js +4 -0
- package/dist/lib/json.js.map +1 -0
- package/dist/lib/public-mail.js +29 -0
- package/dist/lib/public-mail.js.map +1 -0
- package/dist/lib/remote-auth.js +407 -0
- package/dist/lib/remote-auth.js.map +1 -0
- package/dist/lib/time.js +4 -0
- package/dist/lib/time.js.map +1 -0
- package/dist/lib/write-safety.js +34 -0
- package/dist/lib/write-safety.js.map +1 -0
- package/dist/paths.js +29 -0
- package/dist/paths.js.map +1 -0
- package/dist/providers/gmail/adapter.js +1102 -0
- package/dist/providers/gmail/adapter.js.map +1 -0
- package/dist/providers/gmail/api.js +99 -0
- package/dist/providers/gmail/api.js.map +1 -0
- package/dist/providers/gmail/normalize.js +336 -0
- package/dist/providers/gmail/normalize.js.map +1 -0
- package/dist/providers/gmail/oauth.js +328 -0
- package/dist/providers/gmail/oauth.js.map +1 -0
- package/dist/providers/index.js +12 -0
- package/dist/providers/index.js.map +1 -0
- package/dist/providers/outlook/adapter.js +1443 -0
- package/dist/providers/outlook/adapter.js.map +1 -0
- package/dist/providers/outlook/extract.js +416 -0
- package/dist/providers/outlook/extract.js.map +1 -0
- package/dist/providers/outlook/normalize.js +126 -0
- package/dist/providers/outlook/normalize.js.map +1 -0
- package/dist/providers/outlook/session.js +178 -0
- package/dist/providers/outlook/session.js.map +1 -0
- package/dist/providers/shared/html.js +88 -0
- package/dist/providers/shared/html.js.map +1 -0
- package/dist/providers/shared/types.js +2 -0
- package/dist/providers/shared/types.js.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/refs.js +18 -0
- package/dist/refs.js.map +1 -0
- package/dist/runtime.js +23 -0
- package/dist/runtime.js.map +1 -0
- package/dist/state/database.js +731 -0
- package/dist/state/database.js.map +1 -0
- package/dist/summarizer.js +217 -0
- package/dist/summarizer.js.map +1 -0
- 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
|
+
```
|