@ainyc/canonry 2.5.1 → 2.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,55 @@
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
+ configExists,
22
+ deliveryFailed,
23
+ determineAnswerMentioned,
24
+ effectiveDomains,
25
+ extractAnswerMentions,
26
+ findDuplicateLocationLabels,
27
+ hasLocationLabel,
28
+ internalError,
29
+ isAgentProviderId,
30
+ isBrowserProvider,
31
+ loadConfig,
32
+ locationContextSchema,
33
+ missingDependency,
34
+ normalizeProjectDomain,
35
+ notFound,
36
+ notImplemented,
37
+ parseWindow,
38
+ projectConfigSchema,
39
+ projectUpsertRequestSchema,
40
+ providerError,
41
+ runInProgress,
42
+ runNotCancellable,
43
+ runTriggerRequestSchema,
44
+ saveConfigPatch,
45
+ scheduleUpsertRequestSchema,
46
+ snapshotRequestSchema,
47
+ unsupportedKind,
48
+ validationError,
49
+ visibilityStateFromAnswerMentioned,
50
+ windowCutoff,
51
+ wordpressEnvSchema
52
+ } from "./chunk-3DUTT6H2.js";
1
53
  import {
2
54
  IntelligenceService,
3
55
  agentMemory,
@@ -30,172 +82,7 @@ import {
30
82
  runs,
31
83
  schedules,
32
84
  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
- }
85
+ } from "./chunk-PYHANJ3B.js";
199
86
 
200
87
  // src/telemetry.ts
201
88
  import crypto from "crypto";
@@ -261,1266 +148,22 @@ function trackEvent(event, properties) {
261
148
  const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
262
149
  timeout.unref();
263
150
  fetch(TELEMETRY_ENDPOINT, {
264
- method: "POST",
265
- headers: { "Content-Type": "application/json" },
266
- body: JSON.stringify(payload),
267
- signal: controller.signal
268
- }).catch(() => {
269
- }).finally(() => clearTimeout(timeout));
270
- }
271
-
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
- // src/server.ts
348
- import { createRequire as createRequire3 } from "module";
349
- import crypto28 from "crypto";
350
- import fs13 from "fs";
351
- import path15 from "path";
352
- import { fileURLToPath as fileURLToPath2 } from "url";
353
- import { eq as eq30 } from "drizzle-orm";
354
- import Fastify from "fastify";
355
-
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
- });
151
+ method: "POST",
152
+ headers: { "Content-Type": "application/json" },
153
+ body: JSON.stringify(payload),
154
+ signal: controller.signal
155
+ }).catch(() => {
156
+ }).finally(() => clearTimeout(timeout));
157
+ }
158
+
159
+ // src/server.ts
160
+ import { createRequire as createRequire3 } from "module";
161
+ import crypto28 from "crypto";
162
+ import fs12 from "fs";
163
+ import path14 from "path";
164
+ import { fileURLToPath as fileURLToPath2 } from "url";
165
+ import { eq as eq30 } from "drizzle-orm";
166
+ import Fastify from "fastify";
1524
167
 
1525
168
  // ../api-routes/src/auth.ts
1526
169
  import crypto2 from "crypto";
@@ -2132,11 +775,11 @@ function queueRunIfProjectIdle(db, params) {
2132
775
  async function runRoutes(app, opts) {
2133
776
  app.post("/projects/:name/runs", async (request, reply) => {
2134
777
  const project = resolveProject(app.db, request.params.name);
2135
- const kind = request.body?.kind ?? "answer-visibility";
2136
- if (kind !== "answer-visibility") throw unsupportedKind(kind);
778
+ const body = parseRunTriggerRequest(request.body ?? {});
2137
779
  const now = (/* @__PURE__ */ new Date()).toISOString();
2138
- const trigger = request.body?.trigger ?? "manual";
2139
- const rawProviders = request.body?.providers;
780
+ const kind = body.kind ?? RunKinds["answer-visibility"];
781
+ const trigger = body.trigger ?? RunTriggers.manual;
782
+ const rawProviders = body.providers;
2140
783
  if (rawProviders?.length) {
2141
784
  const normalized = rawProviders.map((p) => p.trim().toLowerCase()).filter(Boolean);
2142
785
  const validNames = opts.validProviderNames ?? [];
@@ -2154,13 +797,13 @@ async function runRoutes(app, opts) {
2154
797
  const providers = rawProviders?.length ? rawProviders : void 0;
2155
798
  let resolvedLocation;
2156
799
  const projectLocations = parseJsonColumn(project.locations, []);
2157
- if (request.body?.noLocation) {
800
+ if (body.noLocation) {
2158
801
  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);
802
+ } else if (body.allLocations) {
803
+ } else if (body.location) {
804
+ const loc = projectLocations.find((l) => l.label === body.location);
2162
805
  if (!loc) {
2163
- throw validationError(`Location "${request.body.location}" not found. Configure it first.`);
806
+ throw validationError(`Location "${body.location}" not found. Configure it first.`);
2164
807
  }
2165
808
  resolvedLocation = loc;
2166
809
  } else if (project.defaultLocation) {
@@ -2170,7 +813,7 @@ async function runRoutes(app, opts) {
2170
813
  }
2171
814
  resolvedLocation = loc;
2172
815
  }
2173
- if (request.body?.allLocations) {
816
+ if (body.allLocations) {
2174
817
  if (projectLocations.length === 0) {
2175
818
  throw validationError("No locations configured for this project");
2176
819
  }
@@ -2339,6 +982,11 @@ async function runRoutes(app, opts) {
2339
982
  return reply.send(loadRunDetail(app, run));
2340
983
  });
2341
984
  }
985
+ function parseRunTriggerRequest(value) {
986
+ const result = runTriggerRequestSchema.safeParse(value);
987
+ if (result.success) return result.data;
988
+ throw validationError("Invalid run trigger request", { issues: result.error.issues });
989
+ }
2342
990
  function formatRun(row) {
2343
991
  return {
2344
992
  id: row.id,
@@ -2550,7 +1198,7 @@ async function deliverWebhook(target, payload, webhookSecret) {
2550
1198
  const body = JSON.stringify(payload);
2551
1199
  const isHttps = target.url.protocol === "https:";
2552
1200
  const port = target.url.port ? Number(target.url.port) : isHttps ? 443 : 80;
2553
- const path16 = `${target.url.pathname}${target.url.search}`;
1201
+ const path15 = `${target.url.pathname}${target.url.search}`;
2554
1202
  const headers = {
2555
1203
  "Content-Length": String(Buffer.byteLength(body)),
2556
1204
  "Content-Type": "application/json",
@@ -2566,7 +1214,7 @@ async function deliverWebhook(target, payload, webhookSecret) {
2566
1214
  headers,
2567
1215
  hostname: target.address,
2568
1216
  method: "POST",
2569
- path: path16,
1217
+ path: path15,
2570
1218
  port,
2571
1219
  timeout: REQUEST_TIMEOUT_MS
2572
1220
  };
@@ -3604,7 +2252,7 @@ var booleanSchema = { type: "boolean" };
3604
2252
  var integerSchema = { type: "integer" };
3605
2253
  var objectSchema = { type: "object", additionalProperties: true };
3606
2254
  var stringArraySchema = { type: "array", items: stringSchema };
3607
- var googleConnectionTypeSchema2 = { type: "string", enum: ["gsc", "ga4"] };
2255
+ var googleConnectionTypeSchema = { type: "string", enum: ["gsc", "ga4"] };
3608
2256
  var locationSchema = {
3609
2257
  type: "object",
3610
2258
  required: ["label", "city", "region", "country"],
@@ -3656,7 +2304,7 @@ var googleTypeParameter = {
3656
2304
  in: "path",
3657
2305
  required: true,
3658
2306
  description: "Google connection type.",
3659
- schema: googleConnectionTypeSchema2
2307
+ schema: googleConnectionTypeSchema
3660
2308
  };
3661
2309
  var projectRunIdParameter = {
3662
2310
  name: "runId",
@@ -4649,7 +3297,7 @@ var routeCatalog = [
4649
3297
  type: "object",
4650
3298
  required: ["type"],
4651
3299
  properties: {
4652
- type: googleConnectionTypeSchema2,
3300
+ type: googleConnectionTypeSchema,
4653
3301
  propertyId: stringSchema,
4654
3302
  publicUrl: stringSchema
4655
3303
  }
@@ -6071,8 +4719,8 @@ async function openApiRoutes(app, opts = {}) {
6071
4719
  return reply.type("application/json").send(buildOpenApiDocument(opts));
6072
4720
  });
6073
4721
  }
6074
- function buildOperationId(method, path16) {
6075
- const parts = path16.split("/").filter(Boolean).map((part) => {
4722
+ function buildOperationId(method, path15) {
4723
+ const parts = path15.split("/").filter(Boolean).map((part) => {
6076
4724
  if (part.startsWith("{") && part.endsWith("}")) {
6077
4725
  return `by-${part.slice(1, -1)}`;
6078
4726
  }
@@ -6594,7 +5242,7 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
6594
5242
  const parsed = JSON.parse(body);
6595
5243
  if (parsed.error) detail = parsed.error;
6596
5244
  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"), "***");
5245
+ const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp(clientId), "g"), "***").replace(new RegExp(escapeRegExp(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp(code), "g"), "***");
6598
5246
  detail += detail ? `: ${sanitized}` : sanitized;
6599
5247
  }
6600
5248
  } catch {
@@ -6604,7 +5252,7 @@ async function exchangeCode(clientId, clientSecret, code, redirectUri) {
6604
5252
  }
6605
5253
  return await res.json();
6606
5254
  }
6607
- function escapeRegExp2(str) {
5255
+ function escapeRegExp(str) {
6608
5256
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6609
5257
  }
6610
5258
  async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
@@ -6632,7 +5280,7 @@ async function refreshAccessToken(clientId, clientSecret, currentRefreshToken) {
6632
5280
  const parsed = JSON.parse(body);
6633
5281
  if (parsed.error) detail = parsed.error;
6634
5282
  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"), "***");
5283
+ const sanitized = parsed.error_description.replace(new RegExp(escapeRegExp(clientId), "g"), "***").replace(new RegExp(escapeRegExp(clientSecret), "g"), "***").replace(new RegExp(escapeRegExp(currentRefreshToken), "g"), "***");
6636
5284
  detail += detail ? `: ${sanitized}` : sanitized;
6637
5285
  }
6638
5286
  } catch {
@@ -6946,13 +5594,13 @@ async function getAccessToken(clientEmail, privateKey) {
6946
5594
  const body = await res.text().catch(() => "");
6947
5595
  ga4Log("error", "token.failed", { httpStatus: res.status });
6948
5596
  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"), "***");
5597
+ const sanitizedDetail = detail.replace(new RegExp(escapeRegExp2(clientEmail), "g"), "***").replace(new RegExp(escapeRegExp2(privateKey.slice(0, 32)), "g"), "***");
6950
5598
  throw new GA4ApiError(`Failed to get access token: ${sanitizedDetail}`, res.status);
6951
5599
  }
6952
5600
  const data = await res.json();
6953
5601
  return data.access_token;
6954
5602
  }
6955
- function escapeRegExp3(str) {
5603
+ function escapeRegExp2(str) {
6956
5604
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6957
5605
  }
6958
5606
  async function runReport(accessToken, propertyId, request) {
@@ -8082,7 +6730,7 @@ function bingClientLog(level, action, ctx) {
8082
6730
  const stream = level === "error" ? process.stderr : process.stdout;
8083
6731
  stream.write(JSON.stringify(entry) + "\n");
8084
6732
  }
8085
- function escapeRegExp4(str) {
6733
+ function escapeRegExp3(str) {
8086
6734
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8087
6735
  }
8088
6736
  async function bingFetch(apiKey, endpoint, opts) {
@@ -8110,7 +6758,7 @@ async function bingFetch(apiKey, endpoint, opts) {
8110
6758
  const body = await res.text();
8111
6759
  bingClientLog("error", "http.error", { endpoint, method, httpStatus: res.status });
8112
6760
  let detail = body.length <= 500 ? body : `${body.slice(0, 500)}... [truncated]`;
8113
- detail = detail.replace(new RegExp(escapeRegExp4(apiKey), "g"), "***");
6761
+ detail = detail.replace(new RegExp(escapeRegExp3(apiKey), "g"), "***");
8114
6762
  throw new BingApiError(`Bing API error (${res.status}): ${detail}`, res.status);
8115
6763
  }
8116
6764
  const text = await res.text();
@@ -8654,12 +7302,12 @@ async function bingRoutes(app, opts) {
8654
7302
  }
8655
7303
 
8656
7304
  // ../api-routes/src/cdp.ts
8657
- import fs2 from "fs";
8658
- import path2 from "path";
8659
- import os2 from "os";
7305
+ import fs from "fs";
7306
+ import path from "path";
7307
+ import os from "os";
8660
7308
  import { eq as eq16, and as and5 } from "drizzle-orm";
8661
7309
  function getScreenshotDir() {
8662
- return path2.join(os2.homedir(), ".canonry", "screenshots");
7310
+ return path.join(os.homedir(), ".canonry", "screenshots");
8663
7311
  }
8664
7312
  async function cdpRoutes(app, opts) {
8665
7313
  app.get("/screenshots/:snapshotId", async (request, reply) => {
@@ -8669,17 +7317,17 @@ async function cdpRoutes(app, opts) {
8669
7317
  const err = notFound("Screenshot", snapshotId);
8670
7318
  return reply.code(err.statusCode).send(err.toJSON());
8671
7319
  }
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) {
7320
+ const base = path.resolve(getScreenshotDir());
7321
+ const fullPath = path.resolve(path.join(base, snapshot.screenshotPath));
7322
+ if (!fullPath.startsWith(base + path.sep) && fullPath !== base) {
8675
7323
  const err = notFound("Screenshot", snapshotId);
8676
7324
  return reply.code(err.statusCode).send(err.toJSON());
8677
7325
  }
8678
- if (!fs2.existsSync(fullPath)) {
7326
+ if (!fs.existsSync(fullPath)) {
8679
7327
  const err = notFound("Screenshot file", snapshotId);
8680
7328
  return reply.code(err.statusCode).send(err.toJSON());
8681
7329
  }
8682
- const stream = fs2.createReadStream(fullPath);
7330
+ const stream = fs.createReadStream(fullPath);
8683
7331
  return reply.type("image/png").send(stream);
8684
7332
  });
8685
7333
  app.put("/settings/cdp", async (request, reply) => {
@@ -9684,10 +8332,10 @@ function buildAuthErrorMessage(res, responseText) {
9684
8332
  }
9685
8333
  return "WordPress credentials are invalid or lack permission for this action";
9686
8334
  }
9687
- async function fetchJson(connection, siteUrl, path16, init) {
8335
+ async function fetchJson(connection, siteUrl, path15, init) {
9688
8336
  if (siteUrl.startsWith("http:")) {
9689
8337
  }
9690
- const res = await fetch(`${normalizeSiteUrl(siteUrl)}${path16}`, {
8338
+ const res = await fetch(`${normalizeSiteUrl(siteUrl)}${path15}`, {
9691
8339
  ...init,
9692
8340
  headers: {
9693
8341
  "Authorization": `Basic ${encodeBasicAuth(connection.username, connection.appPassword)}`,
@@ -10202,12 +8850,12 @@ var CANONRY_SCHEMA_START = "<!-- canonry:schema:start -->";
10202
8850
  var CANONRY_SCHEMA_END = "<!-- canonry:schema:end -->";
10203
8851
  function stripCanonrySchema(content) {
10204
8852
  const regex = new RegExp(
10205
- `${escapeRegExp5(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp5(CANONRY_SCHEMA_END)}`,
8853
+ `${escapeRegExp4(CANONRY_SCHEMA_START)}[\\s\\S]*?${escapeRegExp4(CANONRY_SCHEMA_END)}`,
10206
8854
  "g"
10207
8855
  );
10208
8856
  return content.replace(regex, "").replace(/\n{3,}/g, "\n\n").trim();
10209
8857
  }
10210
- function escapeRegExp5(str) {
8858
+ function escapeRegExp4(str) {
10211
8859
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10212
8860
  }
10213
8861
  function injectCanonrySchema(content, schemas) {
@@ -10314,7 +8962,7 @@ async function getSchemaStatus(connection, env) {
10314
8962
  const thirdPartySchemas = [];
10315
8963
  if (hasCanonryMarker) {
10316
8964
  const markerRegex = new RegExp(
10317
- `${escapeRegExp5(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp5(CANONRY_SCHEMA_END)}`
8965
+ `${escapeRegExp4(CANONRY_SCHEMA_START)}([\\s\\S]*?)${escapeRegExp4(CANONRY_SCHEMA_END)}`
10318
8966
  );
10319
8967
  const match = markerRegex.exec(rawContent);
10320
8968
  if (match?.[1]) {
@@ -11108,13 +9756,13 @@ import crypto18 from "crypto";
11108
9756
  import { and as and7, asc as asc2, desc as desc8, eq as eq18, sql as sql5 } from "drizzle-orm";
11109
9757
 
11110
9758
  // ../integration-commoncrawl/src/constants.ts
11111
- import os3 from "os";
11112
- import path3 from "path";
9759
+ import os2 from "os";
9760
+ import path2 from "path";
11113
9761
  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");
9762
+ var PLUGIN_DIR = path2.join(os2.homedir(), ".canonry", "plugins");
9763
+ var PLUGIN_PKG_JSON = path2.join(PLUGIN_DIR, "package.json");
11116
9764
  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");
9765
+ var CC_CACHE_DIR = process.env.CANONRY_CC_CACHE_DIR ?? path2.join(os2.homedir(), ".canonry", "cache", "commoncrawl");
11118
9766
  var RELEASE_ID_REGEX = /^cc-main-(\d{4})-(jan-feb-mar|apr-may-jun|jul-aug-sep|oct-nov-dec)$/;
11119
9767
  function ccReleasePaths(release) {
11120
9768
  const base = `${CC_BASE_URL}/${release}/domain`;
@@ -11144,8 +9792,8 @@ function isValidReleaseId(id) {
11144
9792
  // ../integration-commoncrawl/src/downloader.ts
11145
9793
  import { createHash } from "crypto";
11146
9794
  import { createWriteStream } from "fs";
11147
- import fs3 from "fs/promises";
11148
- import path4 from "path";
9795
+ import fs2 from "fs/promises";
9796
+ import path3 from "path";
11149
9797
  import { pipeline } from "stream/promises";
11150
9798
  import { Readable, Transform } from "stream";
11151
9799
  async function downloadFile(opts) {
@@ -11153,7 +9801,7 @@ async function downloadFile(opts) {
11153
9801
  const fetchImpl = opts.fetchImpl ?? fetch;
11154
9802
  const sidecarPath = `${opts.destPath}.sha256`;
11155
9803
  try {
11156
- const stat = await fs3.stat(opts.destPath);
9804
+ const stat = await fs2.stat(opts.destPath);
11157
9805
  const sidecar = await readSidecar(sidecarPath);
11158
9806
  const sha2562 = sidecar ?? await hashFile(opts.destPath);
11159
9807
  if (!sidecar) await writeSidecar(sidecarPath, sha2562);
@@ -11161,7 +9809,7 @@ async function downloadFile(opts) {
11161
9809
  } catch {
11162
9810
  }
11163
9811
  const partialPath = `${opts.destPath}.partial`;
11164
- await fs3.mkdir(path4.dirname(opts.destPath), { recursive: true });
9812
+ await fs2.mkdir(path3.dirname(opts.destPath), { recursive: true });
11165
9813
  await unlinkIfExists(partialPath);
11166
9814
  const res = await fetchImpl(opts.url);
11167
9815
  if (!res.ok || !res.body) {
@@ -11184,13 +9832,13 @@ async function downloadFile(opts) {
11184
9832
  createWriteStream(partialPath)
11185
9833
  );
11186
9834
  const sha256 = hasher.digest("hex");
11187
- await fs3.rename(partialPath, opts.destPath);
9835
+ await fs2.rename(partialPath, opts.destPath);
11188
9836
  await writeSidecar(sidecarPath, sha256);
11189
9837
  return { bytes, sha256, cached: false, elapsedMs: Date.now() - start };
11190
9838
  }
11191
9839
  async function hashFile(filePath) {
11192
9840
  const hasher = createHash("sha256");
11193
- const handle = await fs3.open(filePath, "r");
9841
+ const handle = await fs2.open(filePath, "r");
11194
9842
  try {
11195
9843
  const stream = handle.createReadStream();
11196
9844
  for await (const chunk of stream) hasher.update(chunk);
@@ -11201,7 +9849,7 @@ async function hashFile(filePath) {
11201
9849
  }
11202
9850
  async function readSidecar(sidecarPath) {
11203
9851
  try {
11204
- const raw = await fs3.readFile(sidecarPath, "utf8");
9852
+ const raw = await fs2.readFile(sidecarPath, "utf8");
11205
9853
  const trimmed = raw.trim();
11206
9854
  return /^[0-9a-f]{64}$/i.test(trimmed) ? trimmed.toLowerCase() : null;
11207
9855
  } catch {
@@ -11209,12 +9857,12 @@ async function readSidecar(sidecarPath) {
11209
9857
  }
11210
9858
  }
11211
9859
  async function writeSidecar(sidecarPath, sha256) {
11212
- await fs3.writeFile(sidecarPath, `${sha256}
9860
+ await fs2.writeFile(sidecarPath, `${sha256}
11213
9861
  `);
11214
9862
  }
11215
9863
  async function unlinkIfExists(p) {
11216
9864
  try {
11217
- await fs3.unlink(p);
9865
+ await fs2.unlink(p);
11218
9866
  } catch {
11219
9867
  }
11220
9868
  }
@@ -11225,20 +9873,20 @@ function parseContentLength(value) {
11225
9873
  }
11226
9874
 
11227
9875
  // ../integration-commoncrawl/src/plugin-resolver.ts
11228
- import fs4 from "fs";
9876
+ import fs3 from "fs";
11229
9877
  import { createRequire as createRequire2 } from "module";
11230
- import path5 from "path";
9878
+ import path4 from "path";
11231
9879
  function pluginDirFor(pkgJson) {
11232
- return path5.dirname(pkgJson);
9880
+ return path4.dirname(pkgJson);
11233
9881
  }
11234
9882
  function duckdbPkgJsonFor(pluginDir) {
11235
- return path5.join(pluginDir, "node_modules", "@duckdb", "node-api", "package.json");
9883
+ return path4.join(pluginDir, "node_modules", "@duckdb", "node-api", "package.json");
11236
9884
  }
11237
9885
  function loadDuckdb(opts = {}) {
11238
9886
  const pkgJson = opts.pluginPkgJson ?? PLUGIN_PKG_JSON;
11239
9887
  const pluginDir = pluginDirFor(pkgJson);
11240
9888
  const duckdbPkg = duckdbPkgJsonFor(pluginDir);
11241
- if (!fs4.existsSync(duckdbPkg)) {
9889
+ if (!fs3.existsSync(duckdbPkg)) {
11242
9890
  throw missingDependency(
11243
9891
  "@duckdb/node-api is not installed. Run `canonry backlinks install` to enable the backlinks feature.",
11244
9892
  { pluginDir }
@@ -11256,12 +9904,12 @@ function loadDuckdb(opts = {}) {
11256
9904
  }
11257
9905
  function isDuckdbInstalled(opts = {}) {
11258
9906
  const pkgJson = opts.pluginPkgJson ?? PLUGIN_PKG_JSON;
11259
- return fs4.existsSync(duckdbPkgJsonFor(pluginDirFor(pkgJson)));
9907
+ return fs3.existsSync(duckdbPkgJsonFor(pluginDirFor(pkgJson)));
11260
9908
  }
11261
9909
  function readInstalledVersion(opts = {}) {
11262
9910
  const pluginDir = opts.pluginPkgJson ? pluginDirFor(opts.pluginPkgJson) : PLUGIN_DIR;
11263
9911
  try {
11264
- const raw = fs4.readFileSync(duckdbPkgJsonFor(pluginDir), "utf8");
9912
+ const raw = fs3.readFileSync(duckdbPkgJsonFor(pluginDir), "utf8");
11265
9913
  const pkg = JSON.parse(raw);
11266
9914
  return pkg.version ?? null;
11267
9915
  } catch {
@@ -11271,11 +9919,11 @@ function readInstalledVersion(opts = {}) {
11271
9919
 
11272
9920
  // ../integration-commoncrawl/src/plugin-installer.ts
11273
9921
  import { spawn } from "child_process";
11274
- import fs5 from "fs/promises";
11275
- import path6 from "path";
9922
+ import fs4 from "fs/promises";
9923
+ import path5 from "path";
11276
9924
  async function installDuckdb(opts = {}) {
11277
9925
  const pluginDir = opts.pluginDir ?? PLUGIN_DIR;
11278
- const pluginPkgJson = path6.join(pluginDir, "package.json");
9926
+ const pluginPkgJson = path5.join(pluginDir, "package.json");
11279
9927
  const spec = opts.spec ?? DUCKDB_SPEC;
11280
9928
  const pkgManager = opts.packageManager ?? "npm";
11281
9929
  await ensurePluginDir(pluginDir, pluginPkgJson);
@@ -11291,12 +9939,12 @@ async function installDuckdb(opts = {}) {
11291
9939
  return { alreadyPresent: false, version, path: pluginDir };
11292
9940
  }
11293
9941
  async function ensurePluginDir(pluginDir = PLUGIN_DIR, pluginPkgJson = PLUGIN_PKG_JSON) {
11294
- await fs5.mkdir(pluginDir, { recursive: true });
9942
+ await fs4.mkdir(pluginDir, { recursive: true });
11295
9943
  try {
11296
- await fs5.access(pluginPkgJson);
9944
+ await fs4.access(pluginPkgJson);
11297
9945
  } catch {
11298
9946
  const contents = JSON.stringify({ name: "canonry-plugins", private: true, dependencies: {} }, null, 2);
11299
- await fs5.writeFile(pluginPkgJson, `${contents}
9947
+ await fs4.writeFile(pluginPkgJson, `${contents}
11300
9948
  `);
11301
9949
  }
11302
9950
  }
@@ -11389,8 +10037,8 @@ function quote(s) {
11389
10037
  }
11390
10038
 
11391
10039
  // ../integration-commoncrawl/src/cache.ts
11392
- import fs6 from "fs";
11393
- import path7 from "path";
10040
+ import fs5 from "fs";
10041
+ import path6 from "path";
11394
10042
  function cacheRoot(opts = {}) {
11395
10043
  return opts.cacheDir ?? CC_CACHE_DIR;
11396
10044
  }
@@ -11400,18 +10048,18 @@ function directoryBytesAndLastUsed(dir) {
11400
10048
  const walk = (p) => {
11401
10049
  let stat;
11402
10050
  try {
11403
- stat = fs6.statSync(p);
10051
+ stat = fs5.statSync(p);
11404
10052
  } catch {
11405
10053
  return;
11406
10054
  }
11407
10055
  if (stat.isDirectory()) {
11408
10056
  let entries;
11409
10057
  try {
11410
- entries = fs6.readdirSync(p);
10058
+ entries = fs5.readdirSync(p);
11411
10059
  } catch {
11412
10060
  return;
11413
10061
  }
11414
- for (const e of entries) walk(path7.join(p, e));
10062
+ for (const e of entries) walk(path6.join(p, e));
11415
10063
  } else if (stat.isFile()) {
11416
10064
  bytes += stat.size;
11417
10065
  const mtime = Math.max(stat.mtimeMs, stat.atimeMs);
@@ -11426,13 +10074,13 @@ function directoryBytesAndLastUsed(dir) {
11426
10074
  }
11427
10075
  function listCachedReleases(opts = {}) {
11428
10076
  const root = cacheRoot(opts);
11429
- if (!fs6.existsSync(root)) return [];
11430
- const entries = fs6.readdirSync(root, { withFileTypes: true });
10077
+ if (!fs5.existsSync(root)) return [];
10078
+ const entries = fs5.readdirSync(root, { withFileTypes: true });
11431
10079
  const result = [];
11432
10080
  for (const entry of entries) {
11433
10081
  if (!entry.isDirectory()) continue;
11434
10082
  if (!RELEASE_ID_REGEX.test(entry.name)) continue;
11435
- const dir = path7.join(root, entry.name);
10083
+ const dir = path6.join(root, entry.name);
11436
10084
  const stats = directoryBytesAndLastUsed(dir);
11437
10085
  result.push({ release: entry.name, bytes: stats.bytes, lastUsedAt: stats.lastUsedAt });
11438
10086
  }
@@ -11443,8 +10091,8 @@ function pruneCachedRelease(release, opts = {}) {
11443
10091
  if (!RELEASE_ID_REGEX.test(release)) {
11444
10092
  throw new Error(`Invalid release id: ${release}`);
11445
10093
  }
11446
- const dir = path7.join(cacheRoot(opts), release);
11447
- fs6.rmSync(dir, { recursive: true, force: true });
10094
+ const dir = path6.join(cacheRoot(opts), release);
10095
+ fs5.rmSync(dir, { recursive: true, force: true });
11448
10096
  }
11449
10097
 
11450
10098
  // ../api-routes/src/backlinks.ts
@@ -11787,7 +10435,7 @@ async function apiRoutes(app, opts) {
11787
10435
  }
11788
10436
 
11789
10437
  // src/server.ts
11790
- import os6 from "os";
10438
+ import os5 from "os";
11791
10439
 
11792
10440
  // ../provider-gemini/src/normalize.ts
11793
10441
  import { GoogleGenAI } from "@google/genai";
@@ -13175,8 +11823,8 @@ var localAdapter = {
13175
11823
  };
13176
11824
 
13177
11825
  // ../provider-cdp/src/adapter.ts
13178
- import path9 from "path";
13179
- import os4 from "os";
11826
+ import path8 from "path";
11827
+ import os3 from "os";
13180
11828
 
13181
11829
  // ../provider-cdp/src/connection.ts
13182
11830
  import CDP from "chrome-remote-interface";
@@ -13540,12 +12188,12 @@ function sleep2(ms) {
13540
12188
  }
13541
12189
 
13542
12190
  // ../provider-cdp/src/screenshot.ts
13543
- import fs7 from "fs";
13544
- import path8 from "path";
12191
+ import fs6 from "fs";
12192
+ import path7 from "path";
13545
12193
  async function captureElementScreenshot(client, selector, outputPath) {
13546
- const dir = path8.dirname(outputPath);
13547
- if (!fs7.existsSync(dir)) {
13548
- fs7.mkdirSync(dir, { recursive: true });
12194
+ const dir = path7.dirname(outputPath);
12195
+ if (!fs6.existsSync(dir)) {
12196
+ fs6.mkdirSync(dir, { recursive: true });
13549
12197
  }
13550
12198
  let clip;
13551
12199
  try {
@@ -13579,7 +12227,7 @@ async function captureElementScreenshot(client, selector, outputPath) {
13579
12227
  }
13580
12228
  const { data } = await client.Page.captureScreenshot(screenshotParams);
13581
12229
  const buffer = Buffer.from(data, "base64");
13582
- fs7.writeFileSync(outputPath, buffer);
12230
+ fs6.writeFileSync(outputPath, buffer);
13583
12231
  return outputPath;
13584
12232
  }
13585
12233
 
@@ -13640,7 +12288,7 @@ function getConnection(config) {
13640
12288
  return conn;
13641
12289
  }
13642
12290
  function getScreenshotDir2() {
13643
- return path9.join(os4.homedir(), ".canonry", "screenshots");
12291
+ return path8.join(os3.homedir(), ".canonry", "screenshots");
13644
12292
  }
13645
12293
  var cdpChatgptAdapter = {
13646
12294
  name: "cdp:chatgpt",
@@ -13704,7 +12352,7 @@ var cdpChatgptAdapter = {
13704
12352
  const answerText = await target.extractAnswer(client);
13705
12353
  const groundingSources = await target.extractCitations(client);
13706
12354
  const screenshotId = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
13707
- const screenshotPath = path9.join(getScreenshotDir2(), `${screenshotId}.png`);
12355
+ const screenshotPath = path8.join(getScreenshotDir2(), `${screenshotId}.png`);
13708
12356
  let capturedScreenshotPath;
13709
12357
  try {
13710
12358
  capturedScreenshotPath = await captureElementScreenshot(
@@ -14241,9 +12889,9 @@ function removeWordpressConnection(config, projectName) {
14241
12889
 
14242
12890
  // src/job-runner.ts
14243
12891
  import crypto19 from "crypto";
14244
- import fs8 from "fs";
14245
- import path10 from "path";
14246
- import os5 from "os";
12892
+ import fs7 from "fs";
12893
+ import path9 from "path";
12894
+ import os4 from "os";
14247
12895
  import { and as and8, eq as eq19, inArray as inArray3, sql as sql6 } from "drizzle-orm";
14248
12896
 
14249
12897
  // src/citation-utils.ts
@@ -14627,12 +13275,12 @@ var JobRunner = class {
14627
13275
  competitorDomains
14628
13276
  );
14629
13277
  let screenshotRelPath = null;
14630
- if (raw.screenshotPath && fs8.existsSync(raw.screenshotPath)) {
13278
+ if (raw.screenshotPath && fs7.existsSync(raw.screenshotPath)) {
14631
13279
  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);
13280
+ const screenshotDir = path9.join(os4.homedir(), ".canonry", "screenshots", runId);
13281
+ if (!fs7.existsSync(screenshotDir)) fs7.mkdirSync(screenshotDir, { recursive: true });
13282
+ const destPath = path9.join(screenshotDir, `${snapshotId}.png`);
13283
+ fs7.renameSync(raw.screenshotPath, destPath);
14636
13284
  screenshotRelPath = `${runId}/${snapshotId}.png`;
14637
13285
  this.db.insert(querySnapshots).values({
14638
13286
  id: snapshotId,
@@ -15421,7 +14069,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
15421
14069
 
15422
14070
  // src/commoncrawl-sync.ts
15423
14071
  import crypto23 from "crypto";
15424
- import path11 from "path";
14072
+ import path10 from "path";
15425
14073
  import { and as and11, eq as eq23, sql as sql8 } from "drizzle-orm";
15426
14074
  var log6 = createLogger("CommonCrawlSync");
15427
14075
  var INSERT_CHUNK_SIZE = 1e4;
@@ -15450,9 +14098,9 @@ async function executeReleaseSync(db, syncId, opts) {
15450
14098
  error: null
15451
14099
  }).where(eq23(ccReleaseSyncs.id, syncId)).run();
15452
14100
  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);
14101
+ const releaseCacheDir = path10.join(deps.cacheDir, release);
14102
+ const vertexPath = path10.join(releaseCacheDir, paths.vertexFilename);
14103
+ const edgesPath = path10.join(releaseCacheDir, paths.edgesFilename);
15456
14104
  const [vertex, edges] = await Promise.all([
15457
14105
  deps.downloadFile({ url: paths.vertexUrl, destPath: vertexPath }),
15458
14106
  deps.downloadFile({ url: paths.edgesUrl, destPath: edgesPath })
@@ -15612,7 +14260,7 @@ function computeSummary(rows) {
15612
14260
 
15613
14261
  // src/backlink-extract.ts
15614
14262
  import crypto24 from "crypto";
15615
- import fs9 from "fs";
14263
+ import fs8 from "fs";
15616
14264
  import { and as and12, desc as desc10, eq as eq24 } from "drizzle-orm";
15617
14265
  var log7 = createLogger("BacklinkExtract");
15618
14266
  function defaultDeps2() {
@@ -15641,7 +14289,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
15641
14289
  if (!sync.vertexPath || !sync.edgesPath) {
15642
14290
  throw new Error(`Release ${sync.release} is missing cached file paths`);
15643
14291
  }
15644
- if (!fs9.existsSync(sync.vertexPath) || !fs9.existsSync(sync.edgesPath)) {
14292
+ if (!fs8.existsSync(sync.vertexPath) || !fs8.existsSync(sync.edgesPath)) {
15645
14293
  throw new Error(
15646
14294
  `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
14295
  );
@@ -16160,8 +14808,8 @@ import crypto27 from "crypto";
16160
14808
  import { eq as eq28 } from "drizzle-orm";
16161
14809
 
16162
14810
  // src/agent/session.ts
16163
- import fs12 from "fs";
16164
- import path14 from "path";
14811
+ import fs11 from "fs";
14812
+ import path13 from "path";
16165
14813
  import { Agent } from "@mariozechner/pi-agent-core";
16166
14814
  import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
16167
14815
 
@@ -16262,26 +14910,26 @@ function buildAgentProvidersResponse(config) {
16262
14910
  }
16263
14911
 
16264
14912
  // src/agent/skill-paths.ts
16265
- import fs10 from "fs";
16266
- import path12 from "path";
14913
+ import fs9 from "fs";
14914
+ import path11 from "path";
16267
14915
  import { fileURLToPath } from "url";
16268
14916
  function resolveAeroSkillDir(pkgDir) {
16269
- const here = pkgDir ?? path12.dirname(fileURLToPath(import.meta.url));
14917
+ const here = pkgDir ?? path11.dirname(fileURLToPath(import.meta.url));
16270
14918
  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")
14919
+ path11.join(here, "../assets/agent-workspace/skills/aero"),
14920
+ path11.join(here, "../../assets/agent-workspace/skills/aero"),
14921
+ path11.join(here, "../../../../skills/aero")
16274
14922
  ];
16275
14923
  for (const candidate of candidates) {
16276
- if (fs10.existsSync(path12.join(candidate, "SKILL.md"))) return candidate;
14924
+ if (fs9.existsSync(path11.join(candidate, "SKILL.md"))) return candidate;
16277
14925
  }
16278
14926
  throw new Error(`Aero skill not found. Searched:
16279
14927
  ${candidates.join("\n ")}`);
16280
14928
  }
16281
14929
 
16282
14930
  // src/agent/skill-tools.ts
16283
- import fs11 from "fs";
16284
- import path13 from "path";
14931
+ import fs10 from "fs";
14932
+ import path12 from "path";
16285
14933
  import { Type } from "@sinclair/typebox";
16286
14934
  var MAX_DOC_CHARS = 2e4;
16287
14935
  function textResult(details) {
@@ -16302,13 +14950,13 @@ function parseDescription(body) {
16302
14950
  return "(no description)";
16303
14951
  }
16304
14952
  function scanSkillDocs(skillDir) {
16305
- const refsDir = path13.join(skillDir ?? resolveAeroSkillDir(), "references");
16306
- if (!fs11.existsSync(refsDir)) return [];
14953
+ const refsDir = path12.join(skillDir ?? resolveAeroSkillDir(), "references");
14954
+ if (!fs10.existsSync(refsDir)) return [];
16307
14955
  const entries = [];
16308
- for (const file of fs11.readdirSync(refsDir)) {
14956
+ for (const file of fs10.readdirSync(refsDir)) {
16309
14957
  if (!file.endsWith(".md")) continue;
16310
- const filePath = path13.join(refsDir, file);
16311
- const body = fs11.readFileSync(filePath, "utf-8");
14958
+ const filePath = path12.join(refsDir, file);
14959
+ const body = fs10.readFileSync(filePath, "utf-8");
16312
14960
  entries.push({
16313
14961
  slug: file.replace(/\.md$/, ""),
16314
14962
  description: parseDescription(body),
@@ -16351,8 +14999,8 @@ function buildReadSkillDocTool() {
16351
14999
  availableSlugs: docs.map((d) => d.slug)
16352
15000
  });
16353
15001
  }
16354
- const filePath = path13.join(skillDir, "references", `${match.slug}.md`);
16355
- const content = fs11.readFileSync(filePath, "utf-8");
15002
+ const filePath = path12.join(skillDir, "references", `${match.slug}.md`);
15003
+ const content = fs10.readFileSync(filePath, "utf-8");
16356
15004
  if (content.length > MAX_DOC_CHARS) {
16357
15005
  return textResult({
16358
15006
  slug: match.slug,
@@ -16906,10 +15554,10 @@ function ensureBuiltinsRegistered() {
16906
15554
  }
16907
15555
  function loadAeroSystemPrompt(pkgDir) {
16908
15556
  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");
15557
+ const skillBody = fs11.readFileSync(path13.join(skillDir, "SKILL.md"), "utf-8");
15558
+ const soulPath = path13.join(skillDir, "soul.md");
15559
+ if (!fs11.existsSync(soulPath)) return skillBody;
15560
+ const soulBody = fs11.readFileSync(soulPath, "utf-8");
16913
15561
  return `${soulBody.trimEnd()}
16914
15562
 
16915
15563
  ---
@@ -17698,593 +16346,6 @@ function registerAgentRoutes(app, opts) {
17698
16346
  );
17699
16347
  }
17700
16348
 
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
16349
  // src/snapshot-service.ts
18289
16350
  import { runAeoAudit } from "@ainyc/aeo-audit";
18290
16351
 
@@ -18307,13 +16368,13 @@ function extractHostname(domain) {
18307
16368
  function fetchWithPinnedAddress(target) {
18308
16369
  return new Promise((resolve) => {
18309
16370
  const port = target.url.port ? Number(target.url.port) : 443;
18310
- const path16 = target.url.pathname + target.url.search;
16371
+ const path15 = target.url.pathname + target.url.search;
18311
16372
  const req = https2.request(
18312
16373
  {
18313
16374
  hostname: target.address,
18314
16375
  family: target.family,
18315
16376
  port,
18316
- path: path16,
16377
+ path: path15,
18317
16378
  method: "GET",
18318
16379
  timeout: FETCH_TIMEOUT_MS,
18319
16380
  servername: target.url.hostname,
@@ -19206,8 +17267,8 @@ async function createServer(opts) {
19206
17267
  );
19207
17268
  jobRunner.onRunCompleted = (runId, projectId) => runCoordinator.onRunCompleted(runId, projectId);
19208
17269
  const snapshotService = new SnapshotService(registry);
19209
- const orphanedOpenClawDir = path15.join(os6.homedir(), ".openclaw-aero");
19210
- if (fs13.existsSync(orphanedOpenClawDir)) {
17270
+ const orphanedOpenClawDir = path14.join(os5.homedir(), ".openclaw-aero");
17271
+ if (fs12.existsSync(orphanedOpenClawDir)) {
19211
17272
  app.log.warn(
19212
17273
  { path: orphanedOpenClawDir },
19213
17274
  "OpenClaw gateway is no longer used. Remove ~/.openclaw-aero/ manually to reclaim the directory."
@@ -19801,10 +17862,10 @@ async function createServer(opts) {
19801
17862
  return snapshotService.createReport(input);
19802
17863
  }
19803
17864
  });
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");
17865
+ const dirname = path14.dirname(fileURLToPath2(import.meta.url));
17866
+ const assetsDir = path14.join(dirname, "..", "assets");
17867
+ if (fs12.existsSync(assetsDir)) {
17868
+ const indexPath = path14.join(assetsDir, "index.html");
19808
17869
  const injectConfig = (html) => {
19809
17870
  const clientConfig = {};
19810
17871
  if (basePath) clientConfig.basePath = basePath;
@@ -19822,8 +17883,8 @@ async function createServer(opts) {
19822
17883
  index: false
19823
17884
  });
19824
17885
  const serveIndex = (_request, reply) => {
19825
- if (fs13.existsSync(indexPath)) {
19826
- const html = fs13.readFileSync(indexPath, "utf-8");
17886
+ if (fs12.existsSync(indexPath)) {
17887
+ const html = fs12.readFileSync(indexPath, "utf-8");
19827
17888
  return reply.type("text/html").send(injectConfig(html));
19828
17889
  }
19829
17890
  return reply.status(404).send({ error: "Dashboard not built" });
@@ -19843,8 +17904,8 @@ async function createServer(opts) {
19843
17904
  if (basePath && !url.startsWith(basePath)) {
19844
17905
  return reply.status(404).send({ error: "Not found", path: request.url });
19845
17906
  }
19846
- if (fs13.existsSync(indexPath)) {
19847
- const html = fs13.readFileSync(indexPath, "utf-8");
17907
+ if (fs12.existsSync(indexPath)) {
17908
+ const html = fs12.readFileSync(indexPath, "utf-8");
19848
17909
  return reply.type("text/html").send(injectConfig(html));
19849
17910
  }
19850
17911
  return reply.status(404).send({ error: "Not found" });
@@ -19913,31 +17974,11 @@ function parseKeywordResponse(raw, count) {
19913
17974
  }
19914
17975
 
19915
17976
  export {
19916
- getConfigDir,
19917
- getConfigPath,
19918
- loadConfig,
19919
- saveConfig,
19920
- saveConfigPatch,
19921
- configExists,
19922
17977
  isTelemetryEnabled,
19923
17978
  getOrCreateAnonymousId,
19924
17979
  isFirstRun,
19925
17980
  showFirstRunNotice,
19926
17981
  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
17982
  reparseStoredResult2 as reparseStoredResult,
19942
17983
  reparseStoredResult3 as reparseStoredResult2,
19943
17984
  reparseStoredResult as reparseStoredResult3,
@@ -19945,7 +17986,6 @@ export {
19945
17986
  determineCitationState,
19946
17987
  computeCompetitorOverlap,
19947
17988
  extractRecommendedCompetitors,
19948
- createApiClient,
19949
17989
  setGoogleAuthConfig,
19950
17990
  formatAuditFactorScore,
19951
17991
  listAgentProviders,