@dench.com/cli 3.10.1-staging.423 → 3.10.2-staging.428

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.
@@ -11,6 +11,7 @@ export type {
11
11
  } from "./enrichment-shapes";
12
12
 
13
13
  import { extractDomain } from "./crm-enrichment";
14
+ import { normalizeGatewayBaseUrl } from "./gateway-url";
14
15
 
15
16
  export type FetchLike = (
16
17
  input: string | URL,
@@ -274,7 +275,7 @@ export function resolveEnrichmentGatewayBaseUrl(
274
275
  env: EnrichmentGatewayEnv = process.env,
275
276
  ): string {
276
277
  const explicit = env.DENCH_GATEWAY_URL?.trim() || env.GATEWAY_URL?.trim();
277
- if (explicit) return explicit.replace(/\/+$/, "").replace(/\/v1$/, "");
278
+ if (explicit) return normalizeGatewayBaseUrl(explicit);
278
279
 
279
280
  const host = env.DENCH_HOST?.trim();
280
281
  if (host && /localhost|127\.0\.0\.1/.test(host)) {
@@ -1690,10 +1691,9 @@ async function callGatewayJson(
1690
1691
  }
1691
1692
 
1692
1693
  function resolveBaseUrl(options: EnrichmentGatewayOptions): string {
1693
- return (
1694
- options.baseUrl?.trim().replace(/\/+$/, "").replace(/\/v1$/, "") ??
1695
- resolveEnrichmentGatewayBaseUrl(options.env)
1696
- );
1694
+ return options.baseUrl != null
1695
+ ? normalizeGatewayBaseUrl(options.baseUrl.trim())
1696
+ : resolveEnrichmentGatewayBaseUrl(options.env);
1697
1697
  }
1698
1698
 
1699
1699
  async function buildGatewayError(
@@ -0,0 +1,12 @@
1
+ /** Remove a URL's trailing slashes in one pass, including long malformed input. */
2
+ export function stripTrailingSlashes(value: string): string {
3
+ let end = value.length;
4
+ while (end > 0 && value.charCodeAt(end - 1) === 47) end--;
5
+ return value.slice(0, end);
6
+ }
7
+
8
+ /** Gateway callers append their own /v1 path; accept either base URL form. */
9
+ export function normalizeGatewayBaseUrl(value: string): string {
10
+ const base = stripTrailingSlashes(value);
11
+ return base.endsWith("/v1") ? base.slice(0, -3) : base;
12
+ }
@@ -229,6 +229,44 @@ export function normalizeTranscript(value: unknown): ImportedSegment[] | null {
229
229
  return null;
230
230
  }
231
231
 
232
+ const LINE_BREAK = /[\r\n\u2028\u2029]/;
233
+
234
+ /** Inspect only the final owner delimiter instead of backtracking over the body. */
235
+ function actionItemOwner(
236
+ text: string,
237
+ ): { text: string; ownerName: string } | null {
238
+ if (text.endsWith(")")) {
239
+ const open = text.lastIndexOf("(");
240
+ const owner = text.slice(open + 1, -1);
241
+ const body = text.slice(0, open).trimEnd();
242
+ if (
243
+ open >= 0 &&
244
+ owner.length > 0 &&
245
+ owner.length <= 60 &&
246
+ !owner.includes(")") &&
247
+ !LINE_BREAK.test(body)
248
+ ) {
249
+ return { text: body.trim(), ownerName: owner.trim() };
250
+ }
251
+ }
252
+ for (let index = text.length - 1; index >= 0; index--) {
253
+ if (!"-–—".includes(text[index])) continue;
254
+ // Only the last dash can separate an owner: owners cannot contain dashes.
255
+ if (
256
+ !/\s/.test(text[index - 1] ?? "") ||
257
+ !/\s/.test(text[index + 1] ?? "")
258
+ ) {
259
+ return null;
260
+ }
261
+ const owner = text.slice(index + 1).trimStart();
262
+ const body = text.slice(0, index).trimEnd();
263
+ return owner.length > 0 && owner.length <= 60 && !LINE_BREAK.test(body)
264
+ ? { text: body.trim(), ownerName: owner.trim() }
265
+ : null;
266
+ }
267
+ return null;
268
+ }
269
+
232
270
  /**
233
271
  * `- [x] Send the deck (Hank)` / `Send the deck — Hank` / `{text, owner,
234
272
  * done}`. Markdown checkboxes carry completion; a trailing parenthesised
@@ -239,20 +277,18 @@ export function normalizeActionItem(value: unknown): ImportedActionItem | null {
239
277
  let text = value.trim().replace(/^[-*•]\s+/, "");
240
278
  if (!text) return null;
241
279
  let done: boolean | undefined;
242
- const box = text.match(/^\[([ xX])\]\s*(.*)$/);
243
- if (box) {
244
- done = box[1] !== " ";
245
- text = box[2].trim();
280
+ if (text[0] === "[" && " xX".includes(text[1] ?? "") && text[2] === "]") {
281
+ const body = text.slice(3).trimStart();
282
+ if (!LINE_BREAK.test(body)) {
283
+ done = text[1] !== " ";
284
+ text = body.trim();
285
+ }
246
286
  }
247
287
  let ownerName: string | null = null;
248
- const paren = text.match(/^(.*?)\s*\(([^()]{1,60})\)\s*$/);
249
- const dash = text.match(/^(.*?)\s+[-–—]\s+([^-–—]{1,60})$/);
250
- if (paren) {
251
- text = paren[1].trim();
252
- ownerName = paren[2].trim();
253
- } else if (dash) {
254
- text = dash[1].trim();
255
- ownerName = dash[2].trim();
288
+ const owner = actionItemOwner(text);
289
+ if (owner) {
290
+ text = owner.text;
291
+ ownerName = owner.ownerName;
256
292
  }
257
293
  if (!text) return null;
258
294
  return { text, ownerName, ...(done !== undefined ? { done } : {}) };
@@ -303,15 +339,28 @@ export function normalizeAttendee(value: unknown): ImportedAttendee | null {
303
339
  if (typeof value === "string") {
304
340
  const raw = value.trim();
305
341
  if (!raw) return null;
306
- const isHost = /\((?:host|organi[sz]er)\)\s*$/i.test(raw);
307
- const cleaned = raw.replace(/\s*\((?:host|organi[sz]er)\)\s*$/i, "");
308
- const angle = cleaned.match(/^(.*?)\s*<([^<>\s]+@[^<>\s]+)>$/);
309
- if (angle) {
310
- return {
311
- name: angle[1].trim() || null,
312
- email: angle[2].trim(),
313
- ...(isHost ? { isHost } : {}),
314
- };
342
+ const markerStart = raw.lastIndexOf("(");
343
+ const marker = raw.slice(markerStart).toLowerCase();
344
+ const isHost = ["(host)", "(organizer)", "(organiser)"].includes(marker);
345
+ const cleaned = isHost ? raw.slice(0, markerStart).trimEnd() : raw;
346
+ if (cleaned.endsWith(">")) {
347
+ const open = cleaned.lastIndexOf("<");
348
+ const email = cleaned.slice(open + 1, -1);
349
+ const at = email.indexOf("@", 1);
350
+ const name = cleaned.slice(0, open).trimEnd();
351
+ if (
352
+ open >= 0 &&
353
+ at >= 1 &&
354
+ at < email.length - 1 &&
355
+ !/[<>\s]/.test(email) &&
356
+ !LINE_BREAK.test(name)
357
+ ) {
358
+ return {
359
+ name: name.trim() || null,
360
+ email,
361
+ ...(isHost ? { isHost } : {}),
362
+ };
363
+ }
315
364
  }
316
365
  if (/^[^\s@]+@[^\s@]+$/.test(cleaned)) {
317
366
  return { name: null, email: cleaned, ...(isHost ? { isHost } : {}) };
@@ -342,9 +391,27 @@ export function normalizeAttendees(value: unknown): ImportedAttendee[] | null {
342
391
  // fall through
343
392
  }
344
393
  }
345
- // Commas inside `Name <email>` are unusual; semicolons and newlines
346
- // are the safer separators, but all three are accepted.
347
- return normalizeAttendees(trimmed.split(/\s*[;\n]\s*|,\s*(?![^<]*>)/));
394
+ // A comma splits unless the next angle bracket is a closing one. Track
395
+ // that bracket while scanning backwards instead of rescanning the tail
396
+ // for every comma. Semicolons and newlines always separate attendees.
397
+ const parts: string[] = [];
398
+ let end = trimmed.length;
399
+ let nextAngleIsClose = false;
400
+ for (let index = trimmed.length - 1; index >= 0; index--) {
401
+ const char = trimmed[index];
402
+ if (char === ">") nextAngleIsClose = true;
403
+ else if (char === "<") nextAngleIsClose = false;
404
+ else if (
405
+ char === ";" ||
406
+ char === "\n" ||
407
+ (char === "," && !nextAngleIsClose)
408
+ ) {
409
+ parts.push(trimmed.slice(index + 1, end));
410
+ end = index;
411
+ }
412
+ }
413
+ parts.push(trimmed.slice(0, end));
414
+ return normalizeAttendees(parts.reverse());
348
415
  }
349
416
  if (Array.isArray(value)) {
350
417
  return value
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dench.com/cli",
3
- "version": "3.10.1-staging.423",
3
+ "version": "3.10.2-staging.428",
4
4
  "description": "Dench agent workspace CLI.",
5
5
  "type": "module",
6
6
  "bin": {
package/search.ts CHANGED
@@ -36,6 +36,7 @@ import {
36
36
  personFullName,
37
37
  } from "./lib/exa-people";
38
38
  import { unsupportedParamsForCategory } from "./lib/exa-search-constraints";
39
+ import { normalizeGatewayBaseUrl } from "./lib/gateway-url";
39
40
 
40
41
  type SearchCliContext = {
41
42
  args: string[];
@@ -119,10 +120,6 @@ function assertFlagsSupportedForCategory(
119
120
  );
120
121
  }
121
122
 
122
- function normalizeGatewayBaseUrl(value: string): string {
123
- return value.replace(/\/+$/, "").replace(/\/v1$/, "");
124
- }
125
-
126
123
  function resolveGatewayBaseUrl(
127
124
  ctx?: Pick<SearchCliContext, "gatewayBaseUrl">,
128
125
  ): string {
package/tools.ts CHANGED
@@ -57,6 +57,10 @@ import {
57
57
  type ConnectionSource,
58
58
  connectionSource,
59
59
  } from "./lib/connection-source";
60
+ import {
61
+ normalizeGatewayBaseUrl,
62
+ stripTrailingSlashes,
63
+ } from "./lib/gateway-url";
60
64
  import {
61
65
  isNativeToolRecord,
62
66
  nativeToolkit,
@@ -673,9 +677,8 @@ async function runSlackConnectSubcommand(
673
677
  toolkit: string,
674
678
  ): Promise<void> {
675
679
  const slug = ctx.workspaceSlug?.trim() || (await resolveWorkspaceSlug(ctx));
676
- const origin = (ctx.appOrigin?.trim() || resolveAppPublicOrigin()).replace(
677
- /\/+$/,
678
- "",
680
+ const origin = stripTrailingSlashes(
681
+ ctx.appOrigin?.trim() || resolveAppPublicOrigin(),
679
682
  );
680
683
  const redirectUrl = `${origin}/api/slack/install?slug=${
681
684
  slug ? encodeURIComponent(slug) : "<workspace-slug>"
@@ -897,10 +900,6 @@ class GatewayHttpError extends ToolCliError {
897
900
  }
898
901
  }
899
902
 
900
- function normalizeGatewayBaseUrl(value: string): string {
901
- return value.replace(/\/+$/, "").replace(/\/v1$/, "");
902
- }
903
-
904
903
  function resolveGatewayBaseUrl(
905
904
  ctx?: Pick<ToolCliContext, "gatewayBaseUrl">,
906
905
  ): string {
package/web.ts CHANGED
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import { assertNoUnknownFlags, getFlag, hasFlag } from "./lib/cli-args";
10
+ import { normalizeGatewayBaseUrl } from "./lib/gateway-url";
10
11
 
11
12
  type WebCliContext = {
12
13
  args: string[];
@@ -32,10 +33,6 @@ function parsePositiveInt(
32
33
  return Math.floor(parsed);
33
34
  }
34
35
 
35
- function normalizeGatewayBaseUrl(value: string): string {
36
- return value.replace(/\/+$/, "").replace(/\/v1$/, "");
37
- }
38
-
39
36
  function resolveGatewayBaseUrl(
40
37
  ctx?: Pick<WebCliContext, "gatewayBaseUrl">,
41
38
  ): string {