@ainyc/canonry 2.5.0 → 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.
- package/assets/assets/{index-agELvqT1.js → index-BsNi8T7D.js} +102 -102
- package/assets/index.html +1 -1
- package/bin/canonry-mcp.mjs +2 -0
- package/dist/chunk-3DUTT6H2.js +2144 -0
- package/dist/{chunk-TIHU2YXW.js → chunk-LF4O276A.js} +319 -2227
- package/dist/chunk-MLKGABMK.js +9 -0
- package/dist/{chunk-32YTAZBL.js → chunk-PYHANJ3B.js} +3 -5
- package/dist/cli.js +23 -20
- package/dist/index.js +6 -3
- package/dist/{intelligence-service-U7YQ4NXV.js → intelligence-service-2ZABHNR4.js} +2 -1
- package/dist/mcp.js +781 -0
- package/package.json +8 -6
|
@@ -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-
|
|
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";
|
|
@@ -269,1259 +156,15 @@ function trackEvent(event, properties) {
|
|
|
269
156
|
}).finally(() => clearTimeout(timeout));
|
|
270
157
|
}
|
|
271
158
|
|
|
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
159
|
// src/server.ts
|
|
348
160
|
import { createRequire as createRequire3 } from "module";
|
|
349
161
|
import crypto28 from "crypto";
|
|
350
|
-
import
|
|
351
|
-
import
|
|
162
|
+
import fs12 from "fs";
|
|
163
|
+
import path14 from "path";
|
|
352
164
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
353
165
|
import { eq as eq30 } from "drizzle-orm";
|
|
354
166
|
import Fastify from "fastify";
|
|
355
167
|
|
|
356
|
-
// ../contracts/src/config-schema.ts
|
|
357
|
-
import { z as z4 } from "zod";
|
|
358
|
-
|
|
359
|
-
// ../contracts/src/provider.ts
|
|
360
|
-
import { z } from "zod";
|
|
361
|
-
var providerQuotaPolicySchema = z.object({
|
|
362
|
-
maxConcurrency: z.number().int().positive(),
|
|
363
|
-
maxRequestsPerMinute: z.number().int().positive(),
|
|
364
|
-
maxRequestsPerDay: z.number().int().positive()
|
|
365
|
-
});
|
|
366
|
-
var ProviderNames = {
|
|
367
|
-
gemini: "gemini",
|
|
368
|
-
openai: "openai",
|
|
369
|
-
claude: "claude",
|
|
370
|
-
perplexity: "perplexity",
|
|
371
|
-
local: "local",
|
|
372
|
-
cdpChatgpt: "cdp:chatgpt"
|
|
373
|
-
};
|
|
374
|
-
var providerNameSchema = z.string().min(1);
|
|
375
|
-
var apiProviderNameSchema = z.string().min(1);
|
|
376
|
-
function isBrowserProvider(name) {
|
|
377
|
-
return name.startsWith("cdp:");
|
|
378
|
-
}
|
|
379
|
-
var CDP_TARGETS = ["cdp:chatgpt"];
|
|
380
|
-
function resolveProviderInput(input) {
|
|
381
|
-
const lower = input.trim().toLowerCase();
|
|
382
|
-
if (lower === "cdp") {
|
|
383
|
-
return [...CDP_TARGETS];
|
|
384
|
-
}
|
|
385
|
-
return lower ? [lower] : [];
|
|
386
|
-
}
|
|
387
|
-
var locationContextSchema = z.object({
|
|
388
|
-
label: z.string().min(1),
|
|
389
|
-
city: z.string().min(1),
|
|
390
|
-
region: z.string().min(1),
|
|
391
|
-
country: z.string().length(2),
|
|
392
|
-
timezone: z.string().optional()
|
|
393
|
-
});
|
|
394
|
-
|
|
395
|
-
// ../contracts/src/notification.ts
|
|
396
|
-
import { z as z2 } from "zod";
|
|
397
|
-
var notificationEventSchema = z2.enum([
|
|
398
|
-
"citation.lost",
|
|
399
|
-
"citation.gained",
|
|
400
|
-
"run.completed",
|
|
401
|
-
"run.failed",
|
|
402
|
-
"insight.critical",
|
|
403
|
-
"insight.high"
|
|
404
|
-
]);
|
|
405
|
-
var notificationDtoSchema = z2.object({
|
|
406
|
-
id: z2.string(),
|
|
407
|
-
projectId: z2.string(),
|
|
408
|
-
channel: z2.literal("webhook"),
|
|
409
|
-
url: z2.string().url(),
|
|
410
|
-
urlDisplay: z2.string(),
|
|
411
|
-
urlHost: z2.string(),
|
|
412
|
-
events: z2.array(notificationEventSchema),
|
|
413
|
-
enabled: z2.boolean().default(true),
|
|
414
|
-
/** Opaque tag identifying the creator (e.g. `"agent"` for Aero webhooks). */
|
|
415
|
-
source: z2.string().optional(),
|
|
416
|
-
webhookSecret: z2.string().optional(),
|
|
417
|
-
createdAt: z2.string(),
|
|
418
|
-
updatedAt: z2.string()
|
|
419
|
-
});
|
|
420
|
-
|
|
421
|
-
// ../contracts/src/project.ts
|
|
422
|
-
import { z as z3 } from "zod";
|
|
423
|
-
var configSourceSchema = z3.enum(["cli", "api", "config-file"]);
|
|
424
|
-
function findDuplicateLocationLabels(locations) {
|
|
425
|
-
const seen = /* @__PURE__ */ new Set();
|
|
426
|
-
const duplicates = /* @__PURE__ */ new Set();
|
|
427
|
-
for (const location of locations) {
|
|
428
|
-
if (seen.has(location.label)) {
|
|
429
|
-
duplicates.add(location.label);
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
seen.add(location.label);
|
|
433
|
-
}
|
|
434
|
-
return [...duplicates];
|
|
435
|
-
}
|
|
436
|
-
function hasLocationLabel(locations, label) {
|
|
437
|
-
if (!label) return true;
|
|
438
|
-
return locations.some((location) => location.label === label);
|
|
439
|
-
}
|
|
440
|
-
var projectUpsertRequestSchema = z3.object({
|
|
441
|
-
displayName: z3.string().min(1),
|
|
442
|
-
canonicalDomain: z3.string().min(1),
|
|
443
|
-
ownedDomains: z3.array(z3.string().min(1)).optional(),
|
|
444
|
-
country: z3.string().length(2),
|
|
445
|
-
language: z3.string().min(2),
|
|
446
|
-
tags: z3.array(z3.string()).optional(),
|
|
447
|
-
labels: z3.record(z3.string(), z3.string()).optional(),
|
|
448
|
-
providers: z3.array(providerNameSchema).optional(),
|
|
449
|
-
locations: z3.array(locationContextSchema).optional(),
|
|
450
|
-
defaultLocation: z3.string().nullable().optional(),
|
|
451
|
-
autoExtractBacklinks: z3.boolean().optional(),
|
|
452
|
-
configSource: configSourceSchema.optional()
|
|
453
|
-
});
|
|
454
|
-
var projectDtoSchema = z3.object({
|
|
455
|
-
id: z3.string(),
|
|
456
|
-
name: z3.string(),
|
|
457
|
-
displayName: z3.string().optional(),
|
|
458
|
-
canonicalDomain: z3.string(),
|
|
459
|
-
ownedDomains: z3.array(z3.string()).default([]),
|
|
460
|
-
country: z3.string().length(2),
|
|
461
|
-
language: z3.string().min(2),
|
|
462
|
-
tags: z3.array(z3.string()).default([]),
|
|
463
|
-
labels: z3.record(z3.string(), z3.string()).default({}),
|
|
464
|
-
locations: z3.array(locationContextSchema).default([]),
|
|
465
|
-
defaultLocation: z3.string().nullable().optional(),
|
|
466
|
-
autoExtractBacklinks: z3.boolean().default(false),
|
|
467
|
-
configSource: configSourceSchema.default("cli"),
|
|
468
|
-
configRevision: z3.number().int().positive().default(1),
|
|
469
|
-
createdAt: z3.string().optional(),
|
|
470
|
-
updatedAt: z3.string().optional()
|
|
471
|
-
});
|
|
472
|
-
function normalizeProjectDomain(input) {
|
|
473
|
-
let domain = input.trim().toLowerCase();
|
|
474
|
-
try {
|
|
475
|
-
if (domain.includes("://")) {
|
|
476
|
-
domain = new URL(domain).hostname.toLowerCase();
|
|
477
|
-
}
|
|
478
|
-
} catch {
|
|
479
|
-
}
|
|
480
|
-
return domain.replace(/^www\./, "");
|
|
481
|
-
}
|
|
482
|
-
function effectiveDomains(project) {
|
|
483
|
-
const all = [project.canonicalDomain, ...project.ownedDomains ?? []];
|
|
484
|
-
const seen = /* @__PURE__ */ new Set();
|
|
485
|
-
const result = [];
|
|
486
|
-
for (const d of all) {
|
|
487
|
-
const trimmed = d.trim();
|
|
488
|
-
if (!trimmed) continue;
|
|
489
|
-
const norm = normalizeProjectDomain(trimmed);
|
|
490
|
-
if (seen.has(norm)) continue;
|
|
491
|
-
seen.add(norm);
|
|
492
|
-
result.push(trimmed);
|
|
493
|
-
}
|
|
494
|
-
return result;
|
|
495
|
-
}
|
|
496
|
-
|
|
497
|
-
// ../contracts/src/config-schema.ts
|
|
498
|
-
var configMetadataSchema = z4.object({
|
|
499
|
-
name: z4.string().min(1).max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, {
|
|
500
|
-
message: "Name must be a lowercase slug (letters, numbers, hyphens)"
|
|
501
|
-
}),
|
|
502
|
-
labels: z4.record(z4.string(), z4.string()).optional().default({})
|
|
503
|
-
});
|
|
504
|
-
var configScheduleSchema = z4.object({
|
|
505
|
-
preset: z4.string().optional(),
|
|
506
|
-
cron: z4.string().optional(),
|
|
507
|
-
timezone: z4.string().optional().default("UTC"),
|
|
508
|
-
providers: z4.array(providerNameSchema).optional().default([])
|
|
509
|
-
}).refine(
|
|
510
|
-
(data) => data.preset && !data.cron || !data.preset && data.cron,
|
|
511
|
-
{ message: 'Exactly one of "preset" or "cron" must be provided' }
|
|
512
|
-
).optional();
|
|
513
|
-
var configNotificationSchema = z4.object({
|
|
514
|
-
channel: z4.literal("webhook"),
|
|
515
|
-
url: z4.string().url(),
|
|
516
|
-
events: z4.array(notificationEventSchema).min(1)
|
|
517
|
-
});
|
|
518
|
-
var configGoogleSchema = z4.object({
|
|
519
|
-
gsc: z4.object({
|
|
520
|
-
propertyUrl: z4.string()
|
|
521
|
-
}).optional(),
|
|
522
|
-
syncSchedule: z4.object({
|
|
523
|
-
preset: z4.string().optional(),
|
|
524
|
-
cron: z4.string().optional()
|
|
525
|
-
}).optional()
|
|
526
|
-
}).optional();
|
|
527
|
-
var configSpecSchema = z4.object({
|
|
528
|
-
displayName: z4.string().min(1),
|
|
529
|
-
canonicalDomain: z4.string().min(1),
|
|
530
|
-
ownedDomains: z4.array(z4.string().min(1)).optional().default([]),
|
|
531
|
-
country: z4.string().length(2),
|
|
532
|
-
language: z4.string().min(2),
|
|
533
|
-
keywords: z4.array(z4.string().min(1)).optional().default([]),
|
|
534
|
-
competitors: z4.array(z4.string().min(1)).optional().default([]),
|
|
535
|
-
providers: z4.array(providerNameSchema).optional().default([]),
|
|
536
|
-
locations: z4.array(locationContextSchema).optional().default([]),
|
|
537
|
-
defaultLocation: z4.string().optional(),
|
|
538
|
-
schedule: configScheduleSchema,
|
|
539
|
-
notifications: z4.array(configNotificationSchema).optional().default([]),
|
|
540
|
-
google: configGoogleSchema,
|
|
541
|
-
autoExtractBacklinks: z4.boolean().optional().default(false)
|
|
542
|
-
}).superRefine((spec, ctx) => {
|
|
543
|
-
const duplicateLabels = findDuplicateLocationLabels(spec.locations);
|
|
544
|
-
if (duplicateLabels.length > 0) {
|
|
545
|
-
ctx.addIssue({
|
|
546
|
-
code: "custom",
|
|
547
|
-
message: `Duplicate location labels are not allowed: ${duplicateLabels.join(", ")}`,
|
|
548
|
-
path: ["locations"]
|
|
549
|
-
});
|
|
550
|
-
}
|
|
551
|
-
if (!hasLocationLabel(spec.locations, spec.defaultLocation)) {
|
|
552
|
-
ctx.addIssue({
|
|
553
|
-
code: "custom",
|
|
554
|
-
message: `defaultLocation "${spec.defaultLocation}" must match a configured location label`,
|
|
555
|
-
path: ["defaultLocation"]
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
});
|
|
559
|
-
var projectConfigSchema = z4.object({
|
|
560
|
-
apiVersion: z4.literal("canonry/v1"),
|
|
561
|
-
kind: z4.literal("Project"),
|
|
562
|
-
metadata: configMetadataSchema,
|
|
563
|
-
spec: configSpecSchema
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
// ../contracts/src/errors.ts
|
|
567
|
-
var AppError = class extends Error {
|
|
568
|
-
code;
|
|
569
|
-
statusCode;
|
|
570
|
-
details;
|
|
571
|
-
constructor(code, message, statusCode, details) {
|
|
572
|
-
super(message);
|
|
573
|
-
this.name = "AppError";
|
|
574
|
-
this.code = code;
|
|
575
|
-
this.statusCode = statusCode;
|
|
576
|
-
this.details = details;
|
|
577
|
-
}
|
|
578
|
-
toJSON() {
|
|
579
|
-
return {
|
|
580
|
-
error: {
|
|
581
|
-
code: this.code,
|
|
582
|
-
message: this.message,
|
|
583
|
-
...this.details ? { details: this.details } : {}
|
|
584
|
-
}
|
|
585
|
-
};
|
|
586
|
-
}
|
|
587
|
-
};
|
|
588
|
-
function notFound(entity, id) {
|
|
589
|
-
return new AppError("NOT_FOUND", `${entity} '${id}' not found`, 404);
|
|
590
|
-
}
|
|
591
|
-
function validationError(message, details) {
|
|
592
|
-
return new AppError("VALIDATION_ERROR", message, 400, details);
|
|
593
|
-
}
|
|
594
|
-
function authRequired() {
|
|
595
|
-
return new AppError("AUTH_REQUIRED", "Authentication required", 401);
|
|
596
|
-
}
|
|
597
|
-
function authInvalid() {
|
|
598
|
-
return new AppError("AUTH_INVALID", "Invalid API key", 401);
|
|
599
|
-
}
|
|
600
|
-
function providerError(message, details) {
|
|
601
|
-
return new AppError("PROVIDER_ERROR", message, 502, details);
|
|
602
|
-
}
|
|
603
|
-
function runInProgress(projectName) {
|
|
604
|
-
return new AppError("RUN_IN_PROGRESS", `A run is already in progress for '${projectName}'`, 409);
|
|
605
|
-
}
|
|
606
|
-
function runNotCancellable(runId, status) {
|
|
607
|
-
return new AppError("RUN_NOT_CANCELLABLE", `Run '${runId}' is already in terminal state '${status}' and cannot be cancelled`, 409);
|
|
608
|
-
}
|
|
609
|
-
function unsupportedKind(kind) {
|
|
610
|
-
return new AppError("UNSUPPORTED_KIND", `Kind '${kind}' is not supported in this version`, 400);
|
|
611
|
-
}
|
|
612
|
-
function notImplemented(message) {
|
|
613
|
-
return new AppError("NOT_IMPLEMENTED", message, 501);
|
|
614
|
-
}
|
|
615
|
-
function deliveryFailed(message) {
|
|
616
|
-
return new AppError("DELIVERY_FAILED", message, 502);
|
|
617
|
-
}
|
|
618
|
-
function agentBusy(projectName) {
|
|
619
|
-
return new AppError(
|
|
620
|
-
"AGENT_BUSY",
|
|
621
|
-
`Aero is already running a turn for '${projectName}'. Retry after the current turn settles.`,
|
|
622
|
-
409
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
function missingDependency(message, details) {
|
|
626
|
-
return new AppError("MISSING_DEPENDENCY", message, 422, details);
|
|
627
|
-
}
|
|
628
|
-
function internalError(message, details) {
|
|
629
|
-
return new AppError("INTERNAL_ERROR", message, 500, details);
|
|
630
|
-
}
|
|
631
|
-
|
|
632
|
-
// ../contracts/src/google.ts
|
|
633
|
-
import { z as z5 } from "zod";
|
|
634
|
-
var googleConnectionTypeSchema = z5.enum(["gsc", "ga4"]);
|
|
635
|
-
var googleConnectionDtoSchema = z5.object({
|
|
636
|
-
id: z5.string(),
|
|
637
|
-
domain: z5.string(),
|
|
638
|
-
connectionType: googleConnectionTypeSchema,
|
|
639
|
-
propertyId: z5.string().nullable().optional(),
|
|
640
|
-
sitemapUrl: z5.string().nullable().optional(),
|
|
641
|
-
scopes: z5.array(z5.string()).default([]),
|
|
642
|
-
createdAt: z5.string(),
|
|
643
|
-
updatedAt: z5.string()
|
|
644
|
-
});
|
|
645
|
-
var gscSearchDataDtoSchema = z5.object({
|
|
646
|
-
date: z5.string(),
|
|
647
|
-
query: z5.string(),
|
|
648
|
-
page: z5.string(),
|
|
649
|
-
country: z5.string().nullable().optional(),
|
|
650
|
-
device: z5.string().nullable().optional(),
|
|
651
|
-
clicks: z5.number(),
|
|
652
|
-
impressions: z5.number(),
|
|
653
|
-
ctr: z5.number(),
|
|
654
|
-
position: z5.number()
|
|
655
|
-
});
|
|
656
|
-
var gscUrlInspectionDtoSchema = z5.object({
|
|
657
|
-
id: z5.string(),
|
|
658
|
-
url: z5.string(),
|
|
659
|
-
indexingState: z5.string().nullable().optional(),
|
|
660
|
-
verdict: z5.string().nullable().optional(),
|
|
661
|
-
coverageState: z5.string().nullable().optional(),
|
|
662
|
-
pageFetchState: z5.string().nullable().optional(),
|
|
663
|
-
robotsTxtState: z5.string().nullable().optional(),
|
|
664
|
-
crawlTime: z5.string().nullable().optional(),
|
|
665
|
-
lastCrawlResult: z5.string().nullable().optional(),
|
|
666
|
-
isMobileFriendly: z5.boolean().nullable().optional(),
|
|
667
|
-
richResults: z5.array(z5.string()).default([]),
|
|
668
|
-
inspectedAt: z5.string()
|
|
669
|
-
});
|
|
670
|
-
var indexTransitionSchema = z5.enum(["stable", "reindexed", "deindexed", "still-missing", "new"]);
|
|
671
|
-
var gscDeindexedRowSchema = z5.object({
|
|
672
|
-
url: z5.string(),
|
|
673
|
-
previousState: z5.string().nullable(),
|
|
674
|
-
currentState: z5.string().nullable(),
|
|
675
|
-
transitionDate: z5.string()
|
|
676
|
-
});
|
|
677
|
-
var gscReasonGroupSchema = z5.object({
|
|
678
|
-
reason: z5.string(),
|
|
679
|
-
count: z5.number(),
|
|
680
|
-
urls: z5.array(gscUrlInspectionDtoSchema).default([])
|
|
681
|
-
});
|
|
682
|
-
var gscCoverageSummaryDtoSchema = z5.object({
|
|
683
|
-
summary: z5.object({
|
|
684
|
-
total: z5.number(),
|
|
685
|
-
indexed: z5.number(),
|
|
686
|
-
notIndexed: z5.number(),
|
|
687
|
-
deindexed: z5.number(),
|
|
688
|
-
percentage: z5.number()
|
|
689
|
-
}),
|
|
690
|
-
lastInspectedAt: z5.string().nullable(),
|
|
691
|
-
indexed: z5.array(gscUrlInspectionDtoSchema).default([]),
|
|
692
|
-
notIndexed: z5.array(gscUrlInspectionDtoSchema).default([]),
|
|
693
|
-
deindexed: z5.array(gscDeindexedRowSchema).default([]),
|
|
694
|
-
reasonGroups: z5.array(gscReasonGroupSchema).default([])
|
|
695
|
-
});
|
|
696
|
-
var indexingNotificationDtoSchema = z5.object({
|
|
697
|
-
url: z5.string(),
|
|
698
|
-
type: z5.enum(["URL_UPDATED", "URL_DELETED"]),
|
|
699
|
-
notifiedAt: z5.string()
|
|
700
|
-
});
|
|
701
|
-
var indexingRequestResultDtoSchema = z5.object({
|
|
702
|
-
url: z5.string(),
|
|
703
|
-
type: z5.enum(["URL_UPDATED", "URL_DELETED"]),
|
|
704
|
-
notifiedAt: z5.string(),
|
|
705
|
-
status: z5.enum(["success", "error"]),
|
|
706
|
-
error: z5.string().optional()
|
|
707
|
-
});
|
|
708
|
-
var gscCoverageSnapshotDtoSchema = z5.object({
|
|
709
|
-
date: z5.string(),
|
|
710
|
-
indexed: z5.number(),
|
|
711
|
-
notIndexed: z5.number(),
|
|
712
|
-
reasonBreakdown: z5.record(z5.string(), z5.number()).default({})
|
|
713
|
-
});
|
|
714
|
-
|
|
715
|
-
// ../contracts/src/bing.ts
|
|
716
|
-
import { z as z6 } from "zod";
|
|
717
|
-
var bingConnectionDtoSchema = z6.object({
|
|
718
|
-
id: z6.string(),
|
|
719
|
-
domain: z6.string(),
|
|
720
|
-
siteUrl: z6.string().nullable().optional(),
|
|
721
|
-
createdAt: z6.string(),
|
|
722
|
-
updatedAt: z6.string()
|
|
723
|
-
});
|
|
724
|
-
var bingUrlInspectionDtoSchema = z6.object({
|
|
725
|
-
id: z6.string(),
|
|
726
|
-
url: z6.string(),
|
|
727
|
-
httpCode: z6.number().nullable().optional(),
|
|
728
|
-
inIndex: z6.boolean().nullable().optional(),
|
|
729
|
-
lastCrawledDate: z6.string().nullable().optional(),
|
|
730
|
-
inIndexDate: z6.string().nullable().optional(),
|
|
731
|
-
inspectedAt: z6.string(),
|
|
732
|
-
// Fields derived from GetUrlInfo response (more reliable than InIndex)
|
|
733
|
-
documentSize: z6.number().nullable().optional(),
|
|
734
|
-
anchorCount: z6.number().nullable().optional(),
|
|
735
|
-
discoveryDate: z6.string().nullable().optional()
|
|
736
|
-
});
|
|
737
|
-
var bingCoverageSummaryDtoSchema = z6.object({
|
|
738
|
-
summary: z6.object({
|
|
739
|
-
total: z6.number(),
|
|
740
|
-
indexed: z6.number(),
|
|
741
|
-
notIndexed: z6.number(),
|
|
742
|
-
unknown: z6.number().optional(),
|
|
743
|
-
percentage: z6.number()
|
|
744
|
-
}),
|
|
745
|
-
lastInspectedAt: z6.string().nullable(),
|
|
746
|
-
indexed: z6.array(bingUrlInspectionDtoSchema).default([]),
|
|
747
|
-
notIndexed: z6.array(bingUrlInspectionDtoSchema).default([]),
|
|
748
|
-
unknown: z6.array(bingUrlInspectionDtoSchema).default([]).optional()
|
|
749
|
-
});
|
|
750
|
-
var bingKeywordStatsDtoSchema = z6.object({
|
|
751
|
-
query: z6.string(),
|
|
752
|
-
impressions: z6.number(),
|
|
753
|
-
clicks: z6.number(),
|
|
754
|
-
ctr: z6.number(),
|
|
755
|
-
averagePosition: z6.number()
|
|
756
|
-
});
|
|
757
|
-
var bingCoverageSnapshotDtoSchema = z6.object({
|
|
758
|
-
date: z6.string(),
|
|
759
|
-
indexed: z6.number(),
|
|
760
|
-
notIndexed: z6.number(),
|
|
761
|
-
unknown: z6.number()
|
|
762
|
-
});
|
|
763
|
-
var bingSubmitResultDtoSchema = z6.object({
|
|
764
|
-
url: z6.string(),
|
|
765
|
-
status: z6.enum(["success", "error"]),
|
|
766
|
-
submittedAt: z6.string(),
|
|
767
|
-
error: z6.string().optional()
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
// ../contracts/src/wordpress.ts
|
|
771
|
-
import { z as z7 } from "zod";
|
|
772
|
-
var wordpressEnvSchema = z7.enum(["live", "staging"]);
|
|
773
|
-
var wordpressConnectionDtoSchema = z7.object({
|
|
774
|
-
projectName: z7.string(),
|
|
775
|
-
url: z7.string(),
|
|
776
|
-
stagingUrl: z7.string().optional(),
|
|
777
|
-
username: z7.string(),
|
|
778
|
-
defaultEnv: wordpressEnvSchema,
|
|
779
|
-
createdAt: z7.string(),
|
|
780
|
-
updatedAt: z7.string()
|
|
781
|
-
});
|
|
782
|
-
var wordpressSiteStatusDtoSchema = z7.object({
|
|
783
|
-
url: z7.string(),
|
|
784
|
-
reachable: z7.boolean(),
|
|
785
|
-
pageCount: z7.number().nullable().optional(),
|
|
786
|
-
version: z7.string().nullable().optional(),
|
|
787
|
-
error: z7.string().nullable().optional(),
|
|
788
|
-
plugins: z7.array(z7.string()).optional(),
|
|
789
|
-
authenticatedUser: z7.object({
|
|
790
|
-
id: z7.number(),
|
|
791
|
-
slug: z7.string()
|
|
792
|
-
}).nullable().optional()
|
|
793
|
-
});
|
|
794
|
-
var wordpressStatusDtoSchema = z7.object({
|
|
795
|
-
connected: z7.boolean(),
|
|
796
|
-
projectName: z7.string(),
|
|
797
|
-
defaultEnv: wordpressEnvSchema,
|
|
798
|
-
live: wordpressSiteStatusDtoSchema.nullable(),
|
|
799
|
-
staging: wordpressSiteStatusDtoSchema.nullable(),
|
|
800
|
-
adminUrl: z7.string().nullable().optional()
|
|
801
|
-
});
|
|
802
|
-
var wordpressPageSummaryDtoSchema = z7.object({
|
|
803
|
-
id: z7.number(),
|
|
804
|
-
slug: z7.string(),
|
|
805
|
-
title: z7.string(),
|
|
806
|
-
status: z7.string(),
|
|
807
|
-
modifiedAt: z7.string().nullable().optional(),
|
|
808
|
-
link: z7.string().nullable().optional()
|
|
809
|
-
});
|
|
810
|
-
var wordpressSeoStateDtoSchema = z7.object({
|
|
811
|
-
title: z7.string().nullable(),
|
|
812
|
-
description: z7.string().nullable(),
|
|
813
|
-
noindex: z7.boolean().nullable(),
|
|
814
|
-
writable: z7.boolean().default(false),
|
|
815
|
-
writeTargets: z7.array(z7.string()).default([])
|
|
816
|
-
});
|
|
817
|
-
var wordpressSchemaBlockDtoSchema = z7.object({
|
|
818
|
-
type: z7.string(),
|
|
819
|
-
json: z7.record(z7.string(), z7.unknown())
|
|
820
|
-
});
|
|
821
|
-
var wordpressPageDetailDtoSchema = wordpressPageSummaryDtoSchema.extend({
|
|
822
|
-
env: wordpressEnvSchema,
|
|
823
|
-
content: z7.string(),
|
|
824
|
-
seo: wordpressSeoStateDtoSchema,
|
|
825
|
-
schemaBlocks: z7.array(wordpressSchemaBlockDtoSchema).default([])
|
|
826
|
-
});
|
|
827
|
-
var wordpressDiffPageDtoSchema = wordpressPageDetailDtoSchema.extend({
|
|
828
|
-
contentHash: z7.string(),
|
|
829
|
-
contentSnippet: z7.string()
|
|
830
|
-
});
|
|
831
|
-
var wordpressManualAssistDtoSchema = z7.object({
|
|
832
|
-
manualRequired: z7.literal(true),
|
|
833
|
-
targetUrl: z7.string(),
|
|
834
|
-
adminUrl: z7.string().nullable().optional(),
|
|
835
|
-
content: z7.string(),
|
|
836
|
-
nextSteps: z7.array(z7.string()).default([])
|
|
837
|
-
});
|
|
838
|
-
var wordpressAuditIssueDtoSchema = z7.object({
|
|
839
|
-
slug: z7.string(),
|
|
840
|
-
severity: z7.enum(["high", "medium", "low"]),
|
|
841
|
-
code: z7.enum([
|
|
842
|
-
"noindex",
|
|
843
|
-
"missing-seo-title",
|
|
844
|
-
"missing-meta-description",
|
|
845
|
-
"missing-schema",
|
|
846
|
-
"thin-content"
|
|
847
|
-
]),
|
|
848
|
-
message: z7.string()
|
|
849
|
-
});
|
|
850
|
-
var wordpressAuditPageDtoSchema = z7.object({
|
|
851
|
-
slug: z7.string(),
|
|
852
|
-
title: z7.string(),
|
|
853
|
-
status: z7.string(),
|
|
854
|
-
wordCount: z7.number(),
|
|
855
|
-
seo: wordpressSeoStateDtoSchema,
|
|
856
|
-
schemaPresent: z7.boolean(),
|
|
857
|
-
issues: z7.array(wordpressAuditIssueDtoSchema).default([])
|
|
858
|
-
});
|
|
859
|
-
var wordpressBulkMetaEntryResultDtoSchema = z7.object({
|
|
860
|
-
slug: z7.string(),
|
|
861
|
-
status: z7.enum(["applied", "skipped", "manual"]),
|
|
862
|
-
error: z7.string().optional(),
|
|
863
|
-
manualAssist: wordpressManualAssistDtoSchema.optional()
|
|
864
|
-
});
|
|
865
|
-
var wordpressBulkMetaResultDtoSchema = z7.object({
|
|
866
|
-
env: wordpressEnvSchema,
|
|
867
|
-
strategy: z7.enum(["plugin", "manual"]),
|
|
868
|
-
results: z7.array(wordpressBulkMetaEntryResultDtoSchema)
|
|
869
|
-
});
|
|
870
|
-
var wordpressSchemaDeployEntryResultDtoSchema = z7.object({
|
|
871
|
-
slug: z7.string(),
|
|
872
|
-
status: z7.enum(["deployed", "stripped", "skipped", "failed"]),
|
|
873
|
-
schemasInjected: z7.array(z7.string()).optional(),
|
|
874
|
-
manualAssist: wordpressManualAssistDtoSchema.optional(),
|
|
875
|
-
error: z7.string().optional()
|
|
876
|
-
});
|
|
877
|
-
var wordpressSchemaDeployResultDtoSchema = z7.object({
|
|
878
|
-
env: wordpressEnvSchema,
|
|
879
|
-
results: z7.array(wordpressSchemaDeployEntryResultDtoSchema)
|
|
880
|
-
});
|
|
881
|
-
var wordpressSchemaStatusPageDtoSchema = z7.object({
|
|
882
|
-
slug: z7.string(),
|
|
883
|
-
title: z7.string(),
|
|
884
|
-
canonrySchemas: z7.array(z7.string()),
|
|
885
|
-
thirdPartySchemas: z7.array(z7.string()),
|
|
886
|
-
hasCanonrySchema: z7.boolean()
|
|
887
|
-
});
|
|
888
|
-
var wordpressSchemaStatusResultDtoSchema = z7.object({
|
|
889
|
-
env: wordpressEnvSchema,
|
|
890
|
-
pages: z7.array(wordpressSchemaStatusPageDtoSchema)
|
|
891
|
-
});
|
|
892
|
-
var wordpressOnboardStepDtoSchema = z7.object({
|
|
893
|
-
name: z7.string(),
|
|
894
|
-
status: z7.enum(["completed", "skipped", "failed"]),
|
|
895
|
-
summary: z7.string().optional(),
|
|
896
|
-
error: z7.string().optional()
|
|
897
|
-
});
|
|
898
|
-
var wordpressOnboardResultDtoSchema = z7.object({
|
|
899
|
-
projectName: z7.string(),
|
|
900
|
-
steps: z7.array(wordpressOnboardStepDtoSchema)
|
|
901
|
-
});
|
|
902
|
-
var wordpressDiffDtoSchema = z7.object({
|
|
903
|
-
slug: z7.string(),
|
|
904
|
-
live: wordpressDiffPageDtoSchema,
|
|
905
|
-
staging: wordpressDiffPageDtoSchema,
|
|
906
|
-
hasDifferences: z7.boolean(),
|
|
907
|
-
differences: z7.object({
|
|
908
|
-
title: z7.boolean(),
|
|
909
|
-
slug: z7.boolean(),
|
|
910
|
-
content: z7.boolean(),
|
|
911
|
-
seoTitle: z7.boolean(),
|
|
912
|
-
seoDescription: z7.boolean(),
|
|
913
|
-
noindex: z7.boolean(),
|
|
914
|
-
schema: z7.boolean()
|
|
915
|
-
})
|
|
916
|
-
});
|
|
917
|
-
|
|
918
|
-
// ../contracts/src/providers.ts
|
|
919
|
-
var ProviderIds = {
|
|
920
|
-
claude: "claude",
|
|
921
|
-
openai: "openai",
|
|
922
|
-
gemini: "gemini",
|
|
923
|
-
perplexity: "perplexity",
|
|
924
|
-
local: "local",
|
|
925
|
-
cdpChatgpt: "cdp:chatgpt",
|
|
926
|
-
zai: "zai"
|
|
927
|
-
};
|
|
928
|
-
var PROVIDER_IDS = Object.values(ProviderIds);
|
|
929
|
-
var SweepProviderIds = {
|
|
930
|
-
claude: ProviderIds.claude,
|
|
931
|
-
openai: ProviderIds.openai,
|
|
932
|
-
gemini: ProviderIds.gemini,
|
|
933
|
-
perplexity: ProviderIds.perplexity,
|
|
934
|
-
local: ProviderIds.local,
|
|
935
|
-
cdpChatgpt: ProviderIds.cdpChatgpt
|
|
936
|
-
};
|
|
937
|
-
var SWEEP_PROVIDER_IDS = Object.values(SweepProviderIds);
|
|
938
|
-
var AgentProviderIds = {
|
|
939
|
-
claude: ProviderIds.claude,
|
|
940
|
-
openai: ProviderIds.openai,
|
|
941
|
-
gemini: ProviderIds.gemini,
|
|
942
|
-
zai: ProviderIds.zai
|
|
943
|
-
};
|
|
944
|
-
var AGENT_PROVIDER_IDS = Object.values(AgentProviderIds);
|
|
945
|
-
function isAgentProviderId(value) {
|
|
946
|
-
return AGENT_PROVIDER_IDS.includes(value);
|
|
947
|
-
}
|
|
948
|
-
|
|
949
|
-
// ../contracts/src/run.ts
|
|
950
|
-
import { z as z8 } from "zod";
|
|
951
|
-
var runStatusSchema = z8.enum(["queued", "running", "completed", "partial", "failed", "cancelled"]);
|
|
952
|
-
var RunStatuses = runStatusSchema.enum;
|
|
953
|
-
var runKindSchema = z8.enum([
|
|
954
|
-
"answer-visibility",
|
|
955
|
-
"site-audit",
|
|
956
|
-
"gsc-sync",
|
|
957
|
-
"inspect-sitemap",
|
|
958
|
-
"ga-sync",
|
|
959
|
-
"bing-inspect",
|
|
960
|
-
"bing-inspect-sitemap",
|
|
961
|
-
"backlink-extract"
|
|
962
|
-
]);
|
|
963
|
-
var RunKinds = runKindSchema.enum;
|
|
964
|
-
var runTriggerSchema = z8.enum(["manual", "scheduled", "config-apply"]);
|
|
965
|
-
var RunTriggers = runTriggerSchema.enum;
|
|
966
|
-
var citationStateSchema = z8.enum(["cited", "not-cited"]);
|
|
967
|
-
var CitationStates = citationStateSchema.enum;
|
|
968
|
-
var visibilityStateSchema = z8.enum(["visible", "not-visible"]);
|
|
969
|
-
var VisibilityStates = visibilityStateSchema.enum;
|
|
970
|
-
var computedTransitionSchema = z8.enum(["new", "cited", "lost", "emerging", "not-cited"]);
|
|
971
|
-
var ComputedTransitions = computedTransitionSchema.enum;
|
|
972
|
-
var runDtoSchema = z8.object({
|
|
973
|
-
id: z8.string(),
|
|
974
|
-
projectId: z8.string(),
|
|
975
|
-
kind: runKindSchema,
|
|
976
|
-
status: runStatusSchema,
|
|
977
|
-
trigger: runTriggerSchema.default("manual"),
|
|
978
|
-
location: z8.string().nullable().optional(),
|
|
979
|
-
startedAt: z8.string().nullable().optional(),
|
|
980
|
-
finishedAt: z8.string().nullable().optional(),
|
|
981
|
-
error: z8.string().nullable().optional(),
|
|
982
|
-
createdAt: z8.string()
|
|
983
|
-
});
|
|
984
|
-
var groundingSourceSchema = z8.object({
|
|
985
|
-
uri: z8.string(),
|
|
986
|
-
title: z8.string()
|
|
987
|
-
});
|
|
988
|
-
var querySnapshotDtoSchema = z8.object({
|
|
989
|
-
id: z8.string(),
|
|
990
|
-
runId: z8.string(),
|
|
991
|
-
keywordId: z8.string(),
|
|
992
|
-
keyword: z8.string().optional(),
|
|
993
|
-
provider: providerNameSchema,
|
|
994
|
-
citationState: citationStateSchema,
|
|
995
|
-
answerMentioned: z8.boolean().optional(),
|
|
996
|
-
visibilityState: visibilityStateSchema.optional(),
|
|
997
|
-
transition: computedTransitionSchema.optional(),
|
|
998
|
-
answerText: z8.string().nullable().optional(),
|
|
999
|
-
citedDomains: z8.array(z8.string()).default([]),
|
|
1000
|
-
competitorOverlap: z8.array(z8.string()).default([]),
|
|
1001
|
-
recommendedCompetitors: z8.array(z8.string()).default([]),
|
|
1002
|
-
matchedTerms: z8.array(z8.string()).default([]),
|
|
1003
|
-
groundingSources: z8.array(groundingSourceSchema).default([]),
|
|
1004
|
-
searchQueries: z8.array(z8.string()).default([]),
|
|
1005
|
-
model: z8.string().nullable().optional(),
|
|
1006
|
-
location: z8.string().nullable().optional(),
|
|
1007
|
-
createdAt: z8.string()
|
|
1008
|
-
});
|
|
1009
|
-
var runDetailDtoSchema = runDtoSchema.extend({
|
|
1010
|
-
snapshots: z8.array(querySnapshotDtoSchema).optional()
|
|
1011
|
-
});
|
|
1012
|
-
var latestProjectRunDtoSchema = z8.object({
|
|
1013
|
-
totalRuns: z8.number().int().nonnegative(),
|
|
1014
|
-
run: runDetailDtoSchema.nullable()
|
|
1015
|
-
});
|
|
1016
|
-
var auditLogEntrySchema = z8.object({
|
|
1017
|
-
id: z8.string(),
|
|
1018
|
-
projectId: z8.string().nullable().optional(),
|
|
1019
|
-
actor: z8.string(),
|
|
1020
|
-
action: z8.string(),
|
|
1021
|
-
entityType: z8.string(),
|
|
1022
|
-
entityId: z8.string().nullable().optional(),
|
|
1023
|
-
diff: z8.unknown().optional(),
|
|
1024
|
-
createdAt: z8.string()
|
|
1025
|
-
});
|
|
1026
|
-
|
|
1027
|
-
// ../contracts/src/snapshot.ts
|
|
1028
|
-
import { z as z9 } from "zod";
|
|
1029
|
-
var snapshotAccuracySchema = z9.enum(["yes", "no", "unknown", "not-mentioned"]);
|
|
1030
|
-
var snapshotRequestSchema = z9.object({
|
|
1031
|
-
companyName: z9.string().min(1),
|
|
1032
|
-
domain: z9.string().min(1),
|
|
1033
|
-
phrases: z9.array(z9.string().min(1)).optional().default([]),
|
|
1034
|
-
competitors: z9.array(z9.string().min(1)).optional().default([])
|
|
1035
|
-
});
|
|
1036
|
-
var snapshotCompetitorEntrySchema = z9.object({
|
|
1037
|
-
name: z9.string(),
|
|
1038
|
-
count: z9.number().int().nonnegative()
|
|
1039
|
-
});
|
|
1040
|
-
var snapshotAuditFactorSchema = z9.object({
|
|
1041
|
-
id: z9.string(),
|
|
1042
|
-
name: z9.string(),
|
|
1043
|
-
weight: z9.number(),
|
|
1044
|
-
score: z9.number(),
|
|
1045
|
-
grade: z9.string(),
|
|
1046
|
-
status: z9.enum(["pass", "partial", "fail"]),
|
|
1047
|
-
findings: z9.array(z9.object({
|
|
1048
|
-
type: z9.string(),
|
|
1049
|
-
message: z9.string()
|
|
1050
|
-
})).default([]),
|
|
1051
|
-
recommendations: z9.array(z9.string()).default([])
|
|
1052
|
-
});
|
|
1053
|
-
var snapshotAuditSchema = z9.object({
|
|
1054
|
-
url: z9.string(),
|
|
1055
|
-
finalUrl: z9.string(),
|
|
1056
|
-
auditedAt: z9.string(),
|
|
1057
|
-
overallScore: z9.number(),
|
|
1058
|
-
overallGrade: z9.string(),
|
|
1059
|
-
summary: z9.string(),
|
|
1060
|
-
factors: z9.array(snapshotAuditFactorSchema).default([])
|
|
1061
|
-
});
|
|
1062
|
-
var snapshotProfileSchema = z9.object({
|
|
1063
|
-
industry: z9.string(),
|
|
1064
|
-
summary: z9.string(),
|
|
1065
|
-
services: z9.array(z9.string()).default([]),
|
|
1066
|
-
categoryTerms: z9.array(z9.string()).default([])
|
|
1067
|
-
});
|
|
1068
|
-
var snapshotProviderResultSchema = z9.object({
|
|
1069
|
-
provider: z9.string(),
|
|
1070
|
-
displayName: z9.string(),
|
|
1071
|
-
model: z9.string().nullable().optional(),
|
|
1072
|
-
mentioned: z9.boolean(),
|
|
1073
|
-
cited: z9.boolean(),
|
|
1074
|
-
describedAccurately: snapshotAccuracySchema,
|
|
1075
|
-
accuracyNotes: z9.string().nullable().optional(),
|
|
1076
|
-
incorrectClaims: z9.array(z9.string()).default([]),
|
|
1077
|
-
recommendedCompetitors: z9.array(z9.string()).default([]),
|
|
1078
|
-
citedDomains: z9.array(z9.string()).default([]),
|
|
1079
|
-
groundingSources: z9.array(groundingSourceSchema).default([]),
|
|
1080
|
-
searchQueries: z9.array(z9.string()).default([]),
|
|
1081
|
-
answerText: z9.string(),
|
|
1082
|
-
error: z9.string().nullable().optional()
|
|
1083
|
-
});
|
|
1084
|
-
var snapshotQueryResultSchema = z9.object({
|
|
1085
|
-
phrase: z9.string(),
|
|
1086
|
-
providerResults: z9.array(snapshotProviderResultSchema).default([])
|
|
1087
|
-
});
|
|
1088
|
-
var snapshotSummarySchema = z9.object({
|
|
1089
|
-
totalQueries: z9.number().int().nonnegative(),
|
|
1090
|
-
totalProviders: z9.number().int().nonnegative(),
|
|
1091
|
-
totalComparisons: z9.number().int().nonnegative(),
|
|
1092
|
-
mentionCount: z9.number().int().nonnegative(),
|
|
1093
|
-
citationCount: z9.number().int().nonnegative(),
|
|
1094
|
-
topCompetitors: z9.array(snapshotCompetitorEntrySchema).default([]),
|
|
1095
|
-
visibilityGap: z9.string(),
|
|
1096
|
-
whatThisMeans: z9.array(z9.string()).default([]),
|
|
1097
|
-
recommendedActions: z9.array(z9.string()).default([])
|
|
1098
|
-
});
|
|
1099
|
-
var snapshotReportSchema = z9.object({
|
|
1100
|
-
companyName: z9.string(),
|
|
1101
|
-
domain: z9.string(),
|
|
1102
|
-
homepageUrl: z9.string(),
|
|
1103
|
-
generatedAt: z9.string(),
|
|
1104
|
-
phrases: z9.array(z9.string()).default([]),
|
|
1105
|
-
competitors: z9.array(z9.string()).default([]),
|
|
1106
|
-
profile: snapshotProfileSchema,
|
|
1107
|
-
audit: snapshotAuditSchema,
|
|
1108
|
-
queryResults: z9.array(snapshotQueryResultSchema).default([]),
|
|
1109
|
-
summary: snapshotSummarySchema
|
|
1110
|
-
});
|
|
1111
|
-
|
|
1112
|
-
// ../contracts/src/schedule.ts
|
|
1113
|
-
import { z as z10 } from "zod";
|
|
1114
|
-
var scheduleDtoSchema = z10.object({
|
|
1115
|
-
id: z10.string(),
|
|
1116
|
-
projectId: z10.string(),
|
|
1117
|
-
cronExpr: z10.string(),
|
|
1118
|
-
preset: z10.string().nullable().optional(),
|
|
1119
|
-
timezone: z10.string().default("UTC"),
|
|
1120
|
-
enabled: z10.boolean().default(true),
|
|
1121
|
-
providers: z10.array(providerNameSchema).default([]),
|
|
1122
|
-
lastRunAt: z10.string().nullable().optional(),
|
|
1123
|
-
nextRunAt: z10.string().nullable().optional(),
|
|
1124
|
-
createdAt: z10.string(),
|
|
1125
|
-
updatedAt: z10.string()
|
|
1126
|
-
});
|
|
1127
|
-
var scheduleUpsertRequestSchema = z10.object({
|
|
1128
|
-
preset: z10.string().optional(),
|
|
1129
|
-
cron: z10.string().optional(),
|
|
1130
|
-
timezone: z10.string().optional().default("UTC"),
|
|
1131
|
-
enabled: z10.boolean().optional().default(true),
|
|
1132
|
-
providers: z10.array(providerNameSchema).optional().default([])
|
|
1133
|
-
}).refine(
|
|
1134
|
-
(data) => data.preset && !data.cron || !data.preset && data.cron,
|
|
1135
|
-
{ message: 'Exactly one of "preset" or "cron" must be provided' }
|
|
1136
|
-
);
|
|
1137
|
-
|
|
1138
|
-
// ../contracts/src/analytics.ts
|
|
1139
|
-
import { z as z11 } from "zod";
|
|
1140
|
-
var visibilityMetricModeSchema = z11.enum(["answer", "citation"]);
|
|
1141
|
-
var VisibilityMetricModes = visibilityMetricModeSchema.enum;
|
|
1142
|
-
function parseWindow(value) {
|
|
1143
|
-
if (value === "7d" || value === "30d" || value === "90d" || value === "all") return value;
|
|
1144
|
-
return "all";
|
|
1145
|
-
}
|
|
1146
|
-
function windowCutoff(window) {
|
|
1147
|
-
if (window === "all") return null;
|
|
1148
|
-
const days = window === "7d" ? 7 : window === "30d" ? 30 : 90;
|
|
1149
|
-
const d = /* @__PURE__ */ new Date();
|
|
1150
|
-
d.setDate(d.getDate() - days);
|
|
1151
|
-
return d.toISOString();
|
|
1152
|
-
}
|
|
1153
|
-
|
|
1154
|
-
// ../contracts/src/source-categories.ts
|
|
1155
|
-
var SOURCE_CATEGORY_RULES = [
|
|
1156
|
-
// Forums
|
|
1157
|
-
{ pattern: "reddit.com", category: "forum", label: "Reddit" },
|
|
1158
|
-
{ pattern: "quora.com", category: "forum", label: "Quora" },
|
|
1159
|
-
{ pattern: "stackexchange.com", category: "forum", label: "Stack Exchange" },
|
|
1160
|
-
{ pattern: "stackoverflow.com", category: "forum", label: "Stack Overflow" },
|
|
1161
|
-
{ pattern: "discourse.org", category: "forum", label: "Discourse" },
|
|
1162
|
-
// Social
|
|
1163
|
-
{ pattern: "linkedin.com", category: "social", label: "LinkedIn" },
|
|
1164
|
-
{ pattern: "twitter.com", category: "social", label: "X (Twitter)" },
|
|
1165
|
-
{ pattern: "x.com", category: "social", label: "X (Twitter)" },
|
|
1166
|
-
{ pattern: "facebook.com", category: "social", label: "Facebook" },
|
|
1167
|
-
{ pattern: "instagram.com", category: "social", label: "Instagram" },
|
|
1168
|
-
{ pattern: "threads.net", category: "social", label: "Threads" },
|
|
1169
|
-
{ pattern: "pinterest.com", category: "social", label: "Pinterest" },
|
|
1170
|
-
{ pattern: "tiktok.com", category: "social", label: "TikTok" },
|
|
1171
|
-
// Video
|
|
1172
|
-
{ pattern: "youtube.com", category: "video", label: "YouTube" },
|
|
1173
|
-
{ pattern: "youtu.be", category: "video", label: "YouTube" },
|
|
1174
|
-
{ pattern: "vimeo.com", category: "video", label: "Vimeo" },
|
|
1175
|
-
// News
|
|
1176
|
-
{ pattern: "nytimes.com", category: "news", label: "NY Times" },
|
|
1177
|
-
{ pattern: "bbc.com", category: "news", label: "BBC" },
|
|
1178
|
-
{ pattern: "bbc.co.uk", category: "news", label: "BBC" },
|
|
1179
|
-
{ pattern: "cnn.com", category: "news", label: "CNN" },
|
|
1180
|
-
{ pattern: "reuters.com", category: "news", label: "Reuters" },
|
|
1181
|
-
{ pattern: "apnews.com", category: "news", label: "AP News" },
|
|
1182
|
-
{ pattern: "theguardian.com", category: "news", label: "The Guardian" },
|
|
1183
|
-
{ pattern: "washingtonpost.com", category: "news", label: "Washington Post" },
|
|
1184
|
-
{ pattern: "wsj.com", category: "news", label: "WSJ" },
|
|
1185
|
-
{ pattern: "forbes.com", category: "news", label: "Forbes" },
|
|
1186
|
-
{ pattern: "techcrunch.com", category: "news", label: "TechCrunch" },
|
|
1187
|
-
{ pattern: "theverge.com", category: "news", label: "The Verge" },
|
|
1188
|
-
{ pattern: "wired.com", category: "news", label: "Wired" },
|
|
1189
|
-
{ pattern: "arstechnica.com", category: "news", label: "Ars Technica" },
|
|
1190
|
-
// Reference
|
|
1191
|
-
{ pattern: "wikipedia.org", category: "reference", label: "Wikipedia" },
|
|
1192
|
-
{ pattern: "wikimedia.org", category: "reference", label: "Wikimedia" },
|
|
1193
|
-
{ pattern: "britannica.com", category: "reference", label: "Britannica" },
|
|
1194
|
-
{ pattern: "merriam-webster.com", category: "reference", label: "Merriam-Webster" },
|
|
1195
|
-
// Blog / Content platforms
|
|
1196
|
-
{ pattern: "medium.com", category: "blog", label: "Medium" },
|
|
1197
|
-
{ pattern: "substack.com", category: "blog", label: "Substack" },
|
|
1198
|
-
{ pattern: "dev.to", category: "blog", label: "DEV Community" },
|
|
1199
|
-
{ pattern: "hashnode.dev", category: "blog", label: "Hashnode" },
|
|
1200
|
-
{ pattern: "wordpress.com", category: "blog", label: "WordPress" },
|
|
1201
|
-
{ pattern: "blogger.com", category: "blog", label: "Blogger" },
|
|
1202
|
-
{ pattern: "hubspot.com", category: "blog", label: "HubSpot" },
|
|
1203
|
-
// E-commerce
|
|
1204
|
-
{ pattern: "amazon.com", category: "ecommerce", label: "Amazon" },
|
|
1205
|
-
{ pattern: "amazon.co.uk", category: "ecommerce", label: "Amazon UK" },
|
|
1206
|
-
{ pattern: "shopify.com", category: "ecommerce", label: "Shopify" },
|
|
1207
|
-
{ pattern: "ebay.com", category: "ecommerce", label: "eBay" },
|
|
1208
|
-
// Academic
|
|
1209
|
-
{ pattern: "scholar.google.com", category: "academic", label: "Google Scholar" },
|
|
1210
|
-
{ pattern: "arxiv.org", category: "academic", label: "arXiv" },
|
|
1211
|
-
{ pattern: "pubmed.ncbi.nlm.nih.gov", category: "academic", label: "PubMed" },
|
|
1212
|
-
{ pattern: "researchgate.net", category: "academic", label: "ResearchGate" },
|
|
1213
|
-
{ pattern: ".edu", category: "academic", label: "Academic (.edu)" }
|
|
1214
|
-
];
|
|
1215
|
-
var CATEGORY_LABELS = {
|
|
1216
|
-
social: "Social Media",
|
|
1217
|
-
forum: "Forums & Q&A",
|
|
1218
|
-
news: "News & Media",
|
|
1219
|
-
reference: "Reference",
|
|
1220
|
-
blog: "Blogs & Content",
|
|
1221
|
-
ecommerce: "E-commerce",
|
|
1222
|
-
video: "Video",
|
|
1223
|
-
academic: "Academic",
|
|
1224
|
-
other: "Other"
|
|
1225
|
-
};
|
|
1226
|
-
function categorizeSource(uri) {
|
|
1227
|
-
let domain;
|
|
1228
|
-
try {
|
|
1229
|
-
const url = new URL(uri.startsWith("http") ? uri : `https://${uri}`);
|
|
1230
|
-
domain = url.hostname.replace(/^www\./, "");
|
|
1231
|
-
} catch {
|
|
1232
|
-
domain = uri.replace(/^https?:\/\//, "").replace(/^www\./, "").split("/")[0] ?? uri;
|
|
1233
|
-
}
|
|
1234
|
-
const domainLower = domain.toLowerCase();
|
|
1235
|
-
for (const rule of SOURCE_CATEGORY_RULES) {
|
|
1236
|
-
if (domainLower === rule.pattern || domainLower.endsWith(`.${rule.pattern}`) || rule.pattern.startsWith(".") && domainLower.endsWith(rule.pattern)) {
|
|
1237
|
-
return { category: rule.category, label: rule.label, domain };
|
|
1238
|
-
}
|
|
1239
|
-
}
|
|
1240
|
-
return { category: "other", label: CATEGORY_LABELS.other, domain };
|
|
1241
|
-
}
|
|
1242
|
-
function categoryLabel(category) {
|
|
1243
|
-
return CATEGORY_LABELS[category];
|
|
1244
|
-
}
|
|
1245
|
-
|
|
1246
|
-
// ../contracts/src/ga.ts
|
|
1247
|
-
import { z as z12 } from "zod";
|
|
1248
|
-
var ga4ConnectionDtoSchema = z12.object({
|
|
1249
|
-
id: z12.string(),
|
|
1250
|
-
projectId: z12.string(),
|
|
1251
|
-
propertyId: z12.string(),
|
|
1252
|
-
clientEmail: z12.string(),
|
|
1253
|
-
connected: z12.boolean(),
|
|
1254
|
-
createdAt: z12.string(),
|
|
1255
|
-
updatedAt: z12.string()
|
|
1256
|
-
});
|
|
1257
|
-
var ga4TrafficSnapshotDtoSchema = z12.object({
|
|
1258
|
-
date: z12.string(),
|
|
1259
|
-
landingPage: z12.string(),
|
|
1260
|
-
sessions: z12.number(),
|
|
1261
|
-
organicSessions: z12.number(),
|
|
1262
|
-
users: z12.number()
|
|
1263
|
-
});
|
|
1264
|
-
var ga4SourceDimensionSchema = z12.enum(["session", "first_user", "manual_utm"]);
|
|
1265
|
-
var ga4AiReferralDtoSchema = z12.object({
|
|
1266
|
-
source: z12.string(),
|
|
1267
|
-
medium: z12.string(),
|
|
1268
|
-
sessions: z12.number(),
|
|
1269
|
-
users: z12.number(),
|
|
1270
|
-
sourceDimension: ga4SourceDimensionSchema
|
|
1271
|
-
});
|
|
1272
|
-
var ga4SocialReferralDtoSchema = z12.object({
|
|
1273
|
-
source: z12.string(),
|
|
1274
|
-
medium: z12.string(),
|
|
1275
|
-
sessions: z12.number(),
|
|
1276
|
-
users: z12.number(),
|
|
1277
|
-
/** GA4 default channel group (e.g. 'Organic Social', 'Paid Social') */
|
|
1278
|
-
channelGroup: z12.string()
|
|
1279
|
-
});
|
|
1280
|
-
var ga4TrafficSummaryDtoSchema = z12.object({
|
|
1281
|
-
totalSessions: z12.number(),
|
|
1282
|
-
totalOrganicSessions: z12.number(),
|
|
1283
|
-
totalUsers: z12.number(),
|
|
1284
|
-
topPages: z12.array(z12.object({
|
|
1285
|
-
landingPage: z12.string(),
|
|
1286
|
-
sessions: z12.number(),
|
|
1287
|
-
organicSessions: z12.number(),
|
|
1288
|
-
users: z12.number()
|
|
1289
|
-
})),
|
|
1290
|
-
aiReferrals: z12.array(ga4AiReferralDtoSchema),
|
|
1291
|
-
/** Deduped AI session total: MAX(sessions) per date+source+medium across attribution dimensions, then summed. */
|
|
1292
|
-
aiSessionsDeduped: z12.number(),
|
|
1293
|
-
/** Deduped AI user total: MAX(users) per date+source+medium across attribution dimensions, then summed. */
|
|
1294
|
-
aiUsersDeduped: z12.number(),
|
|
1295
|
-
socialReferrals: z12.array(ga4SocialReferralDtoSchema),
|
|
1296
|
-
/** Total social sessions (session-scoped, no cross-dimension dedup needed). */
|
|
1297
|
-
socialSessions: z12.number(),
|
|
1298
|
-
/** Total social users (session-scoped, no cross-dimension dedup needed). */
|
|
1299
|
-
socialUsers: z12.number(),
|
|
1300
|
-
/** Organic sessions as a percentage of total sessions (0–100, rounded). */
|
|
1301
|
-
organicSharePct: z12.number(),
|
|
1302
|
-
/** Deduped AI sessions as a percentage of total sessions (0–100, rounded). */
|
|
1303
|
-
aiSharePct: z12.number(),
|
|
1304
|
-
/** Social sessions as a percentage of total sessions (0–100, rounded). */
|
|
1305
|
-
socialSharePct: z12.number(),
|
|
1306
|
-
lastSyncedAt: z12.string().nullable()
|
|
1307
|
-
});
|
|
1308
|
-
var ga4AiReferralHistoryEntrySchema = z12.object({
|
|
1309
|
-
date: z12.string(),
|
|
1310
|
-
source: z12.string(),
|
|
1311
|
-
medium: z12.string(),
|
|
1312
|
-
sessions: z12.number(),
|
|
1313
|
-
users: z12.number(),
|
|
1314
|
-
/** Which GA4 dimension this row came from: session (sessionSource), first_user (firstUserSource), or manual_utm (utm_source parameter) */
|
|
1315
|
-
sourceDimension: ga4SourceDimensionSchema
|
|
1316
|
-
});
|
|
1317
|
-
var ga4SocialReferralHistoryEntrySchema = z12.object({
|
|
1318
|
-
date: z12.string(),
|
|
1319
|
-
source: z12.string(),
|
|
1320
|
-
medium: z12.string(),
|
|
1321
|
-
sessions: z12.number(),
|
|
1322
|
-
users: z12.number(),
|
|
1323
|
-
/** GA4 default channel group (e.g. 'Organic Social', 'Paid Social') */
|
|
1324
|
-
channelGroup: z12.string()
|
|
1325
|
-
});
|
|
1326
|
-
var ga4SessionHistoryEntrySchema = z12.object({
|
|
1327
|
-
date: z12.string(),
|
|
1328
|
-
sessions: z12.number(),
|
|
1329
|
-
organicSessions: z12.number(),
|
|
1330
|
-
users: z12.number()
|
|
1331
|
-
});
|
|
1332
|
-
|
|
1333
|
-
// ../contracts/src/answer-visibility.ts
|
|
1334
|
-
var GENERIC_TOKENS = /* @__PURE__ */ new Set([
|
|
1335
|
-
"agency",
|
|
1336
|
-
"app",
|
|
1337
|
-
"company",
|
|
1338
|
-
"corp",
|
|
1339
|
-
"group",
|
|
1340
|
-
"health",
|
|
1341
|
-
"inc",
|
|
1342
|
-
"llc",
|
|
1343
|
-
"online",
|
|
1344
|
-
"platform",
|
|
1345
|
-
"services",
|
|
1346
|
-
"site",
|
|
1347
|
-
"solutions",
|
|
1348
|
-
"software",
|
|
1349
|
-
"systems",
|
|
1350
|
-
"tech"
|
|
1351
|
-
]);
|
|
1352
|
-
function extractAnswerMentions(answerText, displayName, domains) {
|
|
1353
|
-
if (!answerText) return { mentioned: false, matchedTerms: [] };
|
|
1354
|
-
const matchedTerms = [];
|
|
1355
|
-
const lowerAnswer = answerText.toLowerCase();
|
|
1356
|
-
for (const domain of domains) {
|
|
1357
|
-
const normalizedDomain = normalizeProjectDomain(domain);
|
|
1358
|
-
if (!normalizedDomain || !normalizedDomain.includes(".")) continue;
|
|
1359
|
-
if (domainMentioned(lowerAnswer, normalizedDomain)) {
|
|
1360
|
-
matchedTerms.push(normalizedDomain);
|
|
1361
|
-
}
|
|
1362
|
-
}
|
|
1363
|
-
const normalizedDisplayName = normalizeText(displayName);
|
|
1364
|
-
if (normalizedDisplayName && normalizeText(answerText).includes(normalizedDisplayName)) {
|
|
1365
|
-
matchedTerms.push(displayName);
|
|
1366
|
-
}
|
|
1367
|
-
const tokens = collectDistinctiveTokens(displayName, domains);
|
|
1368
|
-
let tokenMatches = 0;
|
|
1369
|
-
const matchedTokens = [];
|
|
1370
|
-
for (const token of tokens) {
|
|
1371
|
-
if (new RegExp(`\\b${escapeRegExp(token)}\\b`).test(lowerAnswer)) {
|
|
1372
|
-
tokenMatches++;
|
|
1373
|
-
matchedTokens.push(token);
|
|
1374
|
-
}
|
|
1375
|
-
}
|
|
1376
|
-
const tokenThresholdMet = tokens.length > 0 && (tokens.length === 1 && tokenMatches >= 1 || tokenMatches >= Math.min(2, tokens.length));
|
|
1377
|
-
if (tokenThresholdMet) {
|
|
1378
|
-
matchedTerms.push(...matchedTokens);
|
|
1379
|
-
}
|
|
1380
|
-
const unique = [...new Set(matchedTerms)];
|
|
1381
|
-
const domainMatches3 = unique.filter((t) => t.includes("."));
|
|
1382
|
-
const dedupedFinal = unique.filter((term) => {
|
|
1383
|
-
if (term.includes(".")) return true;
|
|
1384
|
-
return !domainMatches3.some((d) => d.toLowerCase().startsWith(term.toLowerCase() + "."));
|
|
1385
|
-
});
|
|
1386
|
-
return { mentioned: dedupedFinal.length > 0, matchedTerms: dedupedFinal };
|
|
1387
|
-
}
|
|
1388
|
-
function determineAnswerMentioned(answerText, displayName, domains) {
|
|
1389
|
-
return extractAnswerMentions(answerText, displayName, domains).mentioned;
|
|
1390
|
-
}
|
|
1391
|
-
function visibilityStateFromAnswerMentioned(answerMentioned) {
|
|
1392
|
-
return answerMentioned ? "visible" : "not-visible";
|
|
1393
|
-
}
|
|
1394
|
-
function brandKeyFromText(value) {
|
|
1395
|
-
return value.toLowerCase().replace(/[^a-z0-9]/g, "");
|
|
1396
|
-
}
|
|
1397
|
-
function domainMentioned(lowerAnswer, normalizedDomain) {
|
|
1398
|
-
const escapedDomain = escapeRegExp(normalizedDomain.toLowerCase());
|
|
1399
|
-
const patterns = [
|
|
1400
|
-
new RegExp(`(^|[^a-z0-9-])${escapedDomain}($|[^a-z0-9-])`),
|
|
1401
|
-
new RegExp(`https?://(?:www\\.)?${escapedDomain}(?:[/:?#]|$)`),
|
|
1402
|
-
new RegExp(`www\\.${escapedDomain}(?:[/:?#]|$)`)
|
|
1403
|
-
];
|
|
1404
|
-
return patterns.some((pattern) => pattern.test(lowerAnswer));
|
|
1405
|
-
}
|
|
1406
|
-
function collectDistinctiveTokens(displayName, domains) {
|
|
1407
|
-
const tokens = /* @__PURE__ */ new Set();
|
|
1408
|
-
for (const token of extractDistinctiveTokens(displayName)) {
|
|
1409
|
-
tokens.add(token);
|
|
1410
|
-
}
|
|
1411
|
-
for (const domain of domains) {
|
|
1412
|
-
const hostname = normalizeProjectDomain(domain).split("/")[0] ?? "";
|
|
1413
|
-
for (const label of hostname.split(".").filter(Boolean)) {
|
|
1414
|
-
const token = label.replace(/[^a-z0-9]/gi, "").toLowerCase();
|
|
1415
|
-
if (isDistinctiveToken(token)) tokens.add(token);
|
|
1416
|
-
}
|
|
1417
|
-
}
|
|
1418
|
-
return [...tokens];
|
|
1419
|
-
}
|
|
1420
|
-
function extractDistinctiveTokens(value) {
|
|
1421
|
-
return normalizeText(value).split(" ").filter(isDistinctiveToken);
|
|
1422
|
-
}
|
|
1423
|
-
function isDistinctiveToken(token) {
|
|
1424
|
-
if (token.length < 4) return false;
|
|
1425
|
-
return !GENERIC_TOKENS.has(token);
|
|
1426
|
-
}
|
|
1427
|
-
function normalizeText(value) {
|
|
1428
|
-
return value.toLowerCase().replace(/[^a-z0-9]+/g, " ").trim();
|
|
1429
|
-
}
|
|
1430
|
-
function escapeRegExp(value) {
|
|
1431
|
-
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1432
|
-
}
|
|
1433
|
-
|
|
1434
|
-
// ../contracts/src/agent.ts
|
|
1435
|
-
import { z as z13 } from "zod";
|
|
1436
|
-
var memorySourceSchema = z13.enum(["aero", "user", "compaction"]);
|
|
1437
|
-
var MemorySources = memorySourceSchema.enum;
|
|
1438
|
-
var AGENT_MEMORY_VALUE_MAX_BYTES = 2 * 1024;
|
|
1439
|
-
var AGENT_MEMORY_KEY_MAX_LENGTH = 128;
|
|
1440
|
-
var agentMemoryUpsertRequestSchema = z13.object({
|
|
1441
|
-
key: z13.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH),
|
|
1442
|
-
value: z13.string().min(1)
|
|
1443
|
-
});
|
|
1444
|
-
var agentMemoryDeleteRequestSchema = z13.object({
|
|
1445
|
-
key: z13.string().min(1).max(AGENT_MEMORY_KEY_MAX_LENGTH)
|
|
1446
|
-
});
|
|
1447
|
-
|
|
1448
|
-
// ../contracts/src/backlinks.ts
|
|
1449
|
-
import { z as z14 } from "zod";
|
|
1450
|
-
var ccReleaseSyncStatusSchema = z14.enum(["queued", "downloading", "querying", "ready", "failed"]);
|
|
1451
|
-
var CcReleaseSyncStatuses = ccReleaseSyncStatusSchema.enum;
|
|
1452
|
-
var ccReleaseSyncDtoSchema = z14.object({
|
|
1453
|
-
id: z14.string(),
|
|
1454
|
-
release: z14.string(),
|
|
1455
|
-
status: ccReleaseSyncStatusSchema,
|
|
1456
|
-
phaseDetail: z14.string().nullable().optional(),
|
|
1457
|
-
vertexPath: z14.string().nullable().optional(),
|
|
1458
|
-
edgesPath: z14.string().nullable().optional(),
|
|
1459
|
-
vertexSha256: z14.string().nullable().optional(),
|
|
1460
|
-
edgesSha256: z14.string().nullable().optional(),
|
|
1461
|
-
vertexBytes: z14.number().int().nullable().optional(),
|
|
1462
|
-
edgesBytes: z14.number().int().nullable().optional(),
|
|
1463
|
-
projectsProcessed: z14.number().int().nullable().optional(),
|
|
1464
|
-
domainsDiscovered: z14.number().int().nullable().optional(),
|
|
1465
|
-
downloadStartedAt: z14.string().nullable().optional(),
|
|
1466
|
-
downloadFinishedAt: z14.string().nullable().optional(),
|
|
1467
|
-
queryStartedAt: z14.string().nullable().optional(),
|
|
1468
|
-
queryFinishedAt: z14.string().nullable().optional(),
|
|
1469
|
-
error: z14.string().nullable().optional(),
|
|
1470
|
-
createdAt: z14.string(),
|
|
1471
|
-
updatedAt: z14.string()
|
|
1472
|
-
});
|
|
1473
|
-
var backlinkDomainDtoSchema = z14.object({
|
|
1474
|
-
linkingDomain: z14.string(),
|
|
1475
|
-
numHosts: z14.number().int()
|
|
1476
|
-
});
|
|
1477
|
-
var backlinkSummaryDtoSchema = z14.object({
|
|
1478
|
-
projectId: z14.string(),
|
|
1479
|
-
release: z14.string(),
|
|
1480
|
-
targetDomain: z14.string(),
|
|
1481
|
-
totalLinkingDomains: z14.number().int(),
|
|
1482
|
-
totalHosts: z14.number().int(),
|
|
1483
|
-
top10HostsShare: z14.string(),
|
|
1484
|
-
queriedAt: z14.string()
|
|
1485
|
-
});
|
|
1486
|
-
var backlinkListResponseSchema = z14.object({
|
|
1487
|
-
summary: backlinkSummaryDtoSchema.nullable(),
|
|
1488
|
-
total: z14.number().int(),
|
|
1489
|
-
rows: z14.array(backlinkDomainDtoSchema)
|
|
1490
|
-
});
|
|
1491
|
-
var backlinkHistoryEntrySchema = z14.object({
|
|
1492
|
-
release: z14.string(),
|
|
1493
|
-
totalLinkingDomains: z14.number().int(),
|
|
1494
|
-
totalHosts: z14.number().int(),
|
|
1495
|
-
top10HostsShare: z14.string(),
|
|
1496
|
-
queriedAt: z14.string()
|
|
1497
|
-
});
|
|
1498
|
-
var backlinksInstallStatusDtoSchema = z14.object({
|
|
1499
|
-
duckdbInstalled: z14.boolean(),
|
|
1500
|
-
duckdbVersion: z14.string().nullable().optional(),
|
|
1501
|
-
duckdbSpec: z14.string(),
|
|
1502
|
-
pluginDir: z14.string()
|
|
1503
|
-
});
|
|
1504
|
-
var backlinksInstallResultDtoSchema = z14.object({
|
|
1505
|
-
installed: z14.boolean(),
|
|
1506
|
-
version: z14.string(),
|
|
1507
|
-
path: z14.string(),
|
|
1508
|
-
alreadyPresent: z14.boolean()
|
|
1509
|
-
});
|
|
1510
|
-
var ccAvailableReleaseSchema = z14.object({
|
|
1511
|
-
release: z14.string(),
|
|
1512
|
-
vertexUrl: z14.string(),
|
|
1513
|
-
edgesUrl: z14.string(),
|
|
1514
|
-
vertexBytes: z14.number().int().nullable(),
|
|
1515
|
-
edgesBytes: z14.number().int().nullable(),
|
|
1516
|
-
lastModified: z14.string().nullable()
|
|
1517
|
-
});
|
|
1518
|
-
var ccCachedReleaseSchema = z14.object({
|
|
1519
|
-
release: z14.string(),
|
|
1520
|
-
syncStatus: ccReleaseSyncStatusSchema.nullable(),
|
|
1521
|
-
bytes: z14.number().int(),
|
|
1522
|
-
lastUsedAt: z14.string().nullable()
|
|
1523
|
-
});
|
|
1524
|
-
|
|
1525
168
|
// ../api-routes/src/auth.ts
|
|
1526
169
|
import crypto2 from "crypto";
|
|
1527
170
|
import { eq } from "drizzle-orm";
|
|
@@ -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
|
|
2136
|
-
if (kind !== "answer-visibility") throw unsupportedKind(kind);
|
|
778
|
+
const body = parseRunTriggerRequest(request.body ?? {});
|
|
2137
779
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
2138
|
-
const
|
|
2139
|
-
const
|
|
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 (
|
|
800
|
+
if (body.noLocation) {
|
|
2158
801
|
resolvedLocation = null;
|
|
2159
|
-
} else if (
|
|
2160
|
-
} else if (
|
|
2161
|
-
const loc = projectLocations.find((l) => l.label ===
|
|
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 "${
|
|
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 (
|
|
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
|
|
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:
|
|
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
|
|
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:
|
|
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:
|
|
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,
|
|
6075
|
-
const parts =
|
|
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(
|
|
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
|
|
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(
|
|
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(
|
|
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
|
|
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
|
|
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(
|
|
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
|
|
8658
|
-
import
|
|
8659
|
-
import
|
|
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
|
|
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 =
|
|
8673
|
-
const fullPath =
|
|
8674
|
-
if (!fullPath.startsWith(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 (!
|
|
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 =
|
|
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,
|
|
8335
|
+
async function fetchJson(connection, siteUrl, path15, init) {
|
|
9688
8336
|
if (siteUrl.startsWith("http:")) {
|
|
9689
8337
|
}
|
|
9690
|
-
const res = await fetch(`${normalizeSiteUrl(siteUrl)}${
|
|
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
|
-
`${
|
|
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
|
|
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
|
-
`${
|
|
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
|
|
11112
|
-
import
|
|
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 =
|
|
11115
|
-
var PLUGIN_PKG_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 ??
|
|
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
|
|
11148
|
-
import
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
9860
|
+
await fs2.writeFile(sidecarPath, `${sha256}
|
|
11213
9861
|
`);
|
|
11214
9862
|
}
|
|
11215
9863
|
async function unlinkIfExists(p) {
|
|
11216
9864
|
try {
|
|
11217
|
-
await
|
|
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
|
|
9876
|
+
import fs3 from "fs";
|
|
11229
9877
|
import { createRequire as createRequire2 } from "module";
|
|
11230
|
-
import
|
|
9878
|
+
import path4 from "path";
|
|
11231
9879
|
function pluginDirFor(pkgJson) {
|
|
11232
|
-
return
|
|
9880
|
+
return path4.dirname(pkgJson);
|
|
11233
9881
|
}
|
|
11234
9882
|
function duckdbPkgJsonFor(pluginDir) {
|
|
11235
|
-
return
|
|
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 (!
|
|
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
|
|
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 =
|
|
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
|
|
11275
|
-
import
|
|
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 =
|
|
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
|
|
9942
|
+
await fs4.mkdir(pluginDir, { recursive: true });
|
|
11295
9943
|
try {
|
|
11296
|
-
await
|
|
9944
|
+
await fs4.access(pluginPkgJson);
|
|
11297
9945
|
} catch {
|
|
11298
9946
|
const contents = JSON.stringify({ name: "canonry-plugins", private: true, dependencies: {} }, null, 2);
|
|
11299
|
-
await
|
|
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
|
|
11393
|
-
import
|
|
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 =
|
|
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 =
|
|
10058
|
+
entries = fs5.readdirSync(p);
|
|
11411
10059
|
} catch {
|
|
11412
10060
|
return;
|
|
11413
10061
|
}
|
|
11414
|
-
for (const e of entries) walk(
|
|
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 (!
|
|
11430
|
-
const entries =
|
|
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 =
|
|
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 =
|
|
11447
|
-
|
|
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
|
|
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
|
|
13179
|
-
import
|
|
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
|
|
13544
|
-
import
|
|
12191
|
+
import fs6 from "fs";
|
|
12192
|
+
import path7 from "path";
|
|
13545
12193
|
async function captureElementScreenshot(client, selector, outputPath) {
|
|
13546
|
-
const dir =
|
|
13547
|
-
if (!
|
|
13548
|
-
|
|
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
|
-
|
|
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
|
|
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 =
|
|
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
|
|
14245
|
-
import
|
|
14246
|
-
import
|
|
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 &&
|
|
13278
|
+
if (raw.screenshotPath && fs7.existsSync(raw.screenshotPath)) {
|
|
14631
13279
|
const snapshotId = crypto19.randomUUID();
|
|
14632
|
-
const screenshotDir =
|
|
14633
|
-
if (!
|
|
14634
|
-
const destPath =
|
|
14635
|
-
|
|
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,
|
|
@@ -14998,6 +13646,7 @@ import crypto21 from "crypto";
|
|
|
14998
13646
|
import { eq as eq21, and as and10 } from "drizzle-orm";
|
|
14999
13647
|
|
|
15000
13648
|
// src/sitemap-parser.ts
|
|
13649
|
+
var log3 = createLogger("SitemapParser");
|
|
15001
13650
|
var LOC_REGEX = /<loc>\s*([^<]+?)\s*<\/loc>/gi;
|
|
15002
13651
|
var SITEMAP_TAG_REGEX = /<sitemap>[\s\S]*?<\/sitemap>/gi;
|
|
15003
13652
|
var PRIVATE_IP_PATTERNS = [
|
|
@@ -15027,26 +13676,77 @@ function validateSitemapUrl(url) {
|
|
|
15027
13676
|
}
|
|
15028
13677
|
}
|
|
15029
13678
|
}
|
|
13679
|
+
async function readSitemapBody(res) {
|
|
13680
|
+
const buf = await res.arrayBuffer();
|
|
13681
|
+
const bytes = new Uint8Array(buf);
|
|
13682
|
+
const isGzipped = bytes.length >= 2 && bytes[0] === 31 && bytes[1] === 139;
|
|
13683
|
+
if (!isGzipped) {
|
|
13684
|
+
return new TextDecoder().decode(bytes);
|
|
13685
|
+
}
|
|
13686
|
+
const decompressed = new Blob([buf]).stream().pipeThrough(new DecompressionStream("gzip"));
|
|
13687
|
+
return new Response(decompressed).text();
|
|
13688
|
+
}
|
|
15030
13689
|
async function fetchAndParseSitemap(sitemapUrl) {
|
|
15031
13690
|
const urls = /* @__PURE__ */ new Set();
|
|
15032
|
-
|
|
13691
|
+
const visited = /* @__PURE__ */ new Set();
|
|
13692
|
+
await parseSitemapRecursive(
|
|
13693
|
+
sitemapUrl,
|
|
13694
|
+
urls,
|
|
13695
|
+
visited,
|
|
13696
|
+
0,
|
|
13697
|
+
/* isChild */
|
|
13698
|
+
false
|
|
13699
|
+
);
|
|
15033
13700
|
return [...urls];
|
|
15034
13701
|
}
|
|
15035
|
-
async function parseSitemapRecursive(url, urls, depth) {
|
|
13702
|
+
async function parseSitemapRecursive(url, urls, visited, depth, isChild) {
|
|
15036
13703
|
if (depth > 3) return;
|
|
13704
|
+
if (visited.has(url)) return;
|
|
13705
|
+
visited.add(url);
|
|
15037
13706
|
validateSitemapUrl(url);
|
|
15038
|
-
|
|
13707
|
+
let res;
|
|
13708
|
+
try {
|
|
13709
|
+
res = await fetch(url);
|
|
13710
|
+
} catch (err) {
|
|
13711
|
+
if (!isChild) throw err;
|
|
13712
|
+
log3.warn("child-sitemap.fetch-failed", {
|
|
13713
|
+
url,
|
|
13714
|
+
error: err instanceof Error ? err.message : String(err)
|
|
13715
|
+
});
|
|
13716
|
+
return;
|
|
13717
|
+
}
|
|
15039
13718
|
if (!res.ok) {
|
|
15040
|
-
|
|
13719
|
+
if (!isChild) {
|
|
13720
|
+
throw new Error(`Failed to fetch sitemap at ${url}: ${res.status} ${res.statusText}`);
|
|
13721
|
+
}
|
|
13722
|
+
log3.warn("child-sitemap.http-error", { url, status: res.status, statusText: res.statusText });
|
|
13723
|
+
return;
|
|
13724
|
+
}
|
|
13725
|
+
let xml;
|
|
13726
|
+
try {
|
|
13727
|
+
xml = await readSitemapBody(res);
|
|
13728
|
+
} catch (err) {
|
|
13729
|
+
if (!isChild) throw err;
|
|
13730
|
+
log3.warn("child-sitemap.parse-failed", {
|
|
13731
|
+
url,
|
|
13732
|
+
error: err instanceof Error ? err.message : String(err)
|
|
13733
|
+
});
|
|
13734
|
+
return;
|
|
15041
13735
|
}
|
|
15042
|
-
const xml = await res.text();
|
|
15043
13736
|
const sitemapEntries = xml.match(SITEMAP_TAG_REGEX);
|
|
15044
13737
|
if (sitemapEntries) {
|
|
15045
13738
|
for (const entry of sitemapEntries) {
|
|
15046
13739
|
const locMatch = LOC_REGEX.exec(entry);
|
|
15047
13740
|
LOC_REGEX.lastIndex = 0;
|
|
15048
13741
|
if (locMatch?.[1]) {
|
|
15049
|
-
await parseSitemapRecursive(
|
|
13742
|
+
await parseSitemapRecursive(
|
|
13743
|
+
locMatch[1],
|
|
13744
|
+
urls,
|
|
13745
|
+
visited,
|
|
13746
|
+
depth + 1,
|
|
13747
|
+
/* isChild */
|
|
13748
|
+
true
|
|
13749
|
+
);
|
|
15050
13750
|
}
|
|
15051
13751
|
}
|
|
15052
13752
|
return;
|
|
@@ -15061,7 +13761,7 @@ async function parseSitemapRecursive(url, urls, depth) {
|
|
|
15061
13761
|
}
|
|
15062
13762
|
|
|
15063
13763
|
// src/gsc-inspect-sitemap.ts
|
|
15064
|
-
var
|
|
13764
|
+
var log4 = createLogger("InspectSitemap");
|
|
15065
13765
|
async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
15066
13766
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15067
13767
|
db.update(runs).set({ status: "running", startedAt: now }).where(eq21(runs.id, runId)).run();
|
|
@@ -15094,9 +13794,9 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
15094
13794
|
saveConfigPatch(opts.config);
|
|
15095
13795
|
}
|
|
15096
13796
|
const sitemapUrl = opts.sitemapUrl || conn.sitemapUrl || `https://${project.canonicalDomain}/sitemap.xml`;
|
|
15097
|
-
|
|
13797
|
+
log4.info("sitemap.fetch", { runId, projectId, sitemapUrl });
|
|
15098
13798
|
const urls = await fetchAndParseSitemap(sitemapUrl);
|
|
15099
|
-
|
|
13799
|
+
log4.info("sitemap.parsed", { runId, projectId, urlCount: urls.length, sitemapUrl });
|
|
15100
13800
|
if (urls.length === 0) {
|
|
15101
13801
|
throw new Error("No URLs found in sitemap");
|
|
15102
13802
|
}
|
|
@@ -15129,10 +13829,10 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
15129
13829
|
createdAt: inspectedAt
|
|
15130
13830
|
}).run();
|
|
15131
13831
|
inspected++;
|
|
15132
|
-
|
|
13832
|
+
log4.info("inspect.url-done", { runId, projectId, url: pageUrl, progress: `${inspected}/${urls.length}` });
|
|
15133
13833
|
} catch (err) {
|
|
15134
13834
|
errors++;
|
|
15135
|
-
|
|
13835
|
+
log4.error("inspect.url-failed", { runId, projectId, url: pageUrl, error: err instanceof Error ? err.message : String(err) });
|
|
15136
13836
|
}
|
|
15137
13837
|
if (inspected + errors < urls.length) {
|
|
15138
13838
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
@@ -15172,11 +13872,11 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
15172
13872
|
}).run();
|
|
15173
13873
|
const status = errors > 0 && inspected > 0 ? "partial" : errors === urls.length ? "failed" : "completed";
|
|
15174
13874
|
db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
|
|
15175
|
-
|
|
13875
|
+
log4.info("inspect.completed", { runId, projectId, inspected, errors, total: urls.length, indexed: snapIndexed, notIndexed: snapNotIndexed });
|
|
15176
13876
|
} catch (err) {
|
|
15177
13877
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
15178
13878
|
db.update(runs).set({ status: "failed", error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq21(runs.id, runId)).run();
|
|
15179
|
-
|
|
13879
|
+
log4.error("inspect.failed", { runId, projectId, error: errorMsg });
|
|
15180
13880
|
throw err;
|
|
15181
13881
|
}
|
|
15182
13882
|
}
|
|
@@ -15184,7 +13884,7 @@ async function executeInspectSitemap(db, runId, projectId, opts) {
|
|
|
15184
13884
|
// src/bing-inspect-sitemap.ts
|
|
15185
13885
|
import crypto22 from "crypto";
|
|
15186
13886
|
import { eq as eq22, desc as desc9 } from "drizzle-orm";
|
|
15187
|
-
var
|
|
13887
|
+
var log5 = createLogger("BingInspectSitemap");
|
|
15188
13888
|
function parseBingDate2(value) {
|
|
15189
13889
|
if (!value) return null;
|
|
15190
13890
|
const match = /\/Date\((-?\d+)[^)]*\)\//.exec(value);
|
|
@@ -15215,16 +13915,16 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
15215
13915
|
throw new Error('No Bing site configured. Run "canonry bing set-site <project> <url>" first.');
|
|
15216
13916
|
}
|
|
15217
13917
|
const sitemapUrl = opts.sitemapUrl ?? `https://${project.canonicalDomain}/sitemap.xml`;
|
|
15218
|
-
|
|
13918
|
+
log5.info("sitemap.fetch", { runId, projectId, sitemapUrl });
|
|
15219
13919
|
const sitemapUrls = await fetchAndParseSitemap(sitemapUrl);
|
|
15220
|
-
|
|
13920
|
+
log5.info("sitemap.parsed", { runId, projectId, urlCount: sitemapUrls.length, sitemapUrl });
|
|
15221
13921
|
if (sitemapUrls.length === 0) {
|
|
15222
13922
|
throw new Error("No URLs found in sitemap");
|
|
15223
13923
|
}
|
|
15224
13924
|
const trackedRows = db.select({ url: bingUrlInspections.url }).from(bingUrlInspections).where(eq22(bingUrlInspections.projectId, projectId)).all();
|
|
15225
13925
|
const trackedUrls = new Set(trackedRows.map((r) => r.url));
|
|
15226
13926
|
const discovered = sitemapUrls.filter((u) => !trackedUrls.has(u));
|
|
15227
|
-
|
|
13927
|
+
log5.info("sitemap.diff", {
|
|
15228
13928
|
runId,
|
|
15229
13929
|
projectId,
|
|
15230
13930
|
sitemapTotal: sitemapUrls.length,
|
|
@@ -15239,9 +13939,9 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
15239
13939
|
blockedUrls.add(issue.Url);
|
|
15240
13940
|
}
|
|
15241
13941
|
}
|
|
15242
|
-
|
|
13942
|
+
log5.info("crawl-issues.loaded", { runId, projectId, blockedCount: blockedUrls.size });
|
|
15243
13943
|
} catch (err) {
|
|
15244
|
-
|
|
13944
|
+
log5.warn("crawl-issues.lookup-failed", {
|
|
15245
13945
|
runId,
|
|
15246
13946
|
projectId,
|
|
15247
13947
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -15285,7 +13985,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
15285
13985
|
discoveryDate
|
|
15286
13986
|
}).run();
|
|
15287
13987
|
inspected++;
|
|
15288
|
-
|
|
13988
|
+
log5.info("inspect.url-done", {
|
|
15289
13989
|
runId,
|
|
15290
13990
|
projectId,
|
|
15291
13991
|
url: pageUrl,
|
|
@@ -15293,7 +13993,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
15293
13993
|
});
|
|
15294
13994
|
} catch (err) {
|
|
15295
13995
|
errors++;
|
|
15296
|
-
|
|
13996
|
+
log5.error("inspect.url-failed", {
|
|
15297
13997
|
runId,
|
|
15298
13998
|
projectId,
|
|
15299
13999
|
url: pageUrl,
|
|
@@ -15348,7 +14048,7 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
15348
14048
|
}).run();
|
|
15349
14049
|
const status = errors === sitemapUrls.length ? RunStatuses.failed : errors > 0 ? RunStatuses.partial : RunStatuses.completed;
|
|
15350
14050
|
db.update(runs).set({ status, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
|
|
15351
|
-
|
|
14051
|
+
log5.info("inspect.completed", {
|
|
15352
14052
|
runId,
|
|
15353
14053
|
projectId,
|
|
15354
14054
|
inspected,
|
|
@@ -15362,16 +14062,16 @@ async function executeBingInspectSitemap(db, runId, projectId, opts) {
|
|
|
15362
14062
|
} catch (err) {
|
|
15363
14063
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
15364
14064
|
db.update(runs).set({ status: RunStatuses.failed, error: errorMsg, finishedAt: (/* @__PURE__ */ new Date()).toISOString() }).where(eq22(runs.id, runId)).run();
|
|
15365
|
-
|
|
14065
|
+
log5.error("inspect.failed", { runId, projectId, error: errorMsg });
|
|
15366
14066
|
throw err;
|
|
15367
14067
|
}
|
|
15368
14068
|
}
|
|
15369
14069
|
|
|
15370
14070
|
// src/commoncrawl-sync.ts
|
|
15371
14071
|
import crypto23 from "crypto";
|
|
15372
|
-
import
|
|
14072
|
+
import path10 from "path";
|
|
15373
14073
|
import { and as and11, eq as eq23, sql as sql8 } from "drizzle-orm";
|
|
15374
|
-
var
|
|
14074
|
+
var log6 = createLogger("CommonCrawlSync");
|
|
15375
14075
|
var INSERT_CHUNK_SIZE = 1e4;
|
|
15376
14076
|
function defaultDeps() {
|
|
15377
14077
|
return {
|
|
@@ -15398,9 +14098,9 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
15398
14098
|
error: null
|
|
15399
14099
|
}).where(eq23(ccReleaseSyncs.id, syncId)).run();
|
|
15400
14100
|
const paths = ccReleasePaths(release);
|
|
15401
|
-
const releaseCacheDir =
|
|
15402
|
-
const vertexPath =
|
|
15403
|
-
const edgesPath =
|
|
14101
|
+
const releaseCacheDir = path10.join(deps.cacheDir, release);
|
|
14102
|
+
const vertexPath = path10.join(releaseCacheDir, paths.vertexFilename);
|
|
14103
|
+
const edgesPath = path10.join(releaseCacheDir, paths.edgesFilename);
|
|
15404
14104
|
const [vertex, edges] = await Promise.all([
|
|
15405
14105
|
deps.downloadFile({ url: paths.vertexUrl, destPath: vertexPath }),
|
|
15406
14106
|
deps.downloadFile({ url: paths.edgesUrl, destPath: edgesPath })
|
|
@@ -15496,7 +14196,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
15496
14196
|
updatedAt: finishedAt,
|
|
15497
14197
|
error: null
|
|
15498
14198
|
}).where(eq23(ccReleaseSyncs.id, syncId)).run();
|
|
15499
|
-
|
|
14199
|
+
log6.info("sync.completed", {
|
|
15500
14200
|
syncId,
|
|
15501
14201
|
release,
|
|
15502
14202
|
projectsProcessed: allProjects.length,
|
|
@@ -15508,7 +14208,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
15508
14208
|
try {
|
|
15509
14209
|
deps.enqueueAutoExtract({ projectId: p.id, release });
|
|
15510
14210
|
} catch (err) {
|
|
15511
|
-
|
|
14211
|
+
log6.error("auto-extract.enqueue-failed", {
|
|
15512
14212
|
syncId,
|
|
15513
14213
|
release,
|
|
15514
14214
|
projectId: p.id,
|
|
@@ -15526,7 +14226,7 @@ async function executeReleaseSync(db, syncId, opts) {
|
|
|
15526
14226
|
phaseDetail: null,
|
|
15527
14227
|
updatedAt: finishedAt
|
|
15528
14228
|
}).where(eq23(ccReleaseSyncs.id, syncId)).run();
|
|
15529
|
-
|
|
14229
|
+
log6.error("sync.failed", { syncId, release, error: errorMsg });
|
|
15530
14230
|
throw err;
|
|
15531
14231
|
}
|
|
15532
14232
|
}
|
|
@@ -15560,9 +14260,9 @@ function computeSummary(rows) {
|
|
|
15560
14260
|
|
|
15561
14261
|
// src/backlink-extract.ts
|
|
15562
14262
|
import crypto24 from "crypto";
|
|
15563
|
-
import
|
|
14263
|
+
import fs8 from "fs";
|
|
15564
14264
|
import { and as and12, desc as desc10, eq as eq24 } from "drizzle-orm";
|
|
15565
|
-
var
|
|
14265
|
+
var log7 = createLogger("BacklinkExtract");
|
|
15566
14266
|
function defaultDeps2() {
|
|
15567
14267
|
return {
|
|
15568
14268
|
queryBacklinks,
|
|
@@ -15589,7 +14289,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
15589
14289
|
if (!sync.vertexPath || !sync.edgesPath) {
|
|
15590
14290
|
throw new Error(`Release ${sync.release} is missing cached file paths`);
|
|
15591
14291
|
}
|
|
15592
|
-
if (!
|
|
14292
|
+
if (!fs8.existsSync(sync.vertexPath) || !fs8.existsSync(sync.edgesPath)) {
|
|
15593
14293
|
throw new Error(
|
|
15594
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.`
|
|
15595
14295
|
);
|
|
@@ -15648,7 +14348,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
15648
14348
|
});
|
|
15649
14349
|
const finishedAt = deps.now().toISOString();
|
|
15650
14350
|
db.update(runs).set({ status: RunStatuses.completed, finishedAt }).where(eq24(runs.id, runId)).run();
|
|
15651
|
-
|
|
14351
|
+
log7.info("extract.completed", { runId, projectId, release, rows: rows.length });
|
|
15652
14352
|
} catch (err) {
|
|
15653
14353
|
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
15654
14354
|
const finishedAt = deps.now().toISOString();
|
|
@@ -15657,7 +14357,7 @@ async function executeBacklinkExtract(db, runId, projectId, opts = {}) {
|
|
|
15657
14357
|
error: errorMsg,
|
|
15658
14358
|
finishedAt
|
|
15659
14359
|
}).where(eq24(runs.id, runId)).run();
|
|
15660
|
-
|
|
14360
|
+
log7.error("extract.failed", { runId, projectId, error: errorMsg });
|
|
15661
14361
|
throw err;
|
|
15662
14362
|
}
|
|
15663
14363
|
}
|
|
@@ -15730,7 +14430,7 @@ var ProviderRegistry = class {
|
|
|
15730
14430
|
// src/scheduler.ts
|
|
15731
14431
|
import cron from "node-cron";
|
|
15732
14432
|
import { eq as eq25 } from "drizzle-orm";
|
|
15733
|
-
var
|
|
14433
|
+
var log8 = createLogger("Scheduler");
|
|
15734
14434
|
var Scheduler = class {
|
|
15735
14435
|
db;
|
|
15736
14436
|
callbacks;
|
|
@@ -15746,11 +14446,11 @@ var Scheduler = class {
|
|
|
15746
14446
|
const missedRunAt = schedule.nextRunAt;
|
|
15747
14447
|
this.registerCronTask(schedule);
|
|
15748
14448
|
if (missedRunAt && new Date(missedRunAt) < /* @__PURE__ */ new Date()) {
|
|
15749
|
-
|
|
14449
|
+
log8.info("run.catch-up", { projectId: schedule.projectId, missedRunAt });
|
|
15750
14450
|
this.triggerRun(schedule.id, schedule.projectId);
|
|
15751
14451
|
}
|
|
15752
14452
|
}
|
|
15753
|
-
|
|
14453
|
+
log8.info("started", { scheduleCount: allSchedules.length });
|
|
15754
14454
|
}
|
|
15755
14455
|
/** Stop all cron tasks for graceful shutdown. */
|
|
15756
14456
|
stop() {
|
|
@@ -15782,12 +14482,12 @@ var Scheduler = class {
|
|
|
15782
14482
|
stopTask(projectId, task, verb) {
|
|
15783
14483
|
task.stop();
|
|
15784
14484
|
task.destroy();
|
|
15785
|
-
|
|
14485
|
+
log8.info(`task.${verb.toLowerCase()}`, { projectId });
|
|
15786
14486
|
}
|
|
15787
14487
|
registerCronTask(schedule) {
|
|
15788
14488
|
const { id: scheduleId, projectId, cronExpr, timezone } = schedule;
|
|
15789
14489
|
if (!cron.validate(cronExpr)) {
|
|
15790
|
-
|
|
14490
|
+
log8.error("cron.invalid", { projectId, cronExpr });
|
|
15791
14491
|
return;
|
|
15792
14492
|
}
|
|
15793
14493
|
const task = cron.schedule(cronExpr, () => {
|
|
@@ -15801,14 +14501,14 @@ var Scheduler = class {
|
|
|
15801
14501
|
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15802
14502
|
}).where(eq25(schedules.id, scheduleId)).run();
|
|
15803
14503
|
const label = schedule.preset ?? cronExpr;
|
|
15804
|
-
|
|
14504
|
+
log8.info("cron.registered", { projectId, schedule: label, timezone });
|
|
15805
14505
|
}
|
|
15806
14506
|
triggerRun(scheduleId, projectId) {
|
|
15807
14507
|
try {
|
|
15808
14508
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
15809
14509
|
const currentSchedule = this.db.select().from(schedules).where(eq25(schedules.id, scheduleId)).get();
|
|
15810
14510
|
if (!currentSchedule || currentSchedule.enabled !== 1) {
|
|
15811
|
-
|
|
14511
|
+
log8.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
|
|
15812
14512
|
this.remove(projectId);
|
|
15813
14513
|
return;
|
|
15814
14514
|
}
|
|
@@ -15816,7 +14516,7 @@ var Scheduler = class {
|
|
|
15816
14516
|
const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
|
|
15817
14517
|
const project = this.db.select().from(projects).where(eq25(projects.id, projectId)).get();
|
|
15818
14518
|
if (!project) {
|
|
15819
|
-
|
|
14519
|
+
log8.error("project.not-found", { projectId, msg: "skipping scheduled run" });
|
|
15820
14520
|
this.remove(projectId);
|
|
15821
14521
|
return;
|
|
15822
14522
|
}
|
|
@@ -15825,7 +14525,7 @@ var Scheduler = class {
|
|
|
15825
14525
|
if (project.defaultLocation) {
|
|
15826
14526
|
const loc = projectLocations.find((l) => l.label === project.defaultLocation);
|
|
15827
14527
|
if (!loc) {
|
|
15828
|
-
|
|
14528
|
+
log8.warn("default-location.stale", { scheduleId, projectId, label: project.defaultLocation });
|
|
15829
14529
|
return;
|
|
15830
14530
|
}
|
|
15831
14531
|
resolvedLocation = loc;
|
|
@@ -15839,7 +14539,7 @@ var Scheduler = class {
|
|
|
15839
14539
|
location: locationLabel
|
|
15840
14540
|
});
|
|
15841
14541
|
if (queueResult.conflict) {
|
|
15842
|
-
|
|
14542
|
+
log8.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
15843
14543
|
this.db.update(schedules).set({
|
|
15844
14544
|
nextRunAt,
|
|
15845
14545
|
updatedAt: now
|
|
@@ -15854,10 +14554,10 @@ var Scheduler = class {
|
|
|
15854
14554
|
}).where(eq25(schedules.id, currentSchedule.id)).run();
|
|
15855
14555
|
const scheduleProviders = parseJsonColumn(currentSchedule.providers, []);
|
|
15856
14556
|
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
15857
|
-
|
|
14557
|
+
log8.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
15858
14558
|
this.callbacks.onRunCreated(runId, projectId, providers, resolvedLocation);
|
|
15859
14559
|
} catch (err) {
|
|
15860
|
-
|
|
14560
|
+
log8.error("trigger.error", { scheduleId, projectId, error: err instanceof Error ? err.message : String(err) });
|
|
15861
14561
|
}
|
|
15862
14562
|
}
|
|
15863
14563
|
};
|
|
@@ -15865,7 +14565,7 @@ var Scheduler = class {
|
|
|
15865
14565
|
// src/notifier.ts
|
|
15866
14566
|
import { eq as eq26, desc as desc11, and as and13, or as or2 } from "drizzle-orm";
|
|
15867
14567
|
import crypto25 from "crypto";
|
|
15868
|
-
var
|
|
14568
|
+
var log9 = createLogger("Notifier");
|
|
15869
14569
|
var Notifier = class {
|
|
15870
14570
|
db;
|
|
15871
14571
|
serverUrl;
|
|
@@ -15875,26 +14575,26 @@ var Notifier = class {
|
|
|
15875
14575
|
}
|
|
15876
14576
|
/** Called after a run completes (success, partial, or failed). */
|
|
15877
14577
|
async onRunCompleted(runId, projectId) {
|
|
15878
|
-
|
|
14578
|
+
log9.info("run.completed", { runId, projectId });
|
|
15879
14579
|
const notifs = this.db.select().from(notifications).where(eq26(notifications.projectId, projectId)).all().filter((n) => n.enabled === 1);
|
|
15880
14580
|
if (notifs.length === 0) {
|
|
15881
|
-
|
|
14581
|
+
log9.info("notifications.none-enabled", { projectId });
|
|
15882
14582
|
return;
|
|
15883
14583
|
}
|
|
15884
|
-
|
|
14584
|
+
log9.info("notifications.found", { projectId, count: notifs.length });
|
|
15885
14585
|
const run = this.db.select().from(runs).where(eq26(runs.id, runId)).get();
|
|
15886
14586
|
if (!run) {
|
|
15887
|
-
|
|
14587
|
+
log9.error("run.not-found", { runId, msg: "skipping notification dispatch" });
|
|
15888
14588
|
return;
|
|
15889
14589
|
}
|
|
15890
14590
|
const project = this.db.select().from(projects).where(eq26(projects.id, projectId)).get();
|
|
15891
14591
|
if (!project) {
|
|
15892
|
-
|
|
14592
|
+
log9.error("project.not-found", { projectId, msg: "skipping notification dispatch" });
|
|
15893
14593
|
return;
|
|
15894
14594
|
}
|
|
15895
14595
|
const transitions = this.computeTransitions(runId, projectId);
|
|
15896
14596
|
const events = [];
|
|
15897
|
-
|
|
14597
|
+
log9.info("run.status", { runId: run.id, status: run.status, projectId });
|
|
15898
14598
|
if (run.status === "completed" || run.status === "partial") {
|
|
15899
14599
|
events.push("run.completed");
|
|
15900
14600
|
}
|
|
@@ -15910,7 +14610,7 @@ var Notifier = class {
|
|
|
15910
14610
|
if (!config.url) continue;
|
|
15911
14611
|
const subscribedEvents = config.events;
|
|
15912
14612
|
const matchingEvents = events.filter((e) => subscribedEvents.includes(e));
|
|
15913
|
-
|
|
14613
|
+
log9.info("notification.match", { notificationId: notif.id, subscribedEvents, matchedEvents: matchingEvents });
|
|
15914
14614
|
if (matchingEvents.length === 0) continue;
|
|
15915
14615
|
for (const event of matchingEvents) {
|
|
15916
14616
|
const relevantTransitions = event === "citation.lost" ? lostTransitions : event === "citation.gained" ? gainedTransitions : transitions;
|
|
@@ -16012,23 +14712,23 @@ var Notifier = class {
|
|
|
16012
14712
|
const targetLabel = redactNotificationUrl(url).urlDisplay;
|
|
16013
14713
|
const targetCheck = await resolveWebhookTarget(url);
|
|
16014
14714
|
if (!targetCheck.ok) {
|
|
16015
|
-
|
|
14715
|
+
log9.error("webhook.ssrf-blocked", { url: targetLabel, reason: targetCheck.message });
|
|
16016
14716
|
this.logDelivery(projectId, notificationId, payload.event, "failed", `SSRF: ${targetCheck.message}`);
|
|
16017
14717
|
return;
|
|
16018
14718
|
}
|
|
16019
|
-
|
|
14719
|
+
log9.info("webhook.send", { event: payload.event, url: targetLabel });
|
|
16020
14720
|
const maxRetries = 3;
|
|
16021
14721
|
const delays = [1e3, 4e3, 16e3];
|
|
16022
14722
|
for (let attempt = 0; attempt < maxRetries; attempt++) {
|
|
16023
14723
|
try {
|
|
16024
14724
|
const response = await deliverWebhook(targetCheck.target, payload, webhookSecret);
|
|
16025
14725
|
if (response.status >= 200 && response.status < 300) {
|
|
16026
|
-
|
|
14726
|
+
log9.info("webhook.delivered", { event: payload.event, url: targetLabel, httpStatus: response.status });
|
|
16027
14727
|
this.logDelivery(projectId, notificationId, payload.event, "sent", null);
|
|
16028
14728
|
return;
|
|
16029
14729
|
}
|
|
16030
14730
|
const errorDetail = response.error ?? `HTTP ${response.status}`;
|
|
16031
|
-
|
|
14731
|
+
log9.warn("webhook.attempt-failed", { event: payload.event, url: targetLabel, attempt: attempt + 1, maxRetries, httpStatus: response.status, error: errorDetail });
|
|
16032
14732
|
if (attempt === maxRetries - 1) {
|
|
16033
14733
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
16034
14734
|
}
|
|
@@ -16036,7 +14736,7 @@ var Notifier = class {
|
|
|
16036
14736
|
const errorDetail = err instanceof Error ? err.message : String(err);
|
|
16037
14737
|
if (attempt === maxRetries - 1) {
|
|
16038
14738
|
this.logDelivery(projectId, notificationId, payload.event, "failed", errorDetail);
|
|
16039
|
-
|
|
14739
|
+
log9.error("webhook.exhausted", { event: payload.event, url: targetLabel, maxRetries, error: errorDetail });
|
|
16040
14740
|
}
|
|
16041
14741
|
}
|
|
16042
14742
|
if (attempt < maxRetries - 1) {
|
|
@@ -16059,7 +14759,7 @@ var Notifier = class {
|
|
|
16059
14759
|
};
|
|
16060
14760
|
|
|
16061
14761
|
// src/run-coordinator.ts
|
|
16062
|
-
var
|
|
14762
|
+
var log10 = createLogger("RunCoordinator");
|
|
16063
14763
|
var RunCoordinator = class {
|
|
16064
14764
|
constructor(notifier, intelligenceService, onInsightsGenerated, onAeroEvent) {
|
|
16065
14765
|
this.notifier = notifier;
|
|
@@ -16081,23 +14781,23 @@ var RunCoordinator = class {
|
|
|
16081
14781
|
try {
|
|
16082
14782
|
await this.onInsightsGenerated(runId, projectId, result);
|
|
16083
14783
|
} catch (err) {
|
|
16084
|
-
|
|
14784
|
+
log10.error("insight-webhook.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
16085
14785
|
}
|
|
16086
14786
|
}
|
|
16087
14787
|
}
|
|
16088
14788
|
} catch (err) {
|
|
16089
|
-
|
|
14789
|
+
log10.error("intelligence.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
16090
14790
|
}
|
|
16091
14791
|
try {
|
|
16092
14792
|
await this.notifier.onRunCompleted(runId, projectId);
|
|
16093
14793
|
} catch (err) {
|
|
16094
|
-
|
|
14794
|
+
log10.error("notifier.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
16095
14795
|
}
|
|
16096
14796
|
if (this.onAeroEvent) {
|
|
16097
14797
|
try {
|
|
16098
14798
|
await this.onAeroEvent({ runId, projectId, insightCount, criticalOrHigh });
|
|
16099
14799
|
} catch (err) {
|
|
16100
|
-
|
|
14800
|
+
log10.error("aero.failed", { runId, error: err instanceof Error ? err.message : String(err) });
|
|
16101
14801
|
}
|
|
16102
14802
|
}
|
|
16103
14803
|
}
|
|
@@ -16108,8 +14808,8 @@ import crypto27 from "crypto";
|
|
|
16108
14808
|
import { eq as eq28 } from "drizzle-orm";
|
|
16109
14809
|
|
|
16110
14810
|
// src/agent/session.ts
|
|
16111
|
-
import
|
|
16112
|
-
import
|
|
14811
|
+
import fs11 from "fs";
|
|
14812
|
+
import path13 from "path";
|
|
16113
14813
|
import { Agent } from "@mariozechner/pi-agent-core";
|
|
16114
14814
|
import { registerBuiltInApiProviders } from "@mariozechner/pi-ai";
|
|
16115
14815
|
|
|
@@ -16210,26 +14910,26 @@ function buildAgentProvidersResponse(config) {
|
|
|
16210
14910
|
}
|
|
16211
14911
|
|
|
16212
14912
|
// src/agent/skill-paths.ts
|
|
16213
|
-
import
|
|
16214
|
-
import
|
|
14913
|
+
import fs9 from "fs";
|
|
14914
|
+
import path11 from "path";
|
|
16215
14915
|
import { fileURLToPath } from "url";
|
|
16216
14916
|
function resolveAeroSkillDir(pkgDir) {
|
|
16217
|
-
const here = pkgDir ??
|
|
14917
|
+
const here = pkgDir ?? path11.dirname(fileURLToPath(import.meta.url));
|
|
16218
14918
|
const candidates = [
|
|
16219
|
-
|
|
16220
|
-
|
|
16221
|
-
|
|
14919
|
+
path11.join(here, "../assets/agent-workspace/skills/aero"),
|
|
14920
|
+
path11.join(here, "../../assets/agent-workspace/skills/aero"),
|
|
14921
|
+
path11.join(here, "../../../../skills/aero")
|
|
16222
14922
|
];
|
|
16223
14923
|
for (const candidate of candidates) {
|
|
16224
|
-
if (
|
|
14924
|
+
if (fs9.existsSync(path11.join(candidate, "SKILL.md"))) return candidate;
|
|
16225
14925
|
}
|
|
16226
14926
|
throw new Error(`Aero skill not found. Searched:
|
|
16227
14927
|
${candidates.join("\n ")}`);
|
|
16228
14928
|
}
|
|
16229
14929
|
|
|
16230
14930
|
// src/agent/skill-tools.ts
|
|
16231
|
-
import
|
|
16232
|
-
import
|
|
14931
|
+
import fs10 from "fs";
|
|
14932
|
+
import path12 from "path";
|
|
16233
14933
|
import { Type } from "@sinclair/typebox";
|
|
16234
14934
|
var MAX_DOC_CHARS = 2e4;
|
|
16235
14935
|
function textResult(details) {
|
|
@@ -16250,13 +14950,13 @@ function parseDescription(body) {
|
|
|
16250
14950
|
return "(no description)";
|
|
16251
14951
|
}
|
|
16252
14952
|
function scanSkillDocs(skillDir) {
|
|
16253
|
-
const refsDir =
|
|
16254
|
-
if (!
|
|
14953
|
+
const refsDir = path12.join(skillDir ?? resolveAeroSkillDir(), "references");
|
|
14954
|
+
if (!fs10.existsSync(refsDir)) return [];
|
|
16255
14955
|
const entries = [];
|
|
16256
|
-
for (const file of
|
|
14956
|
+
for (const file of fs10.readdirSync(refsDir)) {
|
|
16257
14957
|
if (!file.endsWith(".md")) continue;
|
|
16258
|
-
const filePath =
|
|
16259
|
-
const body =
|
|
14958
|
+
const filePath = path12.join(refsDir, file);
|
|
14959
|
+
const body = fs10.readFileSync(filePath, "utf-8");
|
|
16260
14960
|
entries.push({
|
|
16261
14961
|
slug: file.replace(/\.md$/, ""),
|
|
16262
14962
|
description: parseDescription(body),
|
|
@@ -16299,8 +14999,8 @@ function buildReadSkillDocTool() {
|
|
|
16299
14999
|
availableSlugs: docs.map((d) => d.slug)
|
|
16300
15000
|
});
|
|
16301
15001
|
}
|
|
16302
|
-
const filePath =
|
|
16303
|
-
const content =
|
|
15002
|
+
const filePath = path12.join(skillDir, "references", `${match.slug}.md`);
|
|
15003
|
+
const content = fs10.readFileSync(filePath, "utf-8");
|
|
16304
15004
|
if (content.length > MAX_DOC_CHARS) {
|
|
16305
15005
|
return textResult({
|
|
16306
15006
|
slug: match.slug,
|
|
@@ -16854,10 +15554,10 @@ function ensureBuiltinsRegistered() {
|
|
|
16854
15554
|
}
|
|
16855
15555
|
function loadAeroSystemPrompt(pkgDir) {
|
|
16856
15556
|
const skillDir = resolveAeroSkillDir(pkgDir);
|
|
16857
|
-
const skillBody =
|
|
16858
|
-
const soulPath =
|
|
16859
|
-
if (!
|
|
16860
|
-
const soulBody =
|
|
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");
|
|
16861
15561
|
return `${soulBody.trimEnd()}
|
|
16862
15562
|
|
|
16863
15563
|
---
|
|
@@ -17041,7 +15741,7 @@ async function compactMessages(args) {
|
|
|
17041
15741
|
}
|
|
17042
15742
|
|
|
17043
15743
|
// src/agent/session-registry.ts
|
|
17044
|
-
var
|
|
15744
|
+
var log11 = createLogger("SessionRegistry");
|
|
17045
15745
|
var MAX_HYDRATE_NOTES = 20;
|
|
17046
15746
|
var MAX_HYDRATE_BYTES = 32 * 1024;
|
|
17047
15747
|
function escapeMemoryFragment(value) {
|
|
@@ -17272,13 +15972,13 @@ ${lines.join("\n")}
|
|
|
17272
15972
|
agent.state.messages = result.messages;
|
|
17273
15973
|
agent.state.systemPrompt = this.buildHydratedSystemPrompt(projectId, row.systemPrompt);
|
|
17274
15974
|
this.save(projectName);
|
|
17275
|
-
|
|
15975
|
+
log11.info("compaction.completed", {
|
|
17276
15976
|
projectName,
|
|
17277
15977
|
removedCount: result.removedCount,
|
|
17278
15978
|
summaryBytes: Buffer.byteLength(result.summary, "utf8")
|
|
17279
15979
|
});
|
|
17280
15980
|
} catch (err) {
|
|
17281
|
-
|
|
15981
|
+
log11.error("compaction.failed", {
|
|
17282
15982
|
projectName,
|
|
17283
15983
|
error: err instanceof Error ? err.message : String(err)
|
|
17284
15984
|
});
|
|
@@ -17375,7 +16075,7 @@ ${lines.join("\n")}
|
|
|
17375
16075
|
await agent.prompt(msgs);
|
|
17376
16076
|
this.save(projectName);
|
|
17377
16077
|
} catch (err) {
|
|
17378
|
-
|
|
16078
|
+
log11.error("drain.failed", {
|
|
17379
16079
|
projectName,
|
|
17380
16080
|
error: err instanceof Error ? err.message : String(err)
|
|
17381
16081
|
});
|
|
@@ -17646,593 +16346,6 @@ function registerAgentRoutes(app, opts) {
|
|
|
17646
16346
|
);
|
|
17647
16347
|
}
|
|
17648
16348
|
|
|
17649
|
-
// src/client.ts
|
|
17650
|
-
function createApiClient() {
|
|
17651
|
-
const config = loadConfig();
|
|
17652
|
-
const basePathResolved = !!config.basePath || "CANONRY_BASE_PATH" in process.env;
|
|
17653
|
-
return new ApiClient(config.apiUrl, config.apiKey, { skipProbe: basePathResolved });
|
|
17654
|
-
}
|
|
17655
|
-
var ApiClient = class {
|
|
17656
|
-
baseUrl;
|
|
17657
|
-
originUrl;
|
|
17658
|
-
apiKey;
|
|
17659
|
-
probePromise = null;
|
|
17660
|
-
probeSkipped;
|
|
17661
|
-
constructor(baseUrl, apiKey, opts) {
|
|
17662
|
-
this.originUrl = baseUrl.replace(/\/$/, "");
|
|
17663
|
-
this.baseUrl = this.originUrl + "/api/v1";
|
|
17664
|
-
this.apiKey = apiKey;
|
|
17665
|
-
this.probeSkipped = opts?.skipProbe ?? false;
|
|
17666
|
-
}
|
|
17667
|
-
/**
|
|
17668
|
-
* On first API call, probe /health to auto-discover basePath when the user
|
|
17669
|
-
* hasn't configured one locally. This lets `canonry run` in a separate shell
|
|
17670
|
-
* discover that the server is running at e.g. /canonry/ without requiring
|
|
17671
|
-
* config.yaml edits or CANONRY_BASE_PATH in every shell.
|
|
17672
|
-
*/
|
|
17673
|
-
probeBasePath() {
|
|
17674
|
-
if (this.probeSkipped) return Promise.resolve();
|
|
17675
|
-
if (!this.probePromise) {
|
|
17676
|
-
this.probePromise = (async () => {
|
|
17677
|
-
try {
|
|
17678
|
-
const origin = new URL(this.originUrl).origin;
|
|
17679
|
-
const res = await fetch(`${origin}/health`, {
|
|
17680
|
-
signal: AbortSignal.timeout(2e3)
|
|
17681
|
-
});
|
|
17682
|
-
if (res.ok) {
|
|
17683
|
-
const body = await res.json();
|
|
17684
|
-
if (body.basePath && typeof body.basePath === "string") {
|
|
17685
|
-
const normalized = "/" + body.basePath.replace(/^\/|\/$/g, "");
|
|
17686
|
-
if (normalized !== "/") {
|
|
17687
|
-
this.originUrl = origin + normalized;
|
|
17688
|
-
this.baseUrl = this.originUrl + "/api/v1";
|
|
17689
|
-
}
|
|
17690
|
-
}
|
|
17691
|
-
}
|
|
17692
|
-
} catch {
|
|
17693
|
-
}
|
|
17694
|
-
})();
|
|
17695
|
-
}
|
|
17696
|
-
return this.probePromise;
|
|
17697
|
-
}
|
|
17698
|
-
async request(method, path16, body) {
|
|
17699
|
-
await this.probeBasePath();
|
|
17700
|
-
const url = `${this.baseUrl}${path16}`;
|
|
17701
|
-
const serializedBody = body != null ? JSON.stringify(body) : void 0;
|
|
17702
|
-
const headers = {
|
|
17703
|
-
"Authorization": `Bearer ${this.apiKey}`,
|
|
17704
|
-
"Accept": "application/json",
|
|
17705
|
-
...serializedBody != null ? { "Content-Type": "application/json" } : {}
|
|
17706
|
-
};
|
|
17707
|
-
let res;
|
|
17708
|
-
try {
|
|
17709
|
-
res = await fetch(url, {
|
|
17710
|
-
method,
|
|
17711
|
-
headers,
|
|
17712
|
-
body: serializedBody
|
|
17713
|
-
});
|
|
17714
|
-
} catch (err) {
|
|
17715
|
-
if (err instanceof CliError) throw err;
|
|
17716
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
17717
|
-
if (msg.includes("fetch failed") || msg.includes("ECONNREFUSED") || msg.includes("connect ECONNREFUSED")) {
|
|
17718
|
-
throw new CliError({
|
|
17719
|
-
code: "CONNECTION_ERROR",
|
|
17720
|
-
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).`,
|
|
17721
|
-
exitCode: EXIT_SYSTEM_ERROR
|
|
17722
|
-
});
|
|
17723
|
-
}
|
|
17724
|
-
throw new CliError({ code: "CONNECTION_ERROR", message: msg, exitCode: EXIT_SYSTEM_ERROR });
|
|
17725
|
-
}
|
|
17726
|
-
if (!res.ok) {
|
|
17727
|
-
let errorBody;
|
|
17728
|
-
try {
|
|
17729
|
-
errorBody = await res.json();
|
|
17730
|
-
} catch {
|
|
17731
|
-
errorBody = { error: { code: "UNKNOWN", message: res.statusText } };
|
|
17732
|
-
}
|
|
17733
|
-
const errorObj = errorBody && typeof errorBody === "object" && "error" in errorBody && errorBody.error && typeof errorBody.error === "object" ? errorBody.error : null;
|
|
17734
|
-
const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
|
|
17735
|
-
const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
|
|
17736
|
-
const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
|
|
17737
|
-
throw new CliError({ code, message: msg, exitCode, details: { httpStatus: res.status } });
|
|
17738
|
-
}
|
|
17739
|
-
if (res.status === 204) {
|
|
17740
|
-
return void 0;
|
|
17741
|
-
}
|
|
17742
|
-
return await res.json();
|
|
17743
|
-
}
|
|
17744
|
-
async getAgentTranscript(project) {
|
|
17745
|
-
return this.request(
|
|
17746
|
-
"GET",
|
|
17747
|
-
`/projects/${encodeURIComponent(project)}/agent/transcript`
|
|
17748
|
-
);
|
|
17749
|
-
}
|
|
17750
|
-
async resetAgentTranscript(project) {
|
|
17751
|
-
await this.request(
|
|
17752
|
-
"DELETE",
|
|
17753
|
-
`/projects/${encodeURIComponent(project)}/agent/transcript`
|
|
17754
|
-
);
|
|
17755
|
-
}
|
|
17756
|
-
async listAgentProviders(project) {
|
|
17757
|
-
return this.request(
|
|
17758
|
-
"GET",
|
|
17759
|
-
`/projects/${encodeURIComponent(project)}/agent/providers`
|
|
17760
|
-
);
|
|
17761
|
-
}
|
|
17762
|
-
async listAgentMemory(project) {
|
|
17763
|
-
return this.request(
|
|
17764
|
-
"GET",
|
|
17765
|
-
`/projects/${encodeURIComponent(project)}/agent/memory`
|
|
17766
|
-
);
|
|
17767
|
-
}
|
|
17768
|
-
async setAgentMemory(project, body) {
|
|
17769
|
-
return this.request(
|
|
17770
|
-
"PUT",
|
|
17771
|
-
`/projects/${encodeURIComponent(project)}/agent/memory`,
|
|
17772
|
-
body
|
|
17773
|
-
);
|
|
17774
|
-
}
|
|
17775
|
-
async forgetAgentMemory(project, key) {
|
|
17776
|
-
return this.request(
|
|
17777
|
-
"DELETE",
|
|
17778
|
-
`/projects/${encodeURIComponent(project)}/agent/memory`,
|
|
17779
|
-
{ key }
|
|
17780
|
-
);
|
|
17781
|
-
}
|
|
17782
|
-
/**
|
|
17783
|
-
* POST a request whose response body the caller intends to consume as a
|
|
17784
|
-
* stream (e.g. the Aero agent SSE endpoint). Shares the probe + auth +
|
|
17785
|
-
* structured-error behavior of `request()`; the caller reads `res.body`
|
|
17786
|
-
* and releases the response when done.
|
|
17787
|
-
*/
|
|
17788
|
-
async streamPost(path16, body, signal) {
|
|
17789
|
-
await this.probeBasePath();
|
|
17790
|
-
const url = `${this.baseUrl}${path16}`;
|
|
17791
|
-
const headers = {
|
|
17792
|
-
Authorization: `Bearer ${this.apiKey}`,
|
|
17793
|
-
"Content-Type": "application/json",
|
|
17794
|
-
Accept: "text/event-stream"
|
|
17795
|
-
};
|
|
17796
|
-
let res;
|
|
17797
|
-
try {
|
|
17798
|
-
res = await fetch(url, {
|
|
17799
|
-
method: "POST",
|
|
17800
|
-
headers,
|
|
17801
|
-
body: JSON.stringify(body ?? {}),
|
|
17802
|
-
signal
|
|
17803
|
-
});
|
|
17804
|
-
} catch (err) {
|
|
17805
|
-
if (err instanceof CliError) throw err;
|
|
17806
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
17807
|
-
if (msg.includes("fetch failed") || msg.includes("ECONNREFUSED") || msg.includes("connect ECONNREFUSED")) {
|
|
17808
|
-
throw new CliError({
|
|
17809
|
-
code: "CONNECTION_ERROR",
|
|
17810
|
-
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).`,
|
|
17811
|
-
exitCode: EXIT_SYSTEM_ERROR
|
|
17812
|
-
});
|
|
17813
|
-
}
|
|
17814
|
-
throw new CliError({ code: "CONNECTION_ERROR", message: msg, exitCode: EXIT_SYSTEM_ERROR });
|
|
17815
|
-
}
|
|
17816
|
-
if (!res.ok || !res.body) {
|
|
17817
|
-
let errorBody;
|
|
17818
|
-
try {
|
|
17819
|
-
errorBody = await res.json();
|
|
17820
|
-
} catch {
|
|
17821
|
-
errorBody = { error: { code: "UNKNOWN", message: res.statusText } };
|
|
17822
|
-
}
|
|
17823
|
-
const errorObj = errorBody && typeof errorBody === "object" && "error" in errorBody && errorBody.error ? errorBody.error : null;
|
|
17824
|
-
const msg = errorObj?.message ? String(errorObj.message) : `HTTP ${res.status}: ${res.statusText}`;
|
|
17825
|
-
const code = errorObj?.code ? String(errorObj.code) : "API_ERROR";
|
|
17826
|
-
const exitCode = res.status >= 500 ? EXIT_SYSTEM_ERROR : EXIT_USER_ERROR;
|
|
17827
|
-
throw new CliError({ code, message: msg, exitCode, details: { httpStatus: res.status } });
|
|
17828
|
-
}
|
|
17829
|
-
return res;
|
|
17830
|
-
}
|
|
17831
|
-
async putProject(name, body) {
|
|
17832
|
-
return this.request("PUT", `/projects/${encodeURIComponent(name)}`, body);
|
|
17833
|
-
}
|
|
17834
|
-
async listProjects() {
|
|
17835
|
-
return this.request("GET", "/projects");
|
|
17836
|
-
}
|
|
17837
|
-
async getProject(name) {
|
|
17838
|
-
return this.request("GET", `/projects/${encodeURIComponent(name)}`);
|
|
17839
|
-
}
|
|
17840
|
-
async deleteProject(name) {
|
|
17841
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(name)}`);
|
|
17842
|
-
}
|
|
17843
|
-
async putKeywords(project, keywords2) {
|
|
17844
|
-
await this.request("PUT", `/projects/${encodeURIComponent(project)}/keywords`, { keywords: keywords2 });
|
|
17845
|
-
}
|
|
17846
|
-
async listKeywords(project) {
|
|
17847
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/keywords`);
|
|
17848
|
-
}
|
|
17849
|
-
async deleteKeywords(project, keywords2) {
|
|
17850
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/keywords`, { keywords: keywords2 });
|
|
17851
|
-
}
|
|
17852
|
-
async appendKeywords(project, keywords2) {
|
|
17853
|
-
await this.request("POST", `/projects/${encodeURIComponent(project)}/keywords`, { keywords: keywords2 });
|
|
17854
|
-
}
|
|
17855
|
-
async putCompetitors(project, competitors2) {
|
|
17856
|
-
await this.request("PUT", `/projects/${encodeURIComponent(project)}/competitors`, { competitors: competitors2 });
|
|
17857
|
-
}
|
|
17858
|
-
async listCompetitors(project) {
|
|
17859
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/competitors`);
|
|
17860
|
-
}
|
|
17861
|
-
async triggerRun(project, body) {
|
|
17862
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/runs`, body ?? {});
|
|
17863
|
-
}
|
|
17864
|
-
async listRuns(project, limit) {
|
|
17865
|
-
const query = limit != null ? `?limit=${encodeURIComponent(String(limit))}` : "";
|
|
17866
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/runs${query}`);
|
|
17867
|
-
}
|
|
17868
|
-
async getLatestRun(project) {
|
|
17869
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/runs/latest`);
|
|
17870
|
-
}
|
|
17871
|
-
async getRun(id) {
|
|
17872
|
-
return this.request("GET", `/runs/${encodeURIComponent(id)}`);
|
|
17873
|
-
}
|
|
17874
|
-
async cancelRun(id) {
|
|
17875
|
-
return this.request("POST", `/runs/${encodeURIComponent(id)}/cancel`);
|
|
17876
|
-
}
|
|
17877
|
-
async getTimeline(project) {
|
|
17878
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/timeline`);
|
|
17879
|
-
}
|
|
17880
|
-
async getHistory(project) {
|
|
17881
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/history`);
|
|
17882
|
-
}
|
|
17883
|
-
async getExport(project) {
|
|
17884
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/export`);
|
|
17885
|
-
}
|
|
17886
|
-
async apply(config) {
|
|
17887
|
-
return this.request("POST", "/apply", config);
|
|
17888
|
-
}
|
|
17889
|
-
async getStatus(project) {
|
|
17890
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}`);
|
|
17891
|
-
}
|
|
17892
|
-
async getSettings() {
|
|
17893
|
-
return this.request("GET", "/settings");
|
|
17894
|
-
}
|
|
17895
|
-
async createSnapshot(body) {
|
|
17896
|
-
return this.request("POST", "/snapshot", body);
|
|
17897
|
-
}
|
|
17898
|
-
async updateProvider(name, body) {
|
|
17899
|
-
return this.request("PUT", `/settings/providers/${encodeURIComponent(name)}`, body);
|
|
17900
|
-
}
|
|
17901
|
-
async putSchedule(project, body) {
|
|
17902
|
-
return this.request("PUT", `/projects/${encodeURIComponent(project)}/schedule`, body);
|
|
17903
|
-
}
|
|
17904
|
-
async getSchedule(project) {
|
|
17905
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/schedule`);
|
|
17906
|
-
}
|
|
17907
|
-
async deleteSchedule(project) {
|
|
17908
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/schedule`);
|
|
17909
|
-
}
|
|
17910
|
-
async createNotification(project, body) {
|
|
17911
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/notifications`, body);
|
|
17912
|
-
}
|
|
17913
|
-
async listNotifications(project) {
|
|
17914
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/notifications`);
|
|
17915
|
-
}
|
|
17916
|
-
async deleteNotification(project, id) {
|
|
17917
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/notifications/${encodeURIComponent(id)}`);
|
|
17918
|
-
}
|
|
17919
|
-
async testNotification(project, id) {
|
|
17920
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/notifications/${encodeURIComponent(id)}/test`);
|
|
17921
|
-
}
|
|
17922
|
-
async addLocation(project, body) {
|
|
17923
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/locations`, body);
|
|
17924
|
-
}
|
|
17925
|
-
async listLocations(project) {
|
|
17926
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/locations`);
|
|
17927
|
-
}
|
|
17928
|
-
async removeLocation(project, label) {
|
|
17929
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/locations/${encodeURIComponent(label)}`);
|
|
17930
|
-
}
|
|
17931
|
-
async setDefaultLocation(project, label) {
|
|
17932
|
-
return this.request("PUT", `/projects/${encodeURIComponent(project)}/locations/default`, { label });
|
|
17933
|
-
}
|
|
17934
|
-
async getTelemetry() {
|
|
17935
|
-
return this.request("GET", "/telemetry");
|
|
17936
|
-
}
|
|
17937
|
-
async updateTelemetry(enabled) {
|
|
17938
|
-
return this.request("PUT", "/telemetry", { enabled });
|
|
17939
|
-
}
|
|
17940
|
-
async generateKeywords(project, provider, count) {
|
|
17941
|
-
return this.request(
|
|
17942
|
-
"POST",
|
|
17943
|
-
`/projects/${encodeURIComponent(project)}/keywords/generate`,
|
|
17944
|
-
{ provider, count }
|
|
17945
|
-
);
|
|
17946
|
-
}
|
|
17947
|
-
// Google connection management
|
|
17948
|
-
async googleConnect(project, body) {
|
|
17949
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/google/connect`, body);
|
|
17950
|
-
}
|
|
17951
|
-
async googleConnections(project) {
|
|
17952
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/connections`);
|
|
17953
|
-
}
|
|
17954
|
-
async googleDisconnect(project, type) {
|
|
17955
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}`);
|
|
17956
|
-
}
|
|
17957
|
-
async googleProperties(project) {
|
|
17958
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/properties`);
|
|
17959
|
-
}
|
|
17960
|
-
async googleSetProperty(project, type, propertyId) {
|
|
17961
|
-
return this.request("PUT", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}/property`, { propertyId });
|
|
17962
|
-
}
|
|
17963
|
-
async googleSetSitemap(project, type, sitemapUrl) {
|
|
17964
|
-
return this.request("PUT", `/projects/${encodeURIComponent(project)}/google/connections/${encodeURIComponent(type)}/sitemap`, { sitemapUrl });
|
|
17965
|
-
}
|
|
17966
|
-
// GSC data
|
|
17967
|
-
async gscSync(project, body) {
|
|
17968
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/sync`, body ?? {});
|
|
17969
|
-
}
|
|
17970
|
-
async gscPerformance(project, params) {
|
|
17971
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
17972
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/performance${qs}`);
|
|
17973
|
-
}
|
|
17974
|
-
async gscInspect(project, url) {
|
|
17975
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/inspect`, { url });
|
|
17976
|
-
}
|
|
17977
|
-
async gscInspections(project, params) {
|
|
17978
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
17979
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/inspections${qs}`);
|
|
17980
|
-
}
|
|
17981
|
-
async gscDeindexed(project) {
|
|
17982
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/deindexed`);
|
|
17983
|
-
}
|
|
17984
|
-
async gscCoverage(project) {
|
|
17985
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/coverage`);
|
|
17986
|
-
}
|
|
17987
|
-
async gscCoverageHistory(project, params) {
|
|
17988
|
-
const qs = params?.limit != null ? `?limit=${params.limit}` : "";
|
|
17989
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/coverage/history${qs}`);
|
|
17990
|
-
}
|
|
17991
|
-
async gscInspectSitemap(project, body) {
|
|
17992
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/inspect-sitemap`, body ?? {});
|
|
17993
|
-
}
|
|
17994
|
-
async gscSitemaps(project) {
|
|
17995
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/google/gsc/sitemaps`);
|
|
17996
|
-
}
|
|
17997
|
-
async gscDiscoverSitemaps(project) {
|
|
17998
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/google/gsc/discover-sitemaps`, {});
|
|
17999
|
-
}
|
|
18000
|
-
// Analytics
|
|
18001
|
-
async getAnalyticsMetrics(project, window) {
|
|
18002
|
-
const qs = window ? `?window=${encodeURIComponent(window)}` : "";
|
|
18003
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/metrics${qs}`);
|
|
18004
|
-
}
|
|
18005
|
-
async getAnalyticsGaps(project, window) {
|
|
18006
|
-
const qs = window ? `?window=${encodeURIComponent(window)}` : "";
|
|
18007
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/gaps${qs}`);
|
|
18008
|
-
}
|
|
18009
|
-
async getAnalyticsSources(project, window) {
|
|
18010
|
-
const qs = window ? `?window=${encodeURIComponent(window)}` : "";
|
|
18011
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/analytics/sources${qs}`);
|
|
18012
|
-
}
|
|
18013
|
-
// Google Indexing API
|
|
18014
|
-
async googleRequestIndexing(project, body) {
|
|
18015
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/google/indexing/request`, body);
|
|
18016
|
-
}
|
|
18017
|
-
// Bing Webmaster Tools
|
|
18018
|
-
async bingConnect(project, body) {
|
|
18019
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/connect`, body);
|
|
18020
|
-
}
|
|
18021
|
-
async bingDisconnect(project) {
|
|
18022
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/bing/disconnect`);
|
|
18023
|
-
}
|
|
18024
|
-
async bingStatus(project) {
|
|
18025
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/status`);
|
|
18026
|
-
}
|
|
18027
|
-
async bingSites(project) {
|
|
18028
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/sites`);
|
|
18029
|
-
}
|
|
18030
|
-
async bingSetSite(project, siteUrl) {
|
|
18031
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/set-site`, { siteUrl });
|
|
18032
|
-
}
|
|
18033
|
-
async bingCoverage(project) {
|
|
18034
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/coverage`);
|
|
18035
|
-
}
|
|
18036
|
-
async bingCoverageHistory(project, params) {
|
|
18037
|
-
const qs = params?.limit != null ? `?limit=${params.limit}` : "";
|
|
18038
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/coverage/history${qs}`);
|
|
18039
|
-
}
|
|
18040
|
-
async bingInspections(project, params) {
|
|
18041
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
18042
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/inspections${qs}`);
|
|
18043
|
-
}
|
|
18044
|
-
async bingInspectUrl(project, url) {
|
|
18045
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/inspect-url`, { url });
|
|
18046
|
-
}
|
|
18047
|
-
async bingInspectSitemap(project, body) {
|
|
18048
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/inspect-sitemap`, body ?? {});
|
|
18049
|
-
}
|
|
18050
|
-
async bingRequestIndexing(project, body) {
|
|
18051
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/bing/request-indexing`, body);
|
|
18052
|
-
}
|
|
18053
|
-
async bingPerformance(project, params) {
|
|
18054
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
18055
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/bing/performance${qs}`);
|
|
18056
|
-
}
|
|
18057
|
-
// CDP browser provider
|
|
18058
|
-
async getCdpStatus() {
|
|
18059
|
-
return this.request("GET", "/cdp/status");
|
|
18060
|
-
}
|
|
18061
|
-
async cdpScreenshot(query, targets) {
|
|
18062
|
-
return this.request("POST", "/cdp/screenshot", { query, targets });
|
|
18063
|
-
}
|
|
18064
|
-
async getBrowserDiff(project, runId) {
|
|
18065
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/runs/${encodeURIComponent(runId)}/browser-diff`);
|
|
18066
|
-
}
|
|
18067
|
-
// Google Analytics 4
|
|
18068
|
-
async gaConnect(project, body) {
|
|
18069
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/ga/connect`, body);
|
|
18070
|
-
}
|
|
18071
|
-
async gaDisconnect(project) {
|
|
18072
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/ga/disconnect`);
|
|
18073
|
-
}
|
|
18074
|
-
async gaStatus(project) {
|
|
18075
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/status`);
|
|
18076
|
-
}
|
|
18077
|
-
async gaSync(project, body) {
|
|
18078
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/ga/sync`, body ?? {});
|
|
18079
|
-
}
|
|
18080
|
-
async gaTraffic(project, params) {
|
|
18081
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
18082
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/traffic${qs}`);
|
|
18083
|
-
}
|
|
18084
|
-
async gaCoverage(project) {
|
|
18085
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/coverage`);
|
|
18086
|
-
}
|
|
18087
|
-
async gaAiReferralHistory(project, params) {
|
|
18088
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
18089
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/ai-referral-history${qs}`);
|
|
18090
|
-
}
|
|
18091
|
-
async gaSocialReferralHistory(project, params) {
|
|
18092
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
18093
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-history${qs}`);
|
|
18094
|
-
}
|
|
18095
|
-
async gaSocialReferralTrend(project) {
|
|
18096
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/social-referral-trend`);
|
|
18097
|
-
}
|
|
18098
|
-
async gaAttributionTrend(project) {
|
|
18099
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/attribution-trend`);
|
|
18100
|
-
}
|
|
18101
|
-
async gaSessionHistory(project, params) {
|
|
18102
|
-
const qs = params ? "?" + new URLSearchParams(params).toString() : "";
|
|
18103
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/session-history${qs}`);
|
|
18104
|
-
}
|
|
18105
|
-
async wordpressConnect(project, body) {
|
|
18106
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/connect`, body);
|
|
18107
|
-
}
|
|
18108
|
-
async wordpressDisconnect(project) {
|
|
18109
|
-
await this.request("DELETE", `/projects/${encodeURIComponent(project)}/wordpress/disconnect`);
|
|
18110
|
-
}
|
|
18111
|
-
async wordpressStatus(project) {
|
|
18112
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/status`);
|
|
18113
|
-
}
|
|
18114
|
-
async wordpressPages(project, env) {
|
|
18115
|
-
const qs = env ? `?env=${encodeURIComponent(env)}` : "";
|
|
18116
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/pages${qs}`);
|
|
18117
|
-
}
|
|
18118
|
-
async wordpressPage(project, slug, env) {
|
|
18119
|
-
const params = new URLSearchParams({ slug });
|
|
18120
|
-
if (env) params.set("env", env);
|
|
18121
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/page?${params.toString()}`);
|
|
18122
|
-
}
|
|
18123
|
-
async wordpressCreatePage(project, body) {
|
|
18124
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/pages`, body);
|
|
18125
|
-
}
|
|
18126
|
-
async wordpressUpdatePage(project, body) {
|
|
18127
|
-
return this.request("PUT", `/projects/${encodeURIComponent(project)}/wordpress/page`, body);
|
|
18128
|
-
}
|
|
18129
|
-
async wordpressSetMeta(project, body) {
|
|
18130
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/page/meta`, body);
|
|
18131
|
-
}
|
|
18132
|
-
async wordpressBulkSetMeta(project, body) {
|
|
18133
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/pages/meta/bulk`, body);
|
|
18134
|
-
}
|
|
18135
|
-
async wordpressSchema(project, slug, env) {
|
|
18136
|
-
const params = new URLSearchParams({ slug });
|
|
18137
|
-
if (env) params.set("env", env);
|
|
18138
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/schema?${params.toString()}`);
|
|
18139
|
-
}
|
|
18140
|
-
async wordpressSetSchema(project, body) {
|
|
18141
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/schema/manual`, body);
|
|
18142
|
-
}
|
|
18143
|
-
async wordpressSchemaDeploy(project, body) {
|
|
18144
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/schema/deploy`, body);
|
|
18145
|
-
}
|
|
18146
|
-
async wordpressSchemaStatus(project, env) {
|
|
18147
|
-
const params = new URLSearchParams();
|
|
18148
|
-
if (env) params.set("env", env);
|
|
18149
|
-
const qs = params.toString();
|
|
18150
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/schema/status${qs ? `?${qs}` : ""}`);
|
|
18151
|
-
}
|
|
18152
|
-
async wordpressOnboard(project, body) {
|
|
18153
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/onboard`, body);
|
|
18154
|
-
}
|
|
18155
|
-
async wordpressLlmsTxt(project, env) {
|
|
18156
|
-
const qs = env ? `?env=${encodeURIComponent(env)}` : "";
|
|
18157
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/llms-txt${qs}`);
|
|
18158
|
-
}
|
|
18159
|
-
async wordpressSetLlmsTxt(project, body) {
|
|
18160
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/llms-txt/manual`, body);
|
|
18161
|
-
}
|
|
18162
|
-
async wordpressAudit(project, env) {
|
|
18163
|
-
const qs = env ? `?env=${encodeURIComponent(env)}` : "";
|
|
18164
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/audit${qs}`);
|
|
18165
|
-
}
|
|
18166
|
-
async wordpressDiff(project, slug) {
|
|
18167
|
-
const params = new URLSearchParams({ slug });
|
|
18168
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/diff?${params.toString()}`);
|
|
18169
|
-
}
|
|
18170
|
-
async wordpressStagingStatus(project) {
|
|
18171
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/wordpress/staging/status`);
|
|
18172
|
-
}
|
|
18173
|
-
async wordpressStagingPush(project) {
|
|
18174
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/staging/push`);
|
|
18175
|
-
}
|
|
18176
|
-
// ── Intelligence ──────────────────────────────────────────────────────
|
|
18177
|
-
async getInsights(project, opts) {
|
|
18178
|
-
const params = new URLSearchParams();
|
|
18179
|
-
if (opts?.dismissed) params.set("dismissed", "true");
|
|
18180
|
-
if (opts?.runId) params.set("runId", opts.runId);
|
|
18181
|
-
const qs = params.toString();
|
|
18182
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/insights${qs ? `?${qs}` : ""}`);
|
|
18183
|
-
}
|
|
18184
|
-
async dismissInsight(project, id) {
|
|
18185
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/insights/${encodeURIComponent(id)}/dismiss`);
|
|
18186
|
-
}
|
|
18187
|
-
async getHealth(project) {
|
|
18188
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/health/latest`);
|
|
18189
|
-
}
|
|
18190
|
-
async getHealthHistory(project, limit) {
|
|
18191
|
-
const qs = limit ? `?limit=${limit}` : "";
|
|
18192
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/health/history${qs}`);
|
|
18193
|
-
}
|
|
18194
|
-
// --- Backlinks ---------------------------------------------------------
|
|
18195
|
-
async backlinksStatus() {
|
|
18196
|
-
return this.request("GET", "/backlinks/status");
|
|
18197
|
-
}
|
|
18198
|
-
async backlinksInstall() {
|
|
18199
|
-
return this.request("POST", "/backlinks/install");
|
|
18200
|
-
}
|
|
18201
|
-
async backlinksTriggerSync(release) {
|
|
18202
|
-
return this.request("POST", "/backlinks/syncs", { release });
|
|
18203
|
-
}
|
|
18204
|
-
async backlinksLatestSync() {
|
|
18205
|
-
return this.request("GET", "/backlinks/syncs/latest");
|
|
18206
|
-
}
|
|
18207
|
-
async backlinksListSyncs() {
|
|
18208
|
-
return this.request("GET", "/backlinks/syncs");
|
|
18209
|
-
}
|
|
18210
|
-
async backlinksCachedReleases() {
|
|
18211
|
-
return this.request("GET", "/backlinks/releases");
|
|
18212
|
-
}
|
|
18213
|
-
async backlinksPruneCache(release) {
|
|
18214
|
-
return this.request("DELETE", `/backlinks/cache/${encodeURIComponent(release)}`);
|
|
18215
|
-
}
|
|
18216
|
-
async backlinksExtract(project, release) {
|
|
18217
|
-
return this.request("POST", `/projects/${encodeURIComponent(project)}/backlinks/extract`, release ? { release } : {});
|
|
18218
|
-
}
|
|
18219
|
-
async backlinksSummary(project, release) {
|
|
18220
|
-
const qs = release ? `?release=${encodeURIComponent(release)}` : "";
|
|
18221
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/backlinks/summary${qs}`);
|
|
18222
|
-
}
|
|
18223
|
-
async backlinksDomains(project, opts = {}) {
|
|
18224
|
-
const qs = new URLSearchParams();
|
|
18225
|
-
if (opts.limit !== void 0) qs.set("limit", String(opts.limit));
|
|
18226
|
-
if (opts.offset !== void 0) qs.set("offset", String(opts.offset));
|
|
18227
|
-
if (opts.release) qs.set("release", opts.release);
|
|
18228
|
-
const suffix = qs.toString() ? `?${qs.toString()}` : "";
|
|
18229
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/backlinks/domains${suffix}`);
|
|
18230
|
-
}
|
|
18231
|
-
async backlinksHistory(project) {
|
|
18232
|
-
return this.request("GET", `/projects/${encodeURIComponent(project)}/backlinks/history`);
|
|
18233
|
-
}
|
|
18234
|
-
};
|
|
18235
|
-
|
|
18236
16349
|
// src/snapshot-service.ts
|
|
18237
16350
|
import { runAeoAudit } from "@ainyc/aeo-audit";
|
|
18238
16351
|
|
|
@@ -18255,13 +16368,13 @@ function extractHostname(domain) {
|
|
|
18255
16368
|
function fetchWithPinnedAddress(target) {
|
|
18256
16369
|
return new Promise((resolve) => {
|
|
18257
16370
|
const port = target.url.port ? Number(target.url.port) : 443;
|
|
18258
|
-
const
|
|
16371
|
+
const path15 = target.url.pathname + target.url.search;
|
|
18259
16372
|
const req = https2.request(
|
|
18260
16373
|
{
|
|
18261
16374
|
hostname: target.address,
|
|
18262
16375
|
family: target.family,
|
|
18263
16376
|
port,
|
|
18264
|
-
path:
|
|
16377
|
+
path: path15,
|
|
18265
16378
|
method: "GET",
|
|
18266
16379
|
timeout: FETCH_TIMEOUT_MS,
|
|
18267
16380
|
servername: target.url.hostname,
|
|
@@ -18353,7 +16466,7 @@ function formatAuditFactorScore(factor) {
|
|
|
18353
16466
|
}
|
|
18354
16467
|
|
|
18355
16468
|
// src/snapshot-service.ts
|
|
18356
|
-
var
|
|
16469
|
+
var log12 = createLogger("Snapshot");
|
|
18357
16470
|
var ANALYSIS_PROVIDER_PRIORITY = ["openai", "claude", "gemini", "perplexity", "local"];
|
|
18358
16471
|
var SNAPSHOT_QUERY_COUNT = 6;
|
|
18359
16472
|
var ProviderExecutionGate2 = class {
|
|
@@ -18496,7 +16609,7 @@ var SnapshotService = class {
|
|
|
18496
16609
|
return mapAuditReport(report);
|
|
18497
16610
|
} catch (err) {
|
|
18498
16611
|
const message = err instanceof Error ? err.message : String(err);
|
|
18499
|
-
|
|
16612
|
+
log12.warn("audit.failed", { homepageUrl, error: message });
|
|
18500
16613
|
return {
|
|
18501
16614
|
url: homepageUrl,
|
|
18502
16615
|
finalUrl: homepageUrl,
|
|
@@ -18526,7 +16639,7 @@ var SnapshotService = class {
|
|
|
18526
16639
|
phrases: parsedPhrases
|
|
18527
16640
|
};
|
|
18528
16641
|
} catch (err) {
|
|
18529
|
-
|
|
16642
|
+
log12.warn("profile.generation-failed", {
|
|
18530
16643
|
domain: ctx.domain,
|
|
18531
16644
|
provider: ctx.analysisProvider.adapter.name,
|
|
18532
16645
|
error: err instanceof Error ? err.message : String(err)
|
|
@@ -18668,7 +16781,7 @@ var SnapshotService = class {
|
|
|
18668
16781
|
recommendedActions: uniqueStrings(parsed.recommendedActions ?? []).slice(0, 4)
|
|
18669
16782
|
};
|
|
18670
16783
|
} catch (err) {
|
|
18671
|
-
|
|
16784
|
+
log12.warn("response.analysis-failed", {
|
|
18672
16785
|
provider: ctx.analysisProvider.adapter.name,
|
|
18673
16786
|
error: err instanceof Error ? err.message : String(err)
|
|
18674
16787
|
});
|
|
@@ -18953,7 +17066,7 @@ function clipText(value, length) {
|
|
|
18953
17066
|
// src/server.ts
|
|
18954
17067
|
var _require2 = createRequire3(import.meta.url);
|
|
18955
17068
|
var { version: PKG_VERSION } = _require2("../package.json");
|
|
18956
|
-
var
|
|
17069
|
+
var log13 = createLogger("Server");
|
|
18957
17070
|
var DEFAULT_QUOTA = {
|
|
18958
17071
|
maxConcurrency: 2,
|
|
18959
17072
|
maxRequestsPerMinute: 10,
|
|
@@ -19040,7 +17153,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
19040
17153
|
}
|
|
19041
17154
|
if (migratedGoogle > 0) {
|
|
19042
17155
|
saveConfigPatch({ google: config.google });
|
|
19043
|
-
|
|
17156
|
+
log13.info("credentials.migrated", { type: "google", count: migratedGoogle });
|
|
19044
17157
|
}
|
|
19045
17158
|
let migratedGa4 = 0;
|
|
19046
17159
|
for (const row of rows.ga4) {
|
|
@@ -19058,7 +17171,7 @@ function applyLegacyCredentials(rows, config) {
|
|
|
19058
17171
|
}
|
|
19059
17172
|
if (migratedGa4 > 0) {
|
|
19060
17173
|
saveConfigPatch({ ga4: config.ga4 });
|
|
19061
|
-
|
|
17174
|
+
log13.info("credentials.migrated", { type: "ga4", count: migratedGa4 });
|
|
19062
17175
|
}
|
|
19063
17176
|
}
|
|
19064
17177
|
async function createServer(opts) {
|
|
@@ -19090,11 +17203,11 @@ async function createServer(opts) {
|
|
|
19090
17203
|
applyLegacyCredentials(legacyRows, opts.config);
|
|
19091
17204
|
dropLegacyCredentialColumns(opts.db);
|
|
19092
17205
|
} catch (err) {
|
|
19093
|
-
|
|
17206
|
+
log13.warn("credentials.migration.failed", {
|
|
19094
17207
|
error: err instanceof Error ? err.message : String(err)
|
|
19095
17208
|
});
|
|
19096
17209
|
}
|
|
19097
|
-
|
|
17210
|
+
log13.info("providers.configured", { providers: Object.keys(providers).filter((k) => {
|
|
19098
17211
|
const p = providers[k];
|
|
19099
17212
|
return p?.apiKey || p?.baseUrl || p?.vertexProject;
|
|
19100
17213
|
}) });
|
|
@@ -19154,8 +17267,8 @@ async function createServer(opts) {
|
|
|
19154
17267
|
);
|
|
19155
17268
|
jobRunner.onRunCompleted = (runId, projectId) => runCoordinator.onRunCompleted(runId, projectId);
|
|
19156
17269
|
const snapshotService = new SnapshotService(registry);
|
|
19157
|
-
const orphanedOpenClawDir =
|
|
19158
|
-
if (
|
|
17270
|
+
const orphanedOpenClawDir = path14.join(os5.homedir(), ".openclaw-aero");
|
|
17271
|
+
if (fs12.existsSync(orphanedOpenClawDir)) {
|
|
19159
17272
|
app.log.warn(
|
|
19160
17273
|
{ path: orphanedOpenClawDir },
|
|
19161
17274
|
"OpenClaw gateway is no longer used. Remove ~/.openclaw-aero/ manually to reclaim the directory."
|
|
@@ -19749,10 +17862,10 @@ async function createServer(opts) {
|
|
|
19749
17862
|
return snapshotService.createReport(input);
|
|
19750
17863
|
}
|
|
19751
17864
|
});
|
|
19752
|
-
const dirname =
|
|
19753
|
-
const assetsDir =
|
|
19754
|
-
if (
|
|
19755
|
-
const indexPath =
|
|
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");
|
|
19756
17869
|
const injectConfig = (html) => {
|
|
19757
17870
|
const clientConfig = {};
|
|
19758
17871
|
if (basePath) clientConfig.basePath = basePath;
|
|
@@ -19770,8 +17883,8 @@ async function createServer(opts) {
|
|
|
19770
17883
|
index: false
|
|
19771
17884
|
});
|
|
19772
17885
|
const serveIndex = (_request, reply) => {
|
|
19773
|
-
if (
|
|
19774
|
-
const html =
|
|
17886
|
+
if (fs12.existsSync(indexPath)) {
|
|
17887
|
+
const html = fs12.readFileSync(indexPath, "utf-8");
|
|
19775
17888
|
return reply.type("text/html").send(injectConfig(html));
|
|
19776
17889
|
}
|
|
19777
17890
|
return reply.status(404).send({ error: "Dashboard not built" });
|
|
@@ -19791,8 +17904,8 @@ async function createServer(opts) {
|
|
|
19791
17904
|
if (basePath && !url.startsWith(basePath)) {
|
|
19792
17905
|
return reply.status(404).send({ error: "Not found", path: request.url });
|
|
19793
17906
|
}
|
|
19794
|
-
if (
|
|
19795
|
-
const html =
|
|
17907
|
+
if (fs12.existsSync(indexPath)) {
|
|
17908
|
+
const html = fs12.readFileSync(indexPath, "utf-8");
|
|
19796
17909
|
return reply.type("text/html").send(injectConfig(html));
|
|
19797
17910
|
}
|
|
19798
17911
|
return reply.status(404).send({ error: "Not found" });
|
|
@@ -19861,31 +17974,11 @@ function parseKeywordResponse(raw, count) {
|
|
|
19861
17974
|
}
|
|
19862
17975
|
|
|
19863
17976
|
export {
|
|
19864
|
-
getConfigDir,
|
|
19865
|
-
getConfigPath,
|
|
19866
|
-
loadConfig,
|
|
19867
|
-
saveConfig,
|
|
19868
|
-
saveConfigPatch,
|
|
19869
|
-
configExists,
|
|
19870
17977
|
isTelemetryEnabled,
|
|
19871
17978
|
getOrCreateAnonymousId,
|
|
19872
17979
|
isFirstRun,
|
|
19873
17980
|
showFirstRunNotice,
|
|
19874
17981
|
trackEvent,
|
|
19875
|
-
EXIT_SYSTEM_ERROR,
|
|
19876
|
-
CliError,
|
|
19877
|
-
usageError,
|
|
19878
|
-
isEndpointMissing,
|
|
19879
|
-
printCliError,
|
|
19880
|
-
providerQuotaPolicySchema,
|
|
19881
|
-
ProviderNames,
|
|
19882
|
-
resolveProviderInput,
|
|
19883
|
-
notificationEventSchema,
|
|
19884
|
-
effectiveDomains,
|
|
19885
|
-
RunStatuses,
|
|
19886
|
-
RunKinds,
|
|
19887
|
-
determineAnswerMentioned,
|
|
19888
|
-
CcReleaseSyncStatuses,
|
|
19889
17982
|
reparseStoredResult2 as reparseStoredResult,
|
|
19890
17983
|
reparseStoredResult3 as reparseStoredResult2,
|
|
19891
17984
|
reparseStoredResult as reparseStoredResult3,
|
|
@@ -19893,7 +17986,6 @@ export {
|
|
|
19893
17986
|
determineCitationState,
|
|
19894
17987
|
computeCompetitorOverlap,
|
|
19895
17988
|
extractRecommendedCompetitors,
|
|
19896
|
-
createApiClient,
|
|
19897
17989
|
setGoogleAuthConfig,
|
|
19898
17990
|
formatAuditFactorScore,
|
|
19899
17991
|
listAgentProviders,
|