@fulldivevr/workcord-cli 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. package/LICENSE +18 -0
  2. package/README.md +312 -0
  3. package/dist/auth.js +9 -0
  4. package/dist/cli.js +46 -0
  5. package/dist/client.js +125 -0
  6. package/dist/commands/_group.js +5 -0
  7. package/dist/commands/auth.js +125 -0
  8. package/dist/commands/challenges.js +51 -0
  9. package/dist/commands/custom-agents.js +120 -0
  10. package/dist/commands/dm.js +35 -0
  11. package/dist/commands/messages.js +360 -0
  12. package/dist/commands/room-agents.js +47 -0
  13. package/dist/commands/rooms.js +687 -0
  14. package/dist/commands/users.js +126 -0
  15. package/dist/commands/whoami.js +15 -0
  16. package/dist/generated/_helpers.js +8 -0
  17. package/dist/generated/commands/activity.js +46 -0
  18. package/dist/generated/commands/ai.js +40 -0
  19. package/dist/generated/commands/ambassador.js +34 -0
  20. package/dist/generated/commands/apple-iap.js +61 -0
  21. package/dist/generated/commands/attribution.js +130 -0
  22. package/dist/generated/commands/auth.js +205 -0
  23. package/dist/generated/commands/challenges.js +251 -0
  24. package/dist/generated/commands/clips.js +71 -0
  25. package/dist/generated/commands/contact-match.js +92 -0
  26. package/dist/generated/commands/content-crawler.js +27 -0
  27. package/dist/generated/commands/custom-agents.js +346 -0
  28. package/dist/generated/commands/desktop-releases.js +236 -0
  29. package/dist/generated/commands/discover.js +236 -0
  30. package/dist/generated/commands/dm.js +155 -0
  31. package/dist/generated/commands/drafts.js +41 -0
  32. package/dist/generated/commands/embed.js +34 -0
  33. package/dist/generated/commands/explore.js +19 -0
  34. package/dist/generated/commands/feed.js +67 -0
  35. package/dist/generated/commands/feedback.js +44 -0
  36. package/dist/generated/commands/gamification.js +33 -0
  37. package/dist/generated/commands/google-play-iap.js +73 -0
  38. package/dist/generated/commands/health.js +32 -0
  39. package/dist/generated/commands/internal.js +162 -0
  40. package/dist/generated/commands/interview.js +76 -0
  41. package/dist/generated/commands/invite-redirect.js +20 -0
  42. package/dist/generated/commands/jesuscord-admin.js +36 -0
  43. package/dist/generated/commands/join.js +27 -0
  44. package/dist/generated/commands/kits.js +108 -0
  45. package/dist/generated/commands/meetings.js +81 -0
  46. package/dist/generated/commands/message-folders.js +68 -0
  47. package/dist/generated/commands/messages.js +418 -0
  48. package/dist/generated/commands/one-emulator.js +32 -0
  49. package/dist/generated/commands/pdf-export.js +19 -0
  50. package/dist/generated/commands/pin-folders.js +72 -0
  51. package/dist/generated/commands/pinned-messages.js +81 -0
  52. package/dist/generated/commands/polls.js +152 -0
  53. package/dist/generated/commands/premium-feeds.js +32 -0
  54. package/dist/generated/commands/processors.js +22 -0
  55. package/dist/generated/commands/questions.js +54 -0
  56. package/dist/generated/commands/referrals.js +19 -0
  57. package/dist/generated/commands/reports.js +21 -0
  58. package/dist/generated/commands/room-a2-a.js +81 -0
  59. package/dist/generated/commands/room-agents.js +273 -0
  60. package/dist/generated/commands/room-invites.js +155 -0
  61. package/dist/generated/commands/room-owned-agent.js +36 -0
  62. package/dist/generated/commands/room-page.js +20 -0
  63. package/dist/generated/commands/rooms.js +1117 -0
  64. package/dist/generated/commands/saved-messages.js +84 -0
  65. package/dist/generated/commands/saved-stories.js +125 -0
  66. package/dist/generated/commands/search.js +44 -0
  67. package/dist/generated/commands/shadow-home-agent.js +36 -0
  68. package/dist/generated/commands/shadow.js +64 -0
  69. package/dist/generated/commands/share-cards.js +85 -0
  70. package/dist/generated/commands/share.js +20 -0
  71. package/dist/generated/commands/sponsors.js +20 -0
  72. package/dist/generated/commands/stories.js +324 -0
  73. package/dist/generated/commands/story-by-id.js +20 -0
  74. package/dist/generated/commands/submissions.js +112 -0
  75. package/dist/generated/commands/subscriptions.js +103 -0
  76. package/dist/generated/commands/tips.js +76 -0
  77. package/dist/generated/commands/users.js +373 -0
  78. package/dist/generated/commands/voice-token.js +21 -0
  79. package/dist/generated/commands/well-known.js +32 -0
  80. package/dist/generated/commands/wrapped.js +20 -0
  81. package/dist/generated/index.js +131 -0
  82. package/package.json +60 -0
@@ -0,0 +1,51 @@
1
+ import { call } from "../client.js";
2
+ 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
+ /**
10
+ * Custom challenge commands that are NOT covered by the OpenAPI-generated
11
+ * commands (src/generated/commands/challenges.ts). Everything else — create,
12
+ * list, leaderboard, close, regenerate-image, delete, etc. — comes from the
13
+ * generated group and uses its option names (--room-id, --id, …), so we must
14
+ * NOT redefine those here or they'll diverge from what the API expects.
15
+ *
16
+ * This is registered AFTER the generated commands so it only fills the gap.
17
+ */
18
+ export function registerChallenges(program) {
19
+ const group = getOrCreateGroup(program, "challenges", "Challenge operations");
20
+ group
21
+ .command("regenerate-cards")
22
+ .description("Regenerate share cards for every participant of a challenge.")
23
+ .requiredOption("--id <challengeId>", "challenge ID")
24
+ .action(async (opts) => {
25
+ let offset = 0;
26
+ const totals = {
27
+ total: 0,
28
+ regenerated: 0,
29
+ failed: 0,
30
+ failures: [],
31
+ };
32
+ // Server hard-caps the chunk at 10 (CPU-bound resvg + sharp).
33
+ while (true) {
34
+ const data = (await call({
35
+ method: "POST",
36
+ path: `/api/v1/challenges/${encodeURIComponent(opts.id)}/regenerate-cards?offset=${offset}&limit=10`,
37
+ asUserId: currentUser(program),
38
+ body: {},
39
+ }));
40
+ totals.total = data.total;
41
+ totals.regenerated += data.regenerated;
42
+ totals.failed += data.failed;
43
+ totals.failures.push(...data.failures);
44
+ if (data.nextOffset === null)
45
+ break;
46
+ offset = data.nextOffset;
47
+ }
48
+ process.stdout.write(JSON.stringify(totals) + "\n");
49
+ });
50
+ }
51
+ //# sourceMappingURL=challenges.js.map
@@ -0,0 +1,120 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { call } from "../client.js";
3
+ 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
+ function bool(v, name) {
11
+ if (v === undefined)
12
+ return undefined;
13
+ if (v === "true")
14
+ return true;
15
+ if (v === "false")
16
+ return false;
17
+ throw new Error(`${name} must be true or false`);
18
+ }
19
+ function list(v) {
20
+ return v?.split(",").map((s) => s.trim()).filter(Boolean);
21
+ }
22
+ function maybeNumber(v, name) {
23
+ if (v === undefined)
24
+ return undefined;
25
+ const n = Number(v);
26
+ if (!Number.isFinite(n) || n < 0 || n > 2) {
27
+ throw new Error(`${name} must be a number from 0 to 2`);
28
+ }
29
+ return n;
30
+ }
31
+ function promptFrom(opts) {
32
+ if (opts.prompt && opts.promptFile)
33
+ throw new Error("Use either --prompt or --prompt-file, not both");
34
+ return opts.promptFile ? readFileSync(opts.promptFile, "utf8") : opts.prompt;
35
+ }
36
+ export function registerCustomAgents(program) {
37
+ const group = getOrCreateGroup(program, "custom-agents", "CustomAgents operations");
38
+ group
39
+ .command("create-hosted")
40
+ .description("Create a WorkCord-hosted custom agent with typed flags.")
41
+ .requiredOption("--name <name>", "agent name")
42
+ .requiredOption("--description <text>", "short description")
43
+ .option("--prompt <text>", "system prompt")
44
+ .option("--prompt-file <path>", "read system prompt from a local file")
45
+ .option("--model <model>", "model id", "x-ai/grok-4-fast")
46
+ .option("--temperature <n>", "temperature", "0.4")
47
+ .option("--tools <items>", "comma-separated tools, e.g. web_search,image_gen")
48
+ .option("--category <category>", "marketplace category", "other")
49
+ .option("--published <true|false>", "publish to marketplace", "false")
50
+ .option("--rich-card-output <true|false>", "render URLs as rich cards", "true")
51
+ .action(async (opts) => {
52
+ const systemPrompt = promptFrom(opts) ?? "";
53
+ const temperature = maybeNumber(opts.temperature, "--temperature");
54
+ const data = await call({
55
+ method: "POST",
56
+ path: "/api/v1/agents",
57
+ asUserId: currentUser(program),
58
+ body: {
59
+ name: opts.name,
60
+ description: opts.description,
61
+ model: opts.model,
62
+ systemPrompt,
63
+ temperature,
64
+ enabledTools: list(opts.tools) ?? [],
65
+ category: opts.category,
66
+ isPublished: bool(opts.published, "--published"),
67
+ richCardOutput: bool(opts.richCardOutput, "--rich-card-output"),
68
+ },
69
+ });
70
+ process.stdout.write(JSON.stringify(data) + "\n");
71
+ });
72
+ group
73
+ .command("update-hosted")
74
+ .description("Update a WorkCord-hosted custom agent with typed flags.")
75
+ .requiredOption("--agent <agentId>", "hosted custom agent id")
76
+ .option("--name <name>", "agent name")
77
+ .option("--description <text>", "short description")
78
+ .option("--prompt <text>", "system prompt")
79
+ .option("--prompt-file <path>", "read system prompt from a local file")
80
+ .option("--model <model>", "model id")
81
+ .option("--temperature <n>", "temperature")
82
+ .option("--tools <items>", "comma-separated tools, e.g. web_search,image_gen")
83
+ .option("--category <category>", "marketplace category")
84
+ .option("--published <true|false>", "publish to marketplace")
85
+ .option("--rich-card-output <true|false>", "render URLs as rich cards")
86
+ .action(async (opts) => {
87
+ const body = {};
88
+ if (opts.name !== undefined)
89
+ body.name = opts.name;
90
+ if (opts.description !== undefined)
91
+ body.description = opts.description;
92
+ const systemPrompt = promptFrom(opts);
93
+ if (systemPrompt !== undefined)
94
+ body.systemPrompt = systemPrompt;
95
+ if (opts.model !== undefined)
96
+ body.model = opts.model;
97
+ const temperature = maybeNumber(opts.temperature, "--temperature");
98
+ if (temperature !== undefined)
99
+ body.temperature = temperature;
100
+ const enabledTools = list(opts.tools);
101
+ if (enabledTools !== undefined)
102
+ body.enabledTools = enabledTools;
103
+ if (opts.category !== undefined)
104
+ body.category = opts.category;
105
+ const isPublished = bool(opts.published, "--published");
106
+ if (isPublished !== undefined)
107
+ body.isPublished = isPublished;
108
+ const richCardOutput = bool(opts.richCardOutput, "--rich-card-output");
109
+ if (richCardOutput !== undefined)
110
+ body.richCardOutput = richCardOutput;
111
+ const data = await call({
112
+ method: "PATCH",
113
+ path: `/api/v1/agents/${encodeURIComponent(opts.agent)}`,
114
+ asUserId: currentUser(program),
115
+ body,
116
+ });
117
+ process.stdout.write(JSON.stringify(data) + "\n");
118
+ });
119
+ }
120
+ //# sourceMappingURL=custom-agents.js.map
@@ -0,0 +1,35 @@
1
+ import { call } from "../client.js";
2
+ import { getOrCreateGroup } from "./_group.js";
3
+ export function registerDm(program) {
4
+ const group = getOrCreateGroup(program, "dm", "Dm operations");
5
+ group
6
+ .command("send-text")
7
+ .description("Open or get a DM with --to <userId>, then send --text. Composite of dm/start + messages send. " +
8
+ "Returns { dm, message }.")
9
+ .requiredOption("--to <userId>", "the other user")
10
+ .requiredOption("--text <text>", "message body — mention users with @userId inline")
11
+ .option("--reply-to <messageId>", "parent message id (for threaded replies)")
12
+ .action(async (opts) => {
13
+ const { as } = program.opts();
14
+ if (!as)
15
+ throw new Error("--as <userId> is required");
16
+ const dm = (await call({
17
+ method: "POST",
18
+ path: "/api/v1/dm/start",
19
+ body: { targetUserId: opts.to },
20
+ asUserId: as,
21
+ }));
22
+ const roomId = dm?.id ?? dm?.roomId;
23
+ if (!roomId) {
24
+ throw new Error(`dm/start did not return a room id: ${JSON.stringify(dm)}`);
25
+ }
26
+ const message = await call({
27
+ method: "POST",
28
+ path: `/api/v1/rooms/${encodeURIComponent(roomId)}/messages`,
29
+ body: { content: opts.text, type: "text", replyToId: opts.replyTo },
30
+ asUserId: as,
31
+ });
32
+ process.stdout.write(JSON.stringify({ dm, message }) + "\n");
33
+ });
34
+ }
35
+ //# sourceMappingURL=dm.js.map
@@ -0,0 +1,360 @@
1
+ import { call } from "../client.js";
2
+ import { getOrCreateGroup } from "./_group.js";
3
+ const MAX_LIMIT = 500;
4
+ const MAX_PAGE_LIMIT = 100;
5
+ const MESSAGE_FILTERS = new Set(["pinned", "myMessages", "allDiscussions", "myThreads", "myDiscussions"]);
6
+ function parseMessageFilter(v) {
7
+ if (v === undefined)
8
+ return undefined;
9
+ if (!MESSAGE_FILTERS.has(v)) {
10
+ throw new Error(`--filter must be one of: ${Array.from(MESSAGE_FILTERS).join(", ")}`);
11
+ }
12
+ return v;
13
+ }
14
+ function parseIsoOrThrow(v, name) {
15
+ if (v === undefined)
16
+ return undefined;
17
+ if (Number.isNaN(new Date(v).getTime()))
18
+ throw new Error(`${name} not a valid ISO timestamp: ${v}`);
19
+ return v;
20
+ }
21
+ function collect(value, previous = []) {
22
+ previous.push(value);
23
+ return previous;
24
+ }
25
+ function parsePositiveInt(value, name, max) {
26
+ const n = Number(value);
27
+ if (!Number.isInteger(n) || n < 1 || n > max) {
28
+ throw new Error(`${name} must be an integer from 1 to ${max}`);
29
+ }
30
+ return n;
31
+ }
32
+ function parseSince(value) {
33
+ if (value === undefined)
34
+ return undefined;
35
+ const ms = new Date(value).getTime();
36
+ if (Number.isNaN(ms))
37
+ throw new Error(`--since not a valid ISO timestamp: ${value}`);
38
+ return ms;
39
+ }
40
+ function record(value) {
41
+ return value && typeof value === "object" ? value : {};
42
+ }
43
+ function roomId(room) {
44
+ const r = record(room);
45
+ const id = r.id ?? r.roomId;
46
+ return typeof id === "string" ? id : undefined;
47
+ }
48
+ function roomSummary(room) {
49
+ const r = record(room);
50
+ const id = roomId(room) ?? null;
51
+ return {
52
+ id,
53
+ roomId: id,
54
+ name: r.name ?? r.roomName ?? null,
55
+ type: r.type ?? null,
56
+ isPublic: r.isPublic ?? null,
57
+ inboxCategory: r.inboxCategory ?? null,
58
+ lastActivity: r.lastActivity ?? null,
59
+ unreadCount: r.unreadCount ?? null,
60
+ };
61
+ }
62
+ function sentAtMs(message) {
63
+ const sentAt = record(message).sentAt;
64
+ if (typeof sentAt !== "string")
65
+ return 0;
66
+ const ms = new Date(sentAt).getTime();
67
+ return Number.isNaN(ms) ? 0 : ms;
68
+ }
69
+ const MEMBER_PAGE_LIMIT = 500;
70
+ const MAX_MEMBER_PAGES = 20;
71
+ // The members endpoint's ?query= searches displayName only, so a userId lookup
72
+ // has to walk the cursor. Room agents are appended to page 1 with a synthetic
73
+ // `agent-<id>` userId and are not mentionable as users.
74
+ async function resolveMentions(roomId, asUserId, userIds) {
75
+ if (userIds.length === 0)
76
+ return [];
77
+ const wanted = new Set(userIds);
78
+ const found = new Map();
79
+ let cursor;
80
+ for (let page = 0; page < MAX_MEMBER_PAGES && found.size < wanted.size; page++) {
81
+ const res = (await call({
82
+ method: "GET",
83
+ path: `/api/v1/rooms/${encodeURIComponent(roomId)}/members`,
84
+ query: { limit: MEMBER_PAGE_LIMIT, cursor },
85
+ asUserId,
86
+ }));
87
+ for (const m of res.data ?? []) {
88
+ if (!m.userId || m.memberType === "agent")
89
+ continue;
90
+ if (wanted.has(m.userId))
91
+ found.set(m.userId, m.displayName || m.userId);
92
+ }
93
+ cursor = res.cursor ?? undefined;
94
+ if (!res.hasMore || !cursor)
95
+ break;
96
+ }
97
+ return userIds.map((userId) => {
98
+ const name = found.get(userId);
99
+ if (name === undefined)
100
+ throw new Error(`User ${userId} is not a member of room ${roomId}`);
101
+ return { userId, name, type: "user" };
102
+ });
103
+ }
104
+ export function registerMessages(program) {
105
+ const group = getOrCreateGroup(program, "messages", "Messages operations");
106
+ group
107
+ .command("send-text")
108
+ .description("Send a text message to a room. Use --mention-user to mention members without exposing raw ids.")
109
+ .requiredOption("--room <roomId>", "target room id")
110
+ .requiredOption("--text <text>", "message body")
111
+ .option("--reply-to <messageId>", "parent message id (for threaded replies)")
112
+ .option("--mention-user <userId>", "room member to mention by display name; repeatable", collect, [])
113
+ .option("--attachments <urls>", "comma-separated GCS URLs (use `workcord rooms upload-image-file` to get URLs first)")
114
+ .option("--no-shadow-guidance", "do not opt in to Shadow guidance cards")
115
+ .action(async (opts) => {
116
+ const { as } = program.opts();
117
+ if (!as)
118
+ throw new Error("--as <userId> is required");
119
+ const mentions = await resolveMentions(opts.room, as, opts.mentionUser);
120
+ const content = mentions.length > 0 ? `${mentions.map((m) => `@${m.name}`).join(" ")} ${opts.text}` : opts.text;
121
+ const body = {
122
+ content,
123
+ type: "text",
124
+ replyToId: opts.replyTo,
125
+ mentions: mentions.length > 0 ? mentions : undefined,
126
+ attachmentUrls: opts.attachments ? opts.attachments.split(",").map((s) => s.trim()).filter(Boolean) : undefined,
127
+ };
128
+ const data = await call({
129
+ method: "POST",
130
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages`,
131
+ query: { shadowGuidanceCards: opts.shadowGuidance ? "true" : undefined },
132
+ body,
133
+ asUserId: as,
134
+ });
135
+ process.stdout.write(JSON.stringify(data) + "\n");
136
+ });
137
+ group
138
+ .command("list")
139
+ .description("List a page of room messages, newest-first. Output: { data, cursor, hasMore }.")
140
+ .requiredOption("--room <roomId>", "target room id")
141
+ .option("--limit <n>", `page size, 1-${MAX_PAGE_LIMIT}`, "20")
142
+ .option("--cursor <iso>", "pagination cursor (sentAt of the oldest message seen)")
143
+ .option("--after <iso>", "only messages with sentAt > this ISO timestamp; overrides --cursor server-side")
144
+ .option("--filter <filter>", `one of: ${Array.from(MESSAGE_FILTERS).join(", ")}`)
145
+ .action(async (opts) => {
146
+ const { as } = program.opts();
147
+ if (!as)
148
+ throw new Error("--as <userId> is required");
149
+ const data = await call({
150
+ method: "GET",
151
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages`,
152
+ query: {
153
+ limit: parsePositiveInt(opts.limit, "--limit", MAX_PAGE_LIMIT),
154
+ cursor: opts.cursor,
155
+ after: parseIsoOrThrow(opts.after, "--after"),
156
+ filter: parseMessageFilter(opts.filter),
157
+ },
158
+ asUserId: as,
159
+ });
160
+ process.stdout.write(JSON.stringify(data) + "\n");
161
+ });
162
+ group
163
+ .command("thread")
164
+ .description("List replies in a message's thread, oldest-first. Agent replies default to landing in threads, " +
165
+ "so most agent output is only visible here. Output: { data, cursor, hasMore }.")
166
+ .requiredOption("--room <roomId>", "target room id")
167
+ .requiredOption("--message <messageId>", "thread root message id")
168
+ .option("--include-root", "include the root message (only honoured on the first page)")
169
+ .option("--limit <n>", `page size, 1-${MAX_PAGE_LIMIT}`, "20")
170
+ .option("--cursor <iso>", "pagination cursor from the previous page's cursor")
171
+ .action(async (opts) => {
172
+ const { as } = program.opts();
173
+ if (!as)
174
+ throw new Error("--as <userId> is required");
175
+ const data = await call({
176
+ method: "GET",
177
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages/${encodeURIComponent(opts.message)}/thread`,
178
+ query: {
179
+ includeRoot: opts.includeRoot ? "true" : undefined,
180
+ limit: parsePositiveInt(opts.limit, "--limit", MAX_PAGE_LIMIT),
181
+ cursor: opts.cursor,
182
+ },
183
+ asUserId: as,
184
+ });
185
+ process.stdout.write(JSON.stringify(data) + "\n");
186
+ });
187
+ group
188
+ .command("mark-thread-read")
189
+ .description("Mark a thread as read up to now. Output: { threadId, lastReadAt }.")
190
+ .requiredOption("--room <roomId>", "target room id")
191
+ .requiredOption("--message <messageId>", "thread root message id")
192
+ .action(async (opts) => {
193
+ const { as } = program.opts();
194
+ if (!as)
195
+ throw new Error("--as <userId> is required");
196
+ const data = await call({
197
+ method: "POST",
198
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages/${encodeURIComponent(opts.message)}/thread/read`,
199
+ asUserId: as,
200
+ });
201
+ process.stdout.write(JSON.stringify(data) + "\n");
202
+ });
203
+ group
204
+ .command("new")
205
+ .description("Get messages newer than --since (ISO timestamp), oldest-first. Walks pagination internally — " +
206
+ "the server's cursor is 'older than', this flips it for polling. Output: { data, count, fetchedPages, truncated }.")
207
+ .requiredOption("--room <roomId>", "target room id")
208
+ .requiredOption("--since <iso>", 'ISO 8601 timestamp; only messages with sentAt > since are returned (e.g. "2026-04-28T10:00:00Z")')
209
+ .option("--limit <n>", "max messages per page (server caps at 100)", "50")
210
+ .option("--max-pages <n>", "safety cap on pages walked back", "20")
211
+ .action(async (opts) => {
212
+ const { as } = program.opts();
213
+ if (!as)
214
+ throw new Error("--as <userId> is required");
215
+ const sinceMs = new Date(opts.since).getTime();
216
+ if (Number.isNaN(sinceMs))
217
+ throw new Error(`--since not a valid ISO timestamp: ${opts.since}`);
218
+ const limit = Number(opts.limit);
219
+ const maxPages = Number(opts.maxPages);
220
+ const collected = [];
221
+ let cursor;
222
+ let pages = 0;
223
+ let stopped = false;
224
+ while (pages < maxPages) {
225
+ pages++;
226
+ const page = (await call({
227
+ method: "GET",
228
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages`,
229
+ query: { cursor, limit },
230
+ asUserId: as,
231
+ }));
232
+ if (!page.data || page.data.length === 0)
233
+ break;
234
+ for (const msg of page.data) {
235
+ if (new Date(msg.sentAt).getTime() <= sinceMs) {
236
+ stopped = true;
237
+ break;
238
+ }
239
+ collected.push(msg);
240
+ }
241
+ if (stopped || !page.hasMore)
242
+ break;
243
+ cursor = page.data[page.data.length - 1].sentAt;
244
+ }
245
+ const truncated = pages >= maxPages && !stopped;
246
+ collected.reverse();
247
+ process.stdout.write(JSON.stringify({ data: collected, count: collected.length, fetchedPages: pages, truncated }) + "\n");
248
+ });
249
+ group
250
+ .command("latest")
251
+ .description("Get the latest messages visible to the acting user across their rooms. " +
252
+ "Composite command: lists rooms, fetches recent room messages, then merges by sentAt descending.")
253
+ .option("--limit <n>", `max messages to return, 1-${MAX_LIMIT}`, "50")
254
+ .option("--per-room-limit <n>", `messages to fetch per room, 1-${MAX_PAGE_LIMIT}`, "10")
255
+ .option("--room-page-limit <n>", `rooms to fetch per page, 1-${MAX_PAGE_LIMIT}`, "100")
256
+ .option("--max-room-pages <n>", "safety cap on room pages scanned", "10")
257
+ .option("--since <iso>", "only include messages with sentAt > this ISO timestamp")
258
+ .option("--status <value>", "room status filter")
259
+ .option("--inbox-category <value>", "room inbox category filter")
260
+ .option("--no-shadow-guidance", "do not opt in to Shadow guidance cards")
261
+ .action(async (opts) => {
262
+ const { as } = program.opts();
263
+ if (!as)
264
+ throw new Error("--as <userId> is required");
265
+ const limit = parsePositiveInt(opts.limit, "--limit", MAX_LIMIT);
266
+ const perRoomLimit = parsePositiveInt(opts.perRoomLimit, "--per-room-limit", MAX_PAGE_LIMIT);
267
+ const roomPageLimit = parsePositiveInt(opts.roomPageLimit, "--room-page-limit", MAX_PAGE_LIMIT);
268
+ const maxRoomPages = parsePositiveInt(opts.maxRoomPages, "--max-room-pages", 1000);
269
+ const sinceMs = parseSince(opts.since);
270
+ const messages = [];
271
+ const failedRooms = [];
272
+ let roomCursor;
273
+ let roomPagesFetched = 0;
274
+ let roomsScanned = 0;
275
+ let hasMoreRooms = true;
276
+ while (hasMoreRooms && roomPagesFetched < maxRoomPages) {
277
+ roomPagesFetched++;
278
+ const roomsPage = (await call({
279
+ method: "GET",
280
+ path: "/api/v1/rooms",
281
+ query: {
282
+ cursor: roomCursor,
283
+ limit: roomPageLimit,
284
+ status: opts.status,
285
+ inboxCategory: opts.inboxCategory,
286
+ shadowGuidanceCards: opts.shadowGuidance ? "true" : undefined,
287
+ },
288
+ asUserId: as,
289
+ }));
290
+ const rooms = roomsPage.data ?? [];
291
+ for (const room of rooms) {
292
+ const id = roomId(room);
293
+ if (!id)
294
+ continue;
295
+ roomsScanned++;
296
+ try {
297
+ const roomMessages = (await call({
298
+ method: "GET",
299
+ path: `/api/v1/rooms/${encodeURIComponent(id)}/messages`,
300
+ query: { limit: perRoomLimit },
301
+ asUserId: as,
302
+ }));
303
+ const summary = roomSummary(room);
304
+ for (const message of roomMessages.data ?? []) {
305
+ if (sinceMs !== undefined && sentAtMs(message) <= sinceMs)
306
+ continue;
307
+ messages.push({
308
+ ...record(message),
309
+ room: summary,
310
+ roomId: record(message).roomId ?? summary.roomId,
311
+ roomName: summary.name,
312
+ });
313
+ }
314
+ }
315
+ catch (err) {
316
+ const e = err;
317
+ failedRooms.push({
318
+ room: roomSummary(room),
319
+ error: e.message,
320
+ status: e.status ?? null,
321
+ body: e.body ?? null,
322
+ });
323
+ }
324
+ }
325
+ roomCursor = roomsPage.cursor ?? undefined;
326
+ hasMoreRooms = Boolean(roomsPage.hasMore && roomCursor);
327
+ }
328
+ messages.sort((a, b) => sentAtMs(b) - sentAtMs(a));
329
+ const data = messages.slice(0, limit);
330
+ process.stdout.write(JSON.stringify({
331
+ data,
332
+ count: data.length,
333
+ collectedCount: messages.length,
334
+ roomsScanned,
335
+ roomPagesFetched,
336
+ truncatedRooms: hasMoreRooms,
337
+ failedRooms,
338
+ }) + "\n");
339
+ });
340
+ group
341
+ .command("react")
342
+ .description("Add a reaction to a message. --emoji can be either an emoji character or a reaction id " +
343
+ "(server stores it as the `type` field).")
344
+ .requiredOption("--room <roomId>", "target room")
345
+ .requiredOption("--message <messageId>", "target message")
346
+ .requiredOption("--emoji <type>", 'reaction type — emoji or id (e.g. "👍" or "thumbs_up")')
347
+ .action(async (opts) => {
348
+ const { as } = program.opts();
349
+ if (!as)
350
+ throw new Error("--as <userId> is required");
351
+ const data = await call({
352
+ method: "POST",
353
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages/${encodeURIComponent(opts.message)}/reactions`,
354
+ body: { type: opts.emoji },
355
+ asUserId: as,
356
+ });
357
+ process.stdout.write(JSON.stringify(data) + "\n");
358
+ });
359
+ }
360
+ //# sourceMappingURL=messages.js.map
@@ -0,0 +1,47 @@
1
+ import { call } from "../client.js";
2
+ 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
+ function bool(v, name) {
10
+ if (v === undefined)
11
+ return undefined;
12
+ if (v === "true")
13
+ return true;
14
+ if (v === "false")
15
+ return false;
16
+ throw new Error(`${name} must be true or false`);
17
+ }
18
+ export function registerRoomAgents(program) {
19
+ const group = getOrCreateGroup(program, "room-agents", "RoomAgents operations");
20
+ group
21
+ .command("install-hosted")
22
+ .description("Install a WorkCord-hosted custom/system agent into a room.")
23
+ .requiredOption("--room <roomId>", "target room id")
24
+ .requiredOption("--agent <agentId>", "hosted custom/system agent id")
25
+ .option("--reply-in-thread <true|false>", "whether the agent replies in threads")
26
+ .action(async (opts) => {
27
+ const asUserId = currentUser(program);
28
+ const installed = (await call({
29
+ method: "POST",
30
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/agents/install/${encodeURIComponent(opts.agent)}`,
31
+ asUserId,
32
+ }));
33
+ const replyInThread = bool(opts.replyInThread, "--reply-in-thread");
34
+ if (replyInThread === undefined || !installed.id) {
35
+ process.stdout.write(JSON.stringify(installed) + "\n");
36
+ return;
37
+ }
38
+ const updated = await call({
39
+ method: "PATCH",
40
+ path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/agents/${encodeURIComponent(installed.id)}`,
41
+ asUserId,
42
+ body: { replyInThread },
43
+ });
44
+ process.stdout.write(JSON.stringify(updated) + "\n");
45
+ });
46
+ }
47
+ //# sourceMappingURL=room-agents.js.map