@ainyc/canonry 4.33.1 → 4.35.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/dist/cli.js CHANGED
@@ -1,26 +1,27 @@
1
1
  #!/usr/bin/env node --import tsx
2
2
  import {
3
+ backfillAiReferralPaths,
4
+ backfillAiReferralPathsCommand,
5
+ backfillAnswerMentionsCommand,
6
+ backfillAnswerVisibilityCommand,
7
+ backfillInsightsCommand,
8
+ backfillNormalizedPaths,
9
+ backfillNormalizedPathsCommand,
10
+ checkLatestVersionForCli,
3
11
  coerceAgentProvider,
4
- computeCompetitorOverlap,
5
12
  createServer,
6
13
  detectAndTrackUpgrade,
7
- determineCitationState,
8
- extractRecommendedCompetitors,
9
14
  formatAuditFactorScore,
10
15
  getOrCreateAnonymousId,
11
16
  isFirstRun,
12
17
  isTelemetryEnabled,
13
18
  listAgentProviders,
14
19
  renderReportHtml,
15
- reparseStoredResult,
16
- reparseStoredResult2,
17
- reparseStoredResult3,
18
- reparseStoredResult4,
19
20
  setGoogleAuthConfig,
20
21
  setTelemetrySource,
21
22
  showFirstRunNotice,
22
23
  trackEvent
23
- } from "./chunk-DZENHID5.js";
24
+ } from "./chunk-NLV4MZZF.js";
24
25
  import {
25
26
  CliError,
26
27
  EXIT_SYSTEM_ERROR,
@@ -36,42 +37,33 @@ import {
36
37
  saveConfig,
37
38
  saveConfigPatch,
38
39
  usageError
39
- } from "./chunk-5EBN7736.js";
40
+ } from "./chunk-B3FBOECD.js";
40
41
  import {
41
42
  apiKeys,
42
- competitors,
43
43
  createClient,
44
- gaAiReferrals,
45
- gaTrafficSnapshots,
46
44
  migrate,
47
- parseJsonColumn,
48
45
  projects,
49
- queries,
50
- querySnapshots,
51
- runs
52
- } from "./chunk-BJXHETQW.js";
46
+ queries
47
+ } from "./chunk-MLS5KJWK.js";
53
48
  import {
54
49
  CcReleaseSyncStatuses,
55
50
  CheckScopes,
56
51
  CheckStatuses,
57
52
  CitationStates,
58
53
  CodingAgents,
59
- ProviderNames,
60
- RunKinds,
61
54
  RunStatuses,
62
55
  SkillsClients,
63
56
  TrafficEventKinds,
64
- determineAnswerMentioned,
65
57
  discoveryBucketSchema,
66
58
  discoveryCompetitorTypeSchema,
67
59
  effectiveDomains,
68
60
  formatRunErrorOneLine,
69
- normalizeUrlPath,
61
+ normalizeProjectAliases,
70
62
  notificationEventSchema,
71
63
  providerQuotaPolicySchema,
72
64
  resolveProviderInput,
73
65
  skillsClientSchema
74
- } from "./chunk-XW3F5EEW.js";
66
+ } from "./chunk-EM5GVF3C.js";
75
67
 
76
68
  // src/cli.ts
77
69
  import { pathToFileURL } from "url";
@@ -211,501 +203,6 @@ Usage: ${spec.usage}`, {
211
203
  return true;
212
204
  }
213
205
 
214
- // src/commands/backfill.ts
215
- import { and, eq, inArray } from "drizzle-orm";
216
- var SNAPSHOT_BATCH_SIZE = 500;
217
- async function backfillAnswerVisibilityCommand(opts) {
218
- const config = loadConfig();
219
- const db = createClient(config.database);
220
- migrate(db);
221
- const projectFilter = opts?.project?.trim();
222
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq(projects.name, projectFilter)).all() : db.select().from(projects).all();
223
- let examined = 0;
224
- let updated = 0;
225
- let mentioned = 0;
226
- let reparsed = 0;
227
- let providerErrors = 0;
228
- if (scopedProjects.length > 0) {
229
- const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and(
230
- eq(runs.kind, RunKinds["answer-visibility"]),
231
- inArray(runs.projectId, scopedProjects.map((project) => project.id))
232
- )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq(runs.kind, RunKinds["answer-visibility"])).all();
233
- const runIdsByProject = /* @__PURE__ */ new Map();
234
- for (const run of runRows) {
235
- const existing = runIdsByProject.get(run.projectId);
236
- if (existing) existing.push(run.id);
237
- else runIdsByProject.set(run.projectId, [run.id]);
238
- }
239
- for (const project of scopedProjects) {
240
- const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq(competitors.projectId, project.id)).all().map((row) => row.domain);
241
- const runIds = runIdsByProject.get(project.id) ?? [];
242
- if (runIds.length === 0) continue;
243
- const projectDomains = effectiveDomains({
244
- canonicalDomain: project.canonicalDomain,
245
- ownedDomains: parseJsonColumn(project.ownedDomains, [])
246
- });
247
- for (let offset = 0; offset < runIds.length; offset += SNAPSHOT_BATCH_SIZE) {
248
- const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
249
- const snapshotRows = db.select({
250
- id: querySnapshots.id,
251
- provider: querySnapshots.provider,
252
- citationState: querySnapshots.citationState,
253
- answerMentioned: querySnapshots.answerMentioned,
254
- answerText: querySnapshots.answerText,
255
- citedDomains: querySnapshots.citedDomains,
256
- competitorOverlap: querySnapshots.competitorOverlap,
257
- recommendedCompetitors: querySnapshots.recommendedCompetitors,
258
- rawResponse: querySnapshots.rawResponse
259
- }).from(querySnapshots).where(inArray(querySnapshots.runId, batchRunIds)).all();
260
- const pendingUpdates = [];
261
- for (const snapshot of snapshotRows) {
262
- examined++;
263
- const reparsedResult = reparseProviderSnapshot(snapshot.provider, snapshot.rawResponse);
264
- if (reparsedResult) reparsed++;
265
- if (reparsedResult?.providerError) providerErrors++;
266
- const answerText = reparsedResult?.answerText ?? snapshot.answerText ?? "";
267
- const nextValue = determineAnswerMentioned(answerText, project.displayName, projectDomains);
268
- if (nextValue) mentioned++;
269
- const nextPatch = {};
270
- if (snapshot.answerMentioned !== nextValue) {
271
- nextPatch.answerMentioned = nextValue;
272
- }
273
- if ((snapshot.answerText ?? "") !== answerText) {
274
- nextPatch.answerText = answerText;
275
- }
276
- if (reparsedResult) {
277
- const normalized = {
278
- provider: snapshot.provider,
279
- answerText,
280
- citedDomains: reparsedResult.citedDomains,
281
- groundingSources: reparsedResult.groundingSources,
282
- searchQueries: reparsedResult.searchQueries
283
- };
284
- const nextCitationState = determineCitationState(normalized, projectDomains);
285
- const nextCitedDomains = JSON.stringify(reparsedResult.citedDomains);
286
- const nextCompetitorOverlap = JSON.stringify(
287
- computeCompetitorOverlap(normalized, competitorDomains)
288
- );
289
- const nextRecommendedCompetitors = JSON.stringify(
290
- extractRecommendedCompetitors(
291
- normalized.answerText,
292
- projectDomains,
293
- normalized.citedDomains,
294
- competitorDomains
295
- )
296
- );
297
- const nextRawResponse = stringifyStoredSnapshotEnvelope(
298
- snapshot.rawResponse,
299
- reparsedResult
300
- );
301
- if (snapshot.citationState !== nextCitationState) {
302
- nextPatch.citationState = nextCitationState;
303
- }
304
- if (snapshot.citedDomains !== nextCitedDomains) {
305
- nextPatch.citedDomains = nextCitedDomains;
306
- }
307
- if (snapshot.competitorOverlap !== nextCompetitorOverlap) {
308
- nextPatch.competitorOverlap = nextCompetitorOverlap;
309
- }
310
- if (snapshot.recommendedCompetitors !== nextRecommendedCompetitors) {
311
- nextPatch.recommendedCompetitors = nextRecommendedCompetitors;
312
- }
313
- if (snapshot.rawResponse !== nextRawResponse) {
314
- nextPatch.rawResponse = nextRawResponse;
315
- }
316
- }
317
- if (Object.keys(nextPatch).length > 0) {
318
- pendingUpdates.push({ id: snapshot.id, patch: nextPatch });
319
- }
320
- }
321
- if (pendingUpdates.length > 0) {
322
- db.transaction((tx) => {
323
- for (const update of pendingUpdates) {
324
- tx.update(querySnapshots).set(update.patch).where(eq(querySnapshots.id, update.id)).run();
325
- }
326
- });
327
- updated += pendingUpdates.length;
328
- }
329
- }
330
- }
331
- }
332
- const result = {
333
- project: projectFilter ?? null,
334
- projects: scopedProjects.length,
335
- examined,
336
- updated,
337
- mentioned,
338
- reparsed,
339
- providerErrors
340
- };
341
- if (opts?.format === "json") {
342
- console.log(JSON.stringify(result, null, 2));
343
- return;
344
- }
345
- console.log("Answer visibility backfill complete.\n");
346
- if (projectFilter) {
347
- console.log(` Project: ${projectFilter}`);
348
- }
349
- console.log(` Projects: ${scopedProjects.length}`);
350
- console.log(` Examined: ${examined}`);
351
- console.log(` Updated: ${updated}`);
352
- console.log(` Mentioned: ${mentioned}`);
353
- console.log(` Reparsed: ${reparsed}`);
354
- console.log(` Errors: ${providerErrors}`);
355
- }
356
- function backfillNormalizedPaths(db, opts) {
357
- const baseConditions = [];
358
- if (opts?.projectId) {
359
- baseConditions.push(eq(gaTrafficSnapshots.projectId, opts.projectId));
360
- }
361
- const rows = db.select({
362
- id: gaTrafficSnapshots.id,
363
- landingPage: gaTrafficSnapshots.landingPage,
364
- landingPageNormalized: gaTrafficSnapshots.landingPageNormalized
365
- }).from(gaTrafficSnapshots).where(baseConditions.length > 0 ? and(...baseConditions) : void 0).all();
366
- let updated = 0;
367
- let unchanged = 0;
368
- if (rows.length > 0) {
369
- db.transaction((tx) => {
370
- for (const row of rows) {
371
- const next = normalizeUrlPath(row.landingPage);
372
- if (next === null) {
373
- unchanged++;
374
- continue;
375
- }
376
- if (row.landingPageNormalized === next) {
377
- unchanged++;
378
- continue;
379
- }
380
- tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq(gaTrafficSnapshots.id, row.id)).run();
381
- updated++;
382
- }
383
- });
384
- }
385
- return { examined: rows.length, updated, unchanged };
386
- }
387
- async function backfillNormalizedPathsCommand(opts) {
388
- const config = loadConfig();
389
- const db = createClient(config.database);
390
- migrate(db);
391
- const projectFilter = opts?.project?.trim();
392
- let projectId;
393
- if (projectFilter) {
394
- const project = db.select({ id: projects.id }).from(projects).where(eq(projects.name, projectFilter)).get();
395
- if (!project) {
396
- const result2 = {
397
- project: projectFilter,
398
- examined: 0,
399
- updated: 0,
400
- unchanged: 0
401
- };
402
- if (opts?.format === "json") {
403
- console.log(JSON.stringify(result2, null, 2));
404
- return;
405
- }
406
- console.log(`Backfill normalized-paths: project "${projectFilter}" not found.`);
407
- return;
408
- }
409
- projectId = project.id;
410
- }
411
- const { examined, updated, unchanged } = backfillNormalizedPaths(db, { projectId });
412
- const result = {
413
- project: projectFilter ?? null,
414
- examined,
415
- updated,
416
- unchanged
417
- };
418
- if (opts?.format === "json") {
419
- console.log(JSON.stringify(result, null, 2));
420
- return;
421
- }
422
- console.log("Normalized-path backfill complete.\n");
423
- if (projectFilter) console.log(` Project: ${projectFilter}`);
424
- console.log(` Examined: ${examined}`);
425
- console.log(` Updated: ${updated}`);
426
- console.log(` Unchanged: ${unchanged}`);
427
- }
428
- function backfillAiReferralPaths(db, opts) {
429
- const baseConditions = [];
430
- if (opts?.projectId) {
431
- baseConditions.push(eq(gaAiReferrals.projectId, opts.projectId));
432
- }
433
- const rows = db.select({
434
- id: gaAiReferrals.id,
435
- landingPage: gaAiReferrals.landingPage,
436
- landingPageNormalized: gaAiReferrals.landingPageNormalized
437
- }).from(gaAiReferrals).where(baseConditions.length > 0 ? and(...baseConditions) : void 0).all();
438
- let updated = 0;
439
- let unchanged = 0;
440
- if (rows.length > 0) {
441
- db.transaction((tx) => {
442
- for (const row of rows) {
443
- const next = normalizeUrlPath(row.landingPage);
444
- if (next === null) {
445
- unchanged++;
446
- continue;
447
- }
448
- if (row.landingPageNormalized === next) {
449
- unchanged++;
450
- continue;
451
- }
452
- tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq(gaAiReferrals.id, row.id)).run();
453
- updated++;
454
- }
455
- });
456
- }
457
- return { examined: rows.length, updated, unchanged };
458
- }
459
- async function backfillAiReferralPathsCommand(opts) {
460
- const config = loadConfig();
461
- const db = createClient(config.database);
462
- migrate(db);
463
- const projectFilter = opts?.project?.trim();
464
- let projectId;
465
- if (projectFilter) {
466
- const project = db.select({ id: projects.id }).from(projects).where(eq(projects.name, projectFilter)).get();
467
- if (!project) {
468
- const result2 = {
469
- project: projectFilter,
470
- examined: 0,
471
- updated: 0,
472
- unchanged: 0
473
- };
474
- if (opts?.format === "json") {
475
- console.log(JSON.stringify(result2, null, 2));
476
- return;
477
- }
478
- console.log(`Backfill ai-referral-paths: project "${projectFilter}" not found.`);
479
- return;
480
- }
481
- projectId = project.id;
482
- }
483
- const { examined, updated, unchanged } = backfillAiReferralPaths(db, { projectId });
484
- const result = {
485
- project: projectFilter ?? null,
486
- examined,
487
- updated,
488
- unchanged
489
- };
490
- if (opts?.format === "json") {
491
- console.log(JSON.stringify(result, null, 2));
492
- return;
493
- }
494
- console.log("AI referral landing-page backfill complete.\n");
495
- if (projectFilter) console.log(` Project: ${projectFilter}`);
496
- console.log(` Examined: ${examined}`);
497
- console.log(` Updated: ${updated}`);
498
- console.log(` Unchanged: ${unchanged}`);
499
- }
500
- async function backfillAnswerMentionsCommand(opts) {
501
- const config = loadConfig();
502
- const db = createClient(config.database);
503
- migrate(db);
504
- const projectFilter = opts?.project?.trim();
505
- const scopedProjects = projectFilter ? db.select().from(projects).where(eq(projects.name, projectFilter)).all() : db.select().from(projects).all();
506
- let examined = 0;
507
- let updated = 0;
508
- let mentioned = 0;
509
- if (scopedProjects.length > 0) {
510
- const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and(
511
- eq(runs.kind, RunKinds["answer-visibility"]),
512
- inArray(runs.projectId, scopedProjects.map((project) => project.id))
513
- )).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq(runs.kind, RunKinds["answer-visibility"])).all();
514
- const runIdsByProject = /* @__PURE__ */ new Map();
515
- for (const run of runRows) {
516
- const existing = runIdsByProject.get(run.projectId);
517
- if (existing) existing.push(run.id);
518
- else runIdsByProject.set(run.projectId, [run.id]);
519
- }
520
- for (const project of scopedProjects) {
521
- const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq(competitors.projectId, project.id)).all().map((row) => row.domain);
522
- const runIds = runIdsByProject.get(project.id) ?? [];
523
- if (runIds.length === 0) continue;
524
- const projectDomains = effectiveDomains({
525
- canonicalDomain: project.canonicalDomain,
526
- ownedDomains: parseJsonColumn(project.ownedDomains, [])
527
- });
528
- for (let offset = 0; offset < runIds.length; offset += SNAPSHOT_BATCH_SIZE) {
529
- const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
530
- const snapshotRows = db.select({
531
- id: querySnapshots.id,
532
- provider: querySnapshots.provider,
533
- answerMentioned: querySnapshots.answerMentioned,
534
- answerText: querySnapshots.answerText,
535
- citedDomains: querySnapshots.citedDomains,
536
- competitorOverlap: querySnapshots.competitorOverlap,
537
- recommendedCompetitors: querySnapshots.recommendedCompetitors,
538
- rawResponse: querySnapshots.rawResponse
539
- }).from(querySnapshots).where(inArray(querySnapshots.runId, batchRunIds)).all();
540
- const pendingUpdates = [];
541
- for (const snapshot of snapshotRows) {
542
- examined++;
543
- const answerText = snapshot.answerText ?? "";
544
- const nextAnswerMentioned = determineAnswerMentioned(answerText, project.displayName, projectDomains);
545
- if (nextAnswerMentioned) mentioned++;
546
- const citedDomains = parseJsonColumn(snapshot.citedDomains, []);
547
- const groundingSources = readStoredGroundingSources(snapshot.rawResponse);
548
- const normalized = {
549
- provider: snapshot.provider,
550
- answerText,
551
- citedDomains,
552
- groundingSources,
553
- searchQueries: []
554
- };
555
- const nextCompetitorOverlap = JSON.stringify(
556
- computeCompetitorOverlap(normalized, competitorDomains)
557
- );
558
- const nextRecommendedCompetitors = JSON.stringify(
559
- extractRecommendedCompetitors(
560
- answerText,
561
- projectDomains,
562
- citedDomains,
563
- competitorDomains
564
- )
565
- );
566
- const nextPatch = {};
567
- if (snapshot.answerMentioned !== nextAnswerMentioned) {
568
- nextPatch.answerMentioned = nextAnswerMentioned;
569
- }
570
- if (snapshot.competitorOverlap !== nextCompetitorOverlap) {
571
- nextPatch.competitorOverlap = nextCompetitorOverlap;
572
- }
573
- if (snapshot.recommendedCompetitors !== nextRecommendedCompetitors) {
574
- nextPatch.recommendedCompetitors = nextRecommendedCompetitors;
575
- }
576
- if (Object.keys(nextPatch).length > 0) {
577
- pendingUpdates.push({ id: snapshot.id, patch: nextPatch });
578
- }
579
- }
580
- if (pendingUpdates.length > 0) {
581
- db.transaction((tx) => {
582
- for (const update of pendingUpdates) {
583
- tx.update(querySnapshots).set(update.patch).where(eq(querySnapshots.id, update.id)).run();
584
- }
585
- });
586
- updated += pendingUpdates.length;
587
- }
588
- }
589
- }
590
- }
591
- const result = {
592
- project: projectFilter ?? null,
593
- projects: scopedProjects.length,
594
- examined,
595
- updated,
596
- mentioned
597
- };
598
- if (opts?.format === "json") {
599
- console.log(JSON.stringify(result, null, 2));
600
- return;
601
- }
602
- console.log("Answer mentions backfill complete.\n");
603
- if (projectFilter) console.log(` Project: ${projectFilter}`);
604
- console.log(` Projects: ${scopedProjects.length}`);
605
- console.log(` Examined: ${examined}`);
606
- console.log(` Updated: ${updated}`);
607
- console.log(` Mentioned: ${mentioned}`);
608
- }
609
- function readStoredGroundingSources(rawResponse) {
610
- const envelope = parseJsonColumn(rawResponse, {});
611
- const sources = envelope.groundingSources;
612
- if (!Array.isArray(sources)) return [];
613
- const result = [];
614
- for (const source of sources) {
615
- if (source && typeof source === "object") {
616
- const uri = source.uri;
617
- const title = source.title;
618
- if (typeof uri === "string") {
619
- result.push({ uri, title: typeof title === "string" ? title : "" });
620
- }
621
- }
622
- }
623
- return result;
624
- }
625
- async function backfillInsightsCommand(project, opts) {
626
- const { IntelligenceService } = await import("./intelligence-service-XKOUBRCE.js");
627
- const config = loadConfig();
628
- const db = createClient(config.database);
629
- migrate(db);
630
- const service = new IntelligenceService(db);
631
- const isJson = opts?.format === "json";
632
- if (!isJson) {
633
- process.stderr.write(`Backfilling insights for "${project}"...
634
- `);
635
- }
636
- const result = service.backfill(project, {
637
- fromRunId: opts?.fromRun,
638
- toRunId: opts?.toRun
639
- }, (info) => {
640
- if (!isJson) {
641
- process.stderr.write(` [${info.index}/${info.total}] ${info.runId} \u2014 ${info.insights} insights
642
- `);
643
- }
644
- });
645
- const output = {
646
- project,
647
- processed: result.processed,
648
- skipped: result.skipped,
649
- totalInsights: result.totalInsights
650
- };
651
- if (isJson) {
652
- console.log(JSON.stringify(output, null, 2));
653
- return;
654
- }
655
- console.log(`
656
- Backfill complete.`);
657
- console.log(` Processed: ${result.processed}`);
658
- console.log(` Skipped: ${result.skipped}`);
659
- console.log(` Insights: ${result.totalInsights}`);
660
- }
661
- function reparseProviderSnapshot(provider, rawResponse) {
662
- const envelope = parseJsonColumn(rawResponse, {});
663
- const apiResponse = resolveStoredApiResponse(envelope);
664
- if (!apiResponse) return null;
665
- switch (provider) {
666
- case ProviderNames.openai:
667
- return reparseStoredResult(apiResponse);
668
- case ProviderNames.claude:
669
- return reparseStoredResult2(apiResponse);
670
- case ProviderNames.gemini:
671
- return reparseStoredResult3(apiResponse);
672
- case ProviderNames.perplexity:
673
- return reparseStoredResult4(apiResponse);
674
- default:
675
- return null;
676
- }
677
- }
678
- function resolveStoredApiResponse(parsed) {
679
- const nested = parsed.apiResponse;
680
- if (nested !== null && typeof nested === "object" && !Array.isArray(nested)) {
681
- return nested;
682
- }
683
- if (looksLikeProviderApiResponse(parsed)) {
684
- return parsed;
685
- }
686
- return null;
687
- }
688
- function looksLikeProviderApiResponse(value) {
689
- return Array.isArray(value.output) || Array.isArray(value.content) || Array.isArray(value.candidates) || Array.isArray(value.choices);
690
- }
691
- function stringifyStoredSnapshotEnvelope(rawResponse, reparsed) {
692
- const parsed = parseJsonColumn(rawResponse, {});
693
- const apiResponse = resolveStoredApiResponse(parsed);
694
- const envelope = apiResponse === parsed ? {} : { ...parsed };
695
- delete envelope.answerText;
696
- delete envelope.citedDomains;
697
- delete envelope.competitorOverlap;
698
- delete envelope.recommendedCompetitors;
699
- delete envelope.providerError;
700
- return JSON.stringify({
701
- ...envelope,
702
- groundingSources: reparsed.groundingSources,
703
- searchQueries: reparsed.searchQueries,
704
- ...reparsed.providerError ? { providerError: reparsed.providerError } : {},
705
- ...apiResponse ? { apiResponse } : {}
706
- });
707
- }
708
-
709
206
  // src/cli-command-helpers.ts
710
207
  function getString(values, key) {
711
208
  const value = values[key];
@@ -1996,18 +1493,18 @@ function errorMessage(err) {
1996
1493
  async function discoverRun(project, opts) {
1997
1494
  const client = getClient4();
1998
1495
  const { angles, multiAngle } = resolveIcpAngles(opts);
1999
- const runs2 = [];
1496
+ const runs = [];
2000
1497
  for (const angle of angles) {
2001
1498
  try {
2002
1499
  const start = await client.triggerDiscoveryRun(project, buildRunBody(opts, angle));
2003
- runs2.push({ angle, start });
1500
+ runs.push({ angle, start });
2004
1501
  } catch (err) {
2005
- if (runs2.length > 0) {
1502
+ if (runs.length > 0) {
2006
1503
  process.stderr.write(
2007
1504
  `
2008
1505
  Failed to start ${angle ? `angle "${angle}"` : "discovery run"}: ${errorMessage(err)}
2009
1506
  Sessions already started (recover with \`canonry discover show ${project} <id>\`):
2010
- ` + runs2.map((r) => ` ${r.start.sessionId}`).join("\n") + "\n"
1507
+ ` + runs.map((r) => ` ${r.start.sessionId}`).join("\n") + "\n"
2011
1508
  );
2012
1509
  }
2013
1510
  throw err;
@@ -2015,10 +1512,10 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
2015
1512
  }
2016
1513
  if (!opts.wait) {
2017
1514
  if (opts.format === "json") {
2018
- console.log(JSON.stringify(multiAngle ? runs2.map((r) => r.start) : runs2[0].start, null, 2));
1515
+ console.log(JSON.stringify(multiAngle ? runs.map((r) => r.start) : runs[0].start, null, 2));
2019
1516
  return;
2020
1517
  }
2021
- for (const { angle, start } of runs2) {
1518
+ for (const { angle, start } of runs) {
2022
1519
  if (angle) console.log(`[${angle}]`);
2023
1520
  if (start.consolidated) {
2024
1521
  console.log(`Reusing in-flight discovery session: ${start.sessionId}`);
@@ -2031,20 +1528,20 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
2031
1528
  console.log(` Status: ${start.status}`);
2032
1529
  console.log(` Tail: canonry discover show ${project} ${start.sessionId}`);
2033
1530
  }
2034
- if (runs2.length > 1) console.log();
1531
+ if (runs.length > 1) console.log();
2035
1532
  }
2036
1533
  return;
2037
1534
  }
2038
- const parallel = runs2.length > 1;
2039
- if (parallel) process.stderr.write(`Waiting for ${runs2.length} discovery sessions...
1535
+ const parallel = runs.length > 1;
1536
+ if (parallel) process.stderr.write(`Waiting for ${runs.length} discovery sessions...
2040
1537
  `);
2041
1538
  const settled = await Promise.allSettled(
2042
- runs2.map((r) => pollSession(client, project, r.start.sessionId, parallel))
1539
+ runs.map((r) => pollSession(client, project, r.start.sessionId, parallel))
2043
1540
  );
2044
1541
  const results = [];
2045
1542
  const failures = [];
2046
1543
  settled.forEach((outcome, i) => {
2047
- const run = runs2[i];
1544
+ const run = runs[i];
2048
1545
  if (outcome.status === "fulfilled") {
2049
1546
  results.push({ angle: run.angle, session: outcome.value });
2050
1547
  } else {
@@ -2084,7 +1581,7 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
2084
1581
  }
2085
1582
  throw new CliError({
2086
1583
  code: "DISCOVERY_INCOMPLETE",
2087
- message: `${failures.length} of ${runs2.length} discovery session(s) did not complete`,
1584
+ message: `${failures.length} of ${runs.length} discovery session(s) did not complete`,
2088
1585
  details: { failed: failures.map((f) => f.sessionId) }
2089
1586
  });
2090
1587
  }
@@ -6069,10 +5566,10 @@ Brand Gap Analysis`);
6069
5566
  console.log(`
6070
5567
  Opportunity Gaps (competitors cited, you're not):`);
6071
5568
  for (const q of data.gap) {
6072
- const competitors2 = q.competitorsCiting.join(", ");
5569
+ const competitors = q.competitorsCiting.join(", ");
6073
5570
  const cons = q.consistency.totalRuns > 0 ? ` [cited ${q.consistency.citedRuns}/${q.consistency.totalRuns} runs]` : "";
6074
5571
  console.log(` \u2022 ${q.query}${cons}`);
6075
- console.log(` Competitors: ${competitors2}`);
5572
+ console.log(` Competitors: ${competitors}`);
6076
5573
  }
6077
5574
  }
6078
5575
  if (data.cited.length > 0) {
@@ -6155,9 +5652,9 @@ async function loadLatestRunForExport(client, project) {
6155
5652
  } catch (err) {
6156
5653
  if (!isEndpointMissing(err)) throw err;
6157
5654
  }
6158
- const runs2 = await client.listRuns(project);
6159
- if (runs2.length === 0) return null;
6160
- const latestRun = runs2.reduce(
5655
+ const runs = await client.listRuns(project);
5656
+ if (runs.length === 0) return null;
5657
+ const latestRun = runs.reduce(
6161
5658
  (current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
6162
5659
  );
6163
5660
  return client.getRun(latestRun.id);
@@ -6211,14 +5708,14 @@ async function showStatus(project, format) {
6211
5708
  const projectData = await client.getProject(project);
6212
5709
  const latest = await getLatestRunSummary(client, project);
6213
5710
  if (format === "json") {
6214
- let runs2 = [];
5711
+ let runs = [];
6215
5712
  try {
6216
- runs2 = await client.listRuns(project);
5713
+ runs = await client.listRuns(project);
6217
5714
  } catch {
6218
5715
  }
6219
5716
  console.log(JSON.stringify({
6220
5717
  project: projectData,
6221
- runs: runs2,
5718
+ runs,
6222
5719
  latestRun: latest.run,
6223
5720
  totalRuns: latest.totalRuns
6224
5721
  }, null, 2));
@@ -6249,15 +5746,15 @@ async function getLatestRunSummary(client, project) {
6249
5746
  return await client.getLatestRun(project);
6250
5747
  } catch (err) {
6251
5748
  if (!isEndpointMissing(err)) throw err;
6252
- const runs2 = await client.listRuns(project);
6253
- if (runs2.length === 0) {
5749
+ const runs = await client.listRuns(project);
5750
+ if (runs.length === 0) {
6254
5751
  return { totalRuns: 0, run: null };
6255
5752
  }
6256
- const latestRun = runs2.reduce(
5753
+ const latestRun = runs.reduce(
6257
5754
  (current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
6258
5755
  );
6259
5756
  return {
6260
- totalRuns: runs2.length,
5757
+ totalRuns: runs.length,
6261
5758
  run: latestRun
6262
5759
  };
6263
5760
  }
@@ -6347,6 +5844,7 @@ async function createProject(name, opts) {
6347
5844
  displayName: opts.displayName,
6348
5845
  canonicalDomain: opts.domain,
6349
5846
  ownedDomains: opts.ownedDomains ?? [],
5847
+ aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
6350
5848
  country: opts.country,
6351
5849
  language: opts.language
6352
5850
  });
@@ -6400,6 +5898,9 @@ async function showProject(name, format) {
6400
5898
  if (secondaryDomains.length > 0) {
6401
5899
  console.log(` Owned domains: ${secondaryDomains.join(", ")}`);
6402
5900
  }
5901
+ if (project.aliases && project.aliases.length > 0) {
5902
+ console.log(` Aliases: ${project.aliases.join(", ")}`);
5903
+ }
6403
5904
  console.log(` Country: ${project.country}`);
6404
5905
  console.log(` Language: ${project.language}`);
6405
5906
  console.log(` Config source: ${project.configSource}`);
@@ -6422,10 +5923,22 @@ async function updateProjectSettings(name, opts) {
6422
5923
  const toRemove = new Set(opts.removeOwnedDomain);
6423
5924
  ownedDomains = ownedDomains.filter((d) => !toRemove.has(d));
6424
5925
  }
5926
+ const nextDisplayName = opts.displayName ?? project.displayName ?? project.name;
5927
+ let aliases = opts.aliases ?? project.aliases ?? [];
5928
+ if (opts.addAlias) {
5929
+ const existingKeys = new Set(aliases.map((a) => a.toLowerCase()));
5930
+ const toAdd = opts.addAlias.filter((a) => !existingKeys.has(a.toLowerCase()));
5931
+ aliases = [...aliases, ...toAdd];
5932
+ }
5933
+ if (opts.removeAlias) {
5934
+ const toRemove = new Set(opts.removeAlias.map((a) => a.toLowerCase()));
5935
+ aliases = aliases.filter((a) => !toRemove.has(a.toLowerCase()));
5936
+ }
6425
5937
  const result = await client.putProject(name, {
6426
- displayName: opts.displayName ?? project.displayName ?? project.name,
5938
+ displayName: nextDisplayName,
6427
5939
  canonicalDomain: opts.domain ?? project.canonicalDomain,
6428
5940
  ownedDomains,
5941
+ aliases: normalizeProjectAliases(nextDisplayName, aliases),
6429
5942
  country: opts.country ?? project.country,
6430
5943
  language: opts.language ?? project.language
6431
5944
  });
@@ -6508,10 +6021,11 @@ async function setDefaultLocation(project, label, format) {
6508
6021
  var PROJECT_CLI_COMMANDS = [
6509
6022
  {
6510
6023
  path: ["project", "create"],
6511
- usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
6024
+ usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
6512
6025
  options: {
6513
6026
  domain: { type: "string", short: "d" },
6514
6027
  "owned-domain": multiStringOption(),
6028
+ alias: multiStringOption(),
6515
6029
  country: stringOption(),
6516
6030
  language: stringOption(),
6517
6031
  "display-name": stringOption()
@@ -6520,11 +6034,12 @@ var PROJECT_CLI_COMMANDS = [
6520
6034
  const name = requireProject(
6521
6035
  input,
6522
6036
  "project.create",
6523
- "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
6037
+ "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
6524
6038
  );
6525
6039
  await createProject(name, {
6526
6040
  domain: getString(input.values, "domain") ?? name,
6527
6041
  ownedDomains: getStringArray(input.values, "owned-domain") ?? [],
6042
+ aliases: getStringArray(input.values, "alias") ?? [],
6528
6043
  country: getString(input.values, "country") ?? "US",
6529
6044
  language: getString(input.values, "language") ?? "en",
6530
6045
  displayName: getString(input.values, "display-name") ?? name,
@@ -6534,12 +6049,15 @@ var PROJECT_CLI_COMMANDS = [
6534
6049
  },
6535
6050
  {
6536
6051
  path: ["project", "update"],
6537
- usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
6052
+ usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
6538
6053
  options: {
6539
6054
  domain: { type: "string", short: "d" },
6540
6055
  "owned-domain": multiStringOption(),
6541
6056
  "add-domain": multiStringOption(),
6542
6057
  "remove-domain": multiStringOption(),
6058
+ alias: multiStringOption(),
6059
+ "add-alias": multiStringOption(),
6060
+ "remove-alias": multiStringOption(),
6543
6061
  country: stringOption(),
6544
6062
  language: stringOption(),
6545
6063
  "display-name": stringOption()
@@ -6548,7 +6066,7 @@ var PROJECT_CLI_COMMANDS = [
6548
6066
  const name = requireProject(
6549
6067
  input,
6550
6068
  "project.update",
6551
- "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
6069
+ "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
6552
6070
  );
6553
6071
  await updateProjectSettings(name, {
6554
6072
  displayName: getString(input.values, "display-name"),
@@ -6556,6 +6074,9 @@ var PROJECT_CLI_COMMANDS = [
6556
6074
  ownedDomains: getStringArray(input.values, "owned-domain"),
6557
6075
  addOwnedDomain: getStringArray(input.values, "add-domain"),
6558
6076
  removeOwnedDomain: getStringArray(input.values, "remove-domain"),
6077
+ aliases: getStringArray(input.values, "alias"),
6078
+ addAlias: getStringArray(input.values, "add-alias"),
6079
+ removeAlias: getStringArray(input.values, "remove-alias"),
6559
6080
  country: getString(input.values, "country"),
6560
6081
  language: getString(input.values, "language"),
6561
6082
  format: input.format
@@ -6892,8 +6413,8 @@ async function cancelRun(project, runId, format) {
6892
6413
  const client = getClient17();
6893
6414
  let targetId = runId;
6894
6415
  if (!targetId) {
6895
- const runs2 = await client.listRuns(project);
6896
- const active = runs2.find((r) => r.status === "queued" || r.status === "running");
6416
+ const runs = await client.listRuns(project);
6417
+ const active = runs.find((r) => r.status === "queued" || r.status === "running");
6897
6418
  if (!active) {
6898
6419
  throw new CliError({
6899
6420
  code: "NO_ACTIVE_RUN",
@@ -6931,20 +6452,20 @@ async function showRun(id, format) {
6931
6452
  }
6932
6453
  async function listRuns(project, opts) {
6933
6454
  const client = getClient17();
6934
- const runs2 = await client.listRuns(project, opts?.limit);
6455
+ const runs = await client.listRuns(project, opts?.limit);
6935
6456
  if (opts?.format === "json") {
6936
- console.log(JSON.stringify(runs2, null, 2));
6457
+ console.log(JSON.stringify(runs, null, 2));
6937
6458
  return;
6938
6459
  }
6939
- if (runs2.length === 0) {
6460
+ if (runs.length === 0) {
6940
6461
  console.log(`No runs found for "${project}".`);
6941
6462
  return;
6942
6463
  }
6943
- console.log(`Runs for "${project}" (${runs2.length}):
6464
+ console.log(`Runs for "${project}" (${runs.length}):
6944
6465
  `);
6945
6466
  console.log(" ID STATUS KIND TRIGGER CREATED");
6946
6467
  console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
6947
- for (const run of runs2) {
6468
+ for (const run of runs) {
6948
6469
  console.log(
6949
6470
  ` ${run.id} ${run.status.padEnd(10)} ${run.kind.padEnd(18)} ${run.trigger.padEnd(9)} ${run.createdAt}`
6950
6471
  );
@@ -8042,8 +7563,8 @@ function renderQueries(pdf, report) {
8042
7563
  for (const result of queryResult.providerResults) {
8043
7564
  const status = result.error ? "error" : result.mentioned ? result.cited ? "mentioned and cited" : "mentioned" : "not mentioned";
8044
7565
  const accuracy = result.describedAccurately === "not-mentioned" ? "" : `; accuracy: ${result.describedAccurately}`;
8045
- const competitors2 = result.recommendedCompetitors.length > 0 ? `; recommended instead: ${result.recommendedCompetitors.join(", ")}` : "";
8046
- const line = `${result.displayName}: ${status}${accuracy}${competitors2}`;
7566
+ const competitors = result.recommendedCompetitors.length > 0 ? `; recommended instead: ${result.recommendedCompetitors.join(", ")}` : "";
7567
+ const line = `${result.displayName}: ${status}${accuracy}${competitors}`;
8047
7568
  pdf.bullet(line);
8048
7569
  if (result.error) {
8049
7570
  pdf.paragraph(`Error: ${result.error}`, { size: 9, color: FAIL, lineHeight: 12 });
@@ -8190,8 +7711,8 @@ function formatSnapshotMarkdown(report) {
8190
7711
  const mentioned = result.mentioned ? "Yes" : "No";
8191
7712
  const cited = result.cited ? "Yes" : "No";
8192
7713
  const accuracy = result.describedAccurately === "not-mentioned" ? "-" : result.describedAccurately;
8193
- const competitors2 = result.recommendedCompetitors.length > 0 ? result.recommendedCompetitors.join(", ") : "-";
8194
- lines.push(`| ${result.displayName} | ${mentioned} | ${cited} | ${accuracy} | ${competitors2} |`);
7714
+ const competitors = result.recommendedCompetitors.length > 0 ? result.recommendedCompetitors.join(", ") : "-";
7715
+ lines.push(`| ${result.displayName} | ${mentioned} | ${cited} | ${accuracy} | ${competitors} |`);
8195
7716
  }
8196
7717
  lines.push("");
8197
7718
  }
@@ -8421,7 +7942,7 @@ function renderHuman(overview) {
8421
7942
  transitions,
8422
7943
  scores,
8423
7944
  movementSummary,
8424
- competitors: competitors2,
7945
+ competitors,
8425
7946
  providerScores,
8426
7947
  attentionItems,
8427
7948
  runHistory,
@@ -8469,9 +7990,9 @@ function renderHuman(overview) {
8469
7990
  console.log(`
8470
7991
  Transitions since ${transitions.since}: +${transitions.gained} gained, -${transitions.lost} lost, ${transitions.emerging} emerging`);
8471
7992
  }
8472
- if (competitors2.length > 0) {
7993
+ if (competitors.length > 0) {
8473
7994
  console.log("\n Competitors:");
8474
- for (const c of competitors2) {
7995
+ for (const c of competitors) {
8475
7996
  console.log(` ${c.domain.padEnd(28)} ${c.citationCount}/${c.totalQueries} ${c.pressureLabel}`);
8476
7997
  }
8477
7998
  }
@@ -8860,7 +8381,7 @@ var CONTENT_CLI_COMMANDS = [
8860
8381
  // src/commands/bootstrap.ts
8861
8382
  import crypto from "crypto";
8862
8383
  import path7 from "path";
8863
- import { eq as eq2 } from "drizzle-orm";
8384
+ import { eq } from "drizzle-orm";
8864
8385
 
8865
8386
  // ../config/src/index.ts
8866
8387
  import { z } from "zod";
@@ -8898,7 +8419,11 @@ var envSchema = z.object({
8898
8419
  PERPLEXITY_MODEL: z.string().optional(),
8899
8420
  PERPLEXITY_MAX_CONCURRENCY: z.coerce.number().int().positive().default(2),
8900
8421
  PERPLEXITY_MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().positive().default(10),
8901
- PERPLEXITY_MAX_REQUESTS_PER_DAY: z.coerce.number().int().positive().default(1e3)
8422
+ PERPLEXITY_MAX_REQUESTS_PER_DAY: z.coerce.number().int().positive().default(1e3),
8423
+ // Secret for HMAC-signing Google OAuth state parameters. Required for
8424
+ // cloud deployments that mount googleRoutes; the plugin refuses to register
8425
+ // without it (see packages/api-routes/src/google.ts).
8426
+ GOOGLE_STATE_SECRET: z.string().optional()
8902
8427
  });
8903
8428
  var bootstrapEnvSchema = z.object({
8904
8429
  CANONRY_API_KEY: z.string().optional(),
@@ -9038,7 +8563,7 @@ async function bootstrapCommand(_opts) {
9038
8563
  const db = createClient(databasePath);
9039
8564
  migrate(db);
9040
8565
  db.transaction((tx) => {
9041
- tx.delete(apiKeys).where(eq2(apiKeys.name, "default")).run();
8566
+ tx.delete(apiKeys).where(eq(apiKeys.name, "default")).run();
9042
8567
  tx.insert(apiKeys).values({
9043
8568
  id: crypto.randomUUID(),
9044
8569
  name: "default",
@@ -11558,6 +11083,18 @@ async function runCli(args = process.argv.slice(2)) {
11558
11083
  ...setupState ? { setup_state: setupState } : {}
11559
11084
  });
11560
11085
  }
11086
+ if (!isHelpRequest && command !== "telemetry") {
11087
+ void checkLatestVersionForCli().then((update) => {
11088
+ if (!update) return;
11089
+ process.stderr.write(
11090
+ `
11091
+ \u2192 canonry ${update.latest} is available (you have ${update.current}).
11092
+ Upgrade: ${update.upgradeCommand}
11093
+
11094
+ `
11095
+ );
11096
+ });
11097
+ }
11561
11098
  try {
11562
11099
  if (await dispatchRegisteredCommand(args, format, REGISTERED_CLI_COMMANDS)) {
11563
11100
  return 0;