@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.
@@ -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 ?? "",
@@ -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.7-staging.189",
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",