@fulldivevr/workcord-cli 1.3.0 → 1.6.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 +5 -0
- package/dist/cli.js +4 -0
- package/dist/client.js +8 -1
- package/dist/commands/agent-skills.js +127 -0
- package/dist/commands/messages.js +150 -4
- package/dist/commands/reflections.js +96 -0
- package/dist/commands/rooms.js +7 -3
- package/dist/generated/commands/agent-feedback-admin.js +69 -0
- package/dist/generated/commands/ai.js +31 -0
- package/dist/generated/commands/call-recordings.js +56 -0
- package/dist/generated/commands/contact-match.js +20 -1
- package/dist/generated/commands/credit-packs.js +112 -0
- package/dist/generated/commands/custom-agents.js +483 -1
- package/dist/generated/commands/desktop-releases.js +2 -2
- package/dist/generated/commands/edge-sizecheck.js +25 -0
- package/dist/generated/commands/feed.js +6 -0
- package/dist/generated/commands/interactive-events.js +47 -0
- package/dist/generated/commands/internal.js +191 -0
- package/dist/generated/commands/meetings.js +12 -0
- package/dist/generated/commands/messages.js +15 -0
- package/dist/generated/commands/my-feedback.js +18 -0
- package/dist/generated/commands/pinned-messages.js +6 -0
- package/dist/generated/commands/proxy-nodes.js +83 -0
- package/dist/generated/commands/push.js +20 -0
- package/dist/generated/commands/reviews.js +96 -0
- package/dist/generated/commands/room-agents.js +243 -1
- package/dist/generated/commands/room-invites.js +12 -0
- package/dist/generated/commands/rooms.js +79 -1
- package/dist/generated/commands/search.js +1 -1
- package/dist/generated/commands/shadow-dispatch-push.js +20 -0
- package/dist/generated/commands/shadow-reflections.js +111 -0
- package/dist/generated/index.js +20 -0
- package/dist/http.js +6 -2
- package/dist/room-types.js +10 -0
- package/package.json +1 -1
- package/dist/generated/commands/health.js +0 -29
package/README.md
CHANGED
|
@@ -205,6 +205,7 @@ workcord <namespace> <command> [--<path-param> <value>] [--body '<json>'] [--que
|
|
|
205
205
|
```bash
|
|
206
206
|
workcord auth login --email you@company.com
|
|
207
207
|
workcord --as USER messages send-text --room R --text "hello"
|
|
208
|
+
workcord messages ask-agent --room R --agent INSTALLATION_ID --text "Summarize the decision"
|
|
208
209
|
```
|
|
209
210
|
|
|
210
211
|
Semantic aliases cover common agent/admin actions with consistent flags:
|
|
@@ -225,6 +226,10 @@ Server auto-extracts mention tokens from visible `@Name` text. Prefer
|
|
|
225
226
|
member's display name and avoids exposing raw user ids. `auth login` never
|
|
226
227
|
takes `--as` — it is how you obtain a `WORKCORD_TOKEN` in the first place.
|
|
227
228
|
|
|
229
|
+
`messages ask-agent` posts an ordinary visible `@Agent` request, then waits for
|
|
230
|
+
that installed agent's visible reply in the room or request thread. Pass the
|
|
231
|
+
`installationId` returned by Home to `--agent`.
|
|
232
|
+
|
|
228
233
|
### Error handling
|
|
229
234
|
|
|
230
235
|
Every error is **a single JSON line on stderr**, with a non-zero exit code:
|
package/dist/cli.js
CHANGED
|
@@ -14,6 +14,8 @@ import { registerChallenges } from "./commands/challenges.js";
|
|
|
14
14
|
import { registerHealth } from "./commands/health.js";
|
|
15
15
|
import { registerHermes } from "./commands/hermes.js";
|
|
16
16
|
import { registerAgent } from "./commands/agent.js";
|
|
17
|
+
import { registerAgentSkills } from "./commands/agent-skills.js";
|
|
18
|
+
import { registerReflections } from "./commands/reflections.js";
|
|
17
19
|
import { registerAll as registerGenerated } from "./generated/index.js";
|
|
18
20
|
const program = new Command();
|
|
19
21
|
const require = createRequire(import.meta.url);
|
|
@@ -36,6 +38,8 @@ registerDm(program);
|
|
|
36
38
|
registerUsers(program);
|
|
37
39
|
registerRoomAgents(program);
|
|
38
40
|
registerCustomAgents(program);
|
|
41
|
+
registerAgentSkills(program);
|
|
42
|
+
registerReflections(program);
|
|
39
43
|
registerChallenges(program); // adds regenerate-cards (not in the OpenAPI spec)
|
|
40
44
|
registerHealth(program);
|
|
41
45
|
registerHermes(program);
|
package/dist/client.js
CHANGED
|
@@ -1,13 +1,18 @@
|
|
|
1
1
|
import { resolveAuth } from "./auth.js";
|
|
2
2
|
import { DEFAULT_BASE_URL, readConfig } from "./config.js";
|
|
3
3
|
import { applyQuery, fetchWithRetry, httpError, normalizeBaseUrl, safeJson } from "./http.js";
|
|
4
|
+
import { validateRoomCreateBody } from "./room-types.js";
|
|
4
5
|
let baseUrlOverride;
|
|
5
6
|
export function setBaseUrlOverride(url) {
|
|
6
7
|
baseUrlOverride = url;
|
|
7
8
|
}
|
|
8
9
|
export async function getBaseUrl() {
|
|
9
10
|
const config = await readConfig();
|
|
10
|
-
return normalizeBaseUrl(baseUrlOverride
|
|
11
|
+
return normalizeBaseUrl(baseUrlOverride
|
|
12
|
+
|| process.env.WORKCORD_API_URL
|
|
13
|
+
|| process.env.WORKCORD_API_URL_TOKEN
|
|
14
|
+
|| config.baseUrl
|
|
15
|
+
|| DEFAULT_BASE_URL);
|
|
11
16
|
}
|
|
12
17
|
export async function call({ method = "GET", path, query, body, headers, asUserId }) {
|
|
13
18
|
const config = await readConfig();
|
|
@@ -18,6 +23,8 @@ export async function callPublic({ method = "GET", path, query, body, headers })
|
|
|
18
23
|
return request({ method, path, query, body, headers });
|
|
19
24
|
}
|
|
20
25
|
async function request({ method = "GET", path, query, body, headers, bearer }) {
|
|
26
|
+
if (method === "POST" && path === "/api/v1/rooms")
|
|
27
|
+
validateRoomCreateBody(body);
|
|
21
28
|
const base = await getBaseUrl();
|
|
22
29
|
const url = applyQuery(new URL(base + path), query);
|
|
23
30
|
const res = await fetchWithRetry(() => ({
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { actingUserId } from "../auth.js";
|
|
2
|
+
import { call } from "../client.js";
|
|
3
|
+
import { getOrCreateGroup } from "./_group.js";
|
|
4
|
+
const SKILL_ID = /^[a-z][a-z0-9_-]{0,63}$/;
|
|
5
|
+
const SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
6
|
+
export function parseSkillRef(ref) {
|
|
7
|
+
const at = ref.lastIndexOf("@");
|
|
8
|
+
if (at <= 0 || at === ref.length - 1)
|
|
9
|
+
throw new Error("skill must be ID@VERSION, e.g. web-research@1.0.0");
|
|
10
|
+
return { id: skillId(ref.slice(0, at)), version: semver(ref.slice(at + 1)) };
|
|
11
|
+
}
|
|
12
|
+
function skillId(id) {
|
|
13
|
+
if (!SKILL_ID.test(id))
|
|
14
|
+
throw new Error("skill id must be a lowercase slug");
|
|
15
|
+
return id;
|
|
16
|
+
}
|
|
17
|
+
function semver(version) {
|
|
18
|
+
if (!SEMVER.test(version))
|
|
19
|
+
throw new Error("version must be a semantic version, e.g. 1.0.0");
|
|
20
|
+
return version;
|
|
21
|
+
}
|
|
22
|
+
function bool(value) {
|
|
23
|
+
if (value === "true")
|
|
24
|
+
return true;
|
|
25
|
+
if (value === "false")
|
|
26
|
+
return false;
|
|
27
|
+
throw new Error("enabled must be true or false");
|
|
28
|
+
}
|
|
29
|
+
function skillPath(path) {
|
|
30
|
+
const segments = path.split("/");
|
|
31
|
+
if (segments.some((s) => !s || s === "." || s === ".."))
|
|
32
|
+
throw new Error("invalid skill file path");
|
|
33
|
+
return segments.map(encodeURIComponent).join("/");
|
|
34
|
+
}
|
|
35
|
+
// Agent ids are uuids, optionally behind the prefix a room agent is listed under.
|
|
36
|
+
const AGENT_ID = /^(?:workcord-room-agent:|marketplace-install:)?[0-9a-z]{8}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{4}-[0-9a-z]{12}$/i;
|
|
37
|
+
/** Pick the one agent named `name` (exact, case-insensitive) from a list-mine response. */
|
|
38
|
+
export function agentIdByName(name, mine) {
|
|
39
|
+
const agents = (mine?.data ?? []);
|
|
40
|
+
const wanted = name.toLowerCase();
|
|
41
|
+
const found = (Array.isArray(agents) ? agents : [])
|
|
42
|
+
.filter((a) => typeof a?.name === "string" && a.name.toLowerCase() === wanted);
|
|
43
|
+
if (found.length === 0)
|
|
44
|
+
throw new Error(`no agent named ${name}`);
|
|
45
|
+
if (found.length > 1) {
|
|
46
|
+
throw new Error(`more than one agent named ${name}: ${found.map((a) => `${a.name} (${a.id})`).join(", ")}`);
|
|
47
|
+
}
|
|
48
|
+
return String(found[0].id);
|
|
49
|
+
}
|
|
50
|
+
function agentPath(agentId, suffix) {
|
|
51
|
+
return `/api/v1/agents/${encodeURIComponent(agentId)}/${suffix}`;
|
|
52
|
+
}
|
|
53
|
+
export function registerAgentSkills(program, send = call, output = (value) => process.stdout.write(JSON.stringify(value) + "\n")) {
|
|
54
|
+
const agents = getOrCreateGroup(program, "custom-agents", "CustomAgents operations");
|
|
55
|
+
const skills = agents.command("skills").description("Inspect and manage skills on an agent you own");
|
|
56
|
+
const withActor = (req) => {
|
|
57
|
+
const asUserId = actingUserId(program);
|
|
58
|
+
return asUserId ? { ...req, asUserId } : req;
|
|
59
|
+
};
|
|
60
|
+
// The server takes ids only, so a name is resolved here against list-mine.
|
|
61
|
+
const resolve = async (agent) => {
|
|
62
|
+
if (AGENT_ID.test(agent))
|
|
63
|
+
return agent;
|
|
64
|
+
const mine = await send(withActor({ method: "GET", path: "/api/v1/agents/mine", query: { includeShadow: "true" } }));
|
|
65
|
+
return agentIdByName(agent, mine);
|
|
66
|
+
};
|
|
67
|
+
const run = async (agent, req) => {
|
|
68
|
+
const result = await send(withActor(req(await resolve(agent))));
|
|
69
|
+
if (result != null)
|
|
70
|
+
output(result);
|
|
71
|
+
};
|
|
72
|
+
agents.command("stop")
|
|
73
|
+
.description("Stop an agent you own. Its runtime returns to cold-idle.")
|
|
74
|
+
.argument("<agent>", "agent id or exact name")
|
|
75
|
+
.action((agent) => run(agent, (id) => ({ method: "POST", path: agentPath(id, "stop") })));
|
|
76
|
+
skills.command("list")
|
|
77
|
+
.description("List the agent's skills (core, flavor, private, and market).")
|
|
78
|
+
.argument("<agent>", "agent id or exact name")
|
|
79
|
+
.action((agent) => run(agent, (id) => ({ method: "GET", path: agentPath(id, "skills") })));
|
|
80
|
+
skills.command("files")
|
|
81
|
+
.description("List the files attached to one skill.")
|
|
82
|
+
.argument("<agent>", "agent id or exact name")
|
|
83
|
+
.argument("<category>", "skill category")
|
|
84
|
+
.argument("<skill>", "skill name")
|
|
85
|
+
.action((agent, category, skill) => run(agent, (id) => ({
|
|
86
|
+
method: "GET",
|
|
87
|
+
path: agentPath(id, `skills/${encodeURIComponent(category)}/${encodeURIComponent(skill)}/files`),
|
|
88
|
+
})));
|
|
89
|
+
skills.command("read")
|
|
90
|
+
.description("Read one skill file.")
|
|
91
|
+
.argument("<agent>", "agent id or exact name")
|
|
92
|
+
.argument("<path>", "path relative to the skill root, e.g. research/web-research/SKILL.md")
|
|
93
|
+
.action((agent, path) => {
|
|
94
|
+
const file = skillPath(path);
|
|
95
|
+
return run(agent, (id) => ({ method: "GET", path: agentPath(id, `skill-files/${file}`) }));
|
|
96
|
+
});
|
|
97
|
+
skills.command("enabled")
|
|
98
|
+
.description("Enable or disable one skill.")
|
|
99
|
+
.argument("<agent>", "agent id or exact name")
|
|
100
|
+
.argument("<name>", "skill name")
|
|
101
|
+
.argument("<true|false>", "new state")
|
|
102
|
+
.action((agent, name, enabled) => {
|
|
103
|
+
const body = { enabled: bool(enabled) };
|
|
104
|
+
return run(agent, (id) => ({ method: "PUT", path: agentPath(id, `skills/${encodeURIComponent(name)}/state`), body }));
|
|
105
|
+
});
|
|
106
|
+
skills.command("installed")
|
|
107
|
+
.description("List the market skills installed on the agent.")
|
|
108
|
+
.argument("<agent>", "agent id or exact name")
|
|
109
|
+
.action((agent) => run(agent, (id) => ({ method: "GET", path: agentPath(id, "market-skills") })));
|
|
110
|
+
skills.command("install")
|
|
111
|
+
.description("Install or update a market skill at an exact version.")
|
|
112
|
+
.argument("<agent>", "agent id or exact name")
|
|
113
|
+
.argument("<ID@VERSION>", "market skill and version, e.g. web-research@1.0.0")
|
|
114
|
+
.action((agent, ref) => {
|
|
115
|
+
const body = parseSkillRef(ref);
|
|
116
|
+
return run(agent, (id) => ({ method: "POST", path: agentPath(id, "market-skills"), body }));
|
|
117
|
+
});
|
|
118
|
+
skills.command("uninstall")
|
|
119
|
+
.description("Remove a market skill.")
|
|
120
|
+
.argument("<agent>", "agent id or exact name")
|
|
121
|
+
.argument("<id>", "market skill id")
|
|
122
|
+
.action((agent, id) => {
|
|
123
|
+
const skill = encodeURIComponent(skillId(id));
|
|
124
|
+
return run(agent, (agentId) => ({ method: "DELETE", path: agentPath(agentId, `market-skills/${skill}`) }));
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=agent-skills.js.map
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import { actingUserId } from "../auth.js";
|
|
2
3
|
import { call } from "../client.js";
|
|
4
|
+
import { parseDuration } from "./agent.js";
|
|
3
5
|
import { getOrCreateGroup } from "./_group.js";
|
|
4
6
|
const MAX_LIMIT = 500;
|
|
5
7
|
const MAX_PAGE_LIMIT = 100;
|
|
@@ -102,21 +104,165 @@ async function resolveMentions(roomId, asUserId, userIds) {
|
|
|
102
104
|
return { userId, name, type: "user" };
|
|
103
105
|
});
|
|
104
106
|
}
|
|
107
|
+
async function resolveAgentMentions(roomId, asUserId, agentIds) {
|
|
108
|
+
if (agentIds.length === 0)
|
|
109
|
+
return [];
|
|
110
|
+
const res = (await call({
|
|
111
|
+
method: "GET",
|
|
112
|
+
path: `/api/v1/rooms/${encodeURIComponent(roomId)}/members`,
|
|
113
|
+
query: { limit: MEMBER_PAGE_LIMIT },
|
|
114
|
+
asUserId,
|
|
115
|
+
}));
|
|
116
|
+
const names = new Map();
|
|
117
|
+
for (const member of res.data ?? []) {
|
|
118
|
+
if (member.memberType !== "agent" || !member.agentId)
|
|
119
|
+
continue;
|
|
120
|
+
names.set(member.agentId, member.displayName || member.agentId);
|
|
121
|
+
}
|
|
122
|
+
return agentIds.map((agentId) => {
|
|
123
|
+
const name = names.get(agentId);
|
|
124
|
+
if (name === undefined)
|
|
125
|
+
throw new Error(`Agent ${agentId} is not installed in room ${roomId}`);
|
|
126
|
+
return { agentId, name };
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
export function mentionedText(names, text) {
|
|
130
|
+
return names.length > 0 ? `${names.map((name) => `@${name}`).join(" ")} ${text.trimStart()}` : text;
|
|
131
|
+
}
|
|
132
|
+
export function resolveAgentTarget(members, agentId) {
|
|
133
|
+
const agents = members.filter((member) => member.memberType === "agent" && member.agentId);
|
|
134
|
+
const matches = agents.filter((member) => member.agentId === agentId.trim());
|
|
135
|
+
if (matches.length === 0) {
|
|
136
|
+
throw new Error(`Agent ${agentId} is not installed or enabled in this room.`);
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
agentId: matches[0].agentId,
|
|
140
|
+
name: matches[0].displayName || matches[0].agentId,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
export function agentRequestText(agentName, text, requestId) {
|
|
144
|
+
const marker = `[request:${requestId}]`;
|
|
145
|
+
return mentionedText([agentName], `${marker} ${text.trim()}\n\nReply once in this room, address the requesting Assistant by name, and repeat ${marker}.`);
|
|
146
|
+
}
|
|
147
|
+
export function matchingAgentReply(message, requestMessageId, agentId) {
|
|
148
|
+
const candidate = record(message);
|
|
149
|
+
const metadata = record(candidate.metadata);
|
|
150
|
+
const state = typeof metadata.a2aState === "string" ? metadata.a2aState.toLowerCase() : "";
|
|
151
|
+
if (candidate.senderType !== "agent"
|
|
152
|
+
|| candidate.agentId !== agentId
|
|
153
|
+
|| metadata.replyTo !== requestMessageId
|
|
154
|
+
|| state === "working"
|
|
155
|
+
|| state === "submitted") {
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
return candidate;
|
|
159
|
+
}
|
|
160
|
+
export async function waitForAgentReply(input) {
|
|
161
|
+
const now = input.now ?? Date.now;
|
|
162
|
+
const sleep = input.sleep ?? ((ms) => new Promise((resolve) => setTimeout(resolve, ms)));
|
|
163
|
+
const deadline = now() + input.timeoutMs;
|
|
164
|
+
while (true) {
|
|
165
|
+
const reply = (await input.read())
|
|
166
|
+
.map((message) => matchingAgentReply(message, input.requestMessageId, input.agentId))
|
|
167
|
+
.find((message) => message !== undefined);
|
|
168
|
+
if (reply)
|
|
169
|
+
return { reply, timedOut: false };
|
|
170
|
+
const remaining = deadline - now();
|
|
171
|
+
if (remaining <= 0)
|
|
172
|
+
return { reply: null, timedOut: true };
|
|
173
|
+
await sleep(Math.min(input.pollIntervalMs ?? 1000, remaining));
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
function messageId(value) {
|
|
177
|
+
const id = record(value).id;
|
|
178
|
+
if (typeof id !== "string" || !id)
|
|
179
|
+
throw new Error("Sent message response did not contain an id.");
|
|
180
|
+
return id;
|
|
181
|
+
}
|
|
182
|
+
function messageSentAt(value) {
|
|
183
|
+
const sentAt = record(value).sentAt;
|
|
184
|
+
return typeof sentAt === "string" && !Number.isNaN(new Date(sentAt).getTime()) ? sentAt : undefined;
|
|
185
|
+
}
|
|
105
186
|
export function registerMessages(program) {
|
|
106
187
|
const group = getOrCreateGroup(program, "messages", "Messages operations");
|
|
188
|
+
group
|
|
189
|
+
.command("ask-agent")
|
|
190
|
+
.description("Post one visible request to an installed room agent and wait for its visible correlated reply.")
|
|
191
|
+
.requiredOption("--room <roomId>", "target room id")
|
|
192
|
+
.requiredOption("--text <text>", "request for the agent")
|
|
193
|
+
.requiredOption("--agent <agentInstallationId>", "installed room-agent id returned by Home")
|
|
194
|
+
.option("--timeout <duration>", "maximum local wait, such as 30s or 5m", "2m")
|
|
195
|
+
.action(async (opts) => {
|
|
196
|
+
const as = actingUserId(program);
|
|
197
|
+
const text = opts.text.trim();
|
|
198
|
+
if (!text)
|
|
199
|
+
throw new Error("--text must not be blank.");
|
|
200
|
+
const timeoutMs = parseDuration(opts.timeout);
|
|
201
|
+
const members = (await call({
|
|
202
|
+
method: "GET",
|
|
203
|
+
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/members`,
|
|
204
|
+
query: { limit: MEMBER_PAGE_LIMIT },
|
|
205
|
+
asUserId: as,
|
|
206
|
+
}));
|
|
207
|
+
const target = resolveAgentTarget(members.data ?? [], opts.agent);
|
|
208
|
+
const requestId = randomUUID().replace(/-/g, "").slice(0, 8);
|
|
209
|
+
const content = agentRequestText(target.name, text, requestId);
|
|
210
|
+
const request = await call({
|
|
211
|
+
method: "POST",
|
|
212
|
+
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages`,
|
|
213
|
+
body: { content, type: "text" },
|
|
214
|
+
asUserId: as,
|
|
215
|
+
});
|
|
216
|
+
const requestMessageId = messageId(request);
|
|
217
|
+
const after = messageSentAt(request);
|
|
218
|
+
const result = await waitForAgentReply({
|
|
219
|
+
requestMessageId,
|
|
220
|
+
agentId: target.agentId,
|
|
221
|
+
timeoutMs,
|
|
222
|
+
read: async () => {
|
|
223
|
+
const [room, thread] = await Promise.all([
|
|
224
|
+
call({
|
|
225
|
+
method: "GET",
|
|
226
|
+
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages`,
|
|
227
|
+
query: { limit: MAX_PAGE_LIMIT, after },
|
|
228
|
+
asUserId: as,
|
|
229
|
+
}),
|
|
230
|
+
call({
|
|
231
|
+
method: "GET",
|
|
232
|
+
path: `/api/v1/rooms/${encodeURIComponent(opts.room)}/messages/${encodeURIComponent(requestMessageId)}/thread`,
|
|
233
|
+
query: { limit: MAX_PAGE_LIMIT },
|
|
234
|
+
asUserId: as,
|
|
235
|
+
}),
|
|
236
|
+
]);
|
|
237
|
+
return [
|
|
238
|
+
...(Array.isArray(record(room).data) ? record(room).data : []),
|
|
239
|
+
...(Array.isArray(record(thread).data) ? record(thread).data : []),
|
|
240
|
+
];
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
process.stdout.write(JSON.stringify({
|
|
244
|
+
roomId: opts.room,
|
|
245
|
+
requestId,
|
|
246
|
+
target,
|
|
247
|
+
request: { messageId: requestMessageId, content },
|
|
248
|
+
...result,
|
|
249
|
+
}) + "\n");
|
|
250
|
+
});
|
|
107
251
|
group
|
|
108
252
|
.command("send-text")
|
|
109
|
-
.description("Send
|
|
253
|
+
.description("Send one text message to a room. Mention flags prepend visible @Name text; exclude those names from --text.")
|
|
110
254
|
.requiredOption("--room <roomId>", "target room id")
|
|
111
|
-
.requiredOption("--text <text>", "message body")
|
|
255
|
+
.requiredOption("--text <text>", "message body after any mentions added by mention flags")
|
|
112
256
|
.option("--reply-to <messageId>", "parent message id (for threaded replies)")
|
|
113
|
-
.option("--mention-user <userId>", "room member
|
|
257
|
+
.option("--mention-user <userId>", "prepend the room member's visible @Name; repeatable", collect, [])
|
|
258
|
+
.option("--mention-agent <agentId>", "prepend the installed room agent's visible @Name; repeatable", collect, [])
|
|
114
259
|
.option("--attachments <urls>", "comma-separated GCS URLs (use `workcord rooms upload-image-file` to get URLs first)")
|
|
115
260
|
.option("--no-shadow-guidance", "do not opt in to Shadow guidance cards")
|
|
116
261
|
.action(async (opts) => {
|
|
117
262
|
const as = actingUserId(program);
|
|
118
263
|
const mentions = await resolveMentions(opts.room, as, opts.mentionUser);
|
|
119
|
-
const
|
|
264
|
+
const agentMentions = await resolveAgentMentions(opts.room, as, opts.mentionAgent);
|
|
265
|
+
const content = mentionedText([...mentions, ...agentMentions].map((mention) => mention.name), opts.text);
|
|
120
266
|
const body = {
|
|
121
267
|
content,
|
|
122
268
|
type: "text",
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { actingUserId } from "../auth.js";
|
|
2
|
+
import { call } from "../client.js";
|
|
3
|
+
import { getOrCreateGroup } from "./_group.js";
|
|
4
|
+
function list(value) {
|
|
5
|
+
return value?.split(",").map((item) => item.trim()).filter(Boolean);
|
|
6
|
+
}
|
|
7
|
+
function output(value) {
|
|
8
|
+
process.stdout.write(JSON.stringify(value) + "\n");
|
|
9
|
+
}
|
|
10
|
+
export function registerReflections(program) {
|
|
11
|
+
const group = getOrCreateGroup(program, "reflections", "Create and manage Shadow reflections");
|
|
12
|
+
group.command("list").action(async () => output(await call({
|
|
13
|
+
path: "/api/v1/shadow/reflections",
|
|
14
|
+
asUserId: actingUserId(program),
|
|
15
|
+
})));
|
|
16
|
+
group.command("get")
|
|
17
|
+
.requiredOption("--reflection <id>", "reflection id")
|
|
18
|
+
.action(async (opts) => output(await call({
|
|
19
|
+
path: `/api/v1/shadow/reflections/${encodeURIComponent(opts.reflection)}`,
|
|
20
|
+
asUserId: actingUserId(program),
|
|
21
|
+
})));
|
|
22
|
+
group.command("create")
|
|
23
|
+
.requiredOption("--name <name>", "reflection name")
|
|
24
|
+
.option("--description <text>", "short description")
|
|
25
|
+
.option("--style <text>", "response style")
|
|
26
|
+
.option("--topics <items>", "comma-separated allowed topics")
|
|
27
|
+
.option("--refusal-message <text>", "reply used outside allowed topics")
|
|
28
|
+
.option("--knowledge <text>", "knowledge content")
|
|
29
|
+
.option("--policy <text>", "policy content")
|
|
30
|
+
.option("--examples <text>", "examples content")
|
|
31
|
+
.action(async (opts) => {
|
|
32
|
+
const { topics, ...body } = opts;
|
|
33
|
+
output(await call({
|
|
34
|
+
method: "POST",
|
|
35
|
+
path: "/api/v1/shadow/reflections",
|
|
36
|
+
body: { ...body, ...(topics === undefined ? {} : { allowedTopics: list(topics) }) },
|
|
37
|
+
asUserId: actingUserId(program),
|
|
38
|
+
}));
|
|
39
|
+
});
|
|
40
|
+
group.command("update")
|
|
41
|
+
.requiredOption("--reflection <id>", "reflection id")
|
|
42
|
+
.option("--name <name>", "reflection name")
|
|
43
|
+
.option("--description <text>", "short description")
|
|
44
|
+
.option("--style <text>", "response style")
|
|
45
|
+
.option("--topics <items>", "comma-separated allowed topics")
|
|
46
|
+
.option("--refusal-message <text>", "reply used outside allowed topics")
|
|
47
|
+
.option("--knowledge <text>", "knowledge content")
|
|
48
|
+
.option("--policy <text>", "policy content")
|
|
49
|
+
.option("--examples <text>", "examples content")
|
|
50
|
+
.action(async (opts) => {
|
|
51
|
+
const { reflection, topics, ...body } = opts;
|
|
52
|
+
output(await call({
|
|
53
|
+
method: "PATCH",
|
|
54
|
+
path: `/api/v1/shadow/reflections/${encodeURIComponent(reflection)}`,
|
|
55
|
+
body: { ...body, ...(topics === undefined ? {} : { allowedTopics: list(topics) }) },
|
|
56
|
+
asUserId: actingUserId(program),
|
|
57
|
+
}));
|
|
58
|
+
});
|
|
59
|
+
group.command("activate")
|
|
60
|
+
.requiredOption("--reflection <id>", "reflection id")
|
|
61
|
+
.action(async (opts) => output(await call({
|
|
62
|
+
method: "POST",
|
|
63
|
+
path: `/api/v1/shadow/reflections/${encodeURIComponent(opts.reflection)}/activate`,
|
|
64
|
+
asUserId: actingUserId(program),
|
|
65
|
+
})));
|
|
66
|
+
group.command("install")
|
|
67
|
+
.requiredOption("--reflection <id>", "reflection id")
|
|
68
|
+
.requiredOption("--room <roomId>", "target room id")
|
|
69
|
+
.action(async (opts) => output(await call({
|
|
70
|
+
method: "POST",
|
|
71
|
+
path: `/api/v1/shadow/reflections/${encodeURIComponent(opts.reflection)}/install`,
|
|
72
|
+
body: { roomId: opts.room },
|
|
73
|
+
asUserId: actingUserId(program),
|
|
74
|
+
})));
|
|
75
|
+
group.command("set-price")
|
|
76
|
+
.requiredOption("--reflection <id>", "reflection id")
|
|
77
|
+
.requiredOption("--multiplier <n>", "price multiplier")
|
|
78
|
+
.option("--subsidy-daily-cap <n>", "daily subsidy cap in credits")
|
|
79
|
+
.action(async (opts) => output(await call({
|
|
80
|
+
method: "PATCH",
|
|
81
|
+
path: `/api/v1/shadow/reflections/${encodeURIComponent(opts.reflection)}/price`,
|
|
82
|
+
body: {
|
|
83
|
+
multiplier: Number(opts.multiplier),
|
|
84
|
+
...(opts.subsidyDailyCap === undefined ? {} : { subsidyDailyCapCredits: Number(opts.subsidyDailyCap) }),
|
|
85
|
+
},
|
|
86
|
+
asUserId: actingUserId(program),
|
|
87
|
+
})));
|
|
88
|
+
group.command("delete")
|
|
89
|
+
.requiredOption("--reflection <id>", "reflection id")
|
|
90
|
+
.action(async (opts) => output(await call({
|
|
91
|
+
method: "DELETE",
|
|
92
|
+
path: `/api/v1/shadow/reflections/${encodeURIComponent(opts.reflection)}`,
|
|
93
|
+
asUserId: actingUserId(program),
|
|
94
|
+
})));
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=reflections.js.map
|
package/dist/commands/rooms.js
CHANGED
|
@@ -124,13 +124,17 @@ function inviteResolution(inviteToken, room) {
|
|
|
124
124
|
room: normalized,
|
|
125
125
|
};
|
|
126
126
|
}
|
|
127
|
-
function applyRoomVisibility(body, opts) {
|
|
127
|
+
export function applyRoomVisibility(body, opts) {
|
|
128
128
|
if (opts.public && opts.private)
|
|
129
129
|
throw new Error("Use either --public or --private, not both");
|
|
130
|
-
if (opts.public)
|
|
130
|
+
if (opts.public) {
|
|
131
131
|
body.isPublic = true;
|
|
132
|
-
|
|
132
|
+
body.accessLevel = "open";
|
|
133
|
+
}
|
|
134
|
+
if (opts.private) {
|
|
133
135
|
body.isPublic = false;
|
|
136
|
+
body.accessLevel = "inviteOnly";
|
|
137
|
+
}
|
|
134
138
|
}
|
|
135
139
|
function addRoomShapeOptions(command, opts = {}) {
|
|
136
140
|
let withOptions = command;
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { call } from "../../client.js";
|
|
2
|
+
import { actingUserId } from "../../auth.js";
|
|
3
|
+
import { getOrCreateGroup, hasSubcommand } from "../_helpers.js";
|
|
4
|
+
export function register(program) {
|
|
5
|
+
const group = getOrCreateGroup(program, "agent-feedback-admin", "AgentFeedbackAdmin operations");
|
|
6
|
+
if (!hasSubcommand(group, "list")) {
|
|
7
|
+
group
|
|
8
|
+
.command("list")
|
|
9
|
+
.description("GET /api/v1/admin/agent-feedback")
|
|
10
|
+
.option("--status <value>", "string")
|
|
11
|
+
.option("--agent-id <value>", "string")
|
|
12
|
+
.option("--hermes-tenant-id <value>", "string")
|
|
13
|
+
.option("--brand <value>", "string")
|
|
14
|
+
.option("--cursor <value>", "string")
|
|
15
|
+
.option("--limit <value>", "string")
|
|
16
|
+
.action(async (opts) => {
|
|
17
|
+
const as = actingUserId(program);
|
|
18
|
+
const path = `/api/v1/admin/agent-feedback`;
|
|
19
|
+
const query = {};
|
|
20
|
+
{
|
|
21
|
+
const v = opts.status;
|
|
22
|
+
if (v !== undefined)
|
|
23
|
+
query["status"] = v;
|
|
24
|
+
}
|
|
25
|
+
{
|
|
26
|
+
const v = opts.agentId;
|
|
27
|
+
if (v !== undefined)
|
|
28
|
+
query["agentId"] = v;
|
|
29
|
+
}
|
|
30
|
+
{
|
|
31
|
+
const v = opts.hermesTenantId;
|
|
32
|
+
if (v !== undefined)
|
|
33
|
+
query["hermesTenantId"] = v;
|
|
34
|
+
}
|
|
35
|
+
{
|
|
36
|
+
const v = opts.brand;
|
|
37
|
+
if (v !== undefined)
|
|
38
|
+
query["brand"] = v;
|
|
39
|
+
}
|
|
40
|
+
{
|
|
41
|
+
const v = opts.cursor;
|
|
42
|
+
if (v !== undefined)
|
|
43
|
+
query["cursor"] = v;
|
|
44
|
+
}
|
|
45
|
+
{
|
|
46
|
+
const v = opts.limit;
|
|
47
|
+
if (v !== undefined)
|
|
48
|
+
query["limit"] = v;
|
|
49
|
+
}
|
|
50
|
+
const data = await call({ method: "GET", path, asUserId: as, query });
|
|
51
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (!hasSubcommand(group, "update")) {
|
|
55
|
+
group
|
|
56
|
+
.command("update")
|
|
57
|
+
.description("PATCH /api/v1/admin/agent-feedback/{id}")
|
|
58
|
+
.requiredOption("--id <value>", "id (path param)")
|
|
59
|
+
.option("--body <json>", "request body as JSON (no schema available)")
|
|
60
|
+
.action(async (opts) => {
|
|
61
|
+
const as = actingUserId(program);
|
|
62
|
+
const path = `/api/v1/admin/agent-feedback/${encodeURIComponent(opts.id)}`;
|
|
63
|
+
const body = opts.body ? JSON.parse(opts.body) : undefined;
|
|
64
|
+
const data = await call({ method: "PATCH", path, asUserId: as, body });
|
|
65
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=agent-feedback-admin.js.map
|
|
@@ -9,6 +9,7 @@ export function register(program) {
|
|
|
9
9
|
.description("POST /api/v1/rooms/{roomId}/ai/ask")
|
|
10
10
|
.requiredOption("--room-id <value>", "roomId (path param)")
|
|
11
11
|
.requiredOption("--query <value>", "string, ≤500 chars")
|
|
12
|
+
.option("--call-id <value>", "string, ≤64 chars")
|
|
12
13
|
.action(async (opts) => {
|
|
13
14
|
const as = actingUserId(program);
|
|
14
15
|
const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/ai/ask`;
|
|
@@ -18,6 +19,36 @@ export function register(program) {
|
|
|
18
19
|
if (v !== undefined)
|
|
19
20
|
body["query"] = v;
|
|
20
21
|
}
|
|
22
|
+
{
|
|
23
|
+
const v = opts.callId;
|
|
24
|
+
if (v !== undefined)
|
|
25
|
+
body["callId"] = v;
|
|
26
|
+
}
|
|
27
|
+
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
28
|
+
process.stdout.write(JSON.stringify(data) + "\n");
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
if (!hasSubcommand(group, "ask-shadow")) {
|
|
32
|
+
group
|
|
33
|
+
.command("ask-shadow")
|
|
34
|
+
.description("POST /api/v1/rooms/{roomId}/ai/ask-shadow")
|
|
35
|
+
.requiredOption("--room-id <value>", "roomId (path param)")
|
|
36
|
+
.requiredOption("--query <value>", "string, ≤500 chars")
|
|
37
|
+
.option("--call-id <value>", "string, ≤64 chars")
|
|
38
|
+
.action(async (opts) => {
|
|
39
|
+
const as = actingUserId(program);
|
|
40
|
+
const path = `/api/v1/rooms/${encodeURIComponent(opts.roomId)}/ai/ask-shadow`;
|
|
41
|
+
const body = {};
|
|
42
|
+
{
|
|
43
|
+
const v = opts.query;
|
|
44
|
+
if (v !== undefined)
|
|
45
|
+
body["query"] = v;
|
|
46
|
+
}
|
|
47
|
+
{
|
|
48
|
+
const v = opts.callId;
|
|
49
|
+
if (v !== undefined)
|
|
50
|
+
body["callId"] = v;
|
|
51
|
+
}
|
|
21
52
|
const data = await call({ method: "POST", path, asUserId: as, body });
|
|
22
53
|
process.stdout.write(JSON.stringify(data) + "\n");
|
|
23
54
|
});
|