@whatcha/cli 0.1.0 → 0.4.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 +223 -25
- package/dist/auth/session.js +50 -0
- package/dist/auth/session.js.map +1 -1
- package/dist/auth/token-store.js.map +1 -1
- package/dist/cli.js +44 -5
- package/dist/cli.js.map +1 -1
- package/dist/commands/diaries.js +14 -3
- package/dist/commands/diaries.js.map +1 -1
- package/dist/commands/diary-cover.js +54 -0
- package/dist/commands/diary-cover.js.map +1 -0
- package/dist/commands/login.js +82 -7
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/profiles.js +187 -11
- package/dist/commands/profiles.js.map +1 -1
- package/dist/commands/publish.js +81 -0
- package/dist/commands/publish.js.map +1 -0
- package/dist/commands/read.js +7 -1
- package/dist/commands/read.js.map +1 -1
- package/dist/commands/shared-diary.js +255 -0
- package/dist/commands/shared-diary.js.map +1 -0
- package/dist/commands/whoami.js +50 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/commands/wiki.js +386 -0
- package/dist/commands/wiki.js.map +1 -0
- package/dist/commands/write.js +119 -7
- package/dist/commands/write.js.map +1 -1
- package/dist/http-client.js +16 -0
- package/dist/http-client.js.map +1 -1
- package/dist/lib/append-entry.js +90 -11
- package/dist/lib/append-entry.js.map +1 -1
- package/dist/lib/entry-date.js +22 -2
- package/dist/lib/entry-date.js.map +1 -1
- package/dist/lib/entry-get.js +31 -0
- package/dist/lib/entry-get.js.map +1 -0
- package/dist/lib/image-upload.js +126 -0
- package/dist/lib/image-upload.js.map +1 -0
- package/dist/lib/personal-access-token.js +46 -0
- package/dist/lib/personal-access-token.js.map +1 -0
- package/dist/lib/profile-family.js +29 -0
- package/dist/lib/profile-family.js.map +1 -0
- package/dist/lib/publication.js +55 -0
- package/dist/lib/publication.js.map +1 -0
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -25,18 +25,34 @@ Requires Node >= 22 (fetch, `node:util`'s `parseArgs`, and the fixed
|
|
|
25
25
|
### `whatcha login`
|
|
26
26
|
|
|
27
27
|
```
|
|
28
|
-
whatcha login
|
|
28
|
+
whatcha login --key <personal-access-key> [--json]
|
|
29
|
+
whatcha login [--email <email>] [--password <password>] [--json]
|
|
30
|
+
whatcha login --token <refresh-token> [--json]
|
|
29
31
|
```
|
|
30
32
|
|
|
33
|
+
**For scripts, servers and AI agents, use a personal access key (#1663).**
|
|
34
|
+
Issue one in the app under *Profile → Connected apps and keys* (#1469,
|
|
35
|
+
server side in #1550) -- read-only or read/write, with an expiry -- and
|
|
36
|
+
pass it as `--key`, or set `WHATCHA_KEY` and run `whatcha login`. A key
|
|
37
|
+
handed to `--token` / `WHATCHA_TOKEN` is recognised by its `whatcha_pat_`
|
|
38
|
+
prefix and used as a key too. `login` checks the key with `GET /me`
|
|
39
|
+
before saving it. A key is the Bearer credential itself: it is never
|
|
40
|
+
refreshed, never meets a captcha, and stops working the moment it is
|
|
41
|
+
revoked -- every command then fails with `KEY_REVOKED` (or `KEY_EXPIRED`
|
|
42
|
+
once it expires) and a pointer to where to issue a new one. Explicit
|
|
43
|
+
`--email` / `--password` / `--token` flags win over a `WHATCHA_KEY` left
|
|
44
|
+
in the environment.
|
|
45
|
+
|
|
31
46
|
Signs in and stores the session -- an OS credential store when one is
|
|
32
47
|
available (macOS Keychain via `security`, Linux's Secret Service via
|
|
33
48
|
`secret-tool`), otherwise a `0600` file under the OS config dir
|
|
34
49
|
(`~/.config/whatcha/credentials.json` on Linux, `~/Library/.../whatcha` is
|
|
35
50
|
not used on macOS since the Keychain is tried first, `%APPDATA%\whatcha` on
|
|
36
51
|
Windows). Every other command reads that session and refreshes it as
|
|
37
|
-
needed; there is no
|
|
38
|
-
the credential store yourself (Keychain Access,
|
|
39
|
-
whatcha-cli account whatcha`, or delete the
|
|
52
|
+
needed (a key is never refreshed); there is no `whatcha logout` -- revoke
|
|
53
|
+
a key in the app, or clear the credential store yourself (Keychain Access,
|
|
54
|
+
`secret-tool clear service whatcha-cli account whatcha`, or delete the
|
|
55
|
+
file) if you need to.
|
|
40
56
|
|
|
41
57
|
Email and password can come from `--email`/`--password`, from
|
|
42
58
|
`WHATCHA_EMAIL`/`WHATCHA_PASSWORD`, or (only when neither is set) an
|
|
@@ -50,20 +66,29 @@ failed" / "Please complete the verification") trying to sign in an AI
|
|
|
50
66
|
partner account. `POST /auth/login` also answers a captcha rejection with
|
|
51
67
|
the same generic 401 as a wrong password, so `whatcha` frequently cannot
|
|
52
68
|
tell you which one happened. Do not keep retrying the password in that
|
|
53
|
-
case.
|
|
69
|
+
case: use a personal access key (above). A refresh token still works as
|
|
70
|
+
before:
|
|
54
71
|
|
|
55
72
|
```
|
|
56
73
|
whatcha login --token <refresh-token>
|
|
57
74
|
```
|
|
58
75
|
|
|
59
76
|
or set `WHATCHA_TOKEN` to the same value. This calls `POST /auth/refresh`
|
|
60
|
-
directly, which is **not** captcha-gated
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
77
|
+
directly, which is **not** captcha-gated. The refresh token has to come
|
|
78
|
+
from somewhere that *can* solve the challenge -- a browser or the mobile
|
|
79
|
+
app -- and unlike a key it cannot be limited to read-only or revoked on its
|
|
80
|
+
own, so prefer a key.
|
|
81
|
+
|
|
82
|
+
### `whatcha whoami`
|
|
83
|
+
|
|
84
|
+
```
|
|
85
|
+
whatcha whoami [--json]
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Asks the API (`GET /me`) who the saved credential is -- `id`, `username`,
|
|
89
|
+
`displayName`, `email` -- plus `authKind` (`"session"` or `"key"`) and
|
|
90
|
+
whether the CLI is switched into a sub-profile. A revoked key shows up here
|
|
91
|
+
as `KEY_REVOKED`.
|
|
67
92
|
|
|
68
93
|
Advanced: if `WHATCHA_SUPABASE_URL` and `WHATCHA_SUPABASE_ANON_KEY` are both
|
|
69
94
|
set, `whatcha login` talks to GoTrue directly instead of through
|
|
@@ -82,14 +107,114 @@ whatcha diaries [--json]
|
|
|
82
107
|
|
|
83
108
|
Lists the diaries you can see (your personal shelf, same set `GET /diaries`
|
|
84
109
|
without `groupId` returns -- so not diaries in a system group like Public
|
|
85
|
-
Square).
|
|
110
|
+
Square). Each one in `--json` also carries `isPublic` and `publicUrl`
|
|
111
|
+
(#1701; `null` unless public) -- see `whatcha publish` for what makes a
|
|
112
|
+
diary public. Working that out costs one `GET /groups` walk plus one
|
|
113
|
+
`GET /diaries/{id}` per diary in a published group; a shelf with no shared
|
|
114
|
+
diaries costs nothing extra.
|
|
115
|
+
|
|
116
|
+
### `whatcha diary-new` / `whatcha invite`
|
|
117
|
+
|
|
118
|
+
```
|
|
119
|
+
whatcha diary-new <name> [--with <@handle>]... [--timezone <tz>] [--description "..."] [--json]
|
|
120
|
+
whatcha invite <diary> <@handle> [--json]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
A diary written together (#1700) -- an exchange diary with a friend, or with
|
|
124
|
+
an AI partner profile. `diary-new` does what the app's "Write this diary
|
|
125
|
+
with someone" does: it creates a group named after the diary (you own it;
|
|
126
|
+
the plan's limit on owned groups applies), then the diary in that group,
|
|
127
|
+
readable by every member. `--timezone` is an IANA zone (`Asia/Tokyo`) and
|
|
128
|
+
decides which day an entry lands on; without it the server's default is
|
|
129
|
+
used. `--description` (at most 1,000 characters) goes on both. If the diary
|
|
130
|
+
cannot be created, the empty group is deleted again.
|
|
131
|
+
|
|
132
|
+
Membership is the group's, and the API joins nobody without their own
|
|
133
|
+
accept -- there is no "add this user" call. So `--with` (repeatable) and
|
|
134
|
+
`invite` do one of three things per `@handle`:
|
|
135
|
+
|
|
136
|
+
- **One of this login's sub-profiles** (see `profile-add`): it joins on the
|
|
137
|
+
spot. The CLI mints a one-use invite and accepts it *as that profile*,
|
|
138
|
+
with a session for it used once and never saved -- the CLI stays signed
|
|
139
|
+
in as it was.
|
|
140
|
+
- **The profile you are signed in as**: already a member; nothing is sent.
|
|
141
|
+
- **Anybody else** (and the account itself, seen from a sub-profile -- the
|
|
142
|
+
API issues no account session from there): a one-use invite link, valid
|
|
143
|
+
7 days, for them to open and accept, exactly as the app's invite card
|
|
144
|
+
hands out. The `@handle` is only a label for it.
|
|
145
|
+
|
|
146
|
+
Creating an invite is the group owner's, as in the app. `diary-new --json`
|
|
147
|
+
prints `diary` (the created diary), `groupId` and `members`; each member,
|
|
148
|
+
like `invite --json` (which adds `diaryId` and `groupId`), is `handle`,
|
|
149
|
+
`profileId` (`null` when not one of yours), `joined`, `alreadyMember`,
|
|
150
|
+
`inviteUrl`/`expiresAt` (for a link) and `error` (`{code, message}`, when
|
|
151
|
+
that one handle failed). `diary-new` still creates the diary when a member
|
|
152
|
+
fails -- it exits with status 2 and says which, for `whatcha invite` to
|
|
153
|
+
retry. A diary on your personal shelf cannot be shared; start one with
|
|
154
|
+
`diary-new`.
|
|
155
|
+
|
|
156
|
+
### `whatcha publish`
|
|
157
|
+
|
|
158
|
+
```
|
|
159
|
+
whatcha publish <diary> [--off] [--json]
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
Puts a diary on the web for anybody to read, signed in or not, and prints
|
|
163
|
+
where: `https://whatareyoudo.ing/diaries/<id>` (#1701). The API publishes
|
|
164
|
+
groups, not single diaries, so this is the app's "Publish" switch on the
|
|
165
|
+
diary's group (`PUT /groups/{id}/publication`, owner only): **every diary in
|
|
166
|
+
that group its members can read becomes public with it.** A diary from
|
|
167
|
+
`diary-new` has a group of its own, so there that is just the one diary. A
|
|
168
|
+
diary kept to its creator would stay private in a published group, so
|
|
169
|
+
`publish` opens it to the group first (`PUT /diaries/{id}/visibility`,
|
|
170
|
+
reported as `openedToGroup: true`) -- otherwise the printed link would be a
|
|
171
|
+
404. `--off` takes the group back off the web and changes nothing else. A
|
|
172
|
+
diary on your personal shelf cannot be published. `--json` prints
|
|
173
|
+
`diaryId`, `groupId`, `groupName`, `isPublic`, `url` (`null` with `--off`)
|
|
174
|
+
and `openedToGroup`.
|
|
175
|
+
|
|
176
|
+
### `whatcha diary-cover`
|
|
177
|
+
|
|
178
|
+
```
|
|
179
|
+
whatcha diary-cover <diary> <file> | --clear [--json]
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
Sets the picture a diary's page and its share card show (#52), the same
|
|
183
|
+
one the app's cover frame sets: the file (JPEG, PNG, WebP, GIF or AVIF, at
|
|
184
|
+
most 40 MB) is uploaded into the diary's group and saved as the diary's
|
|
185
|
+
cover with `PATCH /diaries/{id}`. `--clear` removes it. Anyone who may edit
|
|
186
|
+
the diary's settings may do this. `--json` prints `diaryId` and
|
|
187
|
+
`coverMediaAssetId` (`null` after `--clear`).
|
|
86
188
|
|
|
87
189
|
### `whatcha write`
|
|
88
190
|
|
|
89
191
|
```
|
|
90
|
-
whatcha write <diary> --text "..."
|
|
192
|
+
whatcha write <diary> (--text "..." | --text-file <path|->) [--title "..."] [--tag <tag>]...
|
|
193
|
+
[--image <file>]... [--cover <file>] [--time HH:MM] [--date YYYY-MM-DD] [--json]
|
|
91
194
|
```
|
|
92
195
|
|
|
196
|
+
`--text-file <path>` (#54) reads the text from a file, or from standard
|
|
197
|
+
input with `-`. Use it for anything longer than a line: on Windows the npm
|
|
198
|
+
command (`whatcha.cmd`) runs through `cmd.exe`, which ends `--text` at its
|
|
199
|
+
first line break and drops every flag after it. The file is read as UTF-8;
|
|
200
|
+
a byte-order mark is dropped and CRLF becomes LF.
|
|
201
|
+
|
|
202
|
+
`--image <file>` (repeat it for several) and `--cover <file>` (#51) add
|
|
203
|
+
pictures: each file is checked first (JPEG, PNG, WebP, GIF or AVIF, at most
|
|
204
|
+
40 MB), then uploaded into the diary's group. Images go after the ones the
|
|
205
|
+
entry already has -- its own are kept. `--cover` follows the `--title`
|
|
206
|
+
rule: it only fills an entry that has no cover (an existing one is kept,
|
|
207
|
+
reported as `coverKept: true`). With pictures, the text may be left out to
|
|
208
|
+
add them to the day's entry without writing anything. `--json` adds
|
|
209
|
+
`images` (the new ids) and `coverKept`.
|
|
210
|
+
|
|
211
|
+
`--title` and `--tag` (#1665) set the same `content.title` / `content.tags`
|
|
212
|
+
the app does, under the same rule as the text -- nothing somebody else
|
|
213
|
+
wrote is removed: `--title` only fills an entry that has no title yet (an
|
|
214
|
+
existing one is kept, reported as `titleKept: true` and a note on stderr),
|
|
215
|
+
and `--tag` (repeat it for several) only adds to the entry's tags --
|
|
216
|
+
trimmed, exact duplicates dropped, at most 32 in all, 64 characters each.
|
|
217
|
+
|
|
93
218
|
`<diary>` is a diary id or its exact name (case-insensitive; an ambiguous
|
|
94
219
|
name lists the matches and asks for the id instead). `--date`/`--time`
|
|
95
220
|
default to today/now in **the diary's own timezone**, not the machine's --
|
|
@@ -121,18 +246,22 @@ never sends a body it has not just read:
|
|
|
121
246
|
whatcha read <diary> [--date YYYY-MM-DD] [--json]
|
|
122
247
|
```
|
|
123
248
|
|
|
124
|
-
Defaults to today (diary timezone). Prints title/tags/body
|
|
249
|
+
Defaults to today (diary timezone). Prints title/tags/body and how many
|
|
250
|
+
images the entry has (`--json`: `images`, their ids, and `coverMediaAssetId`); a locked (Time
|
|
125
251
|
Capsule) entry is reported as locked, never shown early. `GET /entries` has
|
|
126
252
|
no `?date=` filter, so this pages through the diary's entries
|
|
127
253
|
newest-first (`src/lib/entry-find.ts`) until it finds that day or passes it
|
|
128
254
|
-- exact, not fuzzy, but not O(1) for a very old date on a very active diary.
|
|
129
255
|
|
|
130
|
-
### `whatcha profiles` / `whatcha switch`
|
|
256
|
+
### `whatcha profiles` / `whatcha switch` / `whatcha profile-add` / `whatcha profile-rm`
|
|
131
257
|
|
|
132
258
|
```
|
|
133
259
|
whatcha profiles [--json]
|
|
134
260
|
whatcha switch <@handle> [--json]
|
|
135
261
|
whatcha switch --account [--json]
|
|
262
|
+
whatcha profile-add <@handle> --name <display name> [--avatar <file>] [--json]
|
|
263
|
+
whatcha profile-avatar <@handle> <file> [--json]
|
|
264
|
+
whatcha profile-rm <@handle> [--json]
|
|
136
265
|
```
|
|
137
266
|
|
|
138
267
|
Sub-accounts (#1538): one login, more than one profile. A sub-profile is an
|
|
@@ -140,34 +269,103 @@ ordinary user with its own `@handle`, so after a switch `write`, `read` and
|
|
|
140
269
|
`diaries` all act as that profile -- none of them takes a flag, and none of
|
|
141
270
|
them knows the feature exists. The saved session simply is that profile.
|
|
142
271
|
|
|
143
|
-
`profiles` marks the current one with `*` and labels the account
|
|
144
|
-
|
|
145
|
-
|
|
272
|
+
`profiles` marks the current one with `*` and labels the account; `--json`
|
|
273
|
+
adds `remaining`, how many more the plan allows.
|
|
274
|
+
|
|
275
|
+
`profile-add` (#1699) creates one (`POST /me/profiles`, as the app's "Add a
|
|
276
|
+
profile" does): a `@handle` of 3-30 letters, digits, `_` or `-` starting
|
|
277
|
+
with a letter or digit, and a `--name` of at most 80 characters. A taken
|
|
278
|
+
handle or a full plan is the API's refusal. It does not switch -- run
|
|
279
|
+
`whatcha switch <@handle>` for that. `--avatar <file>` (JPEG, PNG, WebP, GIF
|
|
280
|
+
or AVIF, at most 5 MB, checked before anything is created) is set as the
|
|
281
|
+
new profile itself would set it: `POST /me/avatar` with a session for the
|
|
282
|
+
profile, used once and never saved. If the profile is created but the
|
|
283
|
+
avatar fails, the profile stays, `avatarError` says why and the exit status
|
|
284
|
+
is 2. `--json` prints `profile` (`id`, `username`, `displayName`,
|
|
285
|
+
`avatarUrl`, `parentProfileId`) and `avatarError` (`null`, or
|
|
286
|
+
`{code, message}`).
|
|
287
|
+
|
|
288
|
+
`profile-avatar` (#50) changes the avatar of a profile that already exists,
|
|
289
|
+
with the same checks. The profile in use sets its own; another sub-profile
|
|
290
|
+
is set through a session for it, used once, as `profile-add` does. The
|
|
291
|
+
account's own avatar cannot be reached from a sub-profile -- `switch
|
|
292
|
+
--account` first. `--json` prints `profileId` and `avatarUrl`.
|
|
146
293
|
|
|
147
294
|
`switch --account` is how you come back. The API refuses to mint an account
|
|
148
295
|
session from a sub-profile's session on purpose -- a sub-profile that could
|
|
149
296
|
ask for one would be a way to take the account over -- so `switch` sets the
|
|
150
297
|
account's refresh token aside in the same credential store and `--account`
|
|
151
298
|
spends it. Switching from one sub-profile to a sibling carries it forward
|
|
152
|
-
unchanged.
|
|
299
|
+
unchanged. Logged in with a personal access key, it is the key that is set
|
|
300
|
+
aside (`accountKey`), and `--account` goes back to it.
|
|
301
|
+
|
|
302
|
+
`profile-rm` deletes a sub-profile and everything it wrote; the API refuses
|
|
303
|
+
the account itself and the profile currently in use.
|
|
153
304
|
|
|
154
305
|
Billing is the one thing a sub-profile does not have: the account's plan is
|
|
155
306
|
its plan, and a sub-profile's own limits are the account's.
|
|
156
307
|
|
|
308
|
+
### `whatcha wiki`
|
|
309
|
+
|
|
310
|
+
```
|
|
311
|
+
whatcha wiki list [--search <text>] [--group <group-id>] [--limit N] [--cursor <c>] [--json]
|
|
312
|
+
whatcha wiki read <page> [--group <group-id>] [--json]
|
|
313
|
+
whatcha wiki write <page> --text "..." [--image <file>]... [--cover <file>] [--replace] [--message "..."] [--group <group-id>] [--json]
|
|
314
|
+
whatcha wiki cover <page> <file> | --clear [--group <group-id>] [--json]
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
The Experience Wiki (#1664), on the app's own routes: Public Square's
|
|
318
|
+
`/experience-pages`, or with `--group` that group's
|
|
319
|
+
`/groups/:groupId/experience-pages` (#1348; `whatcha diaries --json` shows
|
|
320
|
+
each diary's `groupId`). `<page>` is the page's tag, e.g. `"Kyoto Tower"`
|
|
321
|
+
(a leading `#` is dropped). Who may read or write which page -- group
|
|
322
|
+
membership, a page's Owner and edit policy (#1344), a lock -- is the API's
|
|
323
|
+
call; a page this account cannot see reads as `found: false`, any other
|
|
324
|
+
refusal is printed as the API sent it.
|
|
325
|
+
|
|
326
|
+
`wiki write` appends below the page's current text and creates the page if
|
|
327
|
+
it does not exist; `--replace` saves the text as the page's new version
|
|
328
|
+
instead. Either way every earlier version stays in the page's history. The
|
|
329
|
+
wiki's `PUT` takes no expected revision, so if somebody else saves between
|
|
330
|
+
`write`'s read and its `PUT`, that is reported (`concurrentEdit: true`, a
|
|
331
|
+
warning on stderr, exit status 2) rather than passed off as a clean save --
|
|
332
|
+
their version is in the history to merge back.
|
|
333
|
+
|
|
334
|
+
Images (#1667). `--image <file>` (repeatable) uploads a local image and
|
|
335
|
+
appends `` for it below the text, in the
|
|
336
|
+
order given, one paragraph each; with images, `--text` may be left out.
|
|
337
|
+
`--cover <file>` uploads an image and makes it the page's cover in the same
|
|
338
|
+
save. `wiki cover <page> <file>` sets the cover of an existing page on its
|
|
339
|
+
own, and `wiki cover <page> --clear` removes it. Uploads go through the
|
|
340
|
+
app's own flow (`POST /uploads` into the page's group -- Public Square's for
|
|
341
|
+
the public wiki -- the PUT to the signed URL, then
|
|
342
|
+
`POST /uploads/:id/complete`), so the server's limits apply: JPEG, PNG,
|
|
343
|
+
WebP, GIF or AVIF, at most 40 MB, within the account's storage quota. Both
|
|
344
|
+
are checked before anything is sent -- the type from the file's first
|
|
345
|
+
bytes, not its name -- and so is the page's 20,000-character limit, so a bad
|
|
346
|
+
file or an over-long page uploads nothing. `wiki read` lists each image's
|
|
347
|
+
signed URL (valid for about an hour) after the text, and the cover's above
|
|
348
|
+
it; `--json` adds `media` (`id`, `url`, `mimeType`, `altText`),
|
|
349
|
+
`coverMediaAssetId` and `coverUrl`. `wiki write --json` reports `images`
|
|
350
|
+
(`file`, `mediaAssetId`) and `coverMediaAssetId`.
|
|
351
|
+
|
|
157
352
|
## The `--json` contract
|
|
158
353
|
|
|
159
354
|
Every command accepts `--json`. On success it prints exactly one JSON
|
|
160
355
|
object to stdout: `{"ok": true, ...}`, with the fields documented per
|
|
161
|
-
command above (`diaries`, `entryId`/`mode`/`verified`
|
|
162
|
-
`body`/`title`/`tags`/... for `read`
|
|
356
|
+
command above (`diaries`, `entryId`/`mode`/`verified`/`title`/`tags`/`titleKept`
|
|
357
|
+
for `write`, `body`/`title`/`tags`/... for `read`, `pages` / `body` /
|
|
358
|
+
`mode`/`concurrentEdit` for `wiki`, `profile`/`avatarError` for
|
|
359
|
+
`profile-add`, `diary`/`members` for `diary-new`, `url`/`isPublic` for
|
|
360
|
+
`publish`). On failure it prints
|
|
163
361
|
`{"ok": false, "error": {"code", "message"}}` -- still to stdout, so a
|
|
164
362
|
script doing `whatcha ... --json | jq` gets parseable JSON either way,
|
|
165
363
|
without also having to capture stderr.
|
|
166
364
|
|
|
167
365
|
**Never included, on success or failure:** an access token, a refresh
|
|
168
|
-
token, or a password. `whatcha login --json`'s
|
|
169
|
-
`{"ok", "userId", "email", "credentialStore"}`
|
|
170
|
-
its contents.
|
|
366
|
+
token, a personal access key, or a password. `whatcha login --json`'s
|
|
367
|
+
output is `{"ok", "userId", "email", "credentialStore"}` (plus
|
|
368
|
+
`"authKind": "key"` for a key) -- the store's name, never its contents.
|
|
171
369
|
|
|
172
370
|
## Tests
|
|
173
371
|
|
package/dist/auth/session.js
CHANGED
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.KeyErrorClient = void 0;
|
|
3
4
|
exports.saveSession = saveSession;
|
|
4
5
|
exports.loadSession = loadSession;
|
|
5
6
|
exports.clearSession = clearSession;
|
|
6
7
|
exports.authenticatedClient = authenticatedClient;
|
|
8
|
+
exports.personalAccessTokenClient = personalAccessTokenClient;
|
|
7
9
|
exports.anonymousClient = anonymousClient;
|
|
8
10
|
const http_client_1 = require("../http-client");
|
|
9
11
|
const config_1 = require("../config");
|
|
10
12
|
const errors_1 = require("../errors");
|
|
13
|
+
const personal_access_token_1 = require("../lib/personal-access-token");
|
|
11
14
|
const token_store_1 = require("./token-store");
|
|
12
15
|
/** Refresh this many seconds before actual expiry, so a call in flight does not race the token dying mid-request. */
|
|
13
16
|
const REFRESH_MARGIN_SECONDS = 60;
|
|
@@ -65,6 +68,11 @@ async function authenticatedClient(stores = (0, token_store_1.candidateStores)()
|
|
|
65
68
|
throw new errors_1.CliUsageError("Not logged in. Run `whatcha login` first.");
|
|
66
69
|
}
|
|
67
70
|
const baseUrl = session.apiBaseUrl || (0, config_1.apiBaseUrl)();
|
|
71
|
+
if (session.authKind === "key") {
|
|
72
|
+
// A personal access key is the Bearer itself: nothing to refresh, and
|
|
73
|
+
// its refusals are reworded as the key's (KEY_REVOKED / KEY_EXPIRED).
|
|
74
|
+
return { client: personalAccessTokenClient(baseUrl, session.accessToken), session };
|
|
75
|
+
}
|
|
68
76
|
const nowSeconds = Date.now() / 1000;
|
|
69
77
|
if (session.expiresAt / 1000 - nowSeconds < REFRESH_MARGIN_SECONDS) {
|
|
70
78
|
const anon = new http_client_1.FetchWhatchaClient(baseUrl, async () => undefined);
|
|
@@ -90,6 +98,48 @@ async function authenticatedClient(stores = (0, token_store_1.candidateStores)()
|
|
|
90
98
|
const client = new http_client_1.FetchWhatchaClient(baseUrl, async () => `Bearer ${session.accessToken}`);
|
|
91
99
|
return { client, session };
|
|
92
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* A client that sends a personal access key (#1663) as its Bearer and
|
|
103
|
+
* reports a 401 as the key's problem -- see lib/personal-access-token.ts.
|
|
104
|
+
*/
|
|
105
|
+
function personalAccessTokenClient(baseUrl, key) {
|
|
106
|
+
return new KeyErrorClient(new http_client_1.FetchWhatchaClient(baseUrl, async () => `Bearer ${key}`));
|
|
107
|
+
}
|
|
108
|
+
/** Wraps any client so a 401 reads as KEY_REVOKED/KEY_EXPIRED. Exported for tests. */
|
|
109
|
+
class KeyErrorClient {
|
|
110
|
+
inner;
|
|
111
|
+
constructor(inner) {
|
|
112
|
+
this.inner = inner;
|
|
113
|
+
}
|
|
114
|
+
wrap(p) {
|
|
115
|
+
return p.catch((err) => {
|
|
116
|
+
throw (0, personal_access_token_1.translateKeyError)(err);
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
get(path, query) {
|
|
120
|
+
return this.wrap(this.inner.get(path, query));
|
|
121
|
+
}
|
|
122
|
+
post(path, body) {
|
|
123
|
+
return this.wrap(this.inner.post(path, body));
|
|
124
|
+
}
|
|
125
|
+
put(path, body) {
|
|
126
|
+
return this.wrap(this.inner.put(path, body));
|
|
127
|
+
}
|
|
128
|
+
patch(path, body) {
|
|
129
|
+
return this.wrap(this.inner.patch(path, body));
|
|
130
|
+
}
|
|
131
|
+
delete(path) {
|
|
132
|
+
return this.wrap(this.inner.delete(path));
|
|
133
|
+
}
|
|
134
|
+
getPage(path, query) {
|
|
135
|
+
return this.wrap(this.inner.getPage(path, query));
|
|
136
|
+
}
|
|
137
|
+
/** Not wrapped: the signed URL carries no key, so its 401/403 is never the key's. */
|
|
138
|
+
putBytes(url, headers, bytes) {
|
|
139
|
+
return this.inner.putBytes(url, headers, bytes);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
exports.KeyErrorClient = KeyErrorClient;
|
|
93
143
|
/** An unauthenticated client for `/auth/login` itself, or for `--api-url` probing. */
|
|
94
144
|
function anonymousClient(baseUrl = (0, config_1.apiBaseUrl)()) {
|
|
95
145
|
return new http_client_1.FetchWhatchaClient(baseUrl, async () => undefined);
|
package/dist/auth/session.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../../src/auth/session.ts"],"names":[],"mappings":";;;AAqBA,kCAYC;AAGD,kCAOC;AAGD,oCAIC;AAUD,kDAyCC;AAMD,8DAEC;AAmCD,0CAEC;AAlJD,gDAAyE;AACzE,sCAAuC;AACvC,sCAA0C;AAC1C,wEAAiE;AAEjE,+CAA2E;AAE3E,qHAAqH;AACrH,MAAM,sBAAsB,GAAG,EAAE,CAAC;AAMlC;;;;;;GAMG;AACI,KAAK,UAAU,WAAW,CAAC,OAAsB,EAAE,SAAuB,IAAA,6BAAe,GAAE;IAChG,MAAM,MAAM,GAAa,EAAE,CAAC;IAC5B,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC;YAAE,SAAS;QAC3C,IAAI,CAAC;YACH,MAAM,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;YAC1B,OAAO,EAAE,KAAK,EAAE,CAAC;QACnB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC,IAAI,KAAM,GAAa,CAAC,OAAO,EAAE,CAAC,CAAC;QAC1D,CAAC;IACH,CAAC;IACD,MAAM,IAAI,KAAK,CAAC,+DAA+D,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;AACtG,CAAC;AAED,6EAA6E;AACtE,KAAK,UAAU,WAAW,CAAC,SAAuB,IAAA,6BAAe,GAAE;IACxE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,CAAC,CAAC,MAAM,KAAK,CAAC,WAAW,EAAE,CAAC;YAAE,SAAS;QAC3C,MAAM,OAAO,GAAG,MAAM,KAAK,CAAC,IAAI,EAAE,CAAC;QACnC,IAAI,OAAO;YAAE,OAAO,OAAO,CAAC;IAC9B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC;AAED,kJAAkJ;AAC3I,KAAK,UAAU,YAAY,CAAC,SAAuB,IAAA,6BAAe,GAAE;IACzE,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,IAAI,MAAM,KAAK,CAAC,WAAW,EAAE;YAAE,MAAM,KAAK,CAAC,KAAK,EAAE,CAAC;IACrD,CAAC;AACH,CAAC;AAED;;;;;;;GAOG;AACI,KAAK,UAAU,mBAAmB,CAAC,SAAuB,IAAA,6BAAe,GAAE;IAIhF,IAAI,OAAO,GAAG,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,sBAAa,CAAC,2CAA2C,CAAC,CAAC;IACvE,CAAC;IAED,MAAM,OAAO,GAAG,OAAO,CAAC,UAAU,IAAI,IAAA,mBAAU,GAAE,CAAC;IAEnD,IAAI,OAAO,CAAC,QAAQ,KAAK,KAAK,EAAE,CAAC;QAC/B,sEAAsE;QACtE,sEAAsE;QACtE,OAAO,EAAE,MAAM,EAAE,yBAAyB,CAAC,OAAO,EAAE,OAAO,CAAC,WAAW,CAAC,EAAE,OAAO,EAAE,CAAC;IACtF,CAAC;IAED,MAAM,UAAU,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;IACrC,IAAI,OAAO,CAAC,SAAS,GAAG,IAAI,GAAG,UAAU,GAAG,sBAAsB,EAAE,CAAC;QACnE,MAAM,IAAI,GAAG,IAAI,gCAAkB,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC;QACpE,IAAI,SAAwB,CAAC;QAC7B,IAAI,CAAC;YACH,SAAS,GAAG,MAAM,IAAI,CAAC,IAAI,CAAgB,eAAe,EAAE,EAAE,YAAY,EAAE,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;QACtG,CAAC;QAAC,MAAM,CAAC;YACP,MAAM,IAAI,sBAAa,CAAC,sFAAsF,CAAC,CAAC;QAClH,CAAC;QACD,OAAO,GAAG;YACR,GAAG,OAAO;YACV,WAAW,EAAE,SAAS,CAAC,WAAW;YAClC,YAAY,EAAE,SAAS,CAAC,YAAY;YACpC,SAAS,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,SAAS,GAAG,IAAI;SACnD,CAAC;QACF,kEAAkE;QAClE,kEAAkE;QAClE,oEAAoE;QACpE,WAAW;QACX,MAAM,WAAW,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;IAC5D,CAAC;IAED,MAAM,MAAM,GAAG,IAAI,gCAAkB,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,UAAU,OAAQ,CAAC,WAAW,EAAE,CAAC,CAAC;IAC7F,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC;AAED;;;GAGG;AACH,SAAgB,yBAAyB,CAAC,OAAe,EAAE,GAAW;IACpE,OAAO,IAAI,cAAc,CAAC,IAAI,gCAAkB,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,UAAU,GAAG,EAAE,CAAC,CAAC,CAAC;AAC1F,CAAC;AAED,sFAAsF;AACtF,MAAa,cAAc;IACI;IAA7B,YAA6B,KAAoB;QAApB,UAAK,GAAL,KAAK,CAAe;IAAG,CAAC;IAC7C,IAAI,CAAI,CAAa;QAC3B,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,GAAY,EAAE,EAAE;YAC9B,MAAM,IAAA,yCAAiB,EAAC,GAAG,CAAC,CAAC;QAC/B,CAAC,CAAC,CAAC;IACL,CAAC;IACD,GAAG,CAAI,IAAY,EAAE,KAA0C;QAC7D,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAI,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,IAAI,CAAI,IAAY,EAAE,IAAc;QAClC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAI,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACnD,CAAC;IACD,GAAG,CAAI,IAAY,EAAE,IAAc;QACjC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAI,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IAClD,CAAC;IACD,KAAK,CAAI,IAAY,EAAE,IAAc;QACnC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,CAAI,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACpD,CAAC;IACD,MAAM,CAAI,IAAY;QACpB,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAI,IAAI,CAAC,CAAC,CAAC;IAC/C,CAAC;IACD,OAAO,CAAI,IAAY,EAAE,KAA0C;QACjE,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAI,IAAI,EAAE,KAAK,CAAC,CAAC,CAAC;IACvD,CAAC;IACD,qFAAqF;IACrF,QAAQ,CAAC,GAAW,EAAE,OAA+B,EAAE,KAAiB;QACtE,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,GAAG,EAAE,OAAO,EAAE,KAAK,CAAC,CAAC;IAClD,CAAC;CACF;AA7BD,wCA6BC;AAED,sFAAsF;AACtF,SAAgB,eAAe,CAAC,UAAkB,IAAA,mBAAU,GAAE;IAC5D,OAAO,IAAI,gCAAkB,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,SAAS,CAAC,CAAC;AAChE,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"token-store.js","sourceRoot":"","sources":["../../src/auth/token-store.ts"],"names":[],"mappings":";;;;;;AA2MA,0CAMC;AAjND,2DAA8C;AAC9C,gEAAkC;AAClC,0DAA6B;AAC7B,sCAAgD;AA6ChD,SAAS,YAAY,CAAC,GAAW,EAAE,IAAc,EAAE,KAAc;IAC/D,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,KAAK,GAAG,IAAA,6BAAQ,EAAC,GAAG,EAAE,IAAI,EAAE,EAAE,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,GAAG,IAAI,EAAE,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE;YAC9F,IAAI,GAAG;gBAAE,MAAM,CAAC,GAAG,CAAC,CAAC;;gBAChB,OAAO,CAAC,MAAM,CAAC,CAAC;QACvB,CAAC,CAAC,CAAC;QACH,IAAI,KAAK,KAAK,SAAS;YAAE,KAAK,CAAC,KAAK,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,aAAa,CAAC,GAAW;IACtC,IAAI,CAAC;QACH,MAAM,YAAY,CAAC,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QAC5E,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED,MAAM,OAAO,GAAG,aAAa,CAAC;AAC9B,MAAM,OAAO,GAAG,SAAS,CAAC;AAE1B;;;;;;;GAOG;AACH,MAAM,gBAAgB;IACX,IAAI,GAAG,gBAAgB,CAAC;IAEjC,KAAK,CAAC,WAAW;QACf,OAAO,OAAO,CAAC,QAAQ,KAAK,QAAQ,IAAI,CAAC,MAAM,aAAa,CAAC,UAAU,CAAC,CAAC,CAAC;IAC5E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAsB;QAC/B,uEAAuE;QACvE,MAAM,YAAY,CAAC,UAAU,EAAE;YAC7B,sBAAsB;YACtB,IAAI;YACJ,OAAO;YACP,IAAI;YACJ,OAAO;YACP,IAAI;YACJ,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC;YACvB,IAAI;SACL,CAAC,CAAC;IACL,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,UAAU,EAAE,CAAC,uBAAuB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;YAC1G,OAAO,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,CAAkB,CAAC;QACjD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,UAAU,EAAE,CAAC,yBAAyB,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC;QAC5F,CAAC;QAAC,MAAM,CAAC;YACP,qEAAqE;QACvE,CAAC;IACH,CAAC;CACF;AAED;;;;;;GAMG;AACH,MAAM,oBAAoB;IACf,IAAI,GAAG,kCAAkC,CAAC;IAEnD,KAAK,CAAC,WAAW;QACf,OAAO,OAAO,CAAC,QAAQ,KAAK,OAAO,IAAI,CAAC,MAAM,aAAa,CAAC,aAAa,CAAC,CAAC,CAAC;IAC9E,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAsB;QAC/B,MAAM,YAAY,CAChB,aAAa,EACb,CAAC,OAAO,EAAE,6BAA6B,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,EAChF,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,CACxB,CAAC;IACJ,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,YAAY,CAAC,aAAa,EAAE,CAAC,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;YAClG,MAAM,OAAO,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC;YAC3B,OAAO,OAAO,CAAC,CAAC,CAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAmB,CAAC,CAAC,CAAC,IAAI,CAAC;QACjE,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,IAAI,CAAC;QACd,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,IAAI,CAAC;YACH,MAAM,YAAY,CAAC,aAAa,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC,CAAC;QACvF,CAAC;QAAC,MAAM,CAAC;YACP,kBAAkB;QACpB,CAAC;IACH,CAAC;CACF;AAED;;;;GAIG;AACH,MAAa,cAAc;IAChB,IAAI,GAAG,SAAS,IAAA,4BAAmB,GAAE,GAAG,CAAC;IAElD,KAAK,CAAC,WAAW;QACf,OAAO,IAAI,CAAC;IACd,CAAC;IAED,KAAK,CAAC,IAAI,CAAC,OAAsB;QAC/B,MAAM,IAAI,GAAG,IAAA,4BAAmB,GAAE,CAAC;QACnC,MAAM,kBAAE,CAAC,KAAK,CAAC,mBAAI,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACrE,MAAM,kBAAE,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5E,yEAAyE;QACzE,wEAAwE;QACxE,mCAAmC;QACnC,MAAM,kBAAE,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,MAAM,IAAI,GAAG,MAAM,kBAAE,CAAC,QAAQ,CAAC,IAAA,4BAAmB,GAAE,EAAE,MAAM,CAAC,CAAC;YAC9D,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAkB,CAAC;QAC3C,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAK,GAA6B,CAAC,IAAI,KAAK,QAAQ;gBAAE,OAAO,IAAI,CAAC;YAClE,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,KAAK,CAAC,KAAK;QACT,MAAM,kBAAE,CAAC,EAAE,CAAC,IAAA,4BAAmB,GAAE,EAAE,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IACtD,CAAC;CACF;AA9BD,wCA8BC;AAED;;;;;;;GAOG;AACH,SAAgB,eAAe;IAC7B,MAAM,MAAM,GAAiB,EAAE,CAAC;IAChC,IAAI,OAAO,CAAC,QAAQ,KAAK,QAAQ;QAAE,MAAM,CAAC,IAAI,CAAC,IAAI,gBAAgB,EAAE,CAAC,CAAC;IACvE,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO;QAAE,MAAM,CAAC,IAAI,CAAC,IAAI,oBAAoB,EAAE,CAAC,CAAC;IAC1E,MAAM,CAAC,IAAI,CAAC,IAAI,cAAc,EAAE,CAAC,CAAC;IAClC,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
package/dist/cli.js
CHANGED
|
@@ -7,25 +7,45 @@ const diaries_1 = require("./commands/diaries");
|
|
|
7
7
|
const write_1 = require("./commands/write");
|
|
8
8
|
const read_1 = require("./commands/read");
|
|
9
9
|
const profiles_1 = require("./commands/profiles");
|
|
10
|
+
const shared_diary_1 = require("./commands/shared-diary");
|
|
11
|
+
const publish_1 = require("./commands/publish");
|
|
12
|
+
const diary_cover_1 = require("./commands/diary-cover");
|
|
13
|
+
const whoami_1 = require("./commands/whoami");
|
|
14
|
+
const wiki_1 = require("./commands/wiki");
|
|
10
15
|
const output_1 = require("./lib/output");
|
|
11
16
|
const USAGE = `whatcha -- command-line client for what-are-you-do-ing
|
|
12
17
|
|
|
13
18
|
Usage:
|
|
14
19
|
whatcha login [--email <email>] [--password <password>] [--json]
|
|
20
|
+
whatcha login --key <personal-access-key> [--json]
|
|
15
21
|
whatcha login --token <refresh-token> [--json]
|
|
22
|
+
whatcha whoami [--json]
|
|
16
23
|
whatcha diaries [--json]
|
|
17
|
-
whatcha
|
|
24
|
+
whatcha diary-new <name> [--with <@handle>]... [--timezone <tz>] [--description "..."] [--json]
|
|
25
|
+
whatcha invite <diary> <@handle> [--json]
|
|
26
|
+
whatcha publish <diary> [--off] [--json]
|
|
27
|
+
whatcha diary-cover <diary> <file> | --clear [--json]
|
|
28
|
+
whatcha write <diary> (--text "..." | --text-file <path|->) [--title "..."] [--tag <tag>]...
|
|
29
|
+
[--image <file>]... [--cover <file>] [--time HH:MM] [--date YYYY-MM-DD] [--json]
|
|
18
30
|
whatcha read <diary> [--date YYYY-MM-DD] [--json]
|
|
19
31
|
whatcha profiles [--json]
|
|
20
32
|
whatcha switch <@handle> [--json]
|
|
21
33
|
whatcha switch --account [--json]
|
|
34
|
+
whatcha profile-add <@handle> --name <display name> [--avatar <file>] [--json]
|
|
35
|
+
whatcha profile-avatar <@handle> <file> [--json]
|
|
22
36
|
whatcha profile-rm <@handle> [--json]
|
|
37
|
+
whatcha wiki list [--search <text>] [--group <group-id>] [--limit N] [--cursor <c>] [--json]
|
|
38
|
+
whatcha wiki read <page> [--group <group-id>] [--json]
|
|
39
|
+
whatcha wiki write <page> --text "..." [--image <file>]... [--cover <file>] [--replace] [--message "..."] [--group <group-id>] [--json]
|
|
40
|
+
whatcha wiki cover <page> <file> | --clear [--group <group-id>] [--json]
|
|
23
41
|
|
|
24
42
|
Every command accepts --json for machine-readable output.
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
43
|
+
For scripts and servers, log in with a personal access key (issue one in
|
|
44
|
+
Profile -> Connected apps and keys): --key, or WHATCHA_KEY. A key is used
|
|
45
|
+
as-is, never refreshed, and stops working the moment it is revoked
|
|
46
|
+
(error code KEY_REVOKED). Credentials for login can also come from
|
|
47
|
+
WHATCHA_EMAIL / WHATCHA_PASSWORD, or WHATCHA_TOKEN for a refresh token
|
|
48
|
+
(which may also hold a key).
|
|
29
49
|
`;
|
|
30
50
|
/**
|
|
31
51
|
* The whole CLI as one testable function: argv in, exit code out, nothing
|
|
@@ -55,16 +75,35 @@ async function run(argv) {
|
|
|
55
75
|
return (await (0, login_1.runLogin)((0, login_1.parseLoginArgs)(rest))).exitCode;
|
|
56
76
|
case "diaries":
|
|
57
77
|
return (await (0, diaries_1.runDiaries)((0, diaries_1.parseDiariesArgs)(rest))).exitCode;
|
|
78
|
+
// A diary written together, and putting it on the web (#1700, #1701).
|
|
79
|
+
case "diary-new":
|
|
80
|
+
return (await (0, shared_diary_1.runDiaryNew)((0, shared_diary_1.parseDiaryNewArgs)(rest))).exitCode;
|
|
81
|
+
case "invite":
|
|
82
|
+
return (await (0, shared_diary_1.runInvite)((0, shared_diary_1.parseInviteArgs)(rest))).exitCode;
|
|
83
|
+
case "publish":
|
|
84
|
+
return (await (0, publish_1.runPublish)((0, publish_1.parsePublishArgs)(rest))).exitCode;
|
|
85
|
+
case "diary-cover":
|
|
86
|
+
return (await (0, diary_cover_1.runDiaryCover)((0, diary_cover_1.parseDiaryCoverArgs)(rest))).exitCode;
|
|
58
87
|
case "write":
|
|
59
88
|
return (await (0, write_1.runWrite)((0, write_1.parseWriteArgs)(rest))).exitCode;
|
|
60
89
|
case "read":
|
|
61
90
|
return (await (0, read_1.runRead)((0, read_1.parseReadArgs)(rest))).exitCode;
|
|
91
|
+
case "whoami":
|
|
92
|
+
return (await (0, whoami_1.runWhoami)((0, whoami_1.parseWhoamiArgs)(rest))).exitCode;
|
|
93
|
+
// Experience Wiki (#1664). Public Square's pages, or a group's with
|
|
94
|
+
// --group; images and covers from local files (#1667).
|
|
95
|
+
case "wiki":
|
|
96
|
+
return (await (0, wiki_1.runWiki)((0, wiki_1.parseWikiArgs)(rest))).exitCode;
|
|
62
97
|
// Sub-accounts (#1538). After a switch every command above acts as
|
|
63
98
|
// the profile switched into -- the saved session simply is it.
|
|
64
99
|
case "profiles":
|
|
65
100
|
return (await (0, profiles_1.runProfiles)((0, profiles_1.parseProfilesArgs)(rest))).exitCode;
|
|
66
101
|
case "switch":
|
|
67
102
|
return (await (0, profiles_1.runSwitch)((0, profiles_1.parseSwitchArgs)(rest))).exitCode;
|
|
103
|
+
case "profile-add":
|
|
104
|
+
return (await (0, profiles_1.runAdd)((0, profiles_1.parseAddArgs)(rest))).exitCode;
|
|
105
|
+
case "profile-avatar":
|
|
106
|
+
return (await (0, profiles_1.runAvatar)((0, profiles_1.parseAvatarArgs)(rest))).exitCode;
|
|
68
107
|
case "profile-rm":
|
|
69
108
|
return (await (0, profiles_1.runRemove)((0, profiles_1.parseRemoveArgs)(rest))).exitCode;
|
|
70
109
|
default:
|
package/dist/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/diaries.js
CHANGED
|
@@ -4,9 +4,11 @@ exports.parseDiariesArgs = parseDiariesArgs;
|
|
|
4
4
|
exports.runDiaries = runDiaries;
|
|
5
5
|
const node_util_1 = require("node:util");
|
|
6
6
|
const session_1 = require("../auth/session");
|
|
7
|
+
const config_1 = require("../config");
|
|
7
8
|
const errors_1 = require("../errors");
|
|
8
9
|
const diary_resolve_1 = require("../lib/diary-resolve");
|
|
9
10
|
const output_1 = require("../lib/output");
|
|
11
|
+
const publication_1 = require("../lib/publication");
|
|
10
12
|
function parseDiariesArgs(argv) {
|
|
11
13
|
let values;
|
|
12
14
|
try {
|
|
@@ -22,8 +24,16 @@ function parseDiariesArgs(argv) {
|
|
|
22
24
|
return { json: values.json };
|
|
23
25
|
}
|
|
24
26
|
async function runDiaries(opts, deps = {}) {
|
|
25
|
-
const
|
|
26
|
-
const
|
|
27
|
+
const resolved = deps.client ? null : await (0, session_1.authenticatedClient)();
|
|
28
|
+
const client = deps.client ?? resolved.client;
|
|
29
|
+
const base = deps.apiBaseUrl ?? (resolved?.session.apiBaseUrl || (0, config_1.apiBaseUrl)());
|
|
30
|
+
const listed = await (0, diary_resolve_1.listAllDiaries)(client);
|
|
31
|
+
// #1701: whether each diary is published, and where it is read.
|
|
32
|
+
const published = await (0, publication_1.publicDiaryIds)(client, listed);
|
|
33
|
+
const diaries = listed.map((d) => {
|
|
34
|
+
const isPublic = published.has(d.id);
|
|
35
|
+
return { ...d, isPublic, publicUrl: isPublic ? (0, publication_1.publicDiaryUrl)(base, d.id) : null };
|
|
36
|
+
});
|
|
27
37
|
if (opts.json) {
|
|
28
38
|
(0, output_1.printJson)({ ok: true, diaries });
|
|
29
39
|
return { exitCode: 0 };
|
|
@@ -34,7 +44,8 @@ async function runDiaries(opts, deps = {}) {
|
|
|
34
44
|
}
|
|
35
45
|
for (const d of diaries) {
|
|
36
46
|
const entries = d.entryCount === 1 ? "1 entry" : `${d.entryCount} entries`;
|
|
37
|
-
|
|
47
|
+
const pub = d.publicUrl ? ` public: ${d.publicUrl}` : "";
|
|
48
|
+
(0, output_1.printLine)(`${d.id} ${d.name} [${d.groupName}] ${entries} tz=${d.timezone}${pub}`);
|
|
38
49
|
}
|
|
39
50
|
return { exitCode: 0 };
|
|
40
51
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diaries.js","sourceRoot":"","sources":["../../src/commands/diaries.ts"],"names":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"diaries.js","sourceRoot":"","sources":["../../src/commands/diaries.ts"],"names":[],"mappings":"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"}
|