@dench.com/cli 2.8.7-staging.189 → 2.8.8-staging.192
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/crm-comments.ts +303 -0
- package/crm.ts +46 -7
- package/dench.ts +44 -0
- package/lib/api-schemas.ts +325 -1
- package/lib/command-registry.ts +303 -0
- package/lib/enrichment-gateway.ts +21 -1
- package/notifications.ts +262 -0
- package/package.json +3 -1
|
@@ -240,6 +240,12 @@ export type ApolloPersonEnvelope = {
|
|
|
240
240
|
title?: string;
|
|
241
241
|
headline?: string;
|
|
242
242
|
location?: string;
|
|
243
|
+
organization?: {
|
|
244
|
+
name?: string;
|
|
245
|
+
domain?: string;
|
|
246
|
+
website_url?: string;
|
|
247
|
+
};
|
|
248
|
+
organization_name?: string;
|
|
243
249
|
contact: {
|
|
244
250
|
phone_numbers: Array<{
|
|
245
251
|
sanitized_number: string;
|
|
@@ -258,6 +264,9 @@ type EnrichedPersonUpsertLike = {
|
|
|
258
264
|
currentTitle?: string;
|
|
259
265
|
linkedinID?: string;
|
|
260
266
|
location?: string;
|
|
267
|
+
locationName?: string;
|
|
268
|
+
currentCompanyName?: string;
|
|
269
|
+
currentCompanyWebsite?: string;
|
|
261
270
|
emails?: Array<{
|
|
262
271
|
address?: string;
|
|
263
272
|
normalizedAddress?: string;
|
|
@@ -272,6 +281,8 @@ function shapePersonToApolloEnvelope(
|
|
|
272
281
|
const workEmail = u.emails?.find((e) => e.type === "work")?.address;
|
|
273
282
|
const personalEmail = u.emails?.find((e) => e.type === "personal")?.address;
|
|
274
283
|
const anyEmail = u.emails?.[0]?.address;
|
|
284
|
+
const companyName = u.currentCompanyName;
|
|
285
|
+
const companyWebsite = u.currentCompanyWebsite;
|
|
275
286
|
return {
|
|
276
287
|
person: {
|
|
277
288
|
name: u.fullName,
|
|
@@ -285,7 +296,16 @@ function shapePersonToApolloEnvelope(
|
|
|
285
296
|
: undefined,
|
|
286
297
|
title: u.currentTitle,
|
|
287
298
|
headline: u.headline,
|
|
288
|
-
location: u.location,
|
|
299
|
+
location: u.location ?? u.locationName,
|
|
300
|
+
organization:
|
|
301
|
+
companyName || companyWebsite
|
|
302
|
+
? {
|
|
303
|
+
name: companyName,
|
|
304
|
+
domain: companyWebsite,
|
|
305
|
+
website_url: companyWebsite,
|
|
306
|
+
}
|
|
307
|
+
: undefined,
|
|
308
|
+
organization_name: companyName,
|
|
289
309
|
contact: {
|
|
290
310
|
phone_numbers: (u.phones ?? []).map((p) => ({
|
|
291
311
|
sanitized_number: p.normalizedNumber ?? p.number ?? "",
|
package/notifications.ts
ADDED
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dench notifications <subcommand>` — the signed-in member's inbox (what
|
|
3
|
+
* the bell in the workspace rail shows), from the command line.
|
|
4
|
+
*
|
|
5
|
+
* Reads `functions/notifications:*` with the same bearer every other CLI
|
|
6
|
+
* surface forwards (`dch_agent_*` session or a Dench API key). Agent
|
|
7
|
+
* sessions are user-bound and read their owner's inbox; an org API key
|
|
8
|
+
* with no person behind it is refused by the backend.
|
|
9
|
+
*
|
|
10
|
+
* dench notifications list [--filter inbox|unread|archived|all] [--limit N] [--json]
|
|
11
|
+
* dench notifications count
|
|
12
|
+
* dench notifications read <id> [--unread]
|
|
13
|
+
* dench notifications read-all
|
|
14
|
+
* dench notifications archive <id>
|
|
15
|
+
* dench notifications prefs [--set key=value]...
|
|
16
|
+
*/
|
|
17
|
+
import type { ConvexHttpClient } from "convex/browser";
|
|
18
|
+
import { makeFunctionReference } from "convex/server";
|
|
19
|
+
import {
|
|
20
|
+
assertNoUnknownFlags,
|
|
21
|
+
CliArgError,
|
|
22
|
+
getFlag,
|
|
23
|
+
hasFlag,
|
|
24
|
+
shift,
|
|
25
|
+
} from "./lib/cli-args";
|
|
26
|
+
|
|
27
|
+
class NotificationsCliError extends Error {}
|
|
28
|
+
|
|
29
|
+
type Ctx = {
|
|
30
|
+
convex: ConvexHttpClient;
|
|
31
|
+
args: string[];
|
|
32
|
+
jsonOutput: boolean;
|
|
33
|
+
sessionToken?: string;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const api = {
|
|
37
|
+
list: makeFunctionReference<"query">(
|
|
38
|
+
"functions/notifications:listMyNotifications",
|
|
39
|
+
),
|
|
40
|
+
unreadCount: makeFunctionReference<"query">(
|
|
41
|
+
"functions/notifications:unreadCount",
|
|
42
|
+
),
|
|
43
|
+
setRead: makeFunctionReference<"mutation">(
|
|
44
|
+
"functions/notifications:setNotificationRead",
|
|
45
|
+
),
|
|
46
|
+
markAllRead: makeFunctionReference<"mutation">(
|
|
47
|
+
"functions/notifications:markAllRead",
|
|
48
|
+
),
|
|
49
|
+
archive: makeFunctionReference<"mutation">(
|
|
50
|
+
"functions/notifications:archiveNotification",
|
|
51
|
+
),
|
|
52
|
+
getPrefs: makeFunctionReference<"query">(
|
|
53
|
+
"functions/notifications:getMyNotificationPreferences",
|
|
54
|
+
),
|
|
55
|
+
updatePrefs: makeFunctionReference<"mutation">(
|
|
56
|
+
"functions/notifications:updateMyNotificationPreferences",
|
|
57
|
+
),
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const FILTERS = new Set(["inbox", "unread", "archived", "all"]);
|
|
61
|
+
|
|
62
|
+
const HELP = `dench notifications — your notification inbox
|
|
63
|
+
|
|
64
|
+
dench notifications list [--filter inbox|unread|archived|all] [--limit N] [--json]
|
|
65
|
+
dench notifications count
|
|
66
|
+
dench notifications read <notificationId> [--unread]
|
|
67
|
+
dench notifications read-all
|
|
68
|
+
dench notifications archive <notificationId>
|
|
69
|
+
dench notifications prefs
|
|
70
|
+
dench notifications prefs --set emailEnabled=false --set entry_activity.email=false
|
|
71
|
+
|
|
72
|
+
Kinds: mention, comment_reply, comment_reaction, entry_activity, dench_reply,
|
|
73
|
+
task_assigned, task_completed, date_reminder, automation, system.
|
|
74
|
+
Comment links carry ?entry=<object>:<id>&comment=<commentId>.`;
|
|
75
|
+
|
|
76
|
+
function withToken(ctx: Ctx, args: Record<string, unknown>) {
|
|
77
|
+
return {
|
|
78
|
+
...args,
|
|
79
|
+
...(ctx.sessionToken ? { sessionToken: ctx.sessionToken } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function out(_ctx: Ctx, value: unknown) {
|
|
84
|
+
console.log(JSON.stringify(value, null, 2));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function toCliError(error: unknown): Error {
|
|
88
|
+
if (error instanceof CliArgError)
|
|
89
|
+
return new NotificationsCliError(error.message);
|
|
90
|
+
const data = (error as { data?: unknown } | null)?.data;
|
|
91
|
+
if (typeof data === "string" && data.trim()) {
|
|
92
|
+
return new NotificationsCliError(data.trim());
|
|
93
|
+
}
|
|
94
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** `--set a=b --set c.d=false` → nested preference patch. */
|
|
98
|
+
function parsePrefSets(args: string[]): Record<string, unknown> {
|
|
99
|
+
const patch: Record<string, unknown> = {};
|
|
100
|
+
const perKind: Record<string, { inApp?: boolean; email?: boolean }> = {};
|
|
101
|
+
for (;;) {
|
|
102
|
+
const raw = getFlag(args, "--set");
|
|
103
|
+
if (raw === undefined) break;
|
|
104
|
+
const eq = raw.indexOf("=");
|
|
105
|
+
if (eq <= 0)
|
|
106
|
+
throw new NotificationsCliError(`--set expects key=value, got "${raw}"`);
|
|
107
|
+
const key = raw.slice(0, eq).trim();
|
|
108
|
+
const value = raw.slice(eq + 1).trim();
|
|
109
|
+
const bool =
|
|
110
|
+
value === "true" ? true : value === "false" ? false : undefined;
|
|
111
|
+
const dot = key.indexOf(".");
|
|
112
|
+
if (dot > 0) {
|
|
113
|
+
const kind = key.slice(0, dot);
|
|
114
|
+
const channel = key.slice(dot + 1);
|
|
115
|
+
if ((channel !== "inApp" && channel !== "email") || bool === undefined) {
|
|
116
|
+
throw new NotificationsCliError(
|
|
117
|
+
`--set ${key}: use <kind>.inApp=true|false or <kind>.email=true|false`,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
perKind[kind] = { ...(perKind[kind] ?? {}), [channel]: bool };
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
switch (key) {
|
|
124
|
+
case "inAppEnabled":
|
|
125
|
+
case "emailEnabled":
|
|
126
|
+
if (bool === undefined) {
|
|
127
|
+
throw new NotificationsCliError(`--set ${key} expects true|false`);
|
|
128
|
+
}
|
|
129
|
+
patch[key] = bool;
|
|
130
|
+
break;
|
|
131
|
+
case "digestWindowMinutes":
|
|
132
|
+
case "maxEmailsPerDay": {
|
|
133
|
+
const n = Number.parseInt(value, 10);
|
|
134
|
+
if (!Number.isFinite(n)) {
|
|
135
|
+
throw new NotificationsCliError(`--set ${key} expects an integer`);
|
|
136
|
+
}
|
|
137
|
+
patch[key] = n;
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
case "timezone":
|
|
141
|
+
patch.timezone = value;
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
throw new NotificationsCliError(
|
|
145
|
+
`Unknown preference "${key}". Use inAppEnabled, emailEnabled, digestWindowMinutes, maxEmailsPerDay, timezone, or <kind>.inApp / <kind>.email.`,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
if (Object.keys(perKind).length > 0) patch.perKind = perKind;
|
|
150
|
+
return patch;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
export async function runNotificationsCommand(opts: {
|
|
154
|
+
convex: ConvexHttpClient;
|
|
155
|
+
args: string[];
|
|
156
|
+
sessionToken?: string;
|
|
157
|
+
}): Promise<void> {
|
|
158
|
+
const args = [...opts.args];
|
|
159
|
+
const ctx: Ctx = {
|
|
160
|
+
convex: opts.convex,
|
|
161
|
+
args,
|
|
162
|
+
jsonOutput: hasFlag(args, "--json"),
|
|
163
|
+
sessionToken: opts.sessionToken,
|
|
164
|
+
};
|
|
165
|
+
const verb = args.shift();
|
|
166
|
+
if (!verb || verb === "help" || verb === "--help") {
|
|
167
|
+
console.log(HELP);
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
switch (verb) {
|
|
172
|
+
case "list": {
|
|
173
|
+
const filter = getFlag(args, "--filter") ?? "inbox";
|
|
174
|
+
if (!FILTERS.has(filter)) {
|
|
175
|
+
throw new NotificationsCliError(
|
|
176
|
+
"--filter must be inbox, unread, archived, or all",
|
|
177
|
+
);
|
|
178
|
+
}
|
|
179
|
+
const rawLimit = getFlag(args, "--limit");
|
|
180
|
+
const limit = rawLimit ? Number.parseInt(rawLimit, 10) : undefined;
|
|
181
|
+
assertNoUnknownFlags(args, "notifications list");
|
|
182
|
+
out(
|
|
183
|
+
ctx,
|
|
184
|
+
await ctx.convex.query(
|
|
185
|
+
api.list,
|
|
186
|
+
withToken(ctx, {
|
|
187
|
+
filter,
|
|
188
|
+
...(limit && Number.isFinite(limit) ? { limit } : {}),
|
|
189
|
+
}) as never,
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
case "count": {
|
|
195
|
+
assertNoUnknownFlags(args, "notifications count");
|
|
196
|
+
const count = await ctx.convex.query(
|
|
197
|
+
api.unreadCount,
|
|
198
|
+
withToken(ctx, {}) as never,
|
|
199
|
+
);
|
|
200
|
+
out(ctx, { unread: count });
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
case "read": {
|
|
204
|
+
const notificationId = shift(args, "notification id");
|
|
205
|
+
const unread = hasFlag(args, "--unread");
|
|
206
|
+
assertNoUnknownFlags(args, "notifications read");
|
|
207
|
+
out(
|
|
208
|
+
ctx,
|
|
209
|
+
await ctx.convex.mutation(
|
|
210
|
+
api.setRead,
|
|
211
|
+
withToken(ctx, { notificationId, read: !unread }) as never,
|
|
212
|
+
),
|
|
213
|
+
);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
case "read-all": {
|
|
217
|
+
assertNoUnknownFlags(args, "notifications read-all");
|
|
218
|
+
out(
|
|
219
|
+
ctx,
|
|
220
|
+
await ctx.convex.mutation(
|
|
221
|
+
api.markAllRead,
|
|
222
|
+
withToken(ctx, {}) as never,
|
|
223
|
+
),
|
|
224
|
+
);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
case "archive": {
|
|
228
|
+
const notificationId = shift(args, "notification id");
|
|
229
|
+
assertNoUnknownFlags(args, "notifications archive");
|
|
230
|
+
out(
|
|
231
|
+
ctx,
|
|
232
|
+
await ctx.convex.mutation(
|
|
233
|
+
api.archive,
|
|
234
|
+
withToken(ctx, { notificationId }) as never,
|
|
235
|
+
),
|
|
236
|
+
);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
case "prefs": {
|
|
240
|
+
const patch = parsePrefSets(args);
|
|
241
|
+
assertNoUnknownFlags(args, "notifications prefs");
|
|
242
|
+
if (Object.keys(patch).length > 0) {
|
|
243
|
+
await ctx.convex.mutation(
|
|
244
|
+
api.updatePrefs,
|
|
245
|
+
withToken(ctx, patch) as never,
|
|
246
|
+
);
|
|
247
|
+
}
|
|
248
|
+
out(
|
|
249
|
+
ctx,
|
|
250
|
+
await ctx.convex.query(api.getPrefs, withToken(ctx, {}) as never),
|
|
251
|
+
);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
default:
|
|
255
|
+
throw new NotificationsCliError(
|
|
256
|
+
`Unknown notifications verb: ${verb}. Use list | count | read | read-all | archive | prefs.`,
|
|
257
|
+
);
|
|
258
|
+
}
|
|
259
|
+
} catch (error) {
|
|
260
|
+
throw toCliError(error);
|
|
261
|
+
}
|
|
262
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dench.com/cli",
|
|
3
|
-
"version": "2.8.
|
|
3
|
+
"version": "2.8.8-staging.192",
|
|
4
4
|
"description": "Dench agent workspace CLI.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,10 @@
|
|
|
25
25
|
"chat.ts",
|
|
26
26
|
"chat-spawn.ts",
|
|
27
27
|
"crm.ts",
|
|
28
|
+
"crm-comments.ts",
|
|
28
29
|
"email.ts",
|
|
29
30
|
"members.ts",
|
|
31
|
+
"notifications.ts",
|
|
30
32
|
"cron.ts",
|
|
31
33
|
"browser.ts",
|
|
32
34
|
"ads.ts",
|