@fulldivevr/workcord-cli 1.0.0 → 1.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 +67 -25
- package/dist/auth.js +38 -4
- package/dist/client.js +5 -5
- package/dist/commands/auth.js +7 -2
- 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/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/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/health.js +3 -6
- 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/package.json +3 -2
package/README.md
CHANGED
|
@@ -21,9 +21,10 @@ npx @fulldivevr/workcord-cli --help
|
|
|
21
21
|
| Setting | How |
|
|
22
22
|
| --- | --- |
|
|
23
23
|
| Base URL | `WORKCORD_API_URL` env var or `--base-url <url>` — **required, no default** |
|
|
24
|
-
|
|
|
24
|
+
| User token | `WORKCORD_TOKEN` env var **only** — a per-user access token from `workcord auth login`; the normal way to authenticate |
|
|
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
30
|
is added by the CLI.
|
|
@@ -40,33 +41,46 @@ is added by the CLI.
|
|
|
40
41
|
|
|
41
42
|
```bash
|
|
42
43
|
export WORKCORD_API_URL=https://work-rooms-api.wizeup.app
|
|
43
|
-
export
|
|
44
|
+
export WORKCORD_TOKEN=<token from `workcord auth login`>
|
|
44
45
|
|
|
45
|
-
workcord
|
|
46
|
+
workcord users get-me
|
|
46
47
|
```
|
|
47
48
|
|
|
48
49
|
Output is JSON on stdout, errors on stderr, non-zero exit on failure.
|
|
49
50
|
|
|
50
51
|
### How auth works
|
|
51
52
|
|
|
52
|
-
|
|
53
|
+
Every request carries a bearer token. There are two ways to get one, and one
|
|
54
|
+
extra header for internal admin routes.
|
|
53
55
|
|
|
54
|
-
**1.
|
|
55
|
-
|
|
56
|
+
**1. A real user token (normal path).** Log in once, export the token, and every
|
|
57
|
+
command runs as that user with exactly that user's permissions:
|
|
56
58
|
|
|
57
59
|
```bash
|
|
58
60
|
workcord auth login --email you@company.com
|
|
59
|
-
# → code arrives by email, CLI prompts for it, prints
|
|
61
|
+
# → code arrives by email, CLI prompts for it, prints
|
|
62
|
+
# { token, accessToken, refreshToken, user, isNewUser } on stdout
|
|
63
|
+
# and an `export WORKCORD_TOKEN=...` hint on stderr
|
|
64
|
+
|
|
65
|
+
export WORKCORD_TOKEN=<token>
|
|
66
|
+
workcord users get-me
|
|
67
|
+
workcord whoami # → {"mode":"token","userId":"...","source":"WORKCORD_TOKEN"}
|
|
60
68
|
```
|
|
61
69
|
|
|
62
|
-
|
|
70
|
+
`WORKCORD_TOKEN` is sent as-is in `Authorization: Bearer`; the CLI never signs
|
|
71
|
+
anything in this mode and `--as` is not needed. This is the least-privilege
|
|
72
|
+
setup and the right one for agents.
|
|
73
|
+
|
|
74
|
+
Non-interactive (no TTY) `auth login --email` prints a `sessionId` and you
|
|
75
|
+
finish with:
|
|
63
76
|
|
|
64
77
|
```bash
|
|
65
78
|
workcord auth verify-email --session-id <id> --code 123456
|
|
66
79
|
```
|
|
67
80
|
|
|
68
81
|
Email login returns `accessToken`; phone/social login returns `token`. The CLI
|
|
69
|
-
emits both fields on either path so callers can read one consistently
|
|
82
|
+
emits both fields on either path so callers can read one consistently — either
|
|
83
|
+
value works as `WORKCORD_TOKEN`.
|
|
70
84
|
|
|
71
85
|
Email auth is domain-restricted. WorkCord prod and VIA both allow
|
|
72
86
|
`fulldive.com`, `via.com`, `via.com.tw`, `viatech.com.cn`, `*.tvbs.com`,
|
|
@@ -78,16 +92,37 @@ Email auth is domain-restricted. WorkCord prod and VIA both allow
|
|
|
78
92
|
set `AUTH_PHONE_OTP_DEV_BYPASS`, so `POST /auth/send-otp` returns 503
|
|
79
93
|
`OTP_NOT_AVAILABLE` — the CLI turns that into an explicit "use `--email`" error.
|
|
80
94
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
`
|
|
85
|
-
|
|
95
|
+
Tokens expire. On `401`, log in again and re-export `WORKCORD_TOKEN`.
|
|
96
|
+
|
|
97
|
+
**2. Local minting / impersonation (privileged, trusted CI only).** With
|
|
98
|
+
`WORKCORD_JWT_SECRET` set, the global `--as <userId>` flag mints an HS256 JWT
|
|
99
|
+
locally (payload `{userId, id}`, 60-day expiry) instead of logging in.
|
|
100
|
+
`WORKCORD_JWT_SECRET` must equal the **server's `JWT_SECRET`** env var — the
|
|
101
|
+
names deliberately differ, and this is the most common setup mistake.
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
export WORKCORD_JWT_SECRET=<the server's JWT_SECRET>
|
|
105
|
+
workcord --as <userId> users get-me
|
|
106
|
+
```
|
|
86
107
|
|
|
87
108
|
Same secret on both sides means the CLI can act as **any** user, with no
|
|
88
109
|
password and no server-side check. Treat it as privileged signing material:
|
|
89
110
|
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.
|
|
111
|
+
examples, and run this CLI only from trusted machines and CI jobs. Prefer
|
|
112
|
+
`WORKCORD_TOKEN` unless you actually need to switch identities.
|
|
113
|
+
|
|
114
|
+
Resolution order, in one place (`resolveAuth` in `src/auth.ts`):
|
|
115
|
+
|
|
116
|
+
| Situation | Result |
|
|
117
|
+
| --- | --- |
|
|
118
|
+
| `--as <userId>` passed, `WORKCORD_JWT_SECRET` set | mint and impersonate that user |
|
|
119
|
+
| `--as <userId>` passed, no secret | error — set the secret or drop `--as` |
|
|
120
|
+
| no `--as`, `WORKCORD_TOKEN` set | send that token verbatim |
|
|
121
|
+
| no `--as`, only `WORKCORD_JWT_SECRET` set | error — `--as <userId>` required |
|
|
122
|
+
| neither set | error naming both options |
|
|
123
|
+
|
|
124
|
+
`--as` wins over `WORKCORD_TOKEN` when both are available: passing it is an
|
|
125
|
+
explicit statement of impersonation intent.
|
|
91
126
|
|
|
92
127
|
**3. Internal admin routes.** `WORKCORD_INTERNAL_API_KEY` is sent as the
|
|
93
128
|
`x-internal-key` header; the server compares it to its own `INTERNAL_API_KEY`.
|
|
@@ -99,6 +134,9 @@ Required for `/internal/*` and the jesuscord admin routes. Treat it like the sig
|
|
|
99
134
|
workcord [--as <userId>] <namespace> <command> [flags]
|
|
100
135
|
```
|
|
101
136
|
|
|
137
|
+
`--as` is only needed when minting with `WORKCORD_JWT_SECRET`; with
|
|
138
|
+
`WORKCORD_TOKEN` set, omit it everywhere.
|
|
139
|
+
|
|
102
140
|
- `workcord --help` lists namespaces.
|
|
103
141
|
- `workcord <namespace> --help` lists that namespace's commands.
|
|
104
142
|
- `workcord <namespace> <command> --help` shows the command's flags.
|
|
@@ -133,8 +171,8 @@ profile fields.
|
|
|
133
171
|
|
|
134
172
|
Server auto-extracts mention tokens from visible `@Name` text. Prefer
|
|
135
173
|
`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
|
-
|
|
174
|
+
member's display name and avoids exposing raw user ids. `auth login` never
|
|
175
|
+
takes `--as` — it is how you obtain a `WORKCORD_TOKEN` in the first place.
|
|
138
176
|
|
|
139
177
|
### Error handling
|
|
140
178
|
|
|
@@ -196,8 +234,9 @@ from each room, merges them by `sentAt` descending, and includes
|
|
|
196
234
|
|
|
197
235
|
**Switch user identity per call:**
|
|
198
236
|
|
|
199
|
-
The CLI is stateless
|
|
200
|
-
|
|
237
|
+
The CLI is stateless. With `WORKCORD_TOKEN` the identity is the token's owner;
|
|
238
|
+
swap the env var to swap users. With `WORKCORD_JWT_SECRET` one process can act
|
|
239
|
+
as 100 users by changing `--as <userId>` per invocation.
|
|
201
240
|
|
|
202
241
|
**Onboard a fresh agent identity:**
|
|
203
242
|
|
|
@@ -255,7 +294,7 @@ without running `regen` if you don't need the latest server changes.
|
|
|
255
294
|
```
|
|
256
295
|
src/
|
|
257
296
|
├── cli.ts commander entry, global --as flag
|
|
258
|
-
├── auth.ts
|
|
297
|
+
├── auth.ts auth resolution: WORKCORD_TOKEN vs minted --as JWT
|
|
259
298
|
├── client.ts fetch wrapper (JSON + multipart)
|
|
260
299
|
├── commands/ hand-written semantic commands
|
|
261
300
|
└── generated/ auto-generated from openapi.json
|
|
@@ -266,7 +305,9 @@ scripts/
|
|
|
266
305
|
├── sync-spec.ts backend build → openapi.json
|
|
267
306
|
└── generate-commands.ts spec → commander commands
|
|
268
307
|
|
|
269
|
-
tests/
|
|
308
|
+
tests/
|
|
309
|
+
├── auth.test.ts node --test suite for auth resolution
|
|
310
|
+
└── smoke.sh end-to-end test, needs a live server
|
|
270
311
|
```
|
|
271
312
|
|
|
272
313
|
### Adding a hand-written semantic command
|
|
@@ -283,7 +324,8 @@ flags (e.g. typed fields instead of an opaque `--body`):
|
|
|
283
324
|
### Testing
|
|
284
325
|
|
|
285
326
|
```bash
|
|
286
|
-
npm
|
|
327
|
+
npm test # hermetic, no network — auth resolution rules
|
|
328
|
+
npm run smoke # end-to-end, needs a live server
|
|
287
329
|
```
|
|
288
330
|
|
|
289
331
|
Hermetic end-to-end suite (creates two fresh users per run via OTP, exercises
|
|
@@ -297,14 +339,14 @@ Publishing is a manual maintainer action — there is no auto-publish CI.
|
|
|
297
339
|
|
|
298
340
|
1. `npm run regen` — refresh the spec and generated commands; commit the diff.
|
|
299
341
|
2. `npm run typecheck`
|
|
300
|
-
3. `npm run build`
|
|
342
|
+
3. `npm test` then `npm run build`
|
|
301
343
|
4. `npm pack --dry-run` — confirm the file list is exactly `dist/**/*.js`,
|
|
302
344
|
`README.md`, `LICENSE`, `package.json`. No sources, no `openapi.json`, no
|
|
303
345
|
`.npmrc`.
|
|
304
346
|
5. Smoke: `node dist/cli.js --help` and `node dist/cli.js --version`.
|
|
305
347
|
6. Bump `version` in `package.json`, then `npm publish`.
|
|
306
348
|
|
|
307
|
-
`prepack` re-runs `typecheck` + `build`, so steps 2–3 also gate the publish.
|
|
349
|
+
`prepack` re-runs `typecheck` + `test` + `build`, so steps 2–3 also gate the publish.
|
|
308
350
|
|
|
309
351
|
## License
|
|
310
352
|
|
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) {
|
|
13
|
+
const secret = env.WORKCORD_JWT_SECRET?.trim() || undefined;
|
|
14
|
+
const token = env.WORKCORD_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. Set WORKCORD_TOKEN to a token from `workcord auth login`, or 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/client.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveAuth } from "./auth.js";
|
|
2
2
|
let baseUrlOverride;
|
|
3
3
|
export function setBaseUrlOverride(url) {
|
|
4
4
|
baseUrlOverride = url;
|
|
@@ -52,6 +52,7 @@ async function fetchWithRetry(build) {
|
|
|
52
52
|
throw lastErr;
|
|
53
53
|
}
|
|
54
54
|
export async function call({ method = "GET", path, query, body, headers, asUserId }) {
|
|
55
|
+
const { bearer } = resolveAuth(asUserId);
|
|
55
56
|
const base = getBaseUrl();
|
|
56
57
|
const url = new URL(base + path);
|
|
57
58
|
if (query) {
|
|
@@ -60,14 +61,13 @@ export async function call({ method = "GET", path, query, body, headers, asUserI
|
|
|
60
61
|
url.searchParams.set(k, String(v));
|
|
61
62
|
}
|
|
62
63
|
}
|
|
63
|
-
const token = mintToken(asUserId);
|
|
64
64
|
const res = await fetchWithRetry(() => ({
|
|
65
65
|
url,
|
|
66
66
|
init: {
|
|
67
67
|
method,
|
|
68
68
|
headers: {
|
|
69
69
|
"content-type": "application/json",
|
|
70
|
-
authorization:
|
|
70
|
+
authorization: bearer,
|
|
71
71
|
...headers,
|
|
72
72
|
},
|
|
73
73
|
body: body !== undefined ? JSON.stringify(body) : undefined,
|
|
@@ -92,9 +92,9 @@ function safeJson(s) {
|
|
|
92
92
|
}
|
|
93
93
|
}
|
|
94
94
|
export async function callMultipart({ path, asUserId, fields, file }) {
|
|
95
|
+
const { bearer } = resolveAuth(asUserId);
|
|
95
96
|
const base = getBaseUrl();
|
|
96
97
|
const url = base + path;
|
|
97
|
-
const token = mintToken(asUserId);
|
|
98
98
|
const res = await fetchWithRetry(() => {
|
|
99
99
|
const fd = new FormData();
|
|
100
100
|
if (fields) {
|
|
@@ -107,7 +107,7 @@ export async function callMultipart({ path, asUserId, fields, file }) {
|
|
|
107
107
|
url,
|
|
108
108
|
init: {
|
|
109
109
|
method: "POST",
|
|
110
|
-
headers: { authorization:
|
|
110
|
+
headers: { authorization: bearer },
|
|
111
111
|
body: fd,
|
|
112
112
|
},
|
|
113
113
|
};
|
package/dist/commands/auth.js
CHANGED
|
@@ -38,7 +38,11 @@ function normalize(data) {
|
|
|
38
38
|
return r;
|
|
39
39
|
}
|
|
40
40
|
function emit(data) {
|
|
41
|
-
|
|
41
|
+
const result = normalize(data);
|
|
42
|
+
process.stdout.write(JSON.stringify(result) + "\n");
|
|
43
|
+
if (result.token) {
|
|
44
|
+
process.stderr.write(`export WORKCORD_TOKEN=${result.token}\n`);
|
|
45
|
+
}
|
|
42
46
|
}
|
|
43
47
|
async function promptCode() {
|
|
44
48
|
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
|
@@ -96,7 +100,8 @@ export function registerAuth(program) {
|
|
|
96
100
|
.command("login")
|
|
97
101
|
.description("End-to-end login. Returns { token, accessToken, refreshToken, user, isNewUser }. " +
|
|
98
102
|
"--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."
|
|
103
|
+
"(local/RoomCord only). Does NOT use --as / WORKCORD_JWT_SECRET — the server mints the token. " +
|
|
104
|
+
"Export the returned token as WORKCORD_TOKEN to authenticate every other command.")
|
|
100
105
|
.option("--email <address>", "email address (mutually exclusive with --phone)")
|
|
101
106
|
.option("--phone <e164>", 'phone number in E.164 format (e.g. "+14155550199")')
|
|
102
107
|
.option("--code <code>", "verification code; prompted interactively for --email when omitted")
|
|
@@ -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",
|
|
@@ -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
|
const MAX_LIMIT = 500;
|
|
@@ -113,9 +114,7 @@ export function registerMessages(program) {
|
|
|
113
114
|
.option("--attachments <urls>", "comma-separated GCS URLs (use `workcord rooms upload-image-file` to get URLs first)")
|
|
114
115
|
.option("--no-shadow-guidance", "do not opt in to Shadow guidance cards")
|
|
115
116
|
.action(async (opts) => {
|
|
116
|
-
const
|
|
117
|
-
if (!as)
|
|
118
|
-
throw new Error("--as <userId> is required");
|
|
117
|
+
const as = actingUserId(program);
|
|
119
118
|
const mentions = await resolveMentions(opts.room, as, opts.mentionUser);
|
|
120
119
|
const content = mentions.length > 0 ? `${mentions.map((m) => `@${m.name}`).join(" ")} ${opts.text}` : opts.text;
|
|
121
120
|
const body = {
|
|
@@ -143,9 +142,7 @@ export function registerMessages(program) {
|
|
|
143
142
|
.option("--after <iso>", "only messages with sentAt > this ISO timestamp; overrides --cursor server-side")
|
|
144
143
|
.option("--filter <filter>", `one of: ${Array.from(MESSAGE_FILTERS).join(", ")}`)
|
|
145
144
|
.action(async (opts) => {
|
|
146
|
-
const
|
|
147
|
-
if (!as)
|
|
148
|
-
throw new Error("--as <userId> is required");
|
|
145
|
+
const as = actingUserId(program);
|
|
149
146
|
const data = await call({
|
|
150
147
|
method: "GET",
|
|
151
148
|
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages`,
|
|
@@ -169,9 +166,7 @@ export function registerMessages(program) {
|
|
|
169
166
|
.option("--limit <n>", `page size, 1-${MAX_PAGE_LIMIT}`, "20")
|
|
170
167
|
.option("--cursor <iso>", "pagination cursor from the previous page's cursor")
|
|
171
168
|
.action(async (opts) => {
|
|
172
|
-
const
|
|
173
|
-
if (!as)
|
|
174
|
-
throw new Error("--as <userId> is required");
|
|
169
|
+
const as = actingUserId(program);
|
|
175
170
|
const data = await call({
|
|
176
171
|
method: "GET",
|
|
177
172
|
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages/${encodeURIComponent(opts.message)}/thread`,
|
|
@@ -190,9 +185,7 @@ export function registerMessages(program) {
|
|
|
190
185
|
.requiredOption("--room <roomId>", "target room id")
|
|
191
186
|
.requiredOption("--message <messageId>", "thread root message id")
|
|
192
187
|
.action(async (opts) => {
|
|
193
|
-
const
|
|
194
|
-
if (!as)
|
|
195
|
-
throw new Error("--as <userId> is required");
|
|
188
|
+
const as = actingUserId(program);
|
|
196
189
|
const data = await call({
|
|
197
190
|
method: "POST",
|
|
198
191
|
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages/${encodeURIComponent(opts.message)}/thread/read`,
|
|
@@ -209,9 +202,7 @@ export function registerMessages(program) {
|
|
|
209
202
|
.option("--limit <n>", "max messages per page (server caps at 100)", "50")
|
|
210
203
|
.option("--max-pages <n>", "safety cap on pages walked back", "20")
|
|
211
204
|
.action(async (opts) => {
|
|
212
|
-
const
|
|
213
|
-
if (!as)
|
|
214
|
-
throw new Error("--as <userId> is required");
|
|
205
|
+
const as = actingUserId(program);
|
|
215
206
|
const sinceMs = new Date(opts.since).getTime();
|
|
216
207
|
if (Number.isNaN(sinceMs))
|
|
217
208
|
throw new Error(`--since not a valid ISO timestamp: ${opts.since}`);
|
|
@@ -259,9 +250,7 @@ export function registerMessages(program) {
|
|
|
259
250
|
.option("--inbox-category <value>", "room inbox category filter")
|
|
260
251
|
.option("--no-shadow-guidance", "do not opt in to Shadow guidance cards")
|
|
261
252
|
.action(async (opts) => {
|
|
262
|
-
const
|
|
263
|
-
if (!as)
|
|
264
|
-
throw new Error("--as <userId> is required");
|
|
253
|
+
const as = actingUserId(program);
|
|
265
254
|
const limit = parsePositiveInt(opts.limit, "--limit", MAX_LIMIT);
|
|
266
255
|
const perRoomLimit = parsePositiveInt(opts.perRoomLimit, "--per-room-limit", MAX_PAGE_LIMIT);
|
|
267
256
|
const roomPageLimit = parsePositiveInt(opts.roomPageLimit, "--room-page-limit", MAX_PAGE_LIMIT);
|
|
@@ -345,9 +334,7 @@ export function registerMessages(program) {
|
|
|
345
334
|
.requiredOption("--message <messageId>", "target message")
|
|
346
335
|
.requiredOption("--emoji <type>", 'reaction type — emoji or id (e.g. "👍" or "thumbs_up")')
|
|
347
336
|
.action(async (opts) => {
|
|
348
|
-
const
|
|
349
|
-
if (!as)
|
|
350
|
-
throw new Error("--as <userId> is required");
|
|
337
|
+
const as = actingUserId(program);
|
|
351
338
|
const data = await call({
|
|
352
339
|
method: "POST",
|
|
353
340
|
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages/${encodeURIComponent(opts.message)}/reactions`,
|
|
@@ -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
|
function bool(v, name) {
|
|
10
5
|
if (v === undefined)
|
|
11
6
|
return undefined;
|
|
@@ -24,7 +19,7 @@ export function registerRoomAgents(program) {
|
|
|
24
19
|
.requiredOption("--agent <agentId>", "hosted custom/system agent id")
|
|
25
20
|
.option("--reply-in-thread <true|false>", "whether the agent replies in threads")
|
|
26
21
|
.action(async (opts) => {
|
|
27
|
-
const asUserId =
|
|
22
|
+
const asUserId = actingUserId(program);
|
|
28
23
|
const installed = (await call({
|
|
29
24
|
method: "POST",
|
|
30
25
|
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/agents/install/${encodeURIComponent(opts.agent)}`,
|