@ainyc/canonry 2.5.1 → 2.8.2

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.
@@ -1,3 +1,57 @@
1
+ import {
2
+ AGENT_MEMORY_KEY_MAX_LENGTH,
3
+ AGENT_MEMORY_VALUE_MAX_BYTES,
4
+ AGENT_PROVIDER_IDS,
5
+ AgentProviderIds,
6
+ ApiClient,
7
+ AppError,
8
+ CcReleaseSyncStatuses,
9
+ MemorySources,
10
+ RunKinds,
11
+ RunStatuses,
12
+ RunTriggers,
13
+ agentBusy,
14
+ agentMemoryDeleteRequestSchema,
15
+ agentMemoryUpsertRequestSchema,
16
+ authInvalid,
17
+ authRequired,
18
+ brandKeyFromText,
19
+ categorizeSource,
20
+ categoryLabel,
21
+ competitorBatchRequestSchema,
22
+ configExists,
23
+ deliveryFailed,
24
+ determineAnswerMentioned,
25
+ effectiveDomains,
26
+ extractAnswerMentions,
27
+ findDuplicateLocationLabels,
28
+ hasLocationLabel,
29
+ internalError,
30
+ isAgentProviderId,
31
+ isBrowserProvider,
32
+ keywordGenerateRequestSchema,
33
+ loadConfig,
34
+ locationContextSchema,
35
+ missingDependency,
36
+ normalizeProjectDomain,
37
+ notFound,
38
+ notImplemented,
39
+ parseWindow,
40
+ projectConfigSchema,
41
+ projectUpsertRequestSchema,
42
+ providerError,
43
+ runInProgress,
44
+ runNotCancellable,
45
+ runTriggerRequestSchema,
46
+ saveConfigPatch,
47
+ scheduleUpsertRequestSchema,
48
+ snapshotRequestSchema,
49
+ unsupportedKind,
50
+ validationError,
51
+ visibilityStateFromAnswerMentioned,
52
+ windowCutoff,
53
+ wordpressEnvSchema
54
+ } from "./chunk-FPZUQADO.js";
1
55
  import {
2
56
  IntelligenceService,
3
57
  agentMemory,
@@ -30,172 +84,7 @@ import {
30
84
  runs,
31
85
  schedules,
32
86
  usageCounters
33
- } from "./chunk-32YTAZBL.js";
34
-
35
- // src/config.ts
36
- import fs from "fs";
37
- import path from "path";
38
- import os from "os";
39
- import { parse, stringify } from "yaml";
40
- function normalizeGoogleConfig(config) {
41
- if (!config.google) return;
42
- config.google.connections = (config.google.connections ?? []).map((connection) => ({
43
- ...connection,
44
- propertyId: connection.propertyId ?? null,
45
- refreshToken: connection.refreshToken ?? null,
46
- tokenExpiresAt: connection.tokenExpiresAt ?? null,
47
- scopes: connection.scopes ?? []
48
- }));
49
- }
50
- function normalizeWordpressConfig(config) {
51
- if (!config.wordpress) return;
52
- config.wordpress.connections = (config.wordpress.connections ?? []).map((connection) => ({
53
- ...connection,
54
- url: connection.url.replace(/\/$/, ""),
55
- stagingUrl: connection.stagingUrl?.replace(/\/$/, ""),
56
- defaultEnv: connection.defaultEnv ?? "live"
57
- }));
58
- }
59
- function getConfigDir() {
60
- const override = process.env.CANONRY_CONFIG_DIR?.trim();
61
- if (override) {
62
- return override;
63
- }
64
- return path.join(os.homedir(), ".canonry");
65
- }
66
- function getConfigPath() {
67
- return path.join(getConfigDir(), "config.yaml");
68
- }
69
- function loadConfig() {
70
- const configPath = getConfigPath();
71
- if (!fs.existsSync(configPath)) {
72
- throw new Error(
73
- `Config not found at ${configPath}.
74
- Run "canonry init" to set up interactively, or "canonry init --gemini-key <key>" for non-interactive setup.
75
- For CI/Docker, use "canonry bootstrap" with env vars (GEMINI_API_KEY, OPENAI_API_KEY, ANTHROPIC_API_KEY, PERPLEXITY_API_KEY, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET).`
76
- );
77
- }
78
- const raw = fs.readFileSync(configPath, "utf-8");
79
- const parsed = parse(raw);
80
- if (!parsed.apiUrl || !parsed.database || !parsed.apiKey) {
81
- const missing = [
82
- !parsed.apiUrl && "apiUrl",
83
- !parsed.database && "database",
84
- !parsed.apiKey && "apiKey"
85
- ].filter(Boolean).join(", ");
86
- throw new Error(
87
- `Invalid config at ${configPath} \u2014 missing: ${missing}.
88
- These fields are auto-generated. Run "canonry init" (or "canonry init --gemini-key <key>" for non-interactive setup) to create a valid config.
89
- Do not write config.yaml by hand; use "canonry init", "canonry settings", or "canonry bootstrap" instead.`
90
- );
91
- }
92
- if (parsed.geminiApiKey && !parsed.providers?.gemini) {
93
- parsed.providers = {
94
- ...parsed.providers,
95
- gemini: {
96
- apiKey: parsed.geminiApiKey,
97
- model: parsed.geminiModel,
98
- quota: parsed.geminiQuota
99
- }
100
- };
101
- }
102
- normalizeGoogleConfig(parsed);
103
- normalizeWordpressConfig(parsed);
104
- const portOverride = process.env.CANONRY_PORT?.trim();
105
- if (portOverride) {
106
- try {
107
- const url = new URL(parsed.apiUrl);
108
- url.port = portOverride;
109
- parsed.apiUrl = url.origin;
110
- } catch {
111
- }
112
- }
113
- if ("CANONRY_BASE_PATH" in process.env) {
114
- const val = process.env.CANONRY_BASE_PATH.trim();
115
- parsed.basePath = val || void 0;
116
- }
117
- if (parsed.basePath) {
118
- const normalizedBase = "/" + parsed.basePath.replace(/^\/|\/$/g, "");
119
- try {
120
- const url = new URL(parsed.apiUrl);
121
- if (normalizedBase !== "/" && !url.pathname.startsWith(normalizedBase)) {
122
- parsed.apiUrl = url.origin + normalizedBase;
123
- }
124
- } catch {
125
- }
126
- }
127
- return parsed;
128
- }
129
- function loadConfigRaw() {
130
- const configPath = getConfigPath();
131
- if (!fs.existsSync(configPath)) return null;
132
- try {
133
- return parse(fs.readFileSync(configPath, "utf-8")) ?? null;
134
- } catch {
135
- return null;
136
- }
137
- }
138
- function saveConfig(config) {
139
- const configDir = getConfigDir();
140
- if (!fs.existsSync(configDir)) {
141
- fs.mkdirSync(configDir, { recursive: true });
142
- }
143
- const configPath = getConfigPath();
144
- const onDisk = loadConfigRaw();
145
- const merged = onDisk ? { ...onDisk } : {};
146
- for (const [key, value] of Object.entries(config)) {
147
- if (value !== void 0) {
148
- merged[key] = value;
149
- }
150
- }
151
- if (onDisk) {
152
- if (process.env.CANONRY_PORT?.trim() || onDisk.basePath) {
153
- merged.apiUrl = onDisk.apiUrl;
154
- }
155
- if ("CANONRY_BASE_PATH" in process.env) {
156
- if (onDisk.basePath !== void 0) {
157
- merged.basePath = onDisk.basePath;
158
- } else {
159
- delete merged.basePath;
160
- }
161
- }
162
- }
163
- const yaml = stringify(merged);
164
- fs.writeFileSync(configPath, yaml, { encoding: "utf-8", mode: 384 });
165
- }
166
- function saveConfigPatch(patch) {
167
- const configDir = getConfigDir();
168
- if (!fs.existsSync(configDir)) {
169
- fs.mkdirSync(configDir, { recursive: true });
170
- }
171
- const configPath = getConfigPath();
172
- let base = {};
173
- if (fs.existsSync(configPath)) {
174
- try {
175
- const raw = fs.readFileSync(configPath, "utf-8");
176
- base = parse(raw) ?? {};
177
- } catch {
178
- base = {};
179
- }
180
- }
181
- const merged = { ...base, ...patch };
182
- if (base.database) merged.database = base.database;
183
- if (base.apiKey) merged.apiKey = base.apiKey;
184
- if (base.anonymousId) merged.anonymousId = base.anonymousId;
185
- if (base.dashboardPasswordHash) merged.dashboardPasswordHash = base.dashboardPasswordHash;
186
- if (base.providers && patch.providers) {
187
- merged.providers = { ...base.providers };
188
- for (const [key, patchEntry] of Object.entries(patch.providers)) {
189
- const baseEntry = base.providers[key] ?? {};
190
- merged.providers[key] = { ...baseEntry, ...patchEntry };
191
- }
192
- }
193
- const yaml = stringify(merged);
194
- fs.writeFileSync(configPath, yaml, { encoding: "utf-8", mode: 384 });
195
- }
196
- function configExists() {
197
- return fs.existsSync(getConfigPath());
198
- }
87
+ } from "./chunk-PYHANJ3B.js";
199
88
 
200
89
  // src/telemetry.ts
201
90
  import crypto from "crypto";
@@ -269,1259 +158,15 @@ function trackEvent(event, properties) {
269
158
  }).finally(() => clearTimeout(timeout));
270
159
  }
271
160
 
272
- // src/cli-error.ts
273
- var EXIT_USER_ERROR = 1;
274
- var EXIT_SYSTEM_ERROR = 2;
275
- var CliError = class extends Error {
276
- code;
277
- displayMessage;
278
- details;
279
- exitCode;
280
- constructor(options) {
281
- super(options.message);
282
- this.name = "CliError";
283
- this.code = options.code;
284
- this.displayMessage = options.displayMessage;
285
- this.details = options.details;
286
- this.exitCode = options.exitCode ?? EXIT_USER_ERROR;
287
- }
288
- };
289
- function usageError(displayMessage, options) {
290
- const firstLine = displayMessage.split("\n", 1)[0] ?? "Error: invalid command usage";
291
- return new CliError({
292
- code: "CLI_USAGE_ERROR",
293
- message: options?.message ?? firstLine.replace(/^Error:\s*/, ""),
294
- displayMessage,
295
- details: options?.details
296
- });
297
- }
298
- function isEndpointMissing(err) {
299
- if (!(err instanceof CliError)) return false;
300
- const status = err.details?.httpStatus;
301
- return status === 404 || status === 405;
302
- }
303
- function printCliError(err, format) {
304
- if (format === "json") {
305
- if (err instanceof CliError) {
306
- console.error(
307
- JSON.stringify(
308
- {
309
- error: {
310
- code: err.code,
311
- message: err.message,
312
- ...err.details ? { details: err.details } : {}
313
- }
314
- },
315
- null,
316
- 2
317
- )
318
- );
319
- return;
320
- }
321
- const message = err instanceof Error ? err.message : "An unexpected error occurred";
322
- console.error(
323
- JSON.stringify(
324
- {
325
- error: {
326
- code: "CLI_ERROR",
327
- message
328
- }
329
- },
330
- null,
331
- 2
332
- )
333
- );
334
- return;
335
- }
336
- if (err instanceof CliError && err.displayMessage) {
337
- console.error(err.displayMessage);
338
- return;
339
- }
340
- if (err instanceof Error) {
341
- console.error(`Error: ${err.message}`);
342
- return;
343
- }
344
- console.error("An unexpected error occurred");
345
- }
346
-
347
161
  // src/server.ts
348
162
  import { createRequire as createRequire3 } from "module";
349
163
  import crypto28 from "crypto";
350
- import fs13 from "fs";
351
- import path15 from "path";
164
+ import fs12 from "fs";
165
+ import path14 from "path";
352
166
  import { fileURLToPath as fileURLToPath2 } from "url";
353
167
  import { eq as eq30 } from "drizzle-orm";
354
168
  import Fastify from "fastify";
355
169
 
356
- // ../contracts/src/config-schema.ts
357
- import { z as z4 } from "zod";
358
-
359
- // ../contracts/src/provider.ts
360
- import { z } from "zod";
361
- var providerQuotaPolicySchema = z.object({
362
- maxConcurrency: z.number().int().positive(),
363
- maxRequestsPerMinute: z.number().int().positive(),
364
- maxRequestsPerDay: z.number().int().positive()
365
- });
366
- var ProviderNames = {
367
- gemini: "gemini",
368
- openai: "openai",
369
- claude: "claude",
370
- perplexity: "perplexity",
371
- local: "local",
372
- cdpChatgpt: "cdp:chatgpt"
373
- };
374
- var providerNameSchema = z.string().min(1);
375
- var apiProviderNameSchema = z.string().min(1);
376
- function isBrowserProvider(name) {
377
- return name.startsWith("cdp:");
378
- }
379
- var CDP_TARGETS = ["cdp:chatgpt"];
380
- function resolveProviderInput(input) {
381
- const lower = input.trim().toLowerCase();
382
- if (lower === "cdp") {
383
- return [...CDP_TARGETS];
384
- }
385
- return lower ? [lower] : [];
386
- }
387
- var locationContextSchema = z.object({
388
- label: z.string().min(1),
389
- city: z.string().min(1),
390
- region: z.string().min(1),
391
- country: z.string().length(2),
392
- timezone: z.string().optional()
393
- });
394
-
395
- // ../contracts/src/notification.ts
396
- import { z as z2 } from "zod";
397
- var notificationEventSchema = z2.enum([
398
- "citation.lost",
399
- "citation.gained",
400
- "run.completed",
401
- "run.failed",
402
- "insight.critical",
403
- "insight.high"
404
- ]);
405
- var notificationDtoSchema = z2.object({
406
- id: z2.string(),
407
- projectId: z2.string(),
408
- channel: z2.literal("webhook"),
409
- url: z2.string().url(),
410
- urlDisplay: z2.string(),
411
- urlHost: z2.string(),
412
- events: z2.array(notificationEventSchema),
413
- enabled: z2.boolean().default(true),
414
- /** Opaque tag identifying the creator (e.g. `"agent"` for Aero webhooks). */
415
- source: z2.string().optional(),
416
- webhookSecret: z2.string().optional(),
417
- createdAt: z2.string(),
418
- updatedAt: z2.string()
419
- });
420
-
421
- // ../contracts/src/project.ts
422
- import { z as z3 } from "zod";
423
- var configSourceSchema = z3.enum(["cli", "api", "config-file"]);
424
- function findDuplicateLocationLabels(locations) {
425
- const seen = /* @__PURE__ */ new Set();
426
- const duplicates = /* @__PURE__ */ new Set();
427
- for (const location of locations) {
428
- if (seen.has(location.label)) {
429
- duplicates.add(location.label);
430
- continue;
431
- }
432
- seen.add(location.label);
433
- }
434
- return [...duplicates];
435
- }
436
- function hasLocationLabel(locations, label) {
437
- if (!label) return true;
438
- return locations.some((location) => location.label === label);
439
- }
440
- var projectUpsertRequestSchema = z3.object({
441
- displayName: z3.string().min(1),
442
- canonicalDomain: z3.string().min(1),
443
- ownedDomains: z3.array(z3.string().min(1)).optional(),
444
- country: z3.string().length(2),
445
- language: z3.string().min(2),
446
- tags: z3.array(z3.string()).optional(),
447
- labels: z3.record(z3.string(), z3.string()).optional(),
448
- providers: z3.array(providerNameSchema).optional(),
449
- locations: z3.array(locationContextSchema).optional(),
450
- defaultLocation: z3.string().nullable().optional(),
451
- autoExtractBacklinks: z3.boolean().optional(),
452
- configSource: configSourceSchema.optional()
453
- });
454
- var projectDtoSchema = z3.object({
455
- id: z3.string(),
456
- name: z3.string(),
457
- displayName: z3.string().optional(),
458
- canonicalDomain: z3.string(),
459
- ownedDomains: z3.array(z3.string()).default([]),
460
- country: z3.string().length(2),
461
- language: z3.string().min(2),
462
- tags: z3.array(z3.string()).default([]),
463
- labels: z3.record(z3.string(), z3.string()).default({}),
464
- locations: z3.array(locationContextSchema).default([]),
465
- defaultLocation: z3.string().nullable().optional(),
466
- autoExtractBacklinks: z3.boolean().default(false),
467
- configSource: configSourceSchema.default("cli"),
468
- configRevision: z3.number().int().positive().default(1),
469
- createdAt: z3.string().optional(),
470
- updatedAt: z3.string().optional()
471
- });
472
- function normalizeProjectDomain(input) {
473
- let domain = input.trim().toLowerCase();
474
- try {
475
- if (domain.includes("://")) {
476
- domain = new URL(domain).hostname.toLowerCase();
477
- }
478
- } catch {
479
- }
480
- return domain.replace(/^www\./, "");
481
- }
482
- function effectiveDomains(project) {
483
- const all = [project.canonicalDomain, ...project.ownedDomains ?? []];
484
- const seen = /* @__PURE__ */ new Set();
485
- const result = [];
486
- for (const d of all) {
487
- const trimmed = d.trim();
488
- if (!trimmed) continue;
489
- const norm = normalizeProjectDomain(trimmed);
490
- if (seen.has(norm)) continue;
491
- seen.add(norm);
492
- result.push(trimmed);
493
- }
494
- return result;
495
- }
496
-
497
- // ../contracts/src/config-schema.ts
498
- var configMetadataSchema = z4.object({
499
- name: z4.string().min(1).max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, {
500
- message: "Name must be a lowercase slug (letters, numbers, hyphens)"
501
- }),
502
- labels: z4.record(z4.string(), z4.string()).optional().default({})
503
- });
504
- var configScheduleSchema = z4.object({
505
- preset: z4.string().optional(),
506
- cron: z4.string().optional(),
507
- timezone: z4.string().optional().default("UTC"),
508
- providers: z4.array(providerNameSchema).optional().default([])
509
- }).refine(
510
- (data) => data.preset && !data.cron || !data.preset && data.cron,
511
- { message: 'Exactly one of "preset" or "cron" must be provided' }
512
- ).optional();
513
- var configNotificationSchema = z4.object({
514
- channel: z4.literal("webhook"),
515
- url: z4.string().url(),
516
- events: z4.array(notificationEventSchema).min(1)
517
- });
518
- var configGoogleSchema = z4.object({
519
- gsc: z4.object({
520
- propertyUrl: z4.string()
521
- }).optional(),
522
- syncSchedule: z4.object({
523
- preset: z4.string().optional(),
524
- cron: z4.string().optional()
525
- }).optional()
526
- }).optional();
527
- var configSpecSchema = z4.object({
528
- displayName: z4.string().min(1),
529
- canonicalDomain: z4.string().min(1),
530
- ownedDomains: z4.array(z4.string().min(1)).optional().default([]),
531
- country: z4.string().length(2),
532
- language: z4.string().min(2),
533
- keywords: z4.array(z4.string().min(1)).optional().default([]),
534
- competitors: z4.array(z4.string().min(1)).optional().default([]),
535
- providers: z4.array(providerNameSchema).optional().default([]),
536
- locations: z4.array(locationContextSchema).optional().default([]),
537
- defaultLocation: z4.string().optional(),
538
- schedule: configScheduleSchema,
539
- notifications: z4.array(configNotificationSchema).optional().default([]),
540
- google: configGoogleSchema,
541
- autoExtractBacklinks: z4.boolean().optional().default(false)
542
- }).superRefine((spec, ctx) => {
543
- const duplicateLabels = findDuplicateLocationLabels(spec.locations);
544
- if (duplicateLabels.length > 0) {
545
- ctx.addIssue({
546
- code: "custom",
547
- message: `Duplicate location labels are not allowed: ${duplicateLabels.join(", ")}`,
548
- path: ["locations"]
549
- });
550
- }
551
- if (!hasLocationLabel(spec.locations, spec.defaultLocation)) {
552
- ctx.addIssue({
553
- code: "custom",
554
- message: `defaultLocation "${spec.defaultLocation}" must match a configured location label`,
555
- path: ["defaultLocation"]
556
- });
557
- }
558
- });
559
- var projectConfigSchema = z4.object({
560
- apiVersion: z4.literal("canonry/v1"),
561
- kind: z4.literal("Project"),
562
- metadata: configMetadataSchema,
563
- spec: configSpecSchema
564
- });
565
-
566
- // ../contracts/src/errors.ts
567
- var AppError = class extends Error {
568
- code;
569
- statusCode;
570
- details;
571
- constructor(code, message, statusCode, details) {
572
- super(message);
573
- this.name = "AppError";
574
- this.code = code;
575
- this.statusCode = statusCode;
576
- this.details = details;
577
- }
578
- toJSON() {
579
- return {
580
- error: {
581
- code: this.code,
582
- message: this.message,
583
- ...this.details ? { details: this.details } : {}
584
- }
585
- };
586
- }
587
- };
588
- function notFound(entity, id) {
589
- return new AppError("NOT_FOUND", `${entity} '${id}' not found`, 404);
590
- }
591
- function validationError(message, details) {
592
- return new AppError("VALIDATION_ERROR", message, 400, details);
593
- }
594
- function authRequired() {
595
- return new AppError("AUTH_REQUIRED", "Authentication required", 401);
596
- }
597
- function authInvalid() {
598
- return new AppError("AUTH_INVALID", "Invalid API key", 401);
599
- }
600
- function providerError(message, details) {
601
- return new AppError("PROVIDER_ERROR", message, 502, details);
602
- }
603
- function runInProgress(projectName) {
604
- return new AppError("RUN_IN_PROGRESS", `A run is already in progress for '${projectName}'`, 409);
605
- }
606
- function runNotCancellable(runId, status) {
607
- return new AppError("RUN_NOT_CANCELLABLE", `Run '${runId}' is already in terminal state '${status}' and cannot be cancelled`, 409);
608
- }
609
- function unsupportedKind(kind) {
610
- return new AppError("UNSUPPORTED_KIND", `Kind '${kind}' is not supported in this version`, 400);
611
- }
612
- function notImplemented(message) {
613
- return new AppError("NOT_IMPLEMENTED", message, 501);
614
- }
615
- function deliveryFailed(message) {
616
- return new AppError("DELIVERY_FAILED", message, 502);
617
- }
618
- function agentBusy(projectName) {
619
- return new AppError(
620
- "AGENT_BUSY",
621
- `Aero is already running a turn for '${projectName}'. Retry after the current turn settles.`,
622
- 409
623
- );
624
- }
625
- function missingDependency(message, details) {
626
- return new AppError("MISSING_DEPENDENCY", message, 422, details);
627
- }
628
- function internalError(message, details) {
629
- return new AppError("INTERNAL_ERROR", message, 500, details);
630
- }
631
-
632
- // ../contracts/src/google.ts
633
- import { z as z5 } from "zod";
634
- var googleConnectionTypeSchema = z5.enum(["gsc", "ga4"]);
635
- var googleConnectionDtoSchema = z5.object({
636
- id: z5.string(),
637
- domain: z5.string(),
638
- connectionType: googleConnectionTypeSchema,
639
- propertyId: z5.string().nullable().optional(),
640
- sitemapUrl: z5.string().nullable().optional(),
641
- scopes: z5.array(z5.string()).default([]),
642
- createdAt: z5.string(),
643
- updatedAt: z5.string()
644
- });
645
- var gscSearchDataDtoSchema = z5.object({
646
- date: z5.string(),
647
- query: z5.string(),
648
- page: z5.string(),
649
- country: z5.string().nullable().optional(),
650
- device: z5.string().nullable().optional(),
651
- clicks: z5.number(),
652
- impressions: z5.number(),
653
- ctr: z5.number(),
654
- position: z5.number()
655
- });
656
- var gscUrlInspectionDtoSchema = z5.object({
657
- id: z5.string(),
658
- url: z5.string(),
659
- indexingState: z5.string().nullable().optional(),
660
- verdict: z5.string().nullable().optional(),
661
- coverageState: z5.string().nullable().optional(),
662
- pageFetchState: z5.string().nullable().optional(),
663
- robotsTxtState: z5.string().nullable().optional(),
664
- crawlTime: z5.string().nullable().optional(),
665
- lastCrawlResult: z5.string().nullable().optional(),
666
- isMobileFriendly: z5.boolean().nullable().optional(),
667
- richResults: z5.array(z5.string()).default([]),
668
- inspectedAt: z5.string()
669
- });
670
- var indexTransitionSchema = z5.enum(["stable", "reindexed", "deindexed", "still-missing", "new"]);
671
- var gscDeindexedRowSchema = z5.object({
672
- url: z5.string(),
673
- previousState: z5.string().nullable(),
674
- currentState: z5.string().nullable(),
675
- transitionDate: z5.string()
676
- });
677
- var gscReasonGroupSchema = z5.object({
678
- reason: z5.string(),
679
- count: z5.number(),
680
- urls: z5.array(gscUrlInspectionDtoSchema).default([])
681
- });
682
- var gscCoverageSummaryDtoSchema = z5.object({
683
- summary: z5.object({
684
- total: z5.number(),
685
- indexed: z5.number(),
686
- notIndexed: z5.number(),
687
- deindexed: z5.number(),
688
- percentage: z5.number()
689
- }),
690
- lastInspectedAt: z5.string().nullable(),
691
- indexed: z5.array(gscUrlInspectionDtoSchema).default([]),
692
- notIndexed: z5.array(gscUrlInspectionDtoSchema).default([]),
693
- deindexed: z5.array(gscDeindexedRowSchema).default([]),
694
- reasonGroups: z5.array(gscReasonGroupSchema).default([])
695
- });
696
- var indexingNotificationDtoSchema = z5.object({
697
- url: z5.string(),
698
- type: z5.enum(["URL_UPDATED", "URL_DELETED"]),
699
- notifiedAt: z5.string()
700
- });
701
- var indexingRequestResultDtoSchema = z5.object({
702
- url: z5.string(),
703
- type: z5.enum(["URL_UPDATED", "URL_DELETED"]),
704
- notifiedAt: z5.string(),
705
- status: z5.enum(["success", "error"]),
706
- error: z5.string().optional()
707
- });
708
- var gscCoverageSnapshotDtoSchema = z5.object({
709
- date: z5.string(),
710
- indexed: z5.number(),
711
- notIndexed: z5.number(),
712
- reasonBreakdown: z5.record(z5.string(), z5.number()).default({})
713
- });
714
-
715
- // ../contracts/src/bing.ts
716
- import { z as z6 } from "zod";
717
- var bingConnectionDtoSchema = z6.object({
718
- id: z6.string(),
719
- domain: z6.string(),
720
- siteUrl: z6.string().nullable().optional(),
721
- createdAt: z6.string(),
722
- updatedAt: z6.string()
723
- });
724
- var bingUrlInspectionDtoSchema = z6.object({
725
- id: z6.string(),
726
- url: z6.string(),
727
- httpCode: z6.number().nullable().optional(),
728
- inIndex: z6.boolean().nullable().optional(),
729
- lastCrawledDate: z6.string().nullable().optional(),
730
- inIndexDate: z6.string().nullable().optional(),
731
- inspectedAt: z6.string(),
732
- // Fields derived from GetUrlInfo response (more reliable than InIndex)
733
- documentSize: z6.number().nullable().optional(),
734
- anchorCount: z6.number().nullable().optional(),
735
- discoveryDate: z6.string().nullable().optional()
736
- });
737
- var bingCoverageSummaryDtoSchema = z6.object({
738
- summary: z6.object({
739
- total: z6.number(),
740
- indexed: z6.number(),
741
- notIndexed: z6.number(),
742
- unknown: z6.number().optional(),
743
- percentage: z6.number()
744
- }),
745
- lastInspectedAt: z6.string().nullable(),
746
- indexed: z6.array(bingUrlInspectionDtoSchema).default([]),
747
- notIndexed: z6.array(bingUrlInspectionDtoSchema).default([]),
748
- unknown: z6.array(bingUrlInspectionDtoSchema).default([]).optional()
749
- });
750
- var bingKeywordStatsDtoSchema = z6.object({
751
- query: z6.string(),
752
- impressions: z6.number(),
753
- clicks: z6.number(),
754
- ctr: z6.number(),
755
- averagePosition: z6.number()
756
- });
757
- var bingCoverageSnapshotDtoSchema = z6.object({
758
- date: z6.string(),
759
- indexed: z6.number(),
760
- notIndexed: z6.number(),
761
- unknown: z6.number()
762
- });
763
- var bingSubmitResultDtoSchema = z6.object({
764
- url: z6.string(),
765
- status: z6.enum(["success", "error"]),
766
- submittedAt: z6.string(),
767
- error: z6.string().optional()
768
- });
769
-
770
- // ../contracts/src/wordpress.ts
771
- import { z as z7 } from "zod";
772
- var wordpressEnvSchema = z7.enum(["live", "staging"]);
773
- var wordpressConnectionDtoSchema = z7.object({
774
- projectName: z7.string(),
775
- url: z7.string(),
776
- stagingUrl: z7.string().optional(),
777
- username: z7.string(),
778
- defaultEnv: wordpressEnvSchema,
779
- createdAt: z7.string(),
780
- updatedAt: z7.string()
781
- });
782
- var wordpressSiteStatusDtoSchema = z7.object({
783
- url: z7.string(),
784
- reachable: z7.boolean(),
785
- pageCount: z7.number().nullable().optional(),
786
- version: z7.string().nullable().optional(),
787
- error: z7.string().nullable().optional(),
788
- plugins: z7.array(z7.string()).optional(),
789
- authenticatedUser: z7.object({
790
- id: z7.number(),
791
- slug: z7.string()
792
- }).nullable().optional()
793
- });
794
- var wordpressStatusDtoSchema = z7.object({
795
- connected: z7.boolean(),
796
- projectName: z7.string(),
797
- defaultEnv: wordpressEnvSchema,
798
- live: wordpressSiteStatusDtoSchema.nullable(),
799
- staging: wordpressSiteStatusDtoSchema.nullable(),
800
- adminUrl: z7.string().nullable().optional()
801
- });
802
- var wordpressPageSummaryDtoSchema = z7.object({
803
- id: z7.number(),
804
- slug: z7.string(),
805
- title: z7.string(),
806
- status: z7.string(),
807
- modifiedAt: z7.string().nullable().optional(),
808
- link: z7.string().nullable().optional()
809
- });
810
- var wordpressSeoStateDtoSchema = z7.object({
811
- title: z7.string().nullable(),
812
- description: z7.string().nullable(),
813
- noindex: z7.boolean().nullable(),
814
- writable: z7.boolean().default(false),
815
- writeTargets: z7.array(z7.string()).default([])
816
- });
817
- var wordpressSchemaBlockDtoSchema = z7.object({
818
- type: z7.string(),
819
- json: z7.record(z7.string(), z7.unknown())
820
- });
821
- var wordpressPageDetailDtoSchema = wordpressPageSummaryDtoSchema.extend({
822
- env: wordpressEnvSchema,
823
- content: z7.string(),
824
- seo: wordpressSeoStateDtoSchema,
825
- schemaBlocks: z7.array(wordpressSchemaBlockDtoSchema).default([])
826
- });
827
- var wordpressDiffPageDtoSchema = wordpressPageDetailDtoSchema.extend({
828
- contentHash: z7.string(),
829
- contentSnippet: z7.string()
830
- });
831
- var wordpressManualAssistDtoSchema = z7.object({
832
- manualRequired: z7.literal(true),
833
- targetUrl: z7.string(),
834
- adminUrl: z7.string().nullable().optional(),
835
- content: z7.string(),
836
- nextSteps: z7.array(z7.string()).default([])
837
- });
838
- var wordpressAuditIssueDtoSchema = z7.object({
839
- slug: z7.string(),
840
- severity: z7.enum(["high", "medium", "low"]),
841
- code: z7.enum([
842
- "noindex",
843
- "missing-seo-title",
844
- "missing-meta-description",
845
- "missing-schema",
846
- "thin-content"
847
- ]),
848
- message: z7.string()
849
- });
850
- var wordpressAuditPageDtoSchema = z7.object({
851
- slug: z7.string(),
852
- title: z7.string(),
853
- status: z7.string(),
854
- wordCount: z7.number(),
855
- seo: wordpressSeoStateDtoSchema,
856
- schemaPresent: z7.boolean(),
857
- issues: z7.array(wordpressAuditIssueDtoSchema).default([])
858
- });
859
- var wordpressBulkMetaEntryResultDtoSchema = z7.object({
860
- slug: z7.string(),
861
- status: z7.enum(["applied", "skipped", "manual"]),
862
- error: z7.string().optional(),
863
- manualAssist: wordpressManualAssistDtoSchema.optional()
864
- });
865
- var wordpressBulkMetaResultDtoSchema = z7.object({
866
- env: wordpressEnvSchema,
867
- strategy: z7.enum(["plugin", "manual"]),
868
- results: z7.array(wordpressBulkMetaEntryResultDtoSchema)
869
- });
870
- var wordpressSchemaDeployEntryResultDtoSchema = z7.object({
871
- slug: z7.string(),
872
- status: z7.enum(["deployed", "stripped", "skipped", "failed"]),
873
- schemasInjected: z7.array(z7.string()).optional(),
874
- manualAssist: wordpressManualAssistDtoSchema.optional(),
875
- error: z7.string().optional()
876
- });
877
- var wordpressSchemaDeployResultDtoSchema = z7.object({
878
- env: wordpressEnvSchema,
879
- results: z7.array(wordpressSchemaDeployEntryResultDtoSchema)
880
- });
881
- var wordpressSchemaStatusPageDtoSchema = z7.object({
882
- slug: z7.string(),
883
- title: z7.string(),
884
- canonrySchemas: z7.array(z7.string()),
885
- thirdPartySchemas: z7.array(z7.string()),
886
- hasCanonrySchema: z7.boolean()
887
- });
888
- var wordpressSchemaStatusResultDtoSchema = z7.object({
889
- env: wordpressEnvSchema,
890
- pages: z7.array(wordpressSchemaStatusPageDtoSchema)
891
- });
892
- var wordpressOnboardStepDtoSchema = z7.object({
893
- name: z7.string(),
894
- status: z7.enum(["completed", "skipped", "failed"]),
895
- summary: z7.string().optional(),
896
- error: z7.string().optional()
897
- });
898
- var wordpressOnboardResultDtoSchema = z7.object({
899
- projectName: z7.string(),
900
- steps: z7.array(wordpressOnboardStepDtoSchema)
901
- });
902
- var wordpressDiffDtoSchema = z7.object({
903
- slug: z7.string(),
904
- live: wordpressDiffPageDtoSchema,
905
- staging: wordpressDiffPageDtoSchema,
906
- hasDifferences: z7.boolean(),
907
- differences: z7.object({
908
- title: z7.boolean(),
909
- slug: z7.boolean(),
910
- content: z7.boolean(),
911
- seoTitle: z7.boolean(),
912
- seoDescription: z7.boolean(),
913
- noindex: z7.boolean(),
914
- schema: z7.boolean()
915
- })
916
- });
917
-
918
- // ../contracts/src/providers.ts
919
- var ProviderIds = {
920
- claude: "claude",
921
- openai: "openai",
922
- gemini: "gemini",
923
- perplexity: "perplexity",
924
- local: "local",
925
- cdpChatgpt: "cdp:chatgpt",
926
- zai: "zai"
927
- };
928
- var PROVIDER_IDS = Object.values(ProviderIds);
929
- var SweepProviderIds = {
930
- claude: ProviderIds.claude,
931
- openai: ProviderIds.openai,
932
- gemini: ProviderIds.gemini,
933
- perplexity: ProviderIds.perplexity,
934
- local: ProviderIds.local,
935
- cdpChatgpt: ProviderIds.cdpChatgpt
936
- };
937
- var SWEEP_PROVIDER_IDS = Object.values(SweepProviderIds);
938
- var AgentProviderIds = {
939
- claude: ProviderIds.claude,
940
- openai: ProviderIds.openai,
941
- gemini: ProviderIds.gemini,
942
- zai: ProviderIds.zai
943
- };
944
- var AGENT_PROVIDER_IDS = Object.values(AgentProviderIds);
945
- function isAgentProviderId(value) {
946
- return AGENT_PROVIDER_IDS.includes(value);
947
- }
948
-
949
- // ../contracts/src/run.ts
950
- import { z as z8 } from "zod";
951
- var runStatusSchema = z8.enum(["queued", "running", "completed", "partial", "failed", "cancelled"]);
952
- var RunStatuses = runStatusSchema.enum;
953
- var runKindSchema = z8.enum([
954
- "answer-visibility",
955
- "site-audit",
956
- "gsc-sync",
957
- "inspect-sitemap",
958
- "ga-sync",
959
- "bing-inspect",
960
- "bing-inspect-sitemap",
961
- "backlink-extract"
962
- ]);
963
- var RunKinds = runKindSchema.enum;
964
- var runTriggerSchema = z8.enum(["manual", "scheduled", "config-apply"]);
965
- var RunTriggers = runTriggerSchema.enum;
966
- var citationStateSchema = z8.enum(["cited", "not-cited"]);
967
- var CitationStates = citationStateSchema.enum;
968
- var visibilityStateSchema = z8.enum(["visible", "not-visible"]);
969
- var VisibilityStates = visibilityStateSchema.enum;
970
- var computedTransitionSchema = z8.enum(["new", "cited", "lost", "emerging", "not-cited"]);
971
- var ComputedTransitions = computedTransitionSchema.enum;
972
- var runDtoSchema = z8.object({
973
- id: z8.string(),
974
- projectId: z8.string(),
975
- kind: runKindSchema,
976
- status: runStatusSchema,
977
- trigger: runTriggerSchema.default("manual"),
978
- location: z8.string().nullable().optional(),
979
- startedAt: z8.string().nullable().optional(),
980
- finishedAt: z8.string().nullable().optional(),
981
- error: z8.string().nullable().optional(),
982
- createdAt: z8.string()
983
- });
984
- var groundingSourceSchema = z8.object({
985
- uri: z8.string(),
986
- title: z8.string()
987
- });
988
- var querySnapshotDtoSchema = z8.object({
989
- id: z8.string(),
990
- runId: z8.string(),
991
- keywordId: z8.string(),
992
- keyword: z8.string().optional(),
993
- provider: providerNameSchema,
994
- citationState: citationStateSchema,
995
- answerMentioned: z8.boolean().optional(),
996
- visibilityState: visibilityStateSchema.optional(),
997
- transition: computedTransitionSchema.optional(),
998
- answerText: z8.string().nullable().optional(),
999
- citedDomains: z8.array(z8.string()).default([]),
1000
- competitorOverlap: z8.array(z8.string()).default([]),
1001
- recommendedCompetitors: z8.array(z8.string()).default([]),
1002
- matchedTerms: z8.array(z8.string()).default([]),
1003
- groundingSources: z8.array(groundingSourceSchema).default([]),
1004
- searchQueries: z8.array(z8.string()).default([]),
1005
- model: z8.string().nullable().optional(),
1006
- location: z8.string().nullable().optional(),
1007
- createdAt: z8.string()
1008
- });
1009
- var runDetailDtoSchema = runDtoSchema.extend({
1010
- snapshots: z8.array(querySnapshotDtoSchema).optional()
1011
- });
1012
- var latestProjectRunDtoSchema = z8.object({
1013
- totalRuns: z8.number().int().nonnegative(),
1014
- run: runDetailDtoSchema.nullable()
1015
- });
1016
- var auditLogEntrySchema = z8.object({
1017
- id: z8.string(),
1018
- projectId: z8.string().nullable().optional(),
1019
- actor: z8.string(),
1020
- action: z8.string(),
1021
- entityType: z8.string(),
1022
- entityId: z8.string().nullable().optional(),
1023
- diff: z8.unknown().optional(),
1024
- createdAt: z8.string()
1025
- });
1026
-
1027
- // ../contracts/src/snapshot.ts
1028
- import { z as z9 } from "zod";
1029
- var snapshotAccuracySchema = z9.enum(["yes", "no", "unknown", "not-mentioned"]);
1030
- var snapshotRequestSchema = z9.object({
1031
- companyName: z9.string().min(1),
1032
- domain: z9.string().min(1),
1033
- phrases: z9.array(z9.string().min(1)).optional().default([]),
1034
- competitors: z9.array(z9.string().min(1)).optional().default([])
1035
- });
1036
- var snapshotCompetitorEntrySchema = z9.object({
1037
- name: z9.string(),
1038
- count: z9.number().int().nonnegative()
1039
- });
1040
- var snapshotAuditFactorSchema = z9.object({
1041
- id: z9.string(),
1042
- name: z9.string(),
1043
- weight: z9.number(),
1044
- score: z9.number(),
1045
- grade: z9.string(),
1046
- status: z9.enum(["pass", "partial", "fail"]),
1047
- findings: z9.array(z9.object({
1048
- type: z9.string(),
1049
- message: z9.string()
1050
- })).default([]),
1051
- recommendations: z9.array(z9.string()).default([])
1052
- });
1053
- var snapshotAuditSchema = z9.object({
1054
- url: z9.string(),
1055
- finalUrl: z9.string(),
1056
- auditedAt: z9.string(),
1057
- overallScore: z9.number(),
1058
- overallGrade: z9.string(),
1059
- summary: z9.string(),
1060
- factors: z9.array(snapshotAuditFactorSchema).default([])
1061
- });
1062
- var snapshotProfileSchema = z9.object({
1063
- industry: z9.string(),
1064
- summary: z9.string(),
1065
- services: z9.array(z9.string()).default([]),
1066
- categoryTerms: z9.array(z9.string()).default([])
1067
- });
1068
- var snapshotProviderResultSchema = z9.object({
1069
- provider: z9.string(),
1070
- displayName: z9.string(),
1071
- model: z9.string().nullable().optional(),
1072
- mentioned: z9.boolean(),
1073
- cited: z9.boolean(),
1074
- describedAccurately: snapshotAccuracySchema,
1075
- accuracyNotes: z9.string().nullable().optional(),
1076
- incorrectClaims: z9.array(z9.string()).default([]),
1077
- recommendedCompetitors: z9.array(z9.string()).default([]),
1078
- citedDomains: z9.array(z9.string()).default([]),
1079
- groundingSources: z9.array(groundingSourceSchema).default([]),
1080
- searchQueries: z9.array(z9.string()).default([]),
1081
- answerText: z9.string(),
1082
- error: z9.string().nullable().optional()
1083
- });
1084
- var snapshotQueryResultSchema = z9.object({
1085
- phrase: z9.string(),
1086
- providerResults: z9.array(snapshotProviderResultSchema).default([])
1087
- });
1088
- var snapshotSummarySchema = z9.object({
1089
- totalQueries: z9.number().int().nonnegative(),
1090
- totalProviders: z9.number().int().nonnegative(),
1091
- totalComparisons: z9.number().int().nonnegative(),
1092
- mentionCount: z9.number().int().nonnegative(),
1093
- citationCount: z9.number().int().nonnegative(),
1094
- topCompetitors: z9.array(snapshotCompetitorEntrySchema).default([]),
1095
- visibilityGap: z9.string(),
1096
- whatThisMeans: z9.array(z9.string()).default([]),
1097
- recommendedActions: z9.array(z9.string()).default([])
1098
- });
1099
- var snapshotReportSchema = z9.object({
1100
- companyName: z9.string(),
1101
- domain: z9.string(),
1102
- homepageUrl: z9.string(),
1103
- generatedAt: z9.string(),
1104
- phrases: z9.array(z9.string()).default([]),
1105
- competitors: z9.array(z9.string()).default([]),
1106
- profile: snapshotProfileSchema,
1107
- audit: snapshotAuditSchema,
1108
- queryResults: z9.array(snapshotQueryResultSchema).default([]),
1109
- summary: snapshotSummarySchema
1110
- });
1111
-
1112
- // ../contracts/src/schedule.ts
1113
- import { z as z10 } from "zod";
1114
- var scheduleDtoSchema = z10.object({
1115
- id: z10.string(),
1116
- projectId: z10.string(),
1117
- cronExpr: z10.string(),
1118
- preset: z10.string().nullable().optional(),
1119
- timezone: z10.string().default("UTC"),
1120
- enabled: z10.boolean().default(true),
1121
- providers: z10.array(providerNameSchema).default([]),
1122
- lastRunAt: z10.string().nullable().optional(),
1123
- nextRunAt: z10.string().nullable().optional(),
1124
- createdAt: z10.string(),
1125
- updatedAt: z10.string()
1126
- });
1127
- var scheduleUpsertRequestSchema = z10.object({
1128
- preset: z10.string().optional(),
1129
- cron: z10.string().optional(),
1130
- timezone: z10.string().optional().default("UTC"),
1131
- enabled: z10.boolean().optional().default(true),
1132
- providers: z10.array(providerNameSchema).optional().default([])
1133
- }).refine(
1134
- (data) => data.preset && !data.cron || !data.preset && data.cron,
1135
- { message: 'Exactly one of "preset" or "cron" must be provided' }
1136
- );
1137
-
1138
- // ../contracts/src/analytics.ts
1139
- import { z as z11 } from "zod";
1140
- var visibilityMetricModeSchema = z11.enum(["answer", "citation"]);
1141
- var VisibilityMetricModes = visibilityMetricModeSchema.enum;
1142
- function parseWindow(value) {
1143
- if (value === "7d" || value === "30d" || value === "90d" || value === "all") return value;
1144
- return "all";
1145
- }
1146
- function windowCutoff(window) {
1147
- if (window === "all") return null;
1148
- const days = window === "7d" ? 7 : window === "30d" ? 30 : 90;
1149
- const d = /* @__PURE__ */ new Date();
1150
- d.setDate(d.getDate() - days);
1151
- return d.toISOString();
1152
- }
1153
-
1154
- // ../contracts/src/source-categories.ts
1155
- var SOURCE_CATEGORY_RULES = [
1156
- // Forums
1157
- { pattern: "reddit.com", category: "forum", label: "Reddit" },
1158
- { pattern: "quora.com", category: "forum", label: "Quora" },
1159
- { pattern: "stackexchange.com", category: "forum", label: "Stack Exchange" },
1160
- { pattern: "stackoverflow.com", category: "forum", label: "Stack Overflow" },
1161
- { pattern: "discourse.org", category: "forum", label: "Discourse" },
1162
- // Social
1163
- { pattern: "linkedin.com", category: "social", label: "LinkedIn" },
1164
- { pattern: "twitter.com", category: "social", label: "X (Twitter)" },
1165
- { pattern: "x.com", category: "social", label: "X (Twitter)" },
1166
- { pattern: "facebook.com", category: "social", label: "Facebook" },
1167
- { pattern: "instagram.com", category: "social", label: "Instagram" },
1168
- { pattern: "threads.net", category: "social", label: "Threads" },
1169
- { pattern: "pinterest.com", category: "social", label: "Pinterest" },
1170
- { pattern: "tiktok.com", category: "social", label: "TikTok" },
1171
- // Video
1172
- { pattern: "youtube.com", category: "video", label: "YouTube" },
1173
- { pattern: "youtu.be", category: "video", label: "YouTube" },
1174
- { pattern: "vimeo.com", category: "video", label: "Vimeo" },
1175
- // News
1176
- { pattern: "nytimes.com", category: "news", label: "NY Times" },
1177
- { pattern: "bbc.com", category: "news", label: "BBC" },
1178
- { pattern: "bbc.co.uk", category: "news", label: "BBC" },
1179
- { pattern: "cnn.com", category: "news", label: "CNN" },
1180
- { pattern: "reuters.com", category: "news", label: "Reuters" },
1181
- { pattern: "apnews.com", category: "news", label: "AP News" },
1182
- { pattern: "theguardian.com", category: "news", label: "The Guardian" },
1183
- { pattern: "washingtonpost.com", category: "news", label: "Washington Post" },
1184
- { pattern: "wsj.com", category: "news", label: "WSJ" },
1185
- { pattern: "forbes.com", category: "news", label: "Forbes" },
1186
- { pattern: "techcrunch.com", category: "news", label: "TechCrunch" },
1187
- { pattern: "theverge.com", category: "news", label: "The Verge" },
1188
- { pattern: "wired.com", category: "news", label: "Wired" },
1189
- { pattern: "arstechnica.com", category: "news", label: "Ars Technica" },
1190
- // Reference
1191
- { pattern: "wikipedia.org", category: "reference", label: "Wikipedia" },
1192
- { pattern: "wikimedia.org", category: "reference", label: "Wikimedia" },
1193
- { pattern: "britannica.com", category: "reference", label: "Britannica" },
1194
- { pattern: "merriam-webster.com", category: "reference", label: "Merriam-Webster" },
1195
- // Blog / Content platforms
1196
- { pattern: "medium.com", category: "blog", label: "Medium" },
1197
- { pattern: "substack.com", category: "blog", label: "Substack" },
1198
- { pattern: "dev.to", category: "blog", label: "DEV Community" },
1199
- { pattern: "hashnode.dev", category: "blog", label: "Hashnode" },
1200
- { pattern: "wordpress.com", category: "blog", label: "WordPress" },
1201
- { pattern: "blogger.com", category: "blog", label: "Blogger" },
1202
- { pattern: "hubspot.com", category: "blog", label: "HubSpot" },
1203
- // E-commerce
1204
- { pattern: "amazon.com", category: "ecommerce", label: "Amazon" },
1205
- { pattern: "amazon.co.uk", category: "ecommerce", label: "Amazon UK" },
1206
- { pattern: "shopify.com", category: "ecommerce", label: "Shopify" },
1207
- { pattern: "ebay.com", category: "ecommerce", label: "eBay" },
1208
- // Academic
1209
- { pattern: "scholar.google.com", category: "academic", label: "Google Scholar" },
1210
- { pattern: "arxiv.org", category: "academic", label: "arXiv" },
1211
- { pattern: "pubmed.ncbi.nlm.nih.gov", category: "academic", label: "PubMed" },
1212
- { pattern: "researchgate.net", category: "academic", label: "ResearchGate" },
1213
- { pattern: ".edu", category: "academic", label: "Academic (.edu)" }
1214
- ];
1215
- var CATEGORY_LABELS = {
1216
- social: "Social Media",
1217
- forum: "Forums & Q&A",
1218
- news: "News & Media",
1219
- reference: "Reference",
1220
- blog: "Blogs & Content",
1221
- ecommerce: "E-commerce",
1222
- video: "Video",
1223
- academic: "Academic",
1224
- other: "Other"
1225
- };
1226
- function categorizeSource(uri) {
1227
- let domain;
1228
- try {
1229
- const url = new URL(uri.startsWith("http") ? uri : `https://${uri}`);
1230
- domain = url.hostname.replace(/^www\./, "");
1231
- } catch {
1232
- domain = uri.replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0] ?? uri;
1233
- }
1234
- const domainLower = domain.toLowerCase();
1235
- for (const rule of SOURCE_CATEGORY_RULES) {
1236
- if (domainLower === rule.pattern || domainLower.endsWith(`.${rule.pattern}`) || rule.pattern.startsWith(".") && domainLower.endsWith(rule.pattern)) {
1237
- return { category: rule.category, label: rule.label, domain };
1238
- }
1239
- }
1240
- return { category: "other", label: CATEGORY_LABELS.other, domain };
1241
- }
1242
- function categoryLabel(category) {
1243
- return CATEGORY_LABELS[category];
1244
- }
1245
-
1246
- // ../contracts/src/ga.ts
1247
- import { z as z12 } from "zod";
1248
- var ga4ConnectionDtoSchema = z12.object({
1249
- id: z12.string(),
1250
- projectId: z12.string(),
1251
- propertyId: z12.string(),
1252
- clientEmail: z12.string(),
1253
- connected: z12.boolean(),
1254
- createdAt: z12.string(),
1255
- updatedAt: z12.string()
1256
- });
1257
- var ga4TrafficSnapshotDtoSchema = z12.object({
1258
- date: z12.string(),
1259
- landingPage: z12.string(),
1260
- sessions: z12.number(),
1261
- organicSessions: z12.number(),
1262
- users: z12.number()
1263
- });
1264
- var ga4SourceDimensionSchema = z12.enum(["session", "first_user", "manual_utm"]);
1265
- var ga4AiReferralDtoSchema = z12.object({
1266
- source: z12.string(),
1267
- medium: z12.string(),
1268
- sessions: z12.number(),
1269
- users: z12.number(),
1270
- sourceDimension: ga4SourceDimensionSchema
1271
- });
1272
- var ga4SocialReferralDtoSchema = z12.object({
1273
- source: z12.string(),
1274
- medium: z12.string(),
1275
- sessions: z12.number(),
1276
- users: z12.number(),
1277
- /** GA4 default channel group (e.g. 'Organic Social', 'Paid Social') */
1278
- channelGroup: z12.string()
1279
- });
1280
- var ga4TrafficSummaryDtoSchema = z12.object({
1281
- totalSessions: z12.number(),
1282
- totalOrganicSessions: z12.number(),
1283
- totalUsers: z12.number(),
1284
- topPages: z12.array(z12.object({
1285
- landingPage: z12.string(),
1286
- sessions: z12.number(),
1287
- organicSessions: z12.number(),
1288
- users: z12.number()
1289
- })),
1290
- aiReferrals: z12.array(ga4AiReferralDtoSchema),
1291
- /** Deduped AI session total: MAX(sessions) per date+source+medium across attribution dimensions, then summed. */
1292
- aiSessionsDeduped: z12.number(),
1293
- /** Deduped AI user total: MAX(users) per date+source+medium across attribution dimensions, then summed. */
1294
- aiUsersDeduped: z12.number(),
1295
- socialReferrals: z12.array(ga4SocialReferralDtoSchema),
1296
- /** Total social sessions (session-scoped, no cross-dimension dedup needed). */
1297
- socialSessions: z12.number(),
1298
- /** Total social users (session-scoped, no cross-dimension dedup needed). */
1299
- socialUsers: z12.number(),
1300
- /** Organic sessions as a percentage of total sessions (0–100, rounded). */
1301
- organicSharePct: z12.number(),
1302
- /** Deduped AI sessions as a percentage of total sessions (0–100, rounded). */
1303
- aiSharePct: z12.number(),
1304
- /** Social sessions as a percentage of total sessions (0–100, rounded). */
1305
- socialSharePct: z12.number(),
1306
- lastSyncedAt: z12.string().nullable()
1307
- });
1308
- var ga4AiReferralHistoryEntrySchema = z12.object({
1309
- date: z12.string(),
1310
- source: z12.string(),
1311
- medium: z12.string(),
1312
- sessions: z12.number(),
1313
- users: z12.number(),
1314
- /** Which GA4 dimension this row came from: session (sessionSource), first_user (firstUserSource), or manual_utm (utm_source parameter) */
1315
- sourceDimension: ga4SourceDimensionSchema
1316
- });
1317
- var ga4SocialReferralHistoryEntrySchema = z12.object({
1318
- date: z12.string(),
1319
- source: z12.string(),
1320
- medium: z12.string(),
1321
- sessions: z12.number(),
1322
- users: z12.number(),
1323
- /** GA4 default channel group (e.g. 'Organic Social', 'Paid Social') */
1324
- channelGroup: z12.string()
1325
- });
1326
- var ga4SessionHistoryEntrySchema = z12.object({
1327
- date: z12.string(),
1328
- sessions: z12.number(),
1329
- organicSessions: z12.number(),
1330
- users: z12.number()
1331
- });
1332
-
1333
- // ../contracts/src/answer-visibility.ts
1334
- var GENERIC_TOKENS = /* @__PURE__ */ new Set([
1335
- "agency",
1336
- "app",
1337
- "company",
1338
- "corp",
1339
- "group",
1340
- "health",
1341
- "inc",
1342
- "llc",
1343
- "online",
1344
- "platform",
1345
- "services",
1346
- "site",
1347
- "solutions",
1348
- "software",
1349
- "systems",
1350
- "tech"
1351
- ]);
1352
- function extractAnswerMentions(answerText, displayName, domains) {
1353
- if (!answerText) return { mentioned: false, matchedTerms: [] };
1354
- const matchedTerms = [];
1355
- const lowerAnswer = answerText.toLowerCase();
1356
- for (const domain of domains) {
1357
- const normalizedDomain = normalizeProjectDomain(domain);
1358
- if (!normalizedDomain || !normalizedDomain.includes(".")) continue;
1359
- if (domainMentioned(lowerAnswer, normalizedDomain)) {
1360
- matchedTerms.push(normalizedDomain);
1361
- }
1362
- }
1363
- const normalizedDisplayName = normalizeText(displayName);
1364
- if (normalizedDisplayName && normalizeText(answerText).includes(normalizedDisplayName)) {
1365
- matchedTerms.push(displayName);
1366
- }
1367
- const tokens = collectDistinctiveTokens(displayName, domains);
1368
- let tokenMatches = 0;
1369
- const matchedTokens = [];
1370
- for (const token of tokens) {
1371
- if (new RegExp(`\\b${escapeRegExp(token)}\\b`).test(lowerAnswer)) {
1372
- tokenMatches++;
1373
- matchedTokens.push(token);
1374
- }
1375
- }
1376
- const tokenThresholdMet = tokens.length > 0 && (tokens.length === 1 && tokenMatches >= 1 || tokenMatches >= Math.min(2, tokens.length));
1377
- if (tokenThresholdMet) {
1378
- matchedTerms.push(...matchedTokens);
1379
- }
1380
- const unique = [...new Set(matchedTerms)];
1381
- const domainMatches3 = unique.filter((t) => t.includes("."));
1382
- const dedupedFinal = unique.filter((term) => {
1383
- if (term.includes(".")) return true;
1384
- return !domainMatches3.some((d) => d.toLowerCase().startsWith(term.toLowerCase() + "."));
1385
- });
1386
- return { mentioned: dedupedFinal.length > 0, matchedTerms: dedupedFinal };
1387
- }
1388
- function determineAnswerMentioned(answerText, displayName, domains) {
1389
- return extractAnswerMentions(answerText, displayName, domains).mentioned;
1390
- }
1391
- function visibilityStateFromAnswerMentioned(answerMentioned) {
1392
- return answerMentioned ? "visible" : "not-visible";
1393
- }
1394
- function brandKeyFromText(value) {
1395
- return value.toLowerCase().replace(/[^a-z0-9]/g, "");
1396
- }
1397
- function domainMentioned(lowerAnswer, normalizedDomain) {
1398
- const escapedDomain = escapeRegExp(normalizedDomain.toLowerCase());
1399
- const patterns = [
1400
- new RegExp(`(^|[^a-z0-9-])${escapedDomain}($|[^a-z0-9-])`),
1401
- new RegExp(`https?://(?:www\\.)?${escapedDomain}(?:[/:?#]|$)`),
1402
- new RegExp(`www\\.${escapedDomain}(?:[/:?#]|$)`)
1403
- ];
1404
- return patterns.some((pattern) => pattern.test(lowerAnswer));
1405
- }
1406
- function collectDistinctiveTokens(displayName, domains) {
1407
- const tokens = /* @__PURE__ */ new Set();
1408
- for (const token of extractDistinctiveTokens(displayName)) {
1409
- tokens.add(token);
1410
- }
1411
- for (const domain of domains) {
1412
- const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
1413
- for (const label of hostname.split(".").filter(Boolean)) {
1414
- const token = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
1415
- if (isDistinctiveToken(token)) tokens.add(token);
1416
- }
1417
- }
1418
- return [...tokens];
1419
- }
1420
- function extractDistinctiveTokens(value) {
1421
- return normalizeText(value).split(" ").filter(isDistinctiveToken);
1422
- }
1423
- function isDistinctiveToken(token) {
1424
- if (token.length < 4) return false;
1425
- return !GENERIC_TOKENS.has(token);
1426
- }
1427
- function normalizeText(value) {
1428
- return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
1429
- }
1430
- function escapeRegExp(value) {
1431
- return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
1432
- }
1433
-
1434
- // ../contracts/src/agent.ts
1435
- import { z as z13 } from "zod";
1436
- var memorySourceSchema = z13.enum(["aero", "user", "compaction"]);
1437
- var MemorySources = memorySourceSchema.enum;
1438
- var AGENT_MEMORY_VALUE_MAX_BYTES = 2 * 1024;
1439
- var AGENT_MEMORY_KEY_MAX_LENGTH = 128;
1440
- var agentMemoryUpsertRequestSchema = z13.object({
1441
- key: z13.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH),
1442
- value: z13.string().min(1)
1443
- });
1444
- var agentMemoryDeleteRequestSchema = z13.object({
1445
- key: z13.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH)
1446
- });
1447
-
1448
- // ../contracts/src/backlinks.ts
1449
- import { z as z14 } from "zod";
1450
- var ccReleaseSyncStatusSchema = z14.enum(["queued", "downloading", "querying", "ready", "failed"]);
1451
- var CcReleaseSyncStatuses = ccReleaseSyncStatusSchema.enum;
1452
- var ccReleaseSyncDtoSchema = z14.object({
1453
- id: z14.string(),
1454
- release: z14.string(),
1455
- status: ccReleaseSyncStatusSchema,
1456
- phaseDetail: z14.string().nullable().optional(),
1457
- vertexPath: z14.string().nullable().optional(),
1458
- edgesPath: z14.string().nullable().optional(),
1459
- vertexSha256: z14.string().nullable().optional(),
1460
- edgesSha256: z14.string().nullable().optional(),
1461
- vertexBytes: z14.number().int().nullable().optional(),
1462
- edgesBytes: z14.number().int().nullable().optional(),
1463
- projectsProcessed: z14.number().int().nullable().optional(),
1464
- domainsDiscovered: z14.number().int().nullable().optional(),
1465
- downloadStartedAt: z14.string().nullable().optional(),
1466
- downloadFinishedAt: z14.string().nullable().optional(),
1467
- queryStartedAt: z14.string().nullable().optional(),
1468
- queryFinishedAt: z14.string().nullable().optional(),
1469
- error: z14.string().nullable().optional(),
1470
- createdAt: z14.string(),
1471
- updatedAt: z14.string()
1472
- });
1473
- var backlinkDomainDtoSchema = z14.object({
1474
- linkingDomain: z14.string(),
1475
- numHosts: z14.number().int()
1476
- });
1477
- var backlinkSummaryDtoSchema = z14.object({
1478
- projectId: z14.string(),
1479
- release: z14.string(),
1480
- targetDomain: z14.string(),
1481
- totalLinkingDomains: z14.number().int(),
1482
- totalHosts: z14.number().int(),
1483
- top10HostsShare: z14.string(),
1484
- queriedAt: z14.string()
1485
- });
1486
- var backlinkListResponseSchema = z14.object({
1487
- summary: backlinkSummaryDtoSchema.nullable(),
1488
- total: z14.number().int(),
1489
- rows: z14.array(backlinkDomainDtoSchema)
1490
- });
1491
- var backlinkHistoryEntrySchema = z14.object({
1492
- release: z14.string(),
1493
- totalLinkingDomains: z14.number().int(),
1494
- totalHosts: z14.number().int(),
1495
- top10HostsShare: z14.string(),
1496
- queriedAt: z14.string()
1497
- });
1498
- var backlinksInstallStatusDtoSchema = z14.object({
1499
- duckdbInstalled: z14.boolean(),
1500
- duckdbVersion: z14.string().nullable().optional(),
1501
- duckdbSpec: z14.string(),
1502
- pluginDir: z14.string()
1503
- });
1504
- var backlinksInstallResultDtoSchema = z14.object({
1505
- installed: z14.boolean(),
1506
- version: z14.string(),
1507
- path: z14.string(),
1508
- alreadyPresent: z14.boolean()
1509
- });
1510
- var ccAvailableReleaseSchema = z14.object({
1511
- release: z14.string(),
1512
- vertexUrl: z14.string(),
1513
- edgesUrl: z14.string(),
1514
- vertexBytes: z14.number().int().nullable(),
1515
- edgesBytes: z14.number().int().nullable(),
1516
- lastModified: z14.string().nullable()
1517
- });
1518
- var ccCachedReleaseSchema = z14.object({
1519
- release: z14.string(),
1520
- syncStatus: ccReleaseSyncStatusSchema.nullable(),
1521
- bytes: z14.number().int(),
1522
- lastUsedAt: z14.string().nullable()
1523
- });
1524
-
1525
170
  // ../api-routes/src/auth.ts
1526
171
  import crypto2 from "crypto";
1527
172
  import { eq } from "drizzle-orm";
@@ -2017,10 +662,16 @@ async function keywordRoutes(app, opts) {
2017
662
  });
2018
663
  app.post("/projects/:name/keywords/generate", async (request, reply) => {
2019
664
  const project = resolveProject(app.db, request.params.name);
2020
- const body = request.body;
2021
- if (!body?.provider || typeof body.provider !== "string") {
2022
- throw validationError('Body must contain a "provider" string');
665
+ const parsed = keywordGenerateRequestSchema.safeParse(request.body);
666
+ if (!parsed.success) {
667
+ throw validationError("Invalid keyword generation request", {
668
+ issues: parsed.error.issues.map((issue) => ({
669
+ path: issue.path.join("."),
670
+ message: issue.message
671
+ }))
672
+ });
2023
673
  }
674
+ const body = parsed.data;
2024
675
  const provider = body.provider.trim().toLowerCase();
2025
676
  const validNames = opts.validProviderNames ?? [];
2026
677
  if (validNames.length && !validNames.includes(provider)) {
@@ -2029,10 +680,7 @@ async function keywordRoutes(app, opts) {
2029
680
  validProviders: validNames
2030
681
  });
2031
682
  }
2032
- if (body.count !== void 0 && (typeof body.count !== "number" || !Number.isFinite(body.count) || !Number.isInteger(body.count))) {
2033
- throw validationError('"count" must be an integer');
2034
- }
2035
- const count = Math.min(Math.max(body.count ?? 5, 1), 20);
683
+ const count = body.count ?? 5;
2036
684
  if (!opts.onGenerateKeywords) {
2037
685
  throw notImplemented("Key phrase generation is not supported in this deployment");
2038
686
  }
@@ -2091,6 +739,79 @@ async function competitorRoutes(app) {
2091
739
  const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
2092
740
  return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
2093
741
  });
742
+ app.post("/projects/:name/competitors", async (request, reply) => {
743
+ const project = resolveProject(app.db, request.params.name);
744
+ const body = parseCompetitorBatch(request.body);
745
+ const now = (/* @__PURE__ */ new Date()).toISOString();
746
+ const requested = uniqueStrings(body.competitors);
747
+ app.db.transaction((tx) => {
748
+ const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
749
+ const existingSet = new Set(existing.map((c) => c.domain));
750
+ const added = requested.filter((domain) => !existingSet.has(domain));
751
+ if (added.length === 0) return;
752
+ for (const domain of added) {
753
+ tx.insert(competitors).values({
754
+ id: crypto6.randomUUID(),
755
+ projectId: project.id,
756
+ domain,
757
+ createdAt: now
758
+ }).onConflictDoNothing({
759
+ target: [competitors.projectId, competitors.domain]
760
+ }).run();
761
+ }
762
+ writeAuditLog(tx, {
763
+ projectId: project.id,
764
+ actor: "api",
765
+ action: "competitors.appended",
766
+ entityType: "competitor",
767
+ diff: { added }
768
+ });
769
+ });
770
+ const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
771
+ return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
772
+ });
773
+ app.delete("/projects/:name/competitors", async (request, reply) => {
774
+ const project = resolveProject(app.db, request.params.name);
775
+ const body = parseCompetitorBatch(request.body);
776
+ const requested = new Set(uniqueStrings(body.competitors));
777
+ app.db.transaction((tx) => {
778
+ const existing = tx.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
779
+ const rowsToDelete = existing.filter((c) => requested.has(c.domain));
780
+ if (rowsToDelete.length === 0) return;
781
+ for (const row of rowsToDelete) {
782
+ tx.delete(competitors).where(eq5(competitors.id, row.id)).run();
783
+ }
784
+ writeAuditLog(tx, {
785
+ projectId: project.id,
786
+ actor: "api",
787
+ action: "competitors.deleted",
788
+ entityType: "competitor",
789
+ diff: { deleted: rowsToDelete.map((row) => row.domain) }
790
+ });
791
+ });
792
+ const rows = app.db.select().from(competitors).where(eq5(competitors.projectId, project.id)).all();
793
+ return reply.send(rows.map((r) => ({ id: r.id, domain: r.domain, createdAt: r.createdAt })));
794
+ });
795
+ }
796
+ function parseCompetitorBatch(value) {
797
+ const result = competitorBatchRequestSchema.safeParse(value);
798
+ if (result.success) return result.data;
799
+ throw validationError("Invalid competitor batch request", {
800
+ issues: result.error.issues.map((issue) => ({
801
+ path: issue.path.join("."),
802
+ message: issue.message
803
+ }))
804
+ });
805
+ }
806
+ function uniqueStrings(values) {
807
+ const seen = /* @__PURE__ */ new Set();
808
+ const result = [];
809
+ for (const value of values) {
810
+ if (seen.has(value)) continue;
811
+ seen.add(value);
812
+ result.push(value);
813
+ }
814
+ return result;
2094
815
  }
2095
816
 
2096
817
  // ../api-routes/src/runs.ts
@@ -2132,11 +853,11 @@ function queueRunIfProjectIdle(db, params) {
2132
853
  async function runRoutes(app, opts) {
2133
854
  app.post("/projects/:name/runs", async (request, reply) => {
2134
855
  const project = resolveProject(app.db, request.params.name);
2135
- const kind = request.body?.kind ?? "answer-visibility";
2136
- if (kind !== "answer-visibility") throw unsupportedKind(kind);
856
+ const body = parseRunTriggerRequest(request.body ?? {});
2137
857
  const now = (/* @__PURE__ */ new Date()).toISOString();
2138
- const trigger = request.body?.trigger ?? "manual";
2139
- const rawProviders = request.body?.providers;
858
+ const kind = body.kind ?? RunKinds["answer-visibility"];
859
+ const trigger = body.trigger ?? RunTriggers.manual;
860
+ const rawProviders = body.providers;
2140
861
  if (rawProviders?.length) {
2141
862
  const normalized = rawProviders.map((p) => p.trim().toLowerCase()).filter(Boolean);
2142
863
  const validNames = opts.validProviderNames ?? [];
@@ -2154,13 +875,13 @@ async function runRoutes(app, opts) {
2154
875
  const providers = rawProviders?.length ? rawProviders : void 0;
2155
876
  let resolvedLocation;
2156
877
  const projectLocations = parseJsonColumn(project.locations, []);
2157
- if (request.body?.noLocation) {
878
+ if (body.noLocation) {
2158
879
  resolvedLocation = null;
2159
- } else if (request.body?.allLocations) {
2160
- } else if (request.body?.location) {
2161
- const loc = projectLocations.find((l) => l.label === request.body.location);
880
+ } else if (body.allLocations) {
881
+ } else if (body.location) {
882
+ const loc = projectLocations.find((l) => l.label === body.location);
2162
883
  if (!loc) {
2163
- throw validationError(`Location "${request.body.location}" not found. Configure it first.`);
884
+ throw validationError(`Location "${body.location}" not found. Configure it first.`);
2164
885
  }
2165
886
  resolvedLocation = loc;
2166
887
  } else if (project.defaultLocation) {
@@ -2170,7 +891,7 @@ async function runRoutes(app, opts) {
2170
891
  }
2171
892
  resolvedLocation = loc;
2172
893
  }
2173
- if (request.body?.allLocations) {
894
+ if (body.allLocations) {
2174
895
  if (projectLocations.length === 0) {
2175
896
  throw validationError("No locations configured for this project");
2176
897
  }
@@ -2339,6 +1060,16 @@ async function runRoutes(app, opts) {
2339
1060
  return reply.send(loadRunDetail(app, run));
2340
1061
  });
2341
1062
  }
1063
+ function parseRunTriggerRequest(value) {
1064
+ const result = runTriggerRequestSchema.safeParse(value);
1065
+ if (result.success) return result.data;
1066
+ throw validationError("Invalid run trigger request", {
1067
+ issues: result.error.issues.map((issue) => ({
1068
+ path: issue.path.join("."),
1069
+ message: issue.message
1070
+ }))
1071
+ });
1072
+ }
2342
1073
  function formatRun(row) {
2343
1074
  return {
2344
1075
  id: row.id,
@@ -2550,7 +1281,7 @@ async function deliverWebhook(target, payload, webhookSecret) {
2550
1281
  const body = JSON.stringify(payload);
2551
1282
  const isHttps = target.url.protocol === "https:";
2552
1283
  const port = target.url.port ? Number(target.url.port) : isHttps ? 443 : 80;
2553
- const path16 = `${target.url.pathname}${target.url.search}`;
1284
+ const path15 = `${target.url.pathname}${target.url.search}`;
2554
1285
  const headers = {
2555
1286
  "Content-Length": String(Buffer.byteLength(body)),
2556
1287
  "Content-Type": "application/json",
@@ -2566,7 +1297,7 @@ async function deliverWebhook(target, payload, webhookSecret) {
2566
1297
  headers,
2567
1298
  hostname: target.address,
2568
1299
  method: "POST",
2569
- path: path16,
1300
+ path: path15,
2570
1301
  port,
2571
1302
  timeout: REQUEST_TIMEOUT_MS
2572
1303
  };
@@ -3604,7 +2335,7 @@ var booleanSchema = { type: "boolean" };
3604
2335
  var integerSchema = { type: "integer" };
3605
2336
  var objectSchema = { type: "object", additionalProperties: true };
3606
2337
  var stringArraySchema = { type: "array", items: stringSchema };
3607
- var googleConnectionTypeSchema2 = { type: "string", enum: ["gsc", "ga4"] };
2338
+ var googleConnectionTypeSchema = { type: "string", enum: ["gsc", "ga4"] };
3608
2339
  var locationSchema = {
3609
2340
  type: "object",
3610
2341
  required: ["label", "city", "region", "country"],
@@ -3656,7 +2387,7 @@ var googleTypeParameter = {
3656
2387
  in: "path",
3657
2388
  required: true,
3658
2389
  description: "Google connection type.",
3659
- schema: googleConnectionTypeSchema2
2390
+ schema: googleConnectionTypeSchema
3660
2391
  };
3661
2392
  var projectRunIdParameter = {
3662
2393
  name: "runId",
@@ -3964,32 +2695,81 @@ var routeCatalog = [
3964
2695
  type: "object",
3965
2696
  required: ["provider"],
3966
2697
  properties: {
3967
- provider: { type: "string", enum: ["gemini", "openai", "claude", "perplexity", "local"] },
3968
- count: integerSchema
2698
+ provider: { type: "string", enum: ["gemini", "openai", "claude", "perplexity", "local"] },
2699
+ count: integerSchema
2700
+ }
2701
+ }
2702
+ }
2703
+ }
2704
+ },
2705
+ responses: {
2706
+ 200: { description: "Keyword suggestions returned." },
2707
+ 501: { description: "Keyword generation is not available." }
2708
+ }
2709
+ },
2710
+ {
2711
+ method: "get",
2712
+ path: "/api/v1/projects/{name}/competitors",
2713
+ summary: "List competitors",
2714
+ tags: ["competitors"],
2715
+ parameters: [nameParameter],
2716
+ responses: {
2717
+ 200: { description: "Competitors returned." }
2718
+ }
2719
+ },
2720
+ {
2721
+ method: "put",
2722
+ path: "/api/v1/projects/{name}/competitors",
2723
+ summary: "Replace competitors",
2724
+ tags: ["competitors"],
2725
+ parameters: [nameParameter],
2726
+ requestBody: {
2727
+ required: true,
2728
+ content: {
2729
+ "application/json": {
2730
+ schema: {
2731
+ type: "object",
2732
+ required: ["competitors"],
2733
+ properties: {
2734
+ competitors: stringArraySchema
2735
+ }
2736
+ }
2737
+ }
2738
+ }
2739
+ },
2740
+ responses: {
2741
+ 200: { description: "Competitors replaced." }
2742
+ }
2743
+ },
2744
+ {
2745
+ method: "post",
2746
+ path: "/api/v1/projects/{name}/competitors",
2747
+ summary: "Append competitors",
2748
+ tags: ["competitors"],
2749
+ parameters: [nameParameter],
2750
+ requestBody: {
2751
+ required: true,
2752
+ content: {
2753
+ "application/json": {
2754
+ schema: {
2755
+ type: "object",
2756
+ required: ["competitors"],
2757
+ properties: {
2758
+ competitors: stringArraySchema
3969
2759
  }
3970
2760
  }
3971
2761
  }
3972
2762
  }
3973
2763
  },
3974
2764
  responses: {
3975
- 200: { description: "Keyword suggestions returned." },
3976
- 501: { description: "Keyword generation is not available." }
3977
- }
3978
- },
3979
- {
3980
- method: "get",
3981
- path: "/api/v1/projects/{name}/competitors",
3982
- summary: "List competitors",
3983
- tags: ["competitors"],
3984
- parameters: [nameParameter],
3985
- responses: {
3986
- 200: { description: "Competitors returned." }
2765
+ 200: { description: "Competitors appended." },
2766
+ 400: { description: "Invalid competitor append request." }
3987
2767
  }
3988
2768
  },
3989
2769
  {
3990
- method: "put",
2770
+ method: "delete",
3991
2771
  path: "/api/v1/projects/{name}/competitors",
3992
- summary: "Replace competitors",
2772
+ summary: "Delete specific competitors",
3993
2773
  tags: ["competitors"],
3994
2774
  parameters: [nameParameter],
3995
2775
  requestBody: {
@@ -4007,7 +2787,8 @@ var routeCatalog = [
4007
2787
  }
4008
2788
  },
4009
2789
  responses: {
4010
- 200: { description: "Competitors replaced." }
2790
+ 200: { description: "Remaining competitors returned." },
2791
+ 400: { description: "Invalid competitor delete request." }
4011
2792
  }
4012
2793
  },
4013
2794
  {
@@ -4649,7 +3430,7 @@ var routeCatalog = [
4649
3430
  type: "object",
4650
3431
  required: ["type"],
4651
3432
  properties: {
4652
- type: googleConnectionTypeSchema2,
3433
+ type: googleConnectionTypeSchema,
4653
3434
  propertyId: stringSchema,
4654
3435
  publicUrl: stringSchema
4655
3436
  }
@@ -6071,8 +4852,8 @@ async function openApiRoutes(app, opts = {}) {
6071
4852
  return reply.type("application/json").send(buildOpenApiDocument(opts));
6072
4853
  });
6073
4854
  }
6074
- function buildOperationId(method, path16) {
6075
- const parts = path16.split("/").filter(Boolean).map((part) => {
4855
+ function buildOperationId(method, path15) {
4856
+ const parts = path15.split("/").filter(Boolean).map((part) => {
6076
4857
  if (part.startsWith("{") && part.endsWith("}")) {
6077
4858
  return `by-${part.slice(1, -1)}`;
6078
4859
  }
@@ -6594,7 +5375,7 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
6594
5375
  const parsed = JSON.parse(body);
6595
5376
  if (parsed.error) detail = parsed.error;
6596
5377
  if (parsed.error_description) {
6597
- const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp2(clientId), "g"), "***").replace(new RegExp(escapeRegExp2(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp2(code), "g"), "***");
5378
+ const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp(clientId), "g"), "***").replace(new RegExp(escapeRegExp(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp(code), "g"), "***");
6598
5379
  detail += detail ? `: ${sanitized}` : sanitized;
6599
5380
  }
6600
5381
  } catch {
@@ -6604,7 +5385,7 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
6604
5385
  }
6605
5386
  return await res.json();
6606
5387
  }
6607
- function escapeRegExp2(str) {
5388
+ function escapeRegExp(str) {
6608
5389
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6609
5390
  }
6610
5391
  async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
@@ -6632,7 +5413,7 @@ async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
6632
5413
  const parsed = JSON.parse(body);
6633
5414
  if (parsed.error) detail = parsed.error;
6634
5415
  if (parsed.error_description) {
6635
- const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp2(clientId), "g"), "***").replace(new RegExp(escapeRegExp2(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp2(currentRefreshToken), "g"), "***");
5416
+ const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp(clientId), "g"), "***").replace(new RegExp(escapeRegExp(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp(currentRefreshToken), "g"), "***");
6636
5417
  detail += detail ? `: ${sanitized}` : sanitized;
6637
5418
  }
6638
5419
  } catch {
@@ -6946,13 +5727,13 @@ async function getAccessToken(clientEmail, privateKey) {
6946
5727
  const body = await res.text().catch(() => "");
6947
5728
  ga4Log("error", "token.failed", { httpStatus: res.status });
6948
5729
  const detail = body.length <= 200 ? body : `${body.slice(0, 200)}... [truncated]`;
6949
- const sanitizedDetail = detail.replace(new RegExp(escapeRegExp3(clientEmail), "g"), "***").replace(new RegExp(escapeRegExp3(privateKey.slice(0, 32)), "g"), "***");
5730
+ const sanitizedDetail = detail.replace(new RegExp(escapeRegExp2(clientEmail), "g"), "***").replace(new RegExp(escapeRegExp2(privateKey.slice(0, 32)), "g"), "***");
6950
5731
  throw new GA4ApiError(`Failed to get access token: ${sanitizedDetail}`, res.status);
6951
5732
  }
6952
5733
  const data = await res.json();
6953
5734
  return data.access_token;
6954
5735
  }
6955
- function escapeRegExp3(str) {
5736
+ function escapeRegExp2(str) {
6956
5737
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6957
5738
  }
6958
5739
  async function runReport(accessToken, propertyId, request) {
@@ -8082,7 +6863,7 @@ function bingClientLog(level, action, ctx) {
8082
6863
  const stream = level === "error" ? process.stderr : process.stdout;
8083
6864
  stream.write(JSON.stringify(entry) + "\n");
8084
6865
  }
8085
- function escapeRegExp4(str) {
6866
+ function escapeRegExp3(str) {
8086
6867
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8087
6868
  }
8088
6869
  async function bingFetch(apiKey, endpoint, opts) {
@@ -8110,7 +6891,7 @@ async function bingFetch(apiKey, endpoint, opts) {
8110
6891
  const body = await res.text();
8111
6892
  bingClientLog("error", "http.error", { endpoint, method, httpStatus: res.status });
8112
6893
  let detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
8113
- detail = detail.replace(new RegExp(escapeRegExp4(apiKey), "g"), "***");
6894
+ detail = detail.replace(new RegExp(escapeRegExp3(apiKey), "g"), "***");
8114
6895
  throw new BingApiError(`Bing API error (${res.status}): ${detail}`, res.status);
8115
6896
  }
8116
6897
  const text = await res.text();
@@ -8654,12 +7435,12 @@ async function bingRoutes(app, opts) {
8654
7435
  }
8655
7436
 
8656
7437
  // ../api-routes/src/cdp.ts
8657
- import fs2 from "fs";
8658
- import path2 from "path";
8659
- import os2 from "os";
7438
+ import fs from "fs";
7439
+ import path from "path";
7440
+ import os from "os";
8660
7441
  import { eq as eq16, and as and5 } from "drizzle-orm";
8661
7442
  function getScreenshotDir() {
8662
- return path2.join(os2.homedir(), ".canonry", "screenshots");
7443
+ return path.join(os.homedir(), ".canonry", "screenshots");
8663
7444
  }
8664
7445
  async function cdpRoutes(app, opts) {
8665
7446
  app.get("/screenshots/:snapshotId", async (request, reply) => {
@@ -8669,17 +7450,17 @@ async function cdpRoutes(app, opts) {
8669
7450
  const err = notFound("Screenshot", snapshotId);
8670
7451
  return reply.code(err.statusCode).send(err.toJSON());
8671
7452
  }
8672
- const base = path2.resolve(getScreenshotDir());
8673
- const fullPath = path2.resolve(path2.join(base, snapshot.screenshotPath));
8674
- if (!fullPath.startsWith(base + path2.sep) && fullPath !== base) {
7453
+ const base = path.resolve(getScreenshotDir());
7454
+ const fullPath = path.resolve(path.join(base, snapshot.screenshotPath));
7455
+ if (!fullPath.startsWith(base + path.sep) && fullPath !== base) {
8675
7456
  const err = notFound("Screenshot", snapshotId);
8676
7457
  return reply.code(err.statusCode).send(err.toJSON());
8677
7458
  }
8678
- if (!fs2.existsSync(fullPath)) {
7459
+ if (!fs.existsSync(fullPath)) {
8679
7460
  const err = notFound("Screenshot file", snapshotId);
8680
7461
  return reply.code(err.statusCode).send(err.toJSON());
8681
7462
  }
8682
- const stream = fs2.createReadStream(fullPath);
7463
+ const stream = fs.createReadStream(fullPath);
8683
7464
  return reply.type("image/png").send(stream);
8684
7465
  });
8685
7466
  app.put("/settings/cdp", async (request, reply) => {
@@ -9684,10 +8465,10 @@ function buildAuthErrorMessage(res, responseText) {
9684
8465
  }
9685
8466
  return "WordPress credentials are invalid or lack permission for this action";
9686
8467
  }
9687
- async function fetchJson(connection, siteUrl, path16, init) {
8468
+ async function fetchJson(connection, siteUrl, path15, init) {
9688
8469
  if (siteUrl.startsWith("http:")) {
9689
8470
  }
9690
- const res = await fetch(`${normalizeSiteUrl(siteUrl)}${path16}`, {
8471
+ const res = await fetch(`${normalizeSiteUrl(siteUrl)}${path15}`, {
9691
8472
  ...init,
9692
8473
  headers: {
9693
8474
  "Authorization": `Basic ${encodeBasicAuth(connection.username, connection.appPassword)}`,
@@ -10202,12 +8983,12 @@ var CANONRY_SCHEMA_START = "<!-- canonry:schema:start -->";
10202
8983
  var CANONRY_SCHEMA_END = "<!-- canonry:schema:end -->";
10203
8984
  function stripCanonrySchema(content) {
10204
8985
  const regex = new RegExp(
10205
- `${escapeRegExp5(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp5(CANONRY_SCHEMA_END)}`,
8986
+ `${escapeRegExp4(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp4(CANONRY_SCHEMA_END)}`,
10206
8987
  "g"
10207
8988
  );
10208
8989
  return content.replace(regex, "").replace(/\n{3,}/g, "\n\n").trim();
10209
8990
  }
10210
- function escapeRegExp5(str) {
8991
+ function escapeRegExp4(str) {
10211
8992
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10212
8993
  }
10213
8994
  function injectCanonrySchema(content, schemas) {
@@ -10314,7 +9095,7 @@ async function getSchemaStatus(connection, env) {
10314
9095
  const thirdPartySchemas = [];
10315
9096
  if (hasCanonryMarker) {
10316
9097
  const markerRegex = new RegExp(
10317
- `${escapeRegExp5(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp5(CANONRY_SCHEMA_END)}`
9098
+ `${escapeRegExp4(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp4(CANONRY_SCHEMA_END)}`
10318
9099
  );
10319
9100
  const match = markerRegex.exec(rawContent);
10320
9101
  if (match?.[1]) {
@@ -11108,13 +9889,13 @@ import crypto18 from "crypto";
11108
9889
  import { and as and7, asc as asc2, desc as desc8, eq as eq18, sql as sql5 } from "drizzle-orm";
11109
9890
 
11110
9891
  // ../integration-commoncrawl/src/constants.ts
11111
- import os3 from "os";
11112
- import path3 from "path";
9892
+ import os2 from "os";
9893
+ import path2 from "path";
11113
9894
  var CC_BASE_URL = "https://data.commoncrawl.org/projects/hyperlinkgraph";
11114
- var PLUGIN_DIR = path3.join(os3.homedir(), ".canonry", "plugins");
11115
- var PLUGIN_PKG_JSON = path3.join(PLUGIN_DIR, "package.json");
9895
+ var PLUGIN_DIR = path2.join(os2.homedir(), ".canonry", "plugins");
9896
+ var PLUGIN_PKG_JSON = path2.join(PLUGIN_DIR, "package.json");
11116
9897
  var DUCKDB_SPEC = process.env.CANONRY_DUCKDB_SPEC ?? "@duckdb/node-api@1.4.4-r.3";
11117
- var CC_CACHE_DIR = process.env.CANONRY_CC_CACHE_DIR ?? path3.join(os3.homedir(), ".canonry", "cache", "commoncrawl");
9898
+ var CC_CACHE_DIR = process.env.CANONRY_CC_CACHE_DIR ?? path2.join(os2.homedir(), ".canonry", "cache", "commoncrawl");
11118
9899
  var RELEASE_ID_REGEX = /^cc-main-(\d{4})-(jan-feb-mar|apr-may-jun|jul-aug-sep|oct-nov-dec)$/;
11119
9900
  function ccReleasePaths(release) {
11120
9901
  const base = `${CC_BASE_URL}/${release}/domain`;
@@ -11144,8 +9925,8 @@ function isValidReleaseId(id) {
11144
9925
  // ../integration-commoncrawl/src/downloader.ts
11145
9926
  import { createHash } from "crypto";
11146
9927
  import { createWriteStream } from "fs";
11147
- import fs3 from "fs/promises";
11148
- import path4 from "path";
9928
+ import fs2 from "fs/promises";
9929
+ import path3 from "path";
11149
9930
  import { pipeline } from "stream/promises";
11150
9931
  import { Readable, Transform } from "stream";
11151
9932
  async function downloadFile(opts) {
@@ -11153,7 +9934,7 @@ async function downloadFile(opts) {
11153
9934
  const fetchImpl = opts.fetchImpl ?? fetch;
11154
9935
  const sidecarPath = `${opts.destPath}.sha256`;
11155
9936
  try {
11156
- const stat = await fs3.stat(opts.destPath);
9937
+ const stat = await fs2.stat(opts.destPath);
11157
9938
  const sidecar = await readSidecar(sidecarPath);
11158
9939
  const sha2562 = sidecar ?? await hashFile(opts.destPath);
11159
9940
  if (!sidecar) await writeSidecar(sidecarPath, sha2562);
@@ -11161,7 +9942,7 @@ async function downloadFile(opts) {
11161
9942
  } catch {
11162
9943
  }
11163
9944
  const partialPath = `${opts.destPath}.partial`;
11164
- await fs3.mkdir(path4.dirname(opts.destPath), { recursive: true });
9945
+ await fs2.mkdir(path3.dirname(opts.destPath), { recursive: true });
11165
9946
  await unlinkIfExists(partialPath);
11166
9947
  const res = await fetchImpl(opts.url);
11167
9948
  if (!res.ok || !res.body) {
@@ -11184,13 +9965,13 @@ async function downloadFile(opts) {
11184
9965
  createWriteStream(partialPath)
11185
9966
  );
11186
9967
  const sha256 = hasher.digest("hex");
11187
- await fs3.rename(partialPath, opts.destPath);
9968
+ await fs2.rename(partialPath, opts.destPath);
11188
9969
  await writeSidecar(sidecarPath, sha256);
11189
9970
  return { bytes, sha256, cached: false, elapsedMs: Date.now() - start };
11190
9971
  }
11191
9972
  async function hashFile(filePath) {
11192
9973
  const hasher = createHash("sha256");
11193
- const handle = await fs3.open(filePath, "r");
9974
+ const handle = await fs2.open(filePath, "r");
11194
9975
  try {
11195
9976
  const stream = handle.createReadStream();
11196
9977
  for await (const chunk of stream) hasher.update(chunk);
@@ -11201,7 +9982,7 @@ async function hashFile(filePath) {
11201
9982
  }
11202
9983
  async function readSidecar(sidecarPath) {
11203
9984
  try {
11204
- const raw = await fs3.readFile(sidecarPath, "utf8");
9985
+ const raw = await fs2.readFile(sidecarPath, "utf8");
11205
9986
  const trimmed = raw.trim();
11206
9987
  return /^[0-9a-f]{64}$/i.test(trimmed) ? trimmed.toLowerCase() : null;
11207
9988
  } catch {
@@ -11209,12 +9990,12 @@ async function readSidecar(sidecarPath) {
11209
9990
  }
11210
9991
  }
11211
9992
  async function writeSidecar(sidecarPath, sha256) {
11212
- await fs3.writeFile(sidecarPath, `${sha256}
9993
+ await fs2.writeFile(sidecarPath, `${sha256}
11213
9994
  `);
11214
9995
  }
11215
9996
  async function unlinkIfExists(p) {
11216
9997
  try {
11217
- await fs3.unlink(p);
9998
+ await fs2.unlink(p);
11218
9999
  } catch {
11219
10000
  }
11220
10001
  }
@@ -11225,20 +10006,20 @@ function parseContentLength(value) {
11225
10006
  }
11226
10007
 
11227
10008
  // ../integration-commoncrawl/src/plugin-resolver.ts
11228
- import fs4 from "fs";
10009
+ import fs3 from "fs";
11229
10010
  import { createRequire as createRequire2 } from "module";
11230
- import path5 from "path";
10011
+ import path4 from "path";
11231
10012
  function pluginDirFor(pkgJson) {
11232
- return path5.dirname(pkgJson);
10013
+ return path4.dirname(pkgJson);
11233
10014
  }
11234
10015
  function duckdbPkgJsonFor(pluginDir) {
11235
- return path5.join(pluginDir, "node_modules", "@duckdb", "node-api", "package.json");
10016
+ return path4.join(pluginDir, "node_modules", "@duckdb", "node-api", "package.json");
11236
10017
  }
11237
10018
  function loadDuckdb(opts = {}) {
11238
10019
  const pkgJson = opts.pluginPkgJson ?? PLUGIN_PKG_JSON;
11239
10020
  const pluginDir = pluginDirFor(pkgJson);
11240
10021
  const duckdbPkg = duckdbPkgJsonFor(pluginDir);
11241
- if (!fs4.existsSync(duckdbPkg)) {
10022
+ if (!fs3.existsSync(duckdbPkg)) {
11242
10023
  throw missingDependency(
11243
10024
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature.",
11244
10025
  { pluginDir }
@@ -11256,12 +10037,12 @@ function loadDuckdb(opts = {}) {
11256
10037
  }
11257
10038
  function isDuckdbInstalled(opts = {}) {
11258
10039
  const pkgJson = opts.pluginPkgJson ?? PLUGIN_PKG_JSON;
11259
- return fs4.existsSync(duckdbPkgJsonFor(pluginDirFor(pkgJson)));
10040
+ return fs3.existsSync(duckdbPkgJsonFor(pluginDirFor(pkgJson)));
11260
10041
  }
11261
10042
  function readInstalledVersion(opts = {}) {
11262
10043
  const pluginDir = opts.pluginPkgJson ? pluginDirFor(opts.pluginPkgJson) : PLUGIN_DIR;
11263
10044
  try {
11264
- const raw = fs4.readFileSync(duckdbPkgJsonFor(pluginDir), "utf8");
10045
+ const raw = fs3.readFileSync(duckdbPkgJsonFor(pluginDir), "utf8");
11265
10046
  const pkg = JSON.parse(raw);
11266
10047
  return pkg.version ?? null;
11267
10048
  } catch {
@@ -11271,11 +10052,11 @@ function readInstalledVersion(opts = {}) {
11271
10052
 
11272
10053
  // ../integration-commoncrawl/src/plugin-installer.ts
11273
10054
  import { spawn } from "child_process";
11274
- import fs5 from "fs/promises";
11275
- import path6 from "path";
10055
+ import fs4 from "fs/promises";
10056
+ import path5 from "path";
11276
10057
  async function installDuckdb(opts = {}) {
11277
10058
  const pluginDir = opts.pluginDir ?? PLUGIN_DIR;
11278
- const pluginPkgJson = path6.join(pluginDir, "package.json");
10059
+ const pluginPkgJson = path5.join(pluginDir, "package.json");
11279
10060
  const spec = opts.spec ?? DUCKDB_SPEC;
11280
10061
  const pkgManager = opts.packageManager ?? "npm";
11281
10062
  await ensurePluginDir(pluginDir, pluginPkgJson);
@@ -11291,12 +10072,12 @@ async function installDuckdb(opts = {}) {
11291
10072
  return { alreadyPresent: false, version, path: pluginDir };
11292
10073
  }
11293
10074
  async function ensurePluginDir(pluginDir = PLUGIN_DIR, pluginPkgJson = PLUGIN_PKG_JSON) {
11294
- await fs5.mkdir(pluginDir, { recursive: true });
10075
+ await fs4.mkdir(pluginDir, { recursive: true });
11295
10076
  try {
11296
- await fs5.access(pluginPkgJson);
10077
+ await fs4.access(pluginPkgJson);
11297
10078
  } catch {
11298
10079
  const contents = JSON.stringify({ name: "canonry-plugins", private: true, dependencies: {} }, null, 2);
11299
- await fs5.writeFile(pluginPkgJson, `${contents}
10080
+ await fs4.writeFile(pluginPkgJson, `${contents}
11300
10081
  `);
11301
10082
  }
11302
10083
  }
@@ -11389,8 +10170,8 @@ function quote(s) {
11389
10170
  }
11390
10171
 
11391
10172
  // ../integration-commoncrawl/src/cache.ts
11392
- import fs6 from "fs";
11393
- import path7 from "path";
10173
+ import fs5 from "fs";
10174
+ import path6 from "path";
11394
10175
  function cacheRoot(opts = {}) {
11395
10176
  return opts.cacheDir ?? CC_CACHE_DIR;
11396
10177
  }
@@ -11400,18 +10181,18 @@ function directoryBytesAndLastUsed(dir) {
11400
10181
  const walk = (p) => {
11401
10182
  let stat;
11402
10183
  try {
11403
- stat = fs6.statSync(p);
10184
+ stat = fs5.statSync(p);
11404
10185
  } catch {
11405
10186
  return;
11406
10187
  }
11407
10188
  if (stat.isDirectory()) {
11408
10189
  let entries;
11409
10190
  try {
11410
- entries = fs6.readdirSync(p);
10191
+ entries = fs5.readdirSync(p);
11411
10192
  } catch {
11412
10193
  return;
11413
10194
  }
11414
- for (const e of entries) walk(path7.join(p, e));
10195
+ for (const e of entries) walk(path6.join(p, e));
11415
10196
  } else if (stat.isFile()) {
11416
10197
  bytes += stat.size;
11417
10198
  const mtime = Math.max(stat.mtimeMs, stat.atimeMs);
@@ -11426,13 +10207,13 @@ function directoryBytesAndLastUsed(dir) {
11426
10207
  }
11427
10208
  function listCachedReleases(opts = {}) {
11428
10209
  const root = cacheRoot(opts);
11429
- if (!fs6.existsSync(root)) return [];
11430
- const entries = fs6.readdirSync(root, { withFileTypes: true });
10210
+ if (!fs5.existsSync(root)) return [];
10211
+ const entries = fs5.readdirSync(root, { withFileTypes: true });
11431
10212
  const result = [];
11432
10213
  for (const entry of entries) {
11433
10214
  if (!entry.isDirectory()) continue;
11434
10215
  if (!RELEASE_ID_REGEX.test(entry.name)) continue;
11435
- const dir = path7.join(root, entry.name);
10216
+ const dir = path6.join(root, entry.name);
11436
10217
  const stats = directoryBytesAndLastUsed(dir);
11437
10218
  result.push({ release: entry.name, bytes: stats.bytes, lastUsedAt: stats.lastUsedAt });
11438
10219
  }
@@ -11443,8 +10224,8 @@ function pruneCachedRelease(release, opts = {}) {
11443
10224
  if (!RELEASE_ID_REGEX.test(release)) {
11444
10225
  throw new Error(`Invalid release id: ${release}`);
11445
10226
  }
11446
- const dir = path7.join(cacheRoot(opts), release);
11447
- fs6.rmSync(dir, { recursive: true, force: true });
10227
+ const dir = path6.join(cacheRoot(opts), release);
10228
+ fs5.rmSync(dir, { recursive: true, force: true });
11448
10229
  }
11449
10230
 
11450
10231
  // ../api-routes/src/backlinks.ts
@@ -11787,7 +10568,7 @@ async function apiRoutes(app, opts) {
11787
10568
  }
11788
10569
 
11789
10570
  // src/server.ts
11790
- import os6 from "os";
10571
+ import os5 from "os";
11791
10572
 
11792
10573
  // ../provider-gemini/src/normalize.ts
11793
10574
  import { GoogleGenAI } from "@google/genai";
@@ -13175,8 +11956,8 @@ var localAdapter = {
13175
11956
  };
13176
11957
 
13177
11958
  // ../provider-cdp/src/adapter.ts
13178
- import path9 from "path";
13179
- import os4 from "os";
11959
+ import path8 from "path";
11960
+ import os3 from "os";
13180
11961
 
13181
11962
  // ../provider-cdp/src/connection.ts
13182
11963
  import CDP from "chrome-remote-interface";
@@ -13540,12 +12321,12 @@ function sleep2(ms) {
13540
12321
  }
13541
12322
 
13542
12323
  // ../provider-cdp/src/screenshot.ts
13543
- import fs7 from "fs";
13544
- import path8 from "path";
12324
+ import fs6 from "fs";
12325
+ import path7 from "path";
13545
12326
  async function captureElementScreenshot(client, selector, outputPath) {
13546
- const dir = path8.dirname(outputPath);
13547
- if (!fs7.existsSync(dir)) {
13548
- fs7.mkdirSync(dir, { recursive: true });
12327
+ const dir = path7.dirname(outputPath);
12328
+ if (!fs6.existsSync(dir)) {
12329
+ fs6.mkdirSync(dir, { recursive: true });
13549
12330
  }
13550
12331
  let clip;
13551
12332
  try {
@@ -13579,7 +12360,7 @@ async function captureElementScreenshot(client, selector, outputPath) {
13579
12360
  }
13580
12361
  const { data } = await client.Page.captureScreenshot(screenshotParams);
13581
12362
  const buffer = Buffer.from(data, "base64");
13582
- fs7.writeFileSync(outputPath, buffer);
12363
+ fs6.writeFileSync(outputPath, buffer);
13583
12364
  return outputPath;
13584
12365
  }
13585
12366
 
@@ -13640,7 +12421,7 @@ function getConnection(config) {
13640
12421
  return conn;
13641
12422
  }
13642
12423
  function getScreenshotDir2() {
13643
- return path9.join(os4.homedir(), ".canonry", "screenshots");
12424
+ return path8.join(os3.homedir(), ".canonry", "screenshots");
13644
12425
  }
13645
12426
  var cdpChatgptAdapter = {
13646
12427
  name: "cdp:chatgpt",
@@ -13704,7 +12485,7 @@ var cdpChatgptAdapter = {
13704
12485
  const answerText = await target.extractAnswer(client);
13705
12486
  const groundingSources = await target.extractCitations(client);
13706
12487
  const screenshotId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
13707
- const screenshotPath = path9.join(getScreenshotDir2(), `${screenshotId}.png`);
12488
+ const screenshotPath = path8.join(getScreenshotDir2(), `${screenshotId}.png`);
13708
12489
  let capturedScreenshotPath;
13709
12490
  try {
13710
12491
  capturedScreenshotPath = await captureElementScreenshot(
@@ -14241,9 +13022,9 @@ function removeWordpressConnection(config, projectName) {
14241
13022
 
14242
13023
  // src/job-runner.ts
14243
13024
  import crypto19 from "crypto";
14244
- import fs8 from "fs";
14245
- import path10 from "path";
14246
- import os5 from "os";
13025
+ import fs7 from "fs";
13026
+ import path9 from "path";
13027
+ import os4 from "os";
14247
13028
  import { and as and8, eq as eq19, inArray as inArray3, sql as sql6 } from "drizzle-orm";
14248
13029
 
14249
13030
  // src/citation-utils.ts
@@ -14627,12 +13408,12 @@ var JobRunner = class {
14627
13408
  competitorDomains
14628
13409
  );
14629
13410
  let screenshotRelPath = null;
14630
- if (raw.screenshotPath && fs8.existsSync(raw.screenshotPath)) {
13411
+ if (raw.screenshotPath && fs7.existsSync(raw.screenshotPath)) {
14631
13412
  const snapshotId = crypto19.randomUUID();
14632
- const screenshotDir = path10.join(os5.homedir(), ".canonry", "screenshots", runId);
14633
- if (!fs8.existsSync(screenshotDir)) fs8.mkdirSync(screenshotDir, { recursive: true });
14634
- const destPath = path10.join(screenshotDir, `${snapshotId}.png`);
14635
- fs8.renameSync(raw.screenshotPath, destPath);
13413
+ const screenshotDir = path9.join(os4.homedir(), ".canonry", "screenshots", runId);
13414
+ if (!fs7.existsSync(screenshotDir)) fs7.mkdirSync(screenshotDir, { recursive: true });
13415
+ const destPath = path9.join(screenshotDir, `${snapshotId}.png`);
13416
+ fs7.renameSync(raw.screenshotPath, destPath);
14636
13417
  screenshotRelPath = `${runId}/${snapshotId}.png`;
14637
13418
  this.db.insert(querySnapshots).values({
14638
13419
  id: snapshotId,
@@ -15421,7 +14202,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
15421
14202
 
15422
14203
  // src/commoncrawl-sync.ts
15423
14204
  import crypto23 from "crypto";
15424
- import path11 from "path";
14205
+ import path10 from "path";
15425
14206
  import { and as and11, eq as eq23, sql as sql8 } from "drizzle-orm";
15426
14207
  var log6 = createLogger("CommonCrawlSync");
15427
14208
  var INSERT_CHUNK_SIZE = 1e4;
@@ -15450,9 +14231,9 @@ async function executeReleaseSync(db, syncId, opts) {
15450
14231
  error: null
15451
14232
  }).where(eq23(ccReleaseSyncs.id, syncId)).run();
15452
14233
  const paths = ccReleasePaths(release);
15453
- const releaseCacheDir = path11.join(deps.cacheDir, release);
15454
- const vertexPath = path11.join(releaseCacheDir, paths.vertexFilename);
15455
- const edgesPath = path11.join(releaseCacheDir, paths.edgesFilename);
14234
+ const releaseCacheDir = path10.join(deps.cacheDir, release);
14235
+ const vertexPath = path10.join(releaseCacheDir, paths.vertexFilename);
14236
+ const edgesPath = path10.join(releaseCacheDir, paths.edgesFilename);
15456
14237
  const [vertex, edges] = await Promise.all([
15457
14238
  deps.downloadFile({ url: paths.vertexUrl, destPath: vertexPath }),
15458
14239
  deps.downloadFile({ url: paths.edgesUrl, destPath: edgesPath })
@@ -15612,7 +14393,7 @@ function computeSummary(rows) {
15612
14393
 
15613
14394
  // src/backlink-extract.ts
15614
14395
  import crypto24 from "crypto";
15615
- import fs9 from "fs";
14396
+ import fs8 from "fs";
15616
14397
  import { and as and12, desc as desc10, eq as eq24 } from "drizzle-orm";
15617
14398
  var log7 = createLogger("BacklinkExtract");
15618
14399
  function defaultDeps2() {
@@ -15641,7 +14422,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
15641
14422
  if (!sync.vertexPath || !sync.edgesPath) {
15642
14423
  throw new Error(`Release ${sync.release} is missing cached file paths`);
15643
14424
  }
15644
- if (!fs9.existsSync(sync.vertexPath) || !fs9.existsSync(sync.edgesPath)) {
14425
+ if (!fs8.existsSync(sync.vertexPath) || !fs8.existsSync(sync.edgesPath)) {
15645
14426
  throw new Error(
15646
14427
  `Cache for release ${sync.release} is missing from disk (expected at ${sync.vertexPath}). The sync record exists in the database, but the ~16 GB dump was deleted or never present on this machine. Re-sync this release from the Backlinks admin page to restore the cache.`
15647
14428
  );
@@ -16160,8 +14941,8 @@ import crypto27 from "crypto";
16160
14941
  import { eq as eq28 } from "drizzle-orm";
16161
14942
 
16162
14943
  // src/agent/session.ts
16163
- import fs12 from "fs";
16164
- import path14 from "path";
14944
+ import fs11 from "fs";
14945
+ import path13 from "path";
16165
14946
  import { Agent } from "@mariozechner/pi-agent-core";
16166
14947
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
16167
14948
 
@@ -16262,26 +15043,26 @@ function buildAgentProvidersResponse(config) {
16262
15043
  }
16263
15044
 
16264
15045
  // src/agent/skill-paths.ts
16265
- import fs10 from "fs";
16266
- import path12 from "path";
15046
+ import fs9 from "fs";
15047
+ import path11 from "path";
16267
15048
  import { fileURLToPath } from "url";
16268
15049
  function resolveAeroSkillDir(pkgDir) {
16269
- const here = pkgDir ?? path12.dirname(fileURLToPath(import.meta.url));
15050
+ const here = pkgDir ?? path11.dirname(fileURLToPath(import.meta.url));
16270
15051
  const candidates = [
16271
- path12.join(here, "../assets/agent-workspace/skills/aero"),
16272
- path12.join(here, "../../assets/agent-workspace/skills/aero"),
16273
- path12.join(here, "../../../../skills/aero")
15052
+ path11.join(here, "../assets/agent-workspace/skills/aero"),
15053
+ path11.join(here, "../../assets/agent-workspace/skills/aero"),
15054
+ path11.join(here, "../../../../skills/aero")
16274
15055
  ];
16275
15056
  for (const candidate of candidates) {
16276
- if (fs10.existsSync(path12.join(candidate, "SKILL.md"))) return candidate;
15057
+ if (fs9.existsSync(path11.join(candidate, "SKILL.md"))) return candidate;
16277
15058
  }
16278
15059
  throw new Error(`Aero skill not found. Searched:
16279
15060
  ${candidates.join("\n ")}`);
16280
15061
  }
16281
15062
 
16282
15063
  // src/agent/skill-tools.ts
16283
- import fs11 from "fs";
16284
- import path13 from "path";
15064
+ import fs10 from "fs";
15065
+ import path12 from "path";
16285
15066
  import { Type } from "@sinclair/typebox";
16286
15067
  var MAX_DOC_CHARS = 2e4;
16287
15068
  function textResult(details) {
@@ -16302,13 +15083,13 @@ function parseDescription(body) {
16302
15083
  return "(no description)";
16303
15084
  }
16304
15085
  function scanSkillDocs(skillDir) {
16305
- const refsDir = path13.join(skillDir ?? resolveAeroSkillDir(), "references");
16306
- if (!fs11.existsSync(refsDir)) return [];
15086
+ const refsDir = path12.join(skillDir ?? resolveAeroSkillDir(), "references");
15087
+ if (!fs10.existsSync(refsDir)) return [];
16307
15088
  const entries = [];
16308
- for (const file of fs11.readdirSync(refsDir)) {
15089
+ for (const file of fs10.readdirSync(refsDir)) {
16309
15090
  if (!file.endsWith(".md")) continue;
16310
- const filePath = path13.join(refsDir, file);
16311
- const body = fs11.readFileSync(filePath, "utf-8");
15091
+ const filePath = path12.join(refsDir, file);
15092
+ const body = fs10.readFileSync(filePath, "utf-8");
16312
15093
  entries.push({
16313
15094
  slug: file.replace(/\.md$/, ""),
16314
15095
  description: parseDescription(body),
@@ -16351,8 +15132,8 @@ function buildReadSkillDocTool() {
16351
15132
  availableSlugs: docs.map((d) => d.slug)
16352
15133
  });
16353
15134
  }
16354
- const filePath = path13.join(skillDir, "references", `${match.slug}.md`);
16355
- const content = fs11.readFileSync(filePath, "utf-8");
15135
+ const filePath = path12.join(skillDir, "references", `${match.slug}.md`);
15136
+ const content = fs10.readFileSync(filePath, "utf-8");
16356
15137
  if (content.length > MAX_DOC_CHARS) {
16357
15138
  return textResult({
16358
15139
  slug: match.slug,
@@ -16747,7 +15528,7 @@ function buildAddCompetitorsTool(ctx) {
16747
15528
  return {
16748
15529
  name: "add_competitors",
16749
15530
  label: "Add competitors",
16750
- description: "Append competitor domains to the project. Fetches the current set, merges with the requested domains (dedup on exact domain match), and persists the combined list.",
15531
+ description: "Append competitor domains to the project. Existing competitors are skipped by the API.",
16751
15532
  parameters: AddCompetitorsSchema,
16752
15533
  execute: async (_toolCallId, params) => {
16753
15534
  const existing = await ctx.client.listCompetitors(ctx.projectName);
@@ -16756,8 +15537,7 @@ function buildAddCompetitorsTool(ctx) {
16756
15537
  if (newDomains.length === 0) {
16757
15538
  return textResult2({ added: [], alreadyTracked: params.domains });
16758
15539
  }
16759
- const merged = [...existing.map((c) => c.domain), ...newDomains];
16760
- await ctx.client.putCompetitors(ctx.projectName, merged);
15540
+ await ctx.client.appendCompetitors(ctx.projectName, newDomains);
16761
15541
  return textResult2({ added: newDomains, alreadyTracked: params.domains.filter((d) => existingDomains.has(d)) });
16762
15542
  }
16763
15543
  };
@@ -16906,10 +15686,10 @@ function ensureBuiltinsRegistered() {
16906
15686
  }
16907
15687
  function loadAeroSystemPrompt(pkgDir) {
16908
15688
  const skillDir = resolveAeroSkillDir(pkgDir);
16909
- const skillBody = fs12.readFileSync(path14.join(skillDir, "SKILL.md"), "utf-8");
16910
- const soulPath = path14.join(skillDir, "soul.md");
16911
- if (!fs12.existsSync(soulPath)) return skillBody;
16912
- const soulBody = fs12.readFileSync(soulPath, "utf-8");
15689
+ const skillBody = fs11.readFileSync(path13.join(skillDir, "SKILL.md"), "utf-8");
15690
+ const soulPath = path13.join(skillDir, "soul.md");
15691
+ if (!fs11.existsSync(soulPath)) return skillBody;
15692
+ const soulBody = fs11.readFileSync(soulPath, "utf-8");
16913
15693
  return `${soulBody.trimEnd()}
16914
15694
 
16915
15695
  ---
@@ -17698,593 +16478,6 @@ function registerAgentRoutes(app, opts) {
17698
16478
  );
17699
16479
  }
17700
16480
 
17701
- // src/client.ts
17702
- function createApiClient() {
17703
- const config = loadConfig();
17704
- const basePathResolved = !!config.basePath || "CANONRY_BASE_PATH" in process.env;
17705
- return new ApiClient(config.apiUrl, config.apiKey, { skipProbe: basePathResolved });
17706
- }
17707
- var ApiClient = class {
17708
- baseUrl;
17709
- originUrl;
17710
- apiKey;
17711
- probePromise = null;
17712
- probeSkipped;
17713
- constructor(baseUrl, apiKey, opts) {
17714
- this.originUrl = baseUrl.replace(/\/$/, "");
17715
- this.baseUrl = this.originUrl + "/api/v1";
17716
- this.apiKey = apiKey;
17717
- this.probeSkipped = opts?.skipProbe ?? false;
17718
- }
17719
- /**
17720
- * On first API call, probe /health to auto-discover basePath when the user
17721
- * hasn't configured one locally. This lets `canonry run` in a separate shell
17722
- * discover that the server is running at e.g. /canonry/ without requiring
17723
- * config.yaml edits or CANONRY_BASE_PATH in every shell.
17724
- */
17725
- probeBasePath() {
17726
- if (this.probeSkipped) return Promise.resolve();
17727
- if (!this.probePromise) {
17728
- this.probePromise = (async () => {
17729
- try {
17730
- const origin = new URL(this.originUrl).origin;
17731
- const res = await fetch(`${origin}/health`, {
17732
- signal: AbortSignal.timeout(2e3)
17733
- });
17734
- if (res.ok) {
17735
- const body = await res.json();
17736
- if (body.basePath && typeof body.basePath === "string") {
17737
- const normalized = "/" + body.basePath.replace(/^\/|\/$/g, "");
17738
- if (normalized !== "/") {
17739
- this.originUrl = origin + normalized;
17740
- this.baseUrl = this.originUrl + "/api/v1";
17741
- }
17742
- }
17743
- }
17744
- } catch {
17745
- }
17746
- })();
17747
- }
17748
- return this.probePromise;
17749
- }
17750
- async request(method, path16, body) {
17751
- await this.probeBasePath();
17752
- const url = `${this.baseUrl}${path16}`;
17753
- const serializedBody = body != null ? JSON.stringify(body) : void 0;
17754
- const headers = {
17755
- "Authorization": `Bearer ${this.apiKey}`,
17756
- "Accept": "application/json",
17757
- ...serializedBody != null ? { "Content-Type": "application/json" } : {}
17758
- };
17759
- let res;
17760
- try {
17761
- res = await fetch(url, {
17762
- method,
17763
- headers,
17764
- body: serializedBody
17765
- });
17766
- } catch (err) {
17767
- if (err instanceof CliError) throw err;
17768
- const msg = err instanceof Error ? err.message : String(err);
17769
- if (msg.includes("fetch failed") || msg.includes("ECONNREFUSED") || msg.includes("connect ECONNREFUSED")) {
17770
- throw new CliError({
17771
- code: "CONNECTION_ERROR",
17772
- message: `Could not connect to canonry server at ${this.baseUrl.replace("/api/v1", "")}. Start it with "canonry serve" (or "canonry serve &" to run in background).`,
17773
- exitCode: EXIT_SYSTEM_ERROR
17774
- });
17775
- }
17776
- throw new CliError({ code: "CONNECTION_ERROR", message: msg, exitCode: EXIT_SYSTEM_ERROR });
17777
- }
17778
- if (!res.ok) {
17779
- let errorBody;
17780
- try {
17781
- errorBody = await res.json();
17782
- } catch {
17783
- errorBody = { error: { code: "UNKNOWN", message: res.statusText } };
17784
- }
17785
- const errorObj = errorBody && typeof errorBody === "object" && "error" in errorBody && errorBody.error && typeof errorBody.error === "object" ? errorBody.error : null;
17786
- const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
17787
- const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
17788
- const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
17789
- throw new CliError({ code, message: msg, exitCode, details: { httpStatus: res.status } });
17790
- }
17791
- if (res.status === 204) {
17792
- return void 0;
17793
- }
17794
- return await res.json();
17795
- }
17796
- async getAgentTranscript(project) {
17797
- return this.request(
17798
- "GET",
17799
- `/projects/${encodeURIComponent(project)}/agent/transcript`
17800
- );
17801
- }
17802
- async resetAgentTranscript(project) {
17803
- await this.request(
17804
- "DELETE",
17805
- `/projects/${encodeURIComponent(project)}/agent/transcript`
17806
- );
17807
- }
17808
- async listAgentProviders(project) {
17809
- return this.request(
17810
- "GET",
17811
- `/projects/${encodeURIComponent(project)}/agent/providers`
17812
- );
17813
- }
17814
- async listAgentMemory(project) {
17815
- return this.request(
17816
- "GET",
17817
- `/projects/${encodeURIComponent(project)}/agent/memory`
17818
- );
17819
- }
17820
- async setAgentMemory(project, body) {
17821
- return this.request(
17822
- "PUT",
17823
- `/projects/${encodeURIComponent(project)}/agent/memory`,
17824
- body
17825
- );
17826
- }
17827
- async forgetAgentMemory(project, key) {
17828
- return this.request(
17829
- "DELETE",
17830
- `/projects/${encodeURIComponent(project)}/agent/memory`,
17831
- { key }
17832
- );
17833
- }
17834
- /**
17835
- * POST a request whose response body the caller intends to consume as a
17836
- * stream (e.g. the Aero agent SSE endpoint). Shares the probe + auth +
17837
- * structured-error behavior of `request()`; the caller reads `res.body`
17838
- * and releases the response when done.
17839
- */
17840
- async streamPost(path16, body, signal) {
17841
- await this.probeBasePath();
17842
- const url = `${this.baseUrl}${path16}`;
17843
- const headers = {
17844
- Authorization: `Bearer ${this.apiKey}`,
17845
- "Content-Type": "application/json",
17846
- Accept: "text/event-stream"
17847
- };
17848
- let res;
17849
- try {
17850
- res = await fetch(url, {
17851
- method: "POST",
17852
- headers,
17853
- body: JSON.stringify(body ?? {}),
17854
- signal
17855
- });
17856
- } catch (err) {
17857
- if (err instanceof CliError) throw err;
17858
- const msg = err instanceof Error ? err.message : String(err);
17859
- if (msg.includes("fetch failed") || msg.includes("ECONNREFUSED") || msg.includes("connect ECONNREFUSED")) {
17860
- throw new CliError({
17861
- code: "CONNECTION_ERROR",
17862
- message: `Could not connect to canonry server at ${this.baseUrl.replace("/api/v1", "")}. Start it with "canonry serve" (or "canonry serve &" to run in background).`,
17863
- exitCode: EXIT_SYSTEM_ERROR
17864
- });
17865
- }
17866
- throw new CliError({ code: "CONNECTION_ERROR", message: msg, exitCode: EXIT_SYSTEM_ERROR });
17867
- }
17868
- if (!res.ok || !res.body) {
17869
- let errorBody;
17870
- try {
17871
- errorBody = await res.json();
17872
- } catch {
17873
- errorBody = { error: { code: "UNKNOWN", message: res.statusText } };
17874
- }
17875
- const errorObj = errorBody && typeof errorBody === "object" && "error" in errorBody && errorBody.error ? errorBody.error : null;
17876
- const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
17877
- const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
17878
- const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
17879
- throw new CliError({ code, message: msg, exitCode, details: { httpStatus: res.status } });
17880
- }
17881
- return res;
17882
- }
17883
- async putProject(name, body) {
17884
- return this.request("PUT", `/projects/${encodeURIComponent(name)}`, body);
17885
- }
17886
- async listProjects() {
17887
- return this.request("GET", "/projects");
17888
- }
17889
- async getProject(name) {
17890
- return this.request("GET", `/projects/${encodeURIComponent(name)}`);
17891
- }
17892
- async deleteProject(name) {
17893
- await this.request("DELETE", `/projects/${encodeURIComponent(name)}`);
17894
- }
17895
- async putKeywords(project, keywords2) {
17896
- await this.request("PUT", `/projects/${encodeURIComponent(project)}/keywords`, { keywords: keywords2 });
17897
- }
17898
- async listKeywords(project) {
17899
- return this.request("GET", `/projects/${encodeURIComponent(project)}/keywords`);
17900
- }
17901
- async deleteKeywords(project, keywords2) {
17902
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/keywords`, { keywords: keywords2 });
17903
- }
17904
- async appendKeywords(project, keywords2) {
17905
- await this.request("POST", `/projects/${encodeURIComponent(project)}/keywords`, { keywords: keywords2 });
17906
- }
17907
- async putCompetitors(project, competitors2) {
17908
- await this.request("PUT", `/projects/${encodeURIComponent(project)}/competitors`, { competitors: competitors2 });
17909
- }
17910
- async listCompetitors(project) {
17911
- return this.request("GET", `/projects/${encodeURIComponent(project)}/competitors`);
17912
- }
17913
- async triggerRun(project, body) {
17914
- return this.request("POST", `/projects/${encodeURIComponent(project)}/runs`, body ?? {});
17915
- }
17916
- async listRuns(project, limit) {
17917
- const query = limit != null ? `?limit=${encodeURIComponent(String(limit))}` : "";
17918
- return this.request("GET", `/projects/${encodeURIComponent(project)}/runs${query}`);
17919
- }
17920
- async getLatestRun(project) {
17921
- return this.request("GET", `/projects/${encodeURIComponent(project)}/runs/latest`);
17922
- }
17923
- async getRun(id) {
17924
- return this.request("GET", `/runs/${encodeURIComponent(id)}`);
17925
- }
17926
- async cancelRun(id) {
17927
- return this.request("POST", `/runs/${encodeURIComponent(id)}/cancel`);
17928
- }
17929
- async getTimeline(project) {
17930
- return this.request("GET", `/projects/${encodeURIComponent(project)}/timeline`);
17931
- }
17932
- async getHistory(project) {
17933
- return this.request("GET", `/projects/${encodeURIComponent(project)}/history`);
17934
- }
17935
- async getExport(project) {
17936
- return this.request("GET", `/projects/${encodeURIComponent(project)}/export`);
17937
- }
17938
- async apply(config) {
17939
- return this.request("POST", "/apply", config);
17940
- }
17941
- async getStatus(project) {
17942
- return this.request("GET", `/projects/${encodeURIComponent(project)}`);
17943
- }
17944
- async getSettings() {
17945
- return this.request("GET", "/settings");
17946
- }
17947
- async createSnapshot(body) {
17948
- return this.request("POST", "/snapshot", body);
17949
- }
17950
- async updateProvider(name, body) {
17951
- return this.request("PUT", `/settings/providers/${encodeURIComponent(name)}`, body);
17952
- }
17953
- async putSchedule(project, body) {
17954
- return this.request("PUT", `/projects/${encodeURIComponent(project)}/schedule`, body);
17955
- }
17956
- async getSchedule(project) {
17957
- return this.request("GET", `/projects/${encodeURIComponent(project)}/schedule`);
17958
- }
17959
- async deleteSchedule(project) {
17960
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/schedule`);
17961
- }
17962
- async createNotification(project, body) {
17963
- return this.request("POST", `/projects/${encodeURIComponent(project)}/notifications`, body);
17964
- }
17965
- async listNotifications(project) {
17966
- return this.request("GET", `/projects/${encodeURIComponent(project)}/notifications`);
17967
- }
17968
- async deleteNotification(project, id) {
17969
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/notifications/${encodeURIComponent(id)}`);
17970
- }
17971
- async testNotification(project, id) {
17972
- return this.request("POST", `/projects/${encodeURIComponent(project)}/notifications/${encodeURIComponent(id)}/test`);
17973
- }
17974
- async addLocation(project, body) {
17975
- return this.request("POST", `/projects/${encodeURIComponent(project)}/locations`, body);
17976
- }
17977
- async listLocations(project) {
17978
- return this.request("GET", `/projects/${encodeURIComponent(project)}/locations`);
17979
- }
17980
- async removeLocation(project, label) {
17981
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/locations/${encodeURIComponent(label)}`);
17982
- }
17983
- async setDefaultLocation(project, label) {
17984
- return this.request("PUT", `/projects/${encodeURIComponent(project)}/locations/default`, { label });
17985
- }
17986
- async getTelemetry() {
17987
- return this.request("GET", "/telemetry");
17988
- }
17989
- async updateTelemetry(enabled) {
17990
- return this.request("PUT", "/telemetry", { enabled });
17991
- }
17992
- async generateKeywords(project, provider, count) {
17993
- return this.request(
17994
- "POST",
17995
- `/projects/${encodeURIComponent(project)}/keywords/generate`,
17996
- { provider, count }
17997
- );
17998
- }
17999
- // Google connection management
18000
- async googleConnect(project, body) {
18001
- return this.request("POST", `/projects/${encodeURIComponent(project)}/google/connect`, body);
18002
- }
18003
- async googleConnections(project) {
18004
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/connections`);
18005
- }
18006
- async googleDisconnect(project, type) {
18007
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}`);
18008
- }
18009
- async googleProperties(project) {
18010
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/properties`);
18011
- }
18012
- async googleSetProperty(project, type, propertyId) {
18013
- return this.request("PUT", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}/property`, { propertyId });
18014
- }
18015
- async googleSetSitemap(project, type, sitemapUrl) {
18016
- return this.request("PUT", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}/sitemap`, { sitemapUrl });
18017
- }
18018
- // GSC data
18019
- async gscSync(project, body) {
18020
- return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/sync`, body ?? {});
18021
- }
18022
- async gscPerformance(project, params) {
18023
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18024
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/performance${qs}`);
18025
- }
18026
- async gscInspect(project, url) {
18027
- return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/inspect`, { url });
18028
- }
18029
- async gscInspections(project, params) {
18030
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18031
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/inspections${qs}`);
18032
- }
18033
- async gscDeindexed(project) {
18034
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/deindexed`);
18035
- }
18036
- async gscCoverage(project) {
18037
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/coverage`);
18038
- }
18039
- async gscCoverageHistory(project, params) {
18040
- const qs = params?.limit != null ? `?limit=${params.limit}` : "";
18041
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/coverage/history${qs}`);
18042
- }
18043
- async gscInspectSitemap(project, body) {
18044
- return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/inspect-sitemap`, body ?? {});
18045
- }
18046
- async gscSitemaps(project) {
18047
- return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/sitemaps`);
18048
- }
18049
- async gscDiscoverSitemaps(project) {
18050
- return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/discover-sitemaps`, {});
18051
- }
18052
- // Analytics
18053
- async getAnalyticsMetrics(project, window) {
18054
- const qs = window ? `?window=${encodeURIComponent(window)}` : "";
18055
- return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/metrics${qs}`);
18056
- }
18057
- async getAnalyticsGaps(project, window) {
18058
- const qs = window ? `?window=${encodeURIComponent(window)}` : "";
18059
- return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/gaps${qs}`);
18060
- }
18061
- async getAnalyticsSources(project, window) {
18062
- const qs = window ? `?window=${encodeURIComponent(window)}` : "";
18063
- return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/sources${qs}`);
18064
- }
18065
- // Google Indexing API
18066
- async googleRequestIndexing(project, body) {
18067
- return this.request("POST", `/projects/${encodeURIComponent(project)}/google/indexing/request`, body);
18068
- }
18069
- // Bing Webmaster Tools
18070
- async bingConnect(project, body) {
18071
- return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/connect`, body);
18072
- }
18073
- async bingDisconnect(project) {
18074
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/bing/disconnect`);
18075
- }
18076
- async bingStatus(project) {
18077
- return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/status`);
18078
- }
18079
- async bingSites(project) {
18080
- return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/sites`);
18081
- }
18082
- async bingSetSite(project, siteUrl) {
18083
- return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/set-site`, { siteUrl });
18084
- }
18085
- async bingCoverage(project) {
18086
- return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/coverage`);
18087
- }
18088
- async bingCoverageHistory(project, params) {
18089
- const qs = params?.limit != null ? `?limit=${params.limit}` : "";
18090
- return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/coverage/history${qs}`);
18091
- }
18092
- async bingInspections(project, params) {
18093
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18094
- return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/inspections${qs}`);
18095
- }
18096
- async bingInspectUrl(project, url) {
18097
- return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/inspect-url`, { url });
18098
- }
18099
- async bingInspectSitemap(project, body) {
18100
- return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/inspect-sitemap`, body ?? {});
18101
- }
18102
- async bingRequestIndexing(project, body) {
18103
- return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/request-indexing`, body);
18104
- }
18105
- async bingPerformance(project, params) {
18106
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18107
- return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/performance${qs}`);
18108
- }
18109
- // CDP browser provider
18110
- async getCdpStatus() {
18111
- return this.request("GET", "/cdp/status");
18112
- }
18113
- async cdpScreenshot(query, targets) {
18114
- return this.request("POST", "/cdp/screenshot", { query, targets });
18115
- }
18116
- async getBrowserDiff(project, runId) {
18117
- return this.request("GET", `/projects/${encodeURIComponent(project)}/runs/${encodeURIComponent(runId)}/browser-diff`);
18118
- }
18119
- // Google Analytics 4
18120
- async gaConnect(project, body) {
18121
- return this.request("POST", `/projects/${encodeURIComponent(project)}/ga/connect`, body);
18122
- }
18123
- async gaDisconnect(project) {
18124
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/ga/disconnect`);
18125
- }
18126
- async gaStatus(project) {
18127
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/status`);
18128
- }
18129
- async gaSync(project, body) {
18130
- return this.request("POST", `/projects/${encodeURIComponent(project)}/ga/sync`, body ?? {});
18131
- }
18132
- async gaTraffic(project, params) {
18133
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18134
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/traffic${qs}`);
18135
- }
18136
- async gaCoverage(project) {
18137
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/coverage`);
18138
- }
18139
- async gaAiReferralHistory(project, params) {
18140
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18141
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/ai-referral-history${qs}`);
18142
- }
18143
- async gaSocialReferralHistory(project, params) {
18144
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18145
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-history${qs}`);
18146
- }
18147
- async gaSocialReferralTrend(project) {
18148
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-trend`);
18149
- }
18150
- async gaAttributionTrend(project) {
18151
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/attribution-trend`);
18152
- }
18153
- async gaSessionHistory(project, params) {
18154
- const qs = params ? "?" + new URLSearchParams(params).toString() : "";
18155
- return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/session-history${qs}`);
18156
- }
18157
- async wordpressConnect(project, body) {
18158
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/connect`, body);
18159
- }
18160
- async wordpressDisconnect(project) {
18161
- await this.request("DELETE", `/projects/${encodeURIComponent(project)}/wordpress/disconnect`);
18162
- }
18163
- async wordpressStatus(project) {
18164
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/status`);
18165
- }
18166
- async wordpressPages(project, env) {
18167
- const qs = env ? `?env=${encodeURIComponent(env)}` : "";
18168
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/pages${qs}`);
18169
- }
18170
- async wordpressPage(project, slug, env) {
18171
- const params = new URLSearchParams({ slug });
18172
- if (env) params.set("env", env);
18173
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/page?${params.toString()}`);
18174
- }
18175
- async wordpressCreatePage(project, body) {
18176
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/pages`, body);
18177
- }
18178
- async wordpressUpdatePage(project, body) {
18179
- return this.request("PUT", `/projects/${encodeURIComponent(project)}/wordpress/page`, body);
18180
- }
18181
- async wordpressSetMeta(project, body) {
18182
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/page/meta`, body);
18183
- }
18184
- async wordpressBulkSetMeta(project, body) {
18185
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/pages/meta/bulk`, body);
18186
- }
18187
- async wordpressSchema(project, slug, env) {
18188
- const params = new URLSearchParams({ slug });
18189
- if (env) params.set("env", env);
18190
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/schema?${params.toString()}`);
18191
- }
18192
- async wordpressSetSchema(project, body) {
18193
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/schema/manual`, body);
18194
- }
18195
- async wordpressSchemaDeploy(project, body) {
18196
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/schema/deploy`, body);
18197
- }
18198
- async wordpressSchemaStatus(project, env) {
18199
- const params = new URLSearchParams();
18200
- if (env) params.set("env", env);
18201
- const qs = params.toString();
18202
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/schema/status${qs ? `?${qs}` : ""}`);
18203
- }
18204
- async wordpressOnboard(project, body) {
18205
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/onboard`, body);
18206
- }
18207
- async wordpressLlmsTxt(project, env) {
18208
- const qs = env ? `?env=${encodeURIComponent(env)}` : "";
18209
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/llms-txt${qs}`);
18210
- }
18211
- async wordpressSetLlmsTxt(project, body) {
18212
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/llms-txt/manual`, body);
18213
- }
18214
- async wordpressAudit(project, env) {
18215
- const qs = env ? `?env=${encodeURIComponent(env)}` : "";
18216
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/audit${qs}`);
18217
- }
18218
- async wordpressDiff(project, slug) {
18219
- const params = new URLSearchParams({ slug });
18220
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/diff?${params.toString()}`);
18221
- }
18222
- async wordpressStagingStatus(project) {
18223
- return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/staging/status`);
18224
- }
18225
- async wordpressStagingPush(project) {
18226
- return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/staging/push`);
18227
- }
18228
- // ── Intelligence ──────────────────────────────────────────────────────
18229
- async getInsights(project, opts) {
18230
- const params = new URLSearchParams();
18231
- if (opts?.dismissed) params.set("dismissed", "true");
18232
- if (opts?.runId) params.set("runId", opts.runId);
18233
- const qs = params.toString();
18234
- return this.request("GET", `/projects/${encodeURIComponent(project)}/insights${qs ? `?${qs}` : ""}`);
18235
- }
18236
- async dismissInsight(project, id) {
18237
- return this.request("POST", `/projects/${encodeURIComponent(project)}/insights/${encodeURIComponent(id)}/dismiss`);
18238
- }
18239
- async getHealth(project) {
18240
- return this.request("GET", `/projects/${encodeURIComponent(project)}/health/latest`);
18241
- }
18242
- async getHealthHistory(project, limit) {
18243
- const qs = limit ? `?limit=${limit}` : "";
18244
- return this.request("GET", `/projects/${encodeURIComponent(project)}/health/history${qs}`);
18245
- }
18246
- // --- Backlinks ---------------------------------------------------------
18247
- async backlinksStatus() {
18248
- return this.request("GET", "/backlinks/status");
18249
- }
18250
- async backlinksInstall() {
18251
- return this.request("POST", "/backlinks/install");
18252
- }
18253
- async backlinksTriggerSync(release) {
18254
- return this.request("POST", "/backlinks/syncs", { release });
18255
- }
18256
- async backlinksLatestSync() {
18257
- return this.request("GET", "/backlinks/syncs/latest");
18258
- }
18259
- async backlinksListSyncs() {
18260
- return this.request("GET", "/backlinks/syncs");
18261
- }
18262
- async backlinksCachedReleases() {
18263
- return this.request("GET", "/backlinks/releases");
18264
- }
18265
- async backlinksPruneCache(release) {
18266
- return this.request("DELETE", `/backlinks/cache/${encodeURIComponent(release)}`);
18267
- }
18268
- async backlinksExtract(project, release) {
18269
- return this.request("POST", `/projects/${encodeURIComponent(project)}/backlinks/extract`, release ? { release } : {});
18270
- }
18271
- async backlinksSummary(project, release) {
18272
- const qs = release ? `?release=${encodeURIComponent(release)}` : "";
18273
- return this.request("GET", `/projects/${encodeURIComponent(project)}/backlinks/summary${qs}`);
18274
- }
18275
- async backlinksDomains(project, opts = {}) {
18276
- const qs = new URLSearchParams();
18277
- if (opts.limit !== void 0) qs.set("limit", String(opts.limit));
18278
- if (opts.offset !== void 0) qs.set("offset", String(opts.offset));
18279
- if (opts.release) qs.set("release", opts.release);
18280
- const suffix = qs.toString() ? `?${qs.toString()}` : "";
18281
- return this.request("GET", `/projects/${encodeURIComponent(project)}/backlinks/domains${suffix}`);
18282
- }
18283
- async backlinksHistory(project) {
18284
- return this.request("GET", `/projects/${encodeURIComponent(project)}/backlinks/history`);
18285
- }
18286
- };
18287
-
18288
16481
  // src/snapshot-service.ts
18289
16482
  import { runAeoAudit } from "@ainyc/aeo-audit";
18290
16483
 
@@ -18307,13 +16500,13 @@ function extractHostname(domain) {
18307
16500
  function fetchWithPinnedAddress(target) {
18308
16501
  return new Promise((resolve) => {
18309
16502
  const port = target.url.port ? Number(target.url.port) : 443;
18310
- const path16 = target.url.pathname + target.url.search;
16503
+ const path15 = target.url.pathname + target.url.search;
18311
16504
  const req = https2.request(
18312
16505
  {
18313
16506
  hostname: target.address,
18314
16507
  family: target.family,
18315
16508
  port,
18316
- path: path16,
16509
+ path: path15,
18317
16510
  method: "GET",
18318
16511
  timeout: FETCH_TIMEOUT_MS,
18319
16512
  servername: target.url.hostname,
@@ -18520,7 +16713,7 @@ var SnapshotService = class {
18520
16713
  });
18521
16714
  const enrichedResults = applyBatchAssessment(queryResults, batchAssessment);
18522
16715
  const summary = buildSnapshotSummary(companyName, profile.phrases, providers, enrichedResults, audit, batchAssessment);
18523
- const reportCompetitors = uniqueStrings([
16716
+ const reportCompetitors = uniqueStrings2([
18524
16717
  ...manualCompetitors,
18525
16718
  ...summary.topCompetitors.map((entry) => entry.name)
18526
16719
  ]);
@@ -18573,8 +16766,8 @@ var SnapshotService = class {
18573
16766
  return {
18574
16767
  industry: parsed.industry?.trim() || "Unknown",
18575
16768
  summary: parsed.summary?.trim() || ctx.audit.summary,
18576
- services: uniqueStrings(parsed.services ?? []).slice(0, 6),
18577
- categoryTerms: uniqueStrings(parsed.categoryTerms ?? []).slice(0, 8),
16769
+ services: uniqueStrings2(parsed.services ?? []).slice(0, 6),
16770
+ categoryTerms: uniqueStrings2(parsed.categoryTerms ?? []).slice(0, 8),
18578
16771
  phrases: parsedPhrases
18579
16772
  };
18580
16773
  } catch (err) {
@@ -18642,9 +16835,9 @@ var SnapshotService = class {
18642
16835
  accuracyNotes: null,
18643
16836
  incorrectClaims: [],
18644
16837
  recommendedCompetitors: preliminaryCompetitors,
18645
- citedDomains: uniqueStrings(normalized.citedDomains),
16838
+ citedDomains: uniqueStrings2(normalized.citedDomains),
18646
16839
  groundingSources: normalized.groundingSources,
18647
- searchQueries: uniqueStrings(normalized.searchQueries),
16840
+ searchQueries: uniqueStrings2(normalized.searchQueries),
18648
16841
  answerText: normalized.answerText,
18649
16842
  error: null
18650
16843
  };
@@ -18710,14 +16903,14 @@ var SnapshotService = class {
18710
16903
  mentioned: assessment.mentioned,
18711
16904
  describedAccurately: assessment.describedAccurately,
18712
16905
  accuracyNotes: assessment.accuracyNotes ?? null,
18713
- incorrectClaims: uniqueStrings(assessment.incorrectClaims ?? []).slice(0, 5),
16906
+ incorrectClaims: uniqueStrings2(assessment.incorrectClaims ?? []).slice(0, 5),
18714
16907
  ...hasReviewedCompetitors ? {
18715
- recommendedCompetitors: uniqueStrings(assessment.recommendedCompetitors ?? []).slice(0, 10)
16908
+ recommendedCompetitors: uniqueStrings2(assessment.recommendedCompetitors ?? []).slice(0, 10)
18716
16909
  } : {}
18717
16910
  };
18718
16911
  }),
18719
- whatThisMeans: uniqueStrings(parsed.whatThisMeans ?? []).slice(0, 4),
18720
- recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
16912
+ whatThisMeans: uniqueStrings2(parsed.whatThisMeans ?? []).slice(0, 4),
16913
+ recommendedActions: uniqueStrings2(parsed.recommendedActions ?? []).slice(0, 4)
18721
16914
  };
18722
16915
  } catch (err) {
18723
16916
  log12.warn("response.analysis-failed", {
@@ -18812,13 +17005,13 @@ function applyBatchAssessment(queryResults, batchAssessment) {
18812
17005
  };
18813
17006
  }
18814
17007
  const reviewedCompetitors = assessment.recommendedCompetitors;
18815
- const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings(reviewedCompetitors) : result.recommendedCompetitors;
17008
+ const recommendedCompetitors = reviewedCompetitors !== void 0 ? uniqueStrings2(reviewedCompetitors) : result.recommendedCompetitors;
18816
17009
  return {
18817
17010
  ...result,
18818
17011
  mentioned: result.mentioned || assessment.mentioned === true,
18819
17012
  describedAccurately: assessment.describedAccurately ?? (result.mentioned ? "unknown" : "not-mentioned"),
18820
17013
  accuracyNotes: assessment.accuracyNotes ?? result.accuracyNotes ?? null,
18821
- incorrectClaims: uniqueStrings([
17014
+ incorrectClaims: uniqueStrings2([
18822
17015
  ...result.incorrectClaims,
18823
17016
  ...assessment.incorrectClaims ?? []
18824
17017
  ]),
@@ -18848,7 +17041,7 @@ function buildSnapshotSummary(companyName, phrases, providers, queryResults, aud
18848
17041
  const whatThisMeans = batchAssessment.whatThisMeans.length > 0 ? batchAssessment.whatThisMeans : [
18849
17042
  defaultMeaning
18850
17043
  ];
18851
- const combinedWhatThisMeans = uniqueStrings([...whatThisMeans, ...failureNote]).slice(0, 5);
17044
+ const combinedWhatThisMeans = uniqueStrings2([...whatThisMeans, ...failureNote]).slice(0, 5);
18852
17045
  const recommendedActions = batchAssessment.recommendedActions.length > 0 ? batchAssessment.recommendedActions : buildFallbackRecommendedActions(audit);
18853
17046
  return {
18854
17047
  totalQueries: phrases.length,
@@ -18891,7 +17084,7 @@ function buildFallbackRecommendedActions(audit) {
18891
17084
  "Add machine-readable trust signals such as schema, FAQs, and llms.txt support.",
18892
17085
  "Build comparison and proof content that makes the category fit unmistakable."
18893
17086
  ];
18894
- return uniqueStrings([...weakestFactors, ...defaults]).slice(0, 4);
17087
+ return uniqueStrings2([...weakestFactors, ...defaults]).slice(0, 4);
18895
17088
  }
18896
17089
  function citesTargetDomain(citedDomains, groundingSources, targetDomain) {
18897
17090
  const normalizedTarget = extractHostname2(targetDomain);
@@ -18965,11 +17158,11 @@ function parseJsonObject(input) {
18965
17158
  }
18966
17159
  function normalizeStringList(values) {
18967
17160
  const items = values.flatMap((value) => value.split(","));
18968
- return uniqueStrings(
17161
+ return uniqueStrings2(
18969
17162
  items.map((value) => value.trim()).filter(Boolean)
18970
17163
  );
18971
17164
  }
18972
- function uniqueStrings(values) {
17165
+ function uniqueStrings2(values) {
18973
17166
  if (!Array.isArray(values)) return [];
18974
17167
  return [...new Set(
18975
17168
  values.filter((value) => typeof value === "string").map((value) => value.trim()).filter(Boolean)
@@ -19206,8 +17399,8 @@ async function createServer(opts) {
19206
17399
  );
19207
17400
  jobRunner.onRunCompleted = (runId, projectId) => runCoordinator.onRunCompleted(runId, projectId);
19208
17401
  const snapshotService = new SnapshotService(registry);
19209
- const orphanedOpenClawDir = path15.join(os6.homedir(), ".openclaw-aero");
19210
- if (fs13.existsSync(orphanedOpenClawDir)) {
17402
+ const orphanedOpenClawDir = path14.join(os5.homedir(), ".openclaw-aero");
17403
+ if (fs12.existsSync(orphanedOpenClawDir)) {
19211
17404
  app.log.warn(
19212
17405
  { path: orphanedOpenClawDir },
19213
17406
  "OpenClaw gateway is no longer used. Remove ~/.openclaw-aero/ manually to reclaim the directory."
@@ -19801,10 +17994,10 @@ async function createServer(opts) {
19801
17994
  return snapshotService.createReport(input);
19802
17995
  }
19803
17996
  });
19804
- const dirname = path15.dirname(fileURLToPath2(import.meta.url));
19805
- const assetsDir = path15.join(dirname, "..", "assets");
19806
- if (fs13.existsSync(assetsDir)) {
19807
- const indexPath = path15.join(assetsDir, "index.html");
17997
+ const dirname = path14.dirname(fileURLToPath2(import.meta.url));
17998
+ const assetsDir = path14.join(dirname, "..", "assets");
17999
+ if (fs12.existsSync(assetsDir)) {
18000
+ const indexPath = path14.join(assetsDir, "index.html");
19808
18001
  const injectConfig = (html) => {
19809
18002
  const clientConfig = {};
19810
18003
  if (basePath) clientConfig.basePath = basePath;
@@ -19822,8 +18015,8 @@ async function createServer(opts) {
19822
18015
  index: false
19823
18016
  });
19824
18017
  const serveIndex = (_request, reply) => {
19825
- if (fs13.existsSync(indexPath)) {
19826
- const html = fs13.readFileSync(indexPath, "utf-8");
18018
+ if (fs12.existsSync(indexPath)) {
18019
+ const html = fs12.readFileSync(indexPath, "utf-8");
19827
18020
  return reply.type("text/html").send(injectConfig(html));
19828
18021
  }
19829
18022
  return reply.status(404).send({ error: "Dashboard not built" });
@@ -19843,8 +18036,8 @@ async function createServer(opts) {
19843
18036
  if (basePath && !url.startsWith(basePath)) {
19844
18037
  return reply.status(404).send({ error: "Not found", path: request.url });
19845
18038
  }
19846
- if (fs13.existsSync(indexPath)) {
19847
- const html = fs13.readFileSync(indexPath, "utf-8");
18039
+ if (fs12.existsSync(indexPath)) {
18040
+ const html = fs12.readFileSync(indexPath, "utf-8");
19848
18041
  return reply.type("text/html").send(injectConfig(html));
19849
18042
  }
19850
18043
  return reply.status(404).send({ error: "Not found" });
@@ -19913,31 +18106,11 @@ function parseKeywordResponse(raw, count) {
19913
18106
  }
19914
18107
 
19915
18108
  export {
19916
- getConfigDir,
19917
- getConfigPath,
19918
- loadConfig,
19919
- saveConfig,
19920
- saveConfigPatch,
19921
- configExists,
19922
18109
  isTelemetryEnabled,
19923
18110
  getOrCreateAnonymousId,
19924
18111
  isFirstRun,
19925
18112
  showFirstRunNotice,
19926
18113
  trackEvent,
19927
- EXIT_SYSTEM_ERROR,
19928
- CliError,
19929
- usageError,
19930
- isEndpointMissing,
19931
- printCliError,
19932
- providerQuotaPolicySchema,
19933
- ProviderNames,
19934
- resolveProviderInput,
19935
- notificationEventSchema,
19936
- effectiveDomains,
19937
- RunStatuses,
19938
- RunKinds,
19939
- determineAnswerMentioned,
19940
- CcReleaseSyncStatuses,
19941
18114
  reparseStoredResult2 as reparseStoredResult,
19942
18115
  reparseStoredResult3 as reparseStoredResult2,
19943
18116
  reparseStoredResult as reparseStoredResult3,
@@ -19945,7 +18118,6 @@ export {
19945
18118
  determineCitationState,
19946
18119
  computeCompetitorOverlap,
19947
18120
  extractRecommendedCompetitors,
19948
- createApiClient,
19949
18121
  setGoogleAuthConfig,
19950
18122
  formatAuditFactorScore,
19951
18123
  listAgentProviders,