@dench.com/cli 0.4.8 → 2.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.
- package/README.md +1 -1
- package/agent-config.ts +645 -0
- package/crm.ts +74 -8
- package/dench.ts +662 -171
- package/host.ts +3 -3
- package/lib/enrichment-gateway.ts +1080 -42
- package/members.ts +192 -0
- package/package.json +4 -2
package/members.ts
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dench members <subcommand>` — workspace member roster for CRM
|
|
3
|
+
* `user`-type fields (and anyone else who needs to look up who is in
|
|
4
|
+
* the org from the command line).
|
|
5
|
+
*
|
|
6
|
+
* Why this exists:
|
|
7
|
+
*
|
|
8
|
+
* CRM `user`-typed cells store a Convex `users` doc id. The UI
|
|
9
|
+
* resolves that id to a real avatar + name against the org's member
|
|
10
|
+
* roster, which the browser SPA fetches via
|
|
11
|
+
* `api.functions.organizations.listMembers`. That query is wired to
|
|
12
|
+
* `getAuthUserId(ctx)` (Convex Auth cookies), so it's invisible to
|
|
13
|
+
* the dench-cli running in a sandbox under `DENCH_API_KEY` or a
|
|
14
|
+
* `dch_agent_*` session token from `dench login`.
|
|
15
|
+
*
|
|
16
|
+
* Before this command existed, the AI agent had no way to discover
|
|
17
|
+
* userIds and would write display names as strings into user-typed
|
|
18
|
+
* cells, which the UI then rendered as unlinked badges. This module
|
|
19
|
+
* pairs with the new `crm/members.list` Convex query (which
|
|
20
|
+
* authenticates via `requireCrmAccess` and accepts every CLI bearer)
|
|
21
|
+
* to give the AI a way to look up `{ userId, name, email, role }` for
|
|
22
|
+
* every member of the org and assign cells correctly.
|
|
23
|
+
*
|
|
24
|
+
* Surfaces:
|
|
25
|
+
* dench members list (top-level, this module)
|
|
26
|
+
* dench crm members list (alias dispatched from cli/crm.ts so the
|
|
27
|
+
* command is also discoverable from
|
|
28
|
+
* inside the `dench crm` namespace, which
|
|
29
|
+
* is what the seed skill points the AI
|
|
30
|
+
* at most of the time)
|
|
31
|
+
*
|
|
32
|
+
* Both surfaces share this implementation so the CLI auth + payload
|
|
33
|
+
* shape stays in lockstep.
|
|
34
|
+
*/
|
|
35
|
+
import { ConvexHttpClient } from "convex/browser";
|
|
36
|
+
import { makeFunctionReference } from "convex/server";
|
|
37
|
+
import { CliArgError, hasFlag } from "./lib/cli-args";
|
|
38
|
+
|
|
39
|
+
class MembersCliError extends Error {}
|
|
40
|
+
|
|
41
|
+
type MembersCliContext = {
|
|
42
|
+
convex: ConvexHttpClient;
|
|
43
|
+
args: string[];
|
|
44
|
+
jsonOutput: boolean;
|
|
45
|
+
/**
|
|
46
|
+
* Bearer forwarded with every Convex call so the server-side
|
|
47
|
+
* `requireCrmAccess` helper can resolve the caller's organization
|
|
48
|
+
* without a Convex Auth cookie. Two accepted formats:
|
|
49
|
+
* • `dch_agent_*` agent session token minted by `dench login`.
|
|
50
|
+
* • Unified Dench API key (`dench_pk_*`, `dench_live_*`, etc.)
|
|
51
|
+
* baked into sandbox envVars as `DENCH_API_KEY`.
|
|
52
|
+
*/
|
|
53
|
+
sessionToken?: string;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
type MemberRow = {
|
|
57
|
+
userId: string;
|
|
58
|
+
name: string | null;
|
|
59
|
+
email: string | null;
|
|
60
|
+
image: string | null;
|
|
61
|
+
role: "ADMIN" | "MEMBER";
|
|
62
|
+
joinedAt: number;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
type ListMembersResult = {
|
|
66
|
+
members: MemberRow[];
|
|
67
|
+
organizationId: string;
|
|
68
|
+
organizationSlug: string | null;
|
|
69
|
+
organizationName: string;
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
const api = {
|
|
73
|
+
members: {
|
|
74
|
+
list: makeFunctionReference<"query">("functions/crm/members:list"),
|
|
75
|
+
},
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
export async function runMembersCommand(opts: {
|
|
79
|
+
convex: ConvexHttpClient;
|
|
80
|
+
args: string[];
|
|
81
|
+
sessionToken?: string;
|
|
82
|
+
}): Promise<void> {
|
|
83
|
+
const args = [...opts.args];
|
|
84
|
+
const jsonOutput = hasFlag(args, "--json");
|
|
85
|
+
const ctx: MembersCliContext = {
|
|
86
|
+
convex: opts.convex,
|
|
87
|
+
args,
|
|
88
|
+
jsonOutput,
|
|
89
|
+
sessionToken: opts.sessionToken,
|
|
90
|
+
};
|
|
91
|
+
const subcommand = args.shift();
|
|
92
|
+
if (!subcommand || subcommand === "help" || subcommand === "--help") {
|
|
93
|
+
membersHelp();
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
switch (subcommand) {
|
|
97
|
+
case "list":
|
|
98
|
+
return await runListMembers(ctx);
|
|
99
|
+
default:
|
|
100
|
+
throw new MembersCliError(
|
|
101
|
+
`Unknown members subcommand: ${subcommand}. Run \`dench members help\`.`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runListMembers(ctx: MembersCliContext): Promise<void> {
|
|
107
|
+
if (ctx.args.some((arg) => arg.startsWith("--"))) {
|
|
108
|
+
// No subflags supported beyond --json (which hasFlag already drained).
|
|
109
|
+
// Surface a typo with a useful hint so the AI doesn't silently get a
|
|
110
|
+
// full list when it asked for a filter that doesn't exist.
|
|
111
|
+
throw new MembersCliError(
|
|
112
|
+
`Unknown flag: ${ctx.args.find((arg) => arg.startsWith("--"))}. \`dench members list\` only accepts --json.`,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
const result = (await callQuery(ctx, api.members.list, {})) as
|
|
116
|
+
| ListMembersResult
|
|
117
|
+
| null
|
|
118
|
+
| undefined;
|
|
119
|
+
if (ctx.jsonOutput) {
|
|
120
|
+
console.log(JSON.stringify(result ?? { members: [] }, null, 2));
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
const members = result?.members ?? [];
|
|
124
|
+
if (members.length === 0) {
|
|
125
|
+
console.log("(no members)");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// Plain table for human consumption — userIds are first column so the
|
|
129
|
+
// operator can copy/paste directly into a `dench crm cells set ...
|
|
130
|
+
// --field Owner --value <userId>` follow-up.
|
|
131
|
+
const headers = ["userId", "role", "name", "email"];
|
|
132
|
+
const rows = members.map((m) => [
|
|
133
|
+
m.userId,
|
|
134
|
+
m.role,
|
|
135
|
+
m.name ?? "",
|
|
136
|
+
m.email ?? "",
|
|
137
|
+
]);
|
|
138
|
+
const widths = headers.map((header, idx) =>
|
|
139
|
+
Math.max(header.length, ...rows.map((row) => row[idx].length)),
|
|
140
|
+
);
|
|
141
|
+
const formatRow = (row: string[]) =>
|
|
142
|
+
row.map((cell, idx) => cell.padEnd(widths[idx])).join(" ");
|
|
143
|
+
console.log(formatRow(headers));
|
|
144
|
+
console.log(widths.map((w) => "-".repeat(w)).join(" "));
|
|
145
|
+
for (const row of rows) {
|
|
146
|
+
console.log(formatRow(row));
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function callQuery(
|
|
151
|
+
ctx: MembersCliContext,
|
|
152
|
+
fn: Parameters<ConvexHttpClient["query"]>[0],
|
|
153
|
+
args: Record<string, unknown> = {},
|
|
154
|
+
): Promise<unknown> {
|
|
155
|
+
try {
|
|
156
|
+
return await ctx.convex.query(fn, {
|
|
157
|
+
...args,
|
|
158
|
+
...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
|
|
159
|
+
} as never);
|
|
160
|
+
} catch (error) {
|
|
161
|
+
if (error instanceof CliArgError) throw new MembersCliError(error.message);
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function membersHelp(): void {
|
|
167
|
+
console.log(`Usage: dench members <subcommand>
|
|
168
|
+
|
|
169
|
+
Workspace members for the active organization. Use these userIds when
|
|
170
|
+
assigning CRM \`user\`-type cells (e.g. \`Owner\`, \`Assignee\`) so the
|
|
171
|
+
UI shows a real linked avatar instead of an unlinked literal string.
|
|
172
|
+
|
|
173
|
+
Subcommands:
|
|
174
|
+
dench members list [--json]
|
|
175
|
+
List every member of the current org as \`userId / role / name / email\`.
|
|
176
|
+
Pass --json for a machine-readable shape that includes
|
|
177
|
+
organizationId / organizationSlug / organizationName.
|
|
178
|
+
|
|
179
|
+
Alias:
|
|
180
|
+
dench crm members list [--json]
|
|
181
|
+
Same surface, dispatched from inside the \`dench crm\` namespace
|
|
182
|
+
since the canonical use case is assigning CRM \`user\` fields.
|
|
183
|
+
|
|
184
|
+
After picking a userId:
|
|
185
|
+
dench crm cells set task --entry <entryId> --field Owner --value <userId>
|
|
186
|
+
dench crm entries create task --data '{"Name":"Ship CRM","Owner":"<userId>"}'
|
|
187
|
+
|
|
188
|
+
The server best-effort-resolves a name / email passed to a \`user\`
|
|
189
|
+
field when the match against the roster is unique, but the explicit
|
|
190
|
+
lookup via \`dench members list\` is the correct pattern.
|
|
191
|
+
`);
|
|
192
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Dench agent workspace CLI.",
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"description": "Dench agent workspace CLI. v2 unifies auth behind `dench signin`; the legacy `dench login` / `dench onboard` / `dench setup` / `dench what-can-i-do` / `dench register` commands now error with a redirect.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"dench": "dench",
|
|
@@ -24,11 +24,13 @@
|
|
|
24
24
|
"chat.ts",
|
|
25
25
|
"chat-spawn.ts",
|
|
26
26
|
"crm.ts",
|
|
27
|
+
"members.ts",
|
|
27
28
|
"cron.ts",
|
|
28
29
|
"search.ts",
|
|
29
30
|
"image.ts",
|
|
30
31
|
"tools.ts",
|
|
31
32
|
"agentKind.ts",
|
|
33
|
+
"agent-config.ts",
|
|
32
34
|
"host.ts",
|
|
33
35
|
"openUrl.ts",
|
|
34
36
|
"session.ts",
|