@fulldivevr/workcord-cli 1.0.0 → 1.1.1
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 +88 -30
- package/dist/auth.js +38 -4
- package/dist/cli.js +3 -1
- package/dist/client.js +21 -12
- package/dist/commands/auth.js +33 -14
- package/dist/commands/challenges.js +2 -7
- package/dist/commands/custom-agents.js +3 -8
- package/dist/commands/dm.js +2 -3
- package/dist/commands/health.js +29 -0
- package/dist/commands/messages.js +8 -21
- package/dist/commands/room-agents.js +2 -7
- package/dist/commands/rooms.js +24 -31
- package/dist/commands/users.js +4 -9
- package/dist/commands/whoami.js +24 -7
- package/dist/config.js +34 -0
- package/dist/generated/commands/activity.js +3 -6
- package/dist/generated/commands/ai.js +3 -6
- package/dist/generated/commands/ambassador.js +3 -6
- package/dist/generated/commands/apple-iap.js +4 -9
- package/dist/generated/commands/attribution.js +6 -15
- package/dist/generated/commands/auth.js +9 -24
- package/dist/generated/commands/challenges.js +13 -36
- package/dist/generated/commands/clips.js +3 -6
- package/dist/generated/commands/contact-match.js +7 -18
- package/dist/generated/commands/content-crawler.js +2 -3
- package/dist/generated/commands/custom-agents.js +22 -63
- package/dist/generated/commands/desktop-releases.js +5 -12
- package/dist/generated/commands/discover.js +9 -24
- package/dist/generated/commands/dm.js +10 -27
- package/dist/generated/commands/drafts.js +3 -6
- package/dist/generated/commands/embed.js +3 -6
- package/dist/generated/commands/explore.js +2 -3
- package/dist/generated/commands/feed.js +4 -9
- package/dist/generated/commands/feedback.js +2 -3
- package/dist/generated/commands/gamification.js +3 -6
- package/dist/generated/commands/google-play-iap.js +4 -9
- package/dist/generated/commands/internal.js +9 -24
- package/dist/generated/commands/interview.js +4 -9
- package/dist/generated/commands/invite-redirect.js +2 -3
- package/dist/generated/commands/jesuscord-admin.js +3 -6
- package/dist/generated/commands/join.js +2 -3
- package/dist/generated/commands/kits.js +7 -18
- package/dist/generated/commands/meetings.js +6 -15
- package/dist/generated/commands/message-folders.js +5 -12
- package/dist/generated/commands/messages.js +17 -48
- package/dist/generated/commands/one-emulator.js +2 -3
- package/dist/generated/commands/pdf-export.js +2 -3
- package/dist/generated/commands/pin-folders.js +5 -12
- package/dist/generated/commands/pinned-messages.js +4 -9
- package/dist/generated/commands/polls.js +6 -15
- package/dist/generated/commands/premium-feeds.js +2 -3
- package/dist/generated/commands/processors.js +2 -3
- package/dist/generated/commands/questions.js +3 -6
- package/dist/generated/commands/referrals.js +2 -3
- package/dist/generated/commands/reports.js +2 -3
- package/dist/generated/commands/room-a2-a.js +6 -15
- package/dist/generated/commands/room-agents.js +13 -36
- package/dist/generated/commands/room-invites.js +9 -24
- package/dist/generated/commands/room-owned-agent.js +3 -6
- package/dist/generated/commands/room-page.js +2 -3
- package/dist/generated/commands/rooms.js +45 -132
- package/dist/generated/commands/saved-messages.js +4 -9
- package/dist/generated/commands/saved-stories.js +7 -18
- package/dist/generated/commands/search.js +2 -3
- package/dist/generated/commands/shadow-home-agent.js +3 -6
- package/dist/generated/commands/shadow.js +5 -12
- package/dist/generated/commands/share-cards.js +4 -9
- package/dist/generated/commands/share.js +2 -3
- package/dist/generated/commands/sponsors.js +2 -3
- package/dist/generated/commands/stories.js +12 -33
- package/dist/generated/commands/story-by-id.js +2 -3
- package/dist/generated/commands/submissions.js +4 -9
- package/dist/generated/commands/subscriptions.js +8 -21
- package/dist/generated/commands/tips.js +3 -6
- package/dist/generated/commands/users.js +16 -45
- package/dist/generated/commands/voice-token.js +2 -3
- package/dist/generated/commands/well-known.js +3 -6
- package/dist/generated/commands/wrapped.js +2 -3
- package/dist/generated/index.js +0 -2
- package/package.json +3 -2
- package/dist/generated/commands/health.js +0 -32
package/README.md
CHANGED
|
@@ -20,13 +20,15 @@ npx @fulldivevr/workcord-cli --help
|
|
|
20
20
|
|
|
21
21
|
| Setting | How |
|
|
22
22
|
| --- | --- |
|
|
23
|
-
| Base URL | `WORKCORD_API_URL
|
|
24
|
-
|
|
|
23
|
+
| Base URL | WorkCord production by default; override with saved configuration, `WORKCORD_API_URL`, or `--base-url <url>` |
|
|
24
|
+
| User token | Saved by `auth login --save`, or supplied by `WORKCORD_TOKEN` |
|
|
25
|
+
| Signing secret | `WORKCORD_JWT_SECRET` env var **only** — never a flag; privileged, enables `--as` impersonation |
|
|
25
26
|
| Internal admin key | `WORKCORD_INTERNAL_API_KEY`, internal commands only |
|
|
26
|
-
| Acting user | `--as <userId>`, per invocation |
|
|
27
|
+
| Acting user | `--as <userId>`, per invocation — only with `WORKCORD_JWT_SECRET` |
|
|
27
28
|
|
|
28
29
|
Base URL must be a bare origin (scheme + host, no path). The `/api/v1` prefix
|
|
29
|
-
is added by the CLI.
|
|
30
|
+
is added by the CLI. Resolution order is `--base-url`, `WORKCORD_API_URL`,
|
|
31
|
+
saved configuration, then the WorkCord production default.
|
|
30
32
|
|
|
31
33
|
| Deployment | `WORKCORD_API_URL` |
|
|
32
34
|
| --- | --- |
|
|
@@ -39,38 +41,66 @@ is added by the CLI.
|
|
|
39
41
|
| Local RoomCord / JesusCord | `http://localhost:3000` |
|
|
40
42
|
|
|
41
43
|
```bash
|
|
42
|
-
|
|
43
|
-
export WORKCORD_JWT_SECRET=<the server's JWT_SECRET>
|
|
44
|
+
workcord auth login --email you@company.com --save
|
|
44
45
|
|
|
45
|
-
workcord
|
|
46
|
+
workcord rooms list
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Use another server for one command:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
workcord --base-url https://via-rooms-api.wizeup.app rooms list
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
Save another server and login for future commands:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
workcord --base-url https://via-rooms-api.wizeup.app auth login --email you@via.com --save
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Reset the saved server to WorkCord by logging in and saving again:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
workcord --base-url https://work-rooms-api.wizeup.app auth login --email you@company.com --save
|
|
46
65
|
```
|
|
47
66
|
|
|
48
67
|
Output is JSON on stdout, errors on stderr, non-zero exit on failure.
|
|
49
68
|
|
|
50
69
|
### How auth works
|
|
51
70
|
|
|
52
|
-
|
|
71
|
+
Every request carries a bearer token. There are two ways to get one, and one
|
|
72
|
+
extra header for internal admin routes.
|
|
53
73
|
|
|
54
|
-
**1.
|
|
55
|
-
|
|
74
|
+
**1. A real user token (normal path).** Log in once with `--save`, and every
|
|
75
|
+
command runs as that user with exactly that user's permissions:
|
|
56
76
|
|
|
57
77
|
```bash
|
|
58
|
-
workcord auth login --email you@company.com
|
|
59
|
-
# → code arrives by email, CLI prompts for it, prints
|
|
78
|
+
workcord auth login --email you@company.com --save
|
|
79
|
+
# → code arrives by email, CLI prompts for it, prints
|
|
80
|
+
# { token, accessToken, refreshToken, user, isNewUser } on stdout
|
|
81
|
+
# and confirms the login was saved locally
|
|
82
|
+
|
|
83
|
+
workcord rooms list
|
|
60
84
|
```
|
|
61
85
|
|
|
62
|
-
|
|
86
|
+
`WORKCORD_TOKEN` is sent as-is in `Authorization: Bearer`; the CLI never signs
|
|
87
|
+
anything in this mode and `--as` is not needed. This is the least-privilege
|
|
88
|
+
setup and the right one for agents.
|
|
89
|
+
|
|
90
|
+
Non-interactive (no TTY) `auth login --email` prints a `sessionId` and you
|
|
91
|
+
finish with:
|
|
63
92
|
|
|
64
93
|
```bash
|
|
65
94
|
workcord auth verify-email --session-id <id> --code 123456
|
|
66
95
|
```
|
|
67
96
|
|
|
68
97
|
Email login returns `accessToken`; phone/social login returns `token`. The CLI
|
|
69
|
-
emits both fields on either path so callers can read one consistently
|
|
98
|
+
emits both fields on either path so callers can read one consistently — either
|
|
99
|
+
value works as `WORKCORD_TOKEN`.
|
|
70
100
|
|
|
71
101
|
Email auth is domain-restricted. WorkCord prod and VIA both allow
|
|
72
102
|
`fulldive.com`, `via.com`, `via.com.tw`, `viatech.com.cn`, `*.tvbs.com`,
|
|
73
|
-
`tvbs.*.com`, `tvbs.com
|
|
103
|
+
`tvbs.*.com`, `tvbs.com.*`, and `*.tvbs.com.tw` (`EMAIL_AUTH_ALLOWED_DOMAINS` in
|
|
74
104
|
`.sc/stacks/wize-rooms-api/client.yaml`). Anything else gets
|
|
75
105
|
`DOMAIN_NOT_ALLOWED`.
|
|
76
106
|
|
|
@@ -78,16 +108,37 @@ Email auth is domain-restricted. WorkCord prod and VIA both allow
|
|
|
78
108
|
set `AUTH_PHONE_OTP_DEV_BYPASS`, so `POST /auth/send-otp` returns 503
|
|
79
109
|
`OTP_NOT_AVAILABLE` — the CLI turns that into an explicit "use `--email`" error.
|
|
80
110
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
111
|
+
Tokens expire. On `401`, log in again with `--save`, or replace `WORKCORD_TOKEN`.
|
|
112
|
+
|
|
113
|
+
**2. Local minting / impersonation (privileged, trusted CI only).** With
|
|
114
|
+
`WORKCORD_JWT_SECRET` set, the global `--as <userId>` flag mints an HS256 JWT
|
|
115
|
+
locally (payload `{userId, id}`, 60-day expiry) instead of logging in.
|
|
116
|
+
`WORKCORD_JWT_SECRET` must equal the **server's `JWT_SECRET`** env var — the
|
|
117
|
+
names deliberately differ, and this is the most common setup mistake.
|
|
118
|
+
|
|
119
|
+
```bash
|
|
120
|
+
export WORKCORD_JWT_SECRET=<the server's JWT_SECRET>
|
|
121
|
+
workcord --as <userId> users get-me
|
|
122
|
+
```
|
|
86
123
|
|
|
87
124
|
Same secret on both sides means the CLI can act as **any** user, with no
|
|
88
125
|
password and no server-side check. Treat it as privileged signing material:
|
|
89
126
|
keep it out of source control, package metadata, logs, screenshots and public
|
|
90
|
-
examples, and run this CLI only from trusted machines and CI jobs.
|
|
127
|
+
examples, and run this CLI only from trusted machines and CI jobs. Prefer
|
|
128
|
+
`WORKCORD_TOKEN` unless you actually need to switch identities.
|
|
129
|
+
|
|
130
|
+
Resolution order, in one place (`resolveAuth` in `src/auth.ts`):
|
|
131
|
+
|
|
132
|
+
| Situation | Result |
|
|
133
|
+
| --- | --- |
|
|
134
|
+
| `--as <userId>` passed, `WORKCORD_JWT_SECRET` set | mint and impersonate that user |
|
|
135
|
+
| `--as <userId>` passed, no secret | error — set the secret or drop `--as` |
|
|
136
|
+
| no `--as`, `WORKCORD_TOKEN` set | send that token verbatim |
|
|
137
|
+
| no `--as`, only `WORKCORD_JWT_SECRET` set | error — `--as <userId>` required |
|
|
138
|
+
| neither set | error naming both options |
|
|
139
|
+
|
|
140
|
+
`--as` wins over `WORKCORD_TOKEN` when both are available: passing it is an
|
|
141
|
+
explicit statement of impersonation intent.
|
|
91
142
|
|
|
92
143
|
**3. Internal admin routes.** `WORKCORD_INTERNAL_API_KEY` is sent as the
|
|
93
144
|
`x-internal-key` header; the server compares it to its own `INTERNAL_API_KEY`.
|
|
@@ -99,6 +150,9 @@ Required for `/internal/*` and the jesuscord admin routes. Treat it like the sig
|
|
|
99
150
|
workcord [--as <userId>] <namespace> <command> [flags]
|
|
100
151
|
```
|
|
101
152
|
|
|
153
|
+
`--as` is only needed when minting with `WORKCORD_JWT_SECRET`; with
|
|
154
|
+
`WORKCORD_TOKEN` set, omit it everywhere.
|
|
155
|
+
|
|
102
156
|
- `workcord --help` lists namespaces.
|
|
103
157
|
- `workcord <namespace> --help` lists that namespace's commands.
|
|
104
158
|
- `workcord <namespace> <command> --help` shows the command's flags.
|
|
@@ -133,8 +187,8 @@ profile fields.
|
|
|
133
187
|
|
|
134
188
|
Server auto-extracts mention tokens from visible `@Name` text. Prefer
|
|
135
189
|
`messages send-text --mention-user USER_ID` so the CLI resolves the room
|
|
136
|
-
member's display name and avoids exposing raw user ids. `auth login`
|
|
137
|
-
|
|
190
|
+
member's display name and avoids exposing raw user ids. `auth login` never
|
|
191
|
+
takes `--as` — it is how you obtain a `WORKCORD_TOKEN` in the first place.
|
|
138
192
|
|
|
139
193
|
### Error handling
|
|
140
194
|
|
|
@@ -196,8 +250,9 @@ from each room, merges them by `sentAt` descending, and includes
|
|
|
196
250
|
|
|
197
251
|
**Switch user identity per call:**
|
|
198
252
|
|
|
199
|
-
|
|
200
|
-
|
|
253
|
+
Saved login is for one interactive user. Agents can use `WORKCORD_TOKEN` to
|
|
254
|
+
choose the token owner per invocation; with `WORKCORD_JWT_SECRET`, one process
|
|
255
|
+
can act as 100 users by changing `--as <userId>` per invocation.
|
|
201
256
|
|
|
202
257
|
**Onboard a fresh agent identity:**
|
|
203
258
|
|
|
@@ -255,7 +310,7 @@ without running `regen` if you don't need the latest server changes.
|
|
|
255
310
|
```
|
|
256
311
|
src/
|
|
257
312
|
├── cli.ts commander entry, global --as flag
|
|
258
|
-
├── auth.ts
|
|
313
|
+
├── auth.ts auth resolution: WORKCORD_TOKEN vs minted --as JWT
|
|
259
314
|
├── client.ts fetch wrapper (JSON + multipart)
|
|
260
315
|
├── commands/ hand-written semantic commands
|
|
261
316
|
└── generated/ auto-generated from openapi.json
|
|
@@ -266,7 +321,9 @@ scripts/
|
|
|
266
321
|
├── sync-spec.ts backend build → openapi.json
|
|
267
322
|
└── generate-commands.ts spec → commander commands
|
|
268
323
|
|
|
269
|
-
tests/
|
|
324
|
+
tests/
|
|
325
|
+
├── auth.test.ts node --test suite for auth resolution
|
|
326
|
+
└── smoke.sh end-to-end test, needs a live server
|
|
270
327
|
```
|
|
271
328
|
|
|
272
329
|
### Adding a hand-written semantic command
|
|
@@ -283,7 +340,8 @@ flags (e.g. typed fields instead of an opaque `--body`):
|
|
|
283
340
|
### Testing
|
|
284
341
|
|
|
285
342
|
```bash
|
|
286
|
-
npm
|
|
343
|
+
npm test # hermetic, no network — auth resolution rules
|
|
344
|
+
npm run smoke # end-to-end, needs a live server
|
|
287
345
|
```
|
|
288
346
|
|
|
289
347
|
Hermetic end-to-end suite (creates two fresh users per run via OTP, exercises
|
|
@@ -297,14 +355,14 @@ Publishing is a manual maintainer action — there is no auto-publish CI.
|
|
|
297
355
|
|
|
298
356
|
1. `npm run regen` — refresh the spec and generated commands; commit the diff.
|
|
299
357
|
2. `npm run typecheck`
|
|
300
|
-
3. `npm run build`
|
|
358
|
+
3. `npm test` then `npm run build`
|
|
301
359
|
4. `npm pack --dry-run` — confirm the file list is exactly `dist/**/*.js`,
|
|
302
360
|
`README.md`, `LICENSE`, `package.json`. No sources, no `openapi.json`, no
|
|
303
361
|
`.npmrc`.
|
|
304
362
|
5. Smoke: `node dist/cli.js --help` and `node dist/cli.js --version`.
|
|
305
363
|
6. Bump `version` in `package.json`, then `npm publish`.
|
|
306
364
|
|
|
307
|
-
`prepack` re-runs `typecheck` + `build`, so steps 2–3 also gate the publish.
|
|
365
|
+
`prepack` re-runs `typecheck` + `test` + `build`, so steps 2–3 also gate the publish.
|
|
308
366
|
|
|
309
367
|
## License
|
|
310
368
|
|
package/dist/auth.js
CHANGED
|
@@ -1,9 +1,43 @@
|
|
|
1
1
|
import jwt from "jsonwebtoken";
|
|
2
|
-
export function
|
|
3
|
-
|
|
4
|
-
|
|
2
|
+
export function actingUserId(program) {
|
|
3
|
+
return program.opts().as;
|
|
4
|
+
}
|
|
5
|
+
export function mintToken(userId, secret) {
|
|
6
|
+
const key = secret ?? process.env.WORKCORD_JWT_SECRET;
|
|
7
|
+
if (!key) {
|
|
5
8
|
throw new Error("WORKCORD_JWT_SECRET not set");
|
|
6
9
|
}
|
|
7
|
-
return jwt.sign({ userId, id: userId },
|
|
10
|
+
return jwt.sign({ userId, id: userId }, key, { algorithm: "HS256", expiresIn: "60d" });
|
|
11
|
+
}
|
|
12
|
+
export function resolveAuth(asUserId, env = process.env, config = {}) {
|
|
13
|
+
const secret = env.WORKCORD_JWT_SECRET?.trim() || undefined;
|
|
14
|
+
const token = env.WORKCORD_TOKEN?.trim() || config.token?.trim() || undefined;
|
|
15
|
+
if (asUserId) {
|
|
16
|
+
if (!secret) {
|
|
17
|
+
throw new Error("--as <userId> mints a token and needs WORKCORD_JWT_SECRET. Set it, or drop --as and set WORKCORD_TOKEN to a token from `workcord auth login`.");
|
|
18
|
+
}
|
|
19
|
+
const minted = mintToken(asUserId, secret);
|
|
20
|
+
return { mode: "minted", bearer: `Bearer ${minted}`, token: minted, userId: asUserId };
|
|
21
|
+
}
|
|
22
|
+
if (token) {
|
|
23
|
+
return { mode: "token", bearer: `Bearer ${token}`, token };
|
|
24
|
+
}
|
|
25
|
+
if (secret) {
|
|
26
|
+
throw new Error("--as <userId> is required when authenticating with WORKCORD_JWT_SECRET");
|
|
27
|
+
}
|
|
28
|
+
throw new Error("No credentials. Run `workcord auth login --email <address> --save`, or set WORKCORD_TOKEN. For privileged impersonation, set WORKCORD_JWT_SECRET and pass --as <userId>.");
|
|
29
|
+
}
|
|
30
|
+
export function decodeJwtPayload(token) {
|
|
31
|
+
const parts = token.split(".");
|
|
32
|
+
if (parts.length !== 3)
|
|
33
|
+
return null;
|
|
34
|
+
try {
|
|
35
|
+
const json = Buffer.from(parts[1], "base64url").toString("utf8");
|
|
36
|
+
const parsed = JSON.parse(json);
|
|
37
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
8
42
|
}
|
|
9
43
|
//# sourceMappingURL=auth.js.map
|
package/dist/cli.js
CHANGED
|
@@ -11,6 +11,7 @@ import { registerUsers } from "./commands/users.js";
|
|
|
11
11
|
import { registerRoomAgents } from "./commands/room-agents.js";
|
|
12
12
|
import { registerCustomAgents } from "./commands/custom-agents.js";
|
|
13
13
|
import { registerChallenges } from "./commands/challenges.js";
|
|
14
|
+
import { registerHealth } from "./commands/health.js";
|
|
14
15
|
import { registerAll as registerGenerated } from "./generated/index.js";
|
|
15
16
|
const program = new Command();
|
|
16
17
|
const require = createRequire(import.meta.url);
|
|
@@ -20,7 +21,7 @@ program
|
|
|
20
21
|
.description("CLI for the WorkCord API. One invocation = one user action.")
|
|
21
22
|
.version(version)
|
|
22
23
|
.option("--as <userId>", "user to act as")
|
|
23
|
-
.option("--base-url <url>", "API base URL (overrides WORKCORD_API_URL)");
|
|
24
|
+
.option("--base-url <url>", "API base URL (overrides WORKCORD_API_URL and saved configuration)");
|
|
24
25
|
program.hook("preAction", () => {
|
|
25
26
|
setBaseUrlOverride(program.opts().baseUrl);
|
|
26
27
|
});
|
|
@@ -33,6 +34,7 @@ registerUsers(program);
|
|
|
33
34
|
registerRoomAgents(program);
|
|
34
35
|
registerCustomAgents(program);
|
|
35
36
|
registerChallenges(program); // adds regenerate-cards (not in the OpenAPI spec)
|
|
37
|
+
registerHealth(program);
|
|
36
38
|
registerGenerated(program);
|
|
37
39
|
program.parseAsync().catch((err) => {
|
|
38
40
|
const out = { error: err.message ?? String(err) };
|
package/dist/client.js
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveAuth } from "./auth.js";
|
|
2
|
+
import { DEFAULT_BASE_URL, readConfig } from "./config.js";
|
|
2
3
|
let baseUrlOverride;
|
|
3
4
|
export function setBaseUrlOverride(url) {
|
|
4
5
|
baseUrlOverride = url;
|
|
5
6
|
}
|
|
6
|
-
export function getBaseUrl() {
|
|
7
|
-
const
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
export async function getBaseUrl() {
|
|
8
|
+
const config = await readConfig();
|
|
9
|
+
return normalizeBaseUrl(baseUrlOverride || process.env.WORKCORD_API_URL || config.baseUrl || DEFAULT_BASE_URL);
|
|
10
|
+
}
|
|
11
|
+
function normalizeBaseUrl(raw) {
|
|
11
12
|
const trimmed = raw.replace(/\/+$/, "");
|
|
12
13
|
let parsed;
|
|
13
14
|
try {
|
|
@@ -52,7 +53,15 @@ async function fetchWithRetry(build) {
|
|
|
52
53
|
throw lastErr;
|
|
53
54
|
}
|
|
54
55
|
export async function call({ method = "GET", path, query, body, headers, asUserId }) {
|
|
55
|
-
const
|
|
56
|
+
const config = await readConfig();
|
|
57
|
+
const { bearer } = resolveAuth(asUserId, process.env, config);
|
|
58
|
+
return request({ method, path, query, body, headers, bearer });
|
|
59
|
+
}
|
|
60
|
+
export async function callPublic({ method = "GET", path, query, body, headers }) {
|
|
61
|
+
return request({ method, path, query, body, headers });
|
|
62
|
+
}
|
|
63
|
+
async function request({ method = "GET", path, query, body, headers, bearer }) {
|
|
64
|
+
const base = await getBaseUrl();
|
|
56
65
|
const url = new URL(base + path);
|
|
57
66
|
if (query) {
|
|
58
67
|
for (const [k, v] of Object.entries(query)) {
|
|
@@ -60,14 +69,13 @@ export async function call({ method = "GET", path, query, body, headers, asUserI
|
|
|
60
69
|
url.searchParams.set(k, String(v));
|
|
61
70
|
}
|
|
62
71
|
}
|
|
63
|
-
const token = mintToken(asUserId);
|
|
64
72
|
const res = await fetchWithRetry(() => ({
|
|
65
73
|
url,
|
|
66
74
|
init: {
|
|
67
75
|
method,
|
|
68
76
|
headers: {
|
|
69
77
|
"content-type": "application/json",
|
|
70
|
-
authorization:
|
|
78
|
+
...(bearer ? { authorization: bearer } : {}),
|
|
71
79
|
...headers,
|
|
72
80
|
},
|
|
73
81
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
@@ -92,9 +100,10 @@ function safeJson(s) {
|
|
|
92
100
|
}
|
|
93
101
|
}
|
|
94
102
|
export async function callMultipart({ path, asUserId, fields, file }) {
|
|
95
|
-
const
|
|
103
|
+
const config = await readConfig();
|
|
104
|
+
const { bearer } = resolveAuth(asUserId, process.env, config);
|
|
105
|
+
const base = await getBaseUrl();
|
|
96
106
|
const url = base + path;
|
|
97
|
-
const token = mintToken(asUserId);
|
|
98
107
|
const res = await fetchWithRetry(() => {
|
|
99
108
|
const fd = new FormData();
|
|
100
109
|
if (fields) {
|
|
@@ -107,7 +116,7 @@ export async function callMultipart({ path, asUserId, fields, file }) {
|
|
|
107
116
|
url,
|
|
108
117
|
init: {
|
|
109
118
|
method: "POST",
|
|
110
|
-
headers: { authorization:
|
|
119
|
+
headers: { authorization: bearer },
|
|
111
120
|
body: fd,
|
|
112
121
|
},
|
|
113
122
|
};
|
package/dist/commands/auth.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { randomUUID } from "node:crypto";
|
|
2
2
|
import { createInterface } from "node:readline/promises";
|
|
3
3
|
import { getBaseUrl } from "../client.js";
|
|
4
|
+
import { getConfigPath, saveConfig } from "../config.js";
|
|
4
5
|
import { getOrCreateGroup } from "./_group.js";
|
|
5
6
|
async function postJson(path, body) {
|
|
6
|
-
const res = await fetch(getBaseUrl() + path, {
|
|
7
|
+
const res = await fetch((await getBaseUrl()) + path, {
|
|
7
8
|
method: "POST",
|
|
8
9
|
headers: { "content-type": "application/json" },
|
|
9
10
|
body: JSON.stringify(body),
|
|
@@ -37,8 +38,22 @@ function normalize(data) {
|
|
|
37
38
|
r.accessToken = r.token;
|
|
38
39
|
return r;
|
|
39
40
|
}
|
|
40
|
-
function emit(data) {
|
|
41
|
-
|
|
41
|
+
async function emit(data, save = false) {
|
|
42
|
+
const result = normalize(data);
|
|
43
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
44
|
+
if (save && result.token) {
|
|
45
|
+
await saveConfig({
|
|
46
|
+
baseUrl: await getBaseUrl(),
|
|
47
|
+
token: result.token,
|
|
48
|
+
});
|
|
49
|
+
if (process.stdout.isTTY) {
|
|
50
|
+
process.stderr.write(`Logged in and saved to ${getConfigPath()}. Next: workcord rooms list\n`);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
if (result.token) {
|
|
55
|
+
process.stderr.write("Logged in. Re-run with --save to remember this login on this computer.\n");
|
|
56
|
+
}
|
|
42
57
|
}
|
|
43
58
|
async function promptCode() {
|
|
44
59
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -55,7 +70,7 @@ async function verifyEmailCode(sessionId, code) {
|
|
|
55
70
|
}
|
|
56
71
|
return postJson("/api/v1/auth/email/verify-code", { sessionId, code });
|
|
57
72
|
}
|
|
58
|
-
async function loginByEmail(email, code) {
|
|
73
|
+
async function loginByEmail(email, code, save) {
|
|
59
74
|
const sessionId = randomUUID();
|
|
60
75
|
try {
|
|
61
76
|
await postJson(`/api/v1/auth/email/login/by-code/${encodeURIComponent(sessionId)}`, { email });
|
|
@@ -63,8 +78,9 @@ async function loginByEmail(email, code) {
|
|
|
63
78
|
catch (err) {
|
|
64
79
|
const c = errorCode(err);
|
|
65
80
|
if (c === "DOMAIN_NOT_ALLOWED" || c === "EMAIL_NOT_ALLOWED") {
|
|
66
|
-
|
|
67
|
-
|
|
81
|
+
const allowed = err.body?.error?.allowedDomains;
|
|
82
|
+
const domains = Array.isArray(allowed) ? allowed.filter((value) => typeof value === "string") : [];
|
|
83
|
+
throw new Error(`Email ${email} cannot sign in on this deployment.${domains.length ? ` Allowed domains: ${domains.join(", ")}.` : ""} Try an allowed work address or ask your WorkCord administrator to enable this domain.`);
|
|
68
84
|
}
|
|
69
85
|
throw err;
|
|
70
86
|
}
|
|
@@ -75,9 +91,9 @@ async function loginByEmail(email, code) {
|
|
|
75
91
|
process.stdout.write(JSON.stringify({ sessionId, codeSent: true }) + "\n");
|
|
76
92
|
return;
|
|
77
93
|
}
|
|
78
|
-
emit(await verifyEmailCode(sessionId, entered));
|
|
94
|
+
await emit(await verifyEmailCode(sessionId, entered), save);
|
|
79
95
|
}
|
|
80
|
-
async function loginByPhone(phone, code) {
|
|
96
|
+
async function loginByPhone(phone, code, save) {
|
|
81
97
|
try {
|
|
82
98
|
await postJson("/api/v1/auth/send-otp", { phone });
|
|
83
99
|
}
|
|
@@ -88,18 +104,20 @@ async function loginByPhone(phone, code) {
|
|
|
88
104
|
}
|
|
89
105
|
throw err;
|
|
90
106
|
}
|
|
91
|
-
emit(await postJson("/api/v1/auth/verify-otp", { phone, code }));
|
|
107
|
+
await emit(await postJson("/api/v1/auth/verify-otp", { phone, code }), save);
|
|
92
108
|
}
|
|
93
109
|
export function registerAuth(program) {
|
|
94
110
|
const group = getOrCreateGroup(program, "auth", "Auth operations");
|
|
95
111
|
group
|
|
96
112
|
.command("login")
|
|
97
|
-
.description("End-to-end login. Returns { token, accessToken, refreshToken, user, isNewUser }. " +
|
|
113
|
+
.description("End-to-end login. Use --save to remember this login on this computer. Returns { token, accessToken, refreshToken, user, isNewUser }. " +
|
|
98
114
|
"--email uses the emailed 6-digit code (deployed WorkCord/VIA); --phone uses OTP " +
|
|
99
|
-
"(local/RoomCord only). Does NOT use --as / WORKCORD_JWT_SECRET — the server mints the token."
|
|
115
|
+
"(local/RoomCord only). Does NOT use --as / WORKCORD_JWT_SECRET — the server mints the token. " +
|
|
116
|
+
"Without --save, set the returned token as WORKCORD_TOKEN to authenticate later commands.")
|
|
100
117
|
.option("--email <address>", "email address (mutually exclusive with --phone)")
|
|
101
118
|
.option("--phone <e164>", 'phone number in E.164 format (e.g. "+14155550199")')
|
|
102
119
|
.option("--code <code>", "verification code; prompted interactively for --email when omitted")
|
|
120
|
+
.option("--save", "remember this login on this computer")
|
|
103
121
|
.action(async (opts) => {
|
|
104
122
|
if (opts.email && opts.phone) {
|
|
105
123
|
throw new Error("--email and --phone are mutually exclusive");
|
|
@@ -108,18 +126,19 @@ export function registerAuth(program) {
|
|
|
108
126
|
throw new Error("one of --email <address> or --phone <e164> is required");
|
|
109
127
|
}
|
|
110
128
|
if (opts.email) {
|
|
111
|
-
await loginByEmail(opts.email, opts.code);
|
|
129
|
+
await loginByEmail(opts.email, opts.code, !!opts.save);
|
|
112
130
|
return;
|
|
113
131
|
}
|
|
114
|
-
await loginByPhone(opts.phone, opts.code ?? "111111");
|
|
132
|
+
await loginByPhone(opts.phone, opts.code ?? "111111", !!opts.save);
|
|
115
133
|
});
|
|
116
134
|
group
|
|
117
135
|
.command("verify-email")
|
|
118
136
|
.description("Verify an emailed code for a session started by `auth login --email`")
|
|
119
137
|
.requiredOption("--session-id <id>", "sessionId printed by `auth login --email`")
|
|
120
138
|
.requiredOption("--code <code>", "6-digit code from the email")
|
|
139
|
+
.option("--save", "remember this login on this computer")
|
|
121
140
|
.action(async (opts) => {
|
|
122
|
-
emit(await verifyEmailCode(opts.sessionId, opts.code));
|
|
141
|
+
await emit(await verifyEmailCode(opts.sessionId, opts.code), !!opts.save);
|
|
123
142
|
});
|
|
124
143
|
}
|
|
125
144
|
//# sourceMappingURL=auth.js.map
|
|
@@ -1,11 +1,6 @@
|
|
|
1
|
+
import { actingUserId } from "../auth.js";
|
|
1
2
|
import { call } from "../client.js";
|
|
2
3
|
import { getOrCreateGroup } from "./_group.js";
|
|
3
|
-
function currentUser(program) {
|
|
4
|
-
const { as } = program.opts();
|
|
5
|
-
if (!as)
|
|
6
|
-
throw new Error("--as <userId> is required");
|
|
7
|
-
return as;
|
|
8
|
-
}
|
|
9
4
|
/**
|
|
10
5
|
* Custom challenge commands that are NOT covered by the OpenAPI-generated
|
|
11
6
|
* commands (src/generated/commands/challenges.ts). Everything else — create,
|
|
@@ -34,7 +29,7 @@ export function registerChallenges(program) {
|
|
|
34
29
|
const data = (await call({
|
|
35
30
|
method: "POST",
|
|
36
31
|
path: `/api/v1/challenges/${encodeURIComponent(opts.id)}/regenerate-cards?offset=${offset}&limit=10`,
|
|
37
|
-
asUserId:
|
|
32
|
+
asUserId: actingUserId(program),
|
|
38
33
|
body: {},
|
|
39
34
|
}));
|
|
40
35
|
totals.total = data.total;
|
|
@@ -1,12 +1,7 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { actingUserId } from "../auth.js";
|
|
2
3
|
import { call } from "../client.js";
|
|
3
4
|
import { getOrCreateGroup } from "./_group.js";
|
|
4
|
-
function currentUser(program) {
|
|
5
|
-
const { as } = program.opts();
|
|
6
|
-
if (!as)
|
|
7
|
-
throw new Error("--as <userId> is required");
|
|
8
|
-
return as;
|
|
9
|
-
}
|
|
10
5
|
function bool(v, name) {
|
|
11
6
|
if (v === undefined)
|
|
12
7
|
return undefined;
|
|
@@ -54,7 +49,7 @@ export function registerCustomAgents(program) {
|
|
|
54
49
|
const data = await call({
|
|
55
50
|
method: "POST",
|
|
56
51
|
path: "/api/v1/agents",
|
|
57
|
-
asUserId:
|
|
52
|
+
asUserId: actingUserId(program),
|
|
58
53
|
body: {
|
|
59
54
|
name: opts.name,
|
|
60
55
|
description: opts.description,
|
|
@@ -111,7 +106,7 @@ export function registerCustomAgents(program) {
|
|
|
111
106
|
const data = await call({
|
|
112
107
|
method: "PATCH",
|
|
113
108
|
path: `/api/v1/agents/${encodeURIComponent(opts.agent)}`,
|
|
114
|
-
asUserId:
|
|
109
|
+
asUserId: actingUserId(program),
|
|
115
110
|
body,
|
|
116
111
|
});
|
|
117
112
|
process.stdout.write(JSON.stringify(data) + "\n");
|
package/dist/commands/dm.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { actingUserId } from "../auth.js";
|
|
1
2
|
import { call } from "../client.js";
|
|
2
3
|
import { getOrCreateGroup } from "./_group.js";
|
|
3
4
|
export function registerDm(program) {
|
|
@@ -10,9 +11,7 @@ export function registerDm(program) {
|
|
|
10
11
|
.requiredOption("--text <text>", "message body — mention users with @userId inline")
|
|
11
12
|
.option("--reply-to <messageId>", "parent message id (for threaded replies)")
|
|
12
13
|
.action(async (opts) => {
|
|
13
|
-
const
|
|
14
|
-
if (!as)
|
|
15
|
-
throw new Error("--as <userId> is required");
|
|
14
|
+
const as = actingUserId(program);
|
|
16
15
|
const dm = (await call({
|
|
17
16
|
method: "POST",
|
|
18
17
|
path: "/api/v1/dm/start",
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { call, callPublic, getBaseUrl } from "../client.js";
|
|
2
|
+
export function registerHealth(program) {
|
|
3
|
+
program
|
|
4
|
+
.command("health")
|
|
5
|
+
.allowExcessArguments(false)
|
|
6
|
+
.description("Check the selected server and saved login")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
const baseUrl = await getBaseUrl();
|
|
9
|
+
const health = await callPublic({ path: "/api/health" });
|
|
10
|
+
let loggedIn = false;
|
|
11
|
+
let userId;
|
|
12
|
+
try {
|
|
13
|
+
const user = await call({ path: "/api/v1/users/me", asUserId: program.opts().as });
|
|
14
|
+
loggedIn = true;
|
|
15
|
+
userId = user.id;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
loggedIn = false;
|
|
19
|
+
}
|
|
20
|
+
process.stdout.write(JSON.stringify({ baseUrl, loggedIn, userId, health }) + "\n");
|
|
21
|
+
});
|
|
22
|
+
program
|
|
23
|
+
.command("hello")
|
|
24
|
+
.description("Check the selected server")
|
|
25
|
+
.action(async () => {
|
|
26
|
+
process.stdout.write(JSON.stringify(await callPublic({ path: "/api/hello" })) + "\n");
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
//# sourceMappingURL=health.js.map
|