@ainyc/canonry 1.35.0 → 1.36.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-B9SBdBOm.css → index-CborW-lk.css} +1 -1
- package/assets/assets/index-Du_w835k.js +281 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-ETP5IOHC.js → chunk-PZKK53EX.js} +116 -51
- package/dist/cli.js +6 -1
- package/dist/index.js +1 -1
- package/package.json +4 -4
- package/assets/assets/index-DyipkdOb.js +0 -281
package/assets/index.html
CHANGED
|
@@ -12,8 +12,8 @@
|
|
|
12
12
|
<link rel="icon" type="image/png" sizes="32x32" href="./favicon-32.png" />
|
|
13
13
|
<link rel="apple-touch-icon" href="./apple-touch-icon.png" />
|
|
14
14
|
<title>Canonry</title>
|
|
15
|
-
<script type="module" crossorigin src="./assets/index-
|
|
16
|
-
<link rel="stylesheet" crossorigin href="./assets/index-
|
|
15
|
+
<script type="module" crossorigin src="./assets/index-Du_w835k.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-CborW-lk.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -1081,6 +1081,12 @@ var ga4AiReferralHistoryEntrySchema = z11.object({
|
|
|
1081
1081
|
sessions: z11.number(),
|
|
1082
1082
|
users: z11.number()
|
|
1083
1083
|
});
|
|
1084
|
+
var ga4SessionHistoryEntrySchema = z11.object({
|
|
1085
|
+
date: z11.string(),
|
|
1086
|
+
sessions: z11.number(),
|
|
1087
|
+
organicSessions: z11.number(),
|
|
1088
|
+
users: z11.number()
|
|
1089
|
+
});
|
|
1084
1090
|
|
|
1085
1091
|
// ../contracts/src/answer-visibility.ts
|
|
1086
1092
|
var GENERIC_TOKENS = /* @__PURE__ */ new Set([
|
|
@@ -1519,6 +1525,7 @@ function createClient(databasePath) {
|
|
|
1519
1525
|
const sqlite = new Database(databasePath);
|
|
1520
1526
|
sqlite.pragma("journal_mode = WAL");
|
|
1521
1527
|
sqlite.pragma("foreign_keys = ON");
|
|
1528
|
+
sqlite.pragma("busy_timeout = 5000");
|
|
1522
1529
|
return drizzle(sqlite, { schema: schema_exports });
|
|
1523
1530
|
}
|
|
1524
1531
|
|
|
@@ -1846,7 +1853,14 @@ var MIGRATIONS = [
|
|
|
1846
1853
|
`CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_source ON ga_ai_referrals(source)`,
|
|
1847
1854
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)`,
|
|
1848
1855
|
// v18: Answer-level visibility derived from answer text
|
|
1849
|
-
`ALTER TABLE query_snapshots ADD COLUMN answer_mentioned INTEGER
|
|
1856
|
+
`ALTER TABLE query_snapshots ADD COLUMN answer_mentioned INTEGER`,
|
|
1857
|
+
// v19: Add named unique indexes and missing columns from early tables
|
|
1858
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_keywords_project_keyword ON keywords(project_id, keyword)`,
|
|
1859
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_competitors_project_domain ON competitors(project_id, domain)`,
|
|
1860
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_schedules_project ON schedules(project_id)`,
|
|
1861
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_scope_period_metric ON usage_counters(scope, period, metric)`,
|
|
1862
|
+
`ALTER TABLE projects ADD COLUMN config_source TEXT NOT NULL DEFAULT 'cli'`,
|
|
1863
|
+
`ALTER TABLE projects ADD COLUMN config_revision INTEGER NOT NULL DEFAULT 1`
|
|
1850
1864
|
];
|
|
1851
1865
|
function migrate(db) {
|
|
1852
1866
|
const statements = MIGRATION_SQL.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -5773,6 +5787,18 @@ var routeCatalog = [
|
|
|
5773
5787
|
404: { description: "Project not found." }
|
|
5774
5788
|
}
|
|
5775
5789
|
},
|
|
5790
|
+
{
|
|
5791
|
+
method: "get",
|
|
5792
|
+
path: "/api/v1/projects/{name}/ga/session-history",
|
|
5793
|
+
summary: "Get total sessions per day for the project",
|
|
5794
|
+
tags: ["ga4"],
|
|
5795
|
+
parameters: [nameParameter],
|
|
5796
|
+
responses: {
|
|
5797
|
+
200: { description: "Session history returned." },
|
|
5798
|
+
400: { description: "GA4 is not connected." },
|
|
5799
|
+
404: { description: "Project not found." }
|
|
5800
|
+
}
|
|
5801
|
+
},
|
|
5776
5802
|
{
|
|
5777
5803
|
method: "get",
|
|
5778
5804
|
path: "/api/v1/projects/{name}/ga/coverage",
|
|
@@ -8504,6 +8530,22 @@ async function ga4Routes(app, opts) {
|
|
|
8504
8530
|
}).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).orderBy(gaAiReferrals.date).all();
|
|
8505
8531
|
return rows;
|
|
8506
8532
|
});
|
|
8533
|
+
app.get("/projects/:name/ga/session-history", async (request, _reply) => {
|
|
8534
|
+
const project = resolveProject(app.db, request.params.name);
|
|
8535
|
+
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
8536
|
+
const rows = app.db.select({
|
|
8537
|
+
date: gaTrafficSnapshots.date,
|
|
8538
|
+
sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
|
|
8539
|
+
organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
8540
|
+
users: sql4`SUM(${gaTrafficSnapshots.users})`
|
|
8541
|
+
}).from(gaTrafficSnapshots).where(eq16(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
8542
|
+
return rows.map((r) => ({
|
|
8543
|
+
date: r.date,
|
|
8544
|
+
sessions: r.sessions ?? 0,
|
|
8545
|
+
organicSessions: r.organicSessions ?? 0,
|
|
8546
|
+
users: r.users ?? 0
|
|
8547
|
+
}));
|
|
8548
|
+
});
|
|
8507
8549
|
app.get("/projects/:name/ga/coverage", async (request, _reply) => {
|
|
8508
8550
|
const project = resolveProject(app.db, request.params.name);
|
|
8509
8551
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
@@ -8930,12 +8972,20 @@ async function getSiteStatus(connection, env) {
|
|
|
8930
8972
|
async function listActivePlugins(connection, env) {
|
|
8931
8973
|
const site = resolveEnvironment(connection, env);
|
|
8932
8974
|
try {
|
|
8933
|
-
const
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
8975
|
+
const allPlugins = [];
|
|
8976
|
+
let page = 1;
|
|
8977
|
+
let totalPages = 1;
|
|
8978
|
+
while (page <= totalPages) {
|
|
8979
|
+
const { body, response } = await fetchJson(
|
|
8980
|
+
connection,
|
|
8981
|
+
site.siteUrl,
|
|
8982
|
+
`/wp-json/wp/v2/plugins?per_page=100&page=${page}&_fields=plugin,status`
|
|
8983
|
+
);
|
|
8984
|
+
totalPages = Number.parseInt(response.headers.get("x-wp-totalpages") ?? "1", 10) || 1;
|
|
8985
|
+
allPlugins.push(...body);
|
|
8986
|
+
page += 1;
|
|
8987
|
+
}
|
|
8988
|
+
return allPlugins.filter((plugin) => plugin.status === "active").map((plugin) => plugin.plugin).sort();
|
|
8939
8989
|
} catch (error) {
|
|
8940
8990
|
if (error instanceof WordpressApiError && (error.statusCode === 403 || error.statusCode === 404)) {
|
|
8941
8991
|
return null;
|
|
@@ -10743,7 +10793,7 @@ function extractCitedDomains2(raw) {
|
|
|
10743
10793
|
function extractDomainFromUri2(uri) {
|
|
10744
10794
|
try {
|
|
10745
10795
|
const url = new URL(uri);
|
|
10746
|
-
return url.hostname.replace(/^www\./, "");
|
|
10796
|
+
return url.hostname.replace(/^www\./, "").toLowerCase();
|
|
10747
10797
|
} catch {
|
|
10748
10798
|
return null;
|
|
10749
10799
|
}
|
|
@@ -10751,11 +10801,11 @@ function extractDomainFromUri2(uri) {
|
|
|
10751
10801
|
async function generateText2(prompt, config) {
|
|
10752
10802
|
const model = config.model ?? DEFAULT_MODEL2;
|
|
10753
10803
|
const client = new OpenAI({ apiKey: config.apiKey });
|
|
10754
|
-
const response = await client.
|
|
10804
|
+
const response = await client.responses.create({
|
|
10755
10805
|
model,
|
|
10756
|
-
|
|
10806
|
+
input: prompt
|
|
10757
10807
|
});
|
|
10758
|
-
return response
|
|
10808
|
+
return extractResponseText(response);
|
|
10759
10809
|
}
|
|
10760
10810
|
function responseToRecord2(response) {
|
|
10761
10811
|
try {
|
|
@@ -11004,7 +11054,7 @@ function extractCitedDomains3(raw) {
|
|
|
11004
11054
|
function extractDomainFromUri3(uri) {
|
|
11005
11055
|
try {
|
|
11006
11056
|
const url = new URL(uri);
|
|
11007
|
-
return url.hostname.replace(/^www\./, "");
|
|
11057
|
+
return url.hostname.replace(/^www\./, "").toLowerCase();
|
|
11008
11058
|
} catch {
|
|
11009
11059
|
return null;
|
|
11010
11060
|
}
|
|
@@ -11170,7 +11220,7 @@ async function executeTrackedQuery4(input) {
|
|
|
11170
11220
|
});
|
|
11171
11221
|
return {
|
|
11172
11222
|
provider: "local",
|
|
11173
|
-
rawResponse:
|
|
11223
|
+
rawResponse: responseToRecord4(response),
|
|
11174
11224
|
model,
|
|
11175
11225
|
groundingSources: [],
|
|
11176
11226
|
searchQueries: []
|
|
@@ -11225,6 +11275,13 @@ function extractDomainMentions(text2) {
|
|
|
11225
11275
|
}
|
|
11226
11276
|
return [...domains];
|
|
11227
11277
|
}
|
|
11278
|
+
function responseToRecord4(response) {
|
|
11279
|
+
try {
|
|
11280
|
+
return JSON.parse(JSON.stringify(response));
|
|
11281
|
+
} catch {
|
|
11282
|
+
return { error: "failed to serialize response" };
|
|
11283
|
+
}
|
|
11284
|
+
}
|
|
11228
11285
|
|
|
11229
11286
|
// ../provider-local/src/adapter.ts
|
|
11230
11287
|
function toLocalConfig(config) {
|
|
@@ -11754,6 +11811,10 @@ function getConnection(config) {
|
|
|
11754
11811
|
if (parts.length >= 1 && parts[0]) host = parts[0];
|
|
11755
11812
|
if (parts.length >= 2 && parts[1]) port = parseInt(parts[1], 10) || 9222;
|
|
11756
11813
|
if (!sharedConnection || sharedConnection.endpoint !== `${host}:${port}`) {
|
|
11814
|
+
if (sharedConnection) {
|
|
11815
|
+
sharedConnection.disconnect().catch(() => {
|
|
11816
|
+
});
|
|
11817
|
+
}
|
|
11757
11818
|
sharedConnection = new CDPConnectionManager(host, port);
|
|
11758
11819
|
}
|
|
11759
11820
|
return sharedConnection;
|
|
@@ -11903,7 +11964,7 @@ async function executeTrackedQuery5(input) {
|
|
|
11903
11964
|
{ role: "user", content: prompt }
|
|
11904
11965
|
]
|
|
11905
11966
|
});
|
|
11906
|
-
const rawResponse =
|
|
11967
|
+
const rawResponse = responseToRecord5(response);
|
|
11907
11968
|
const citations = extractCitations(rawResponse);
|
|
11908
11969
|
const groundingSources = citations.map((url) => ({
|
|
11909
11970
|
uri: url,
|
|
@@ -11967,7 +12028,7 @@ function extractCitedDomains5(groundingSources) {
|
|
|
11967
12028
|
function extractDomainFromUri4(uri) {
|
|
11968
12029
|
try {
|
|
11969
12030
|
const url = new URL(uri);
|
|
11970
|
-
return url.hostname.replace(/^www\./, "");
|
|
12031
|
+
return url.hostname.replace(/^www\./, "").toLowerCase();
|
|
11971
12032
|
} catch {
|
|
11972
12033
|
return null;
|
|
11973
12034
|
}
|
|
@@ -11981,7 +12042,7 @@ async function generateText5(prompt, config) {
|
|
|
11981
12042
|
});
|
|
11982
12043
|
return response.choices[0]?.message?.content ?? "";
|
|
11983
12044
|
}
|
|
11984
|
-
function
|
|
12045
|
+
function responseToRecord5(response) {
|
|
11985
12046
|
try {
|
|
11986
12047
|
return JSON.parse(JSON.stringify(response));
|
|
11987
12048
|
} catch {
|
|
@@ -12613,7 +12674,7 @@ var JobRunner = class {
|
|
|
12613
12674
|
});
|
|
12614
12675
|
if (this.onRunCompleted) {
|
|
12615
12676
|
this.onRunCompleted(runId, projectId).catch((notifErr) => {
|
|
12616
|
-
|
|
12677
|
+
log.error("notification.callback-failed", { runId, error: notifErr instanceof Error ? notifErr.message : String(notifErr) });
|
|
12617
12678
|
});
|
|
12618
12679
|
}
|
|
12619
12680
|
}
|
|
@@ -13313,45 +13374,49 @@ var Scheduler = class {
|
|
|
13313
13374
|
log4.info("cron.registered", { projectId, schedule: label, timezone });
|
|
13314
13375
|
}
|
|
13315
13376
|
triggerRun(scheduleId, projectId) {
|
|
13316
|
-
|
|
13317
|
-
|
|
13318
|
-
|
|
13319
|
-
|
|
13320
|
-
|
|
13321
|
-
|
|
13322
|
-
|
|
13323
|
-
|
|
13324
|
-
|
|
13325
|
-
|
|
13326
|
-
|
|
13327
|
-
|
|
13328
|
-
|
|
13329
|
-
|
|
13330
|
-
|
|
13331
|
-
|
|
13332
|
-
|
|
13333
|
-
|
|
13334
|
-
|
|
13335
|
-
|
|
13336
|
-
|
|
13337
|
-
|
|
13338
|
-
|
|
13377
|
+
try {
|
|
13378
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13379
|
+
const currentSchedule = this.db.select().from(schedules).where(eq20(schedules.id, scheduleId)).get();
|
|
13380
|
+
if (!currentSchedule || currentSchedule.enabled !== 1) {
|
|
13381
|
+
log4.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
|
|
13382
|
+
this.remove(projectId);
|
|
13383
|
+
return;
|
|
13384
|
+
}
|
|
13385
|
+
const task = this.tasks.get(projectId);
|
|
13386
|
+
const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
|
|
13387
|
+
const project = this.db.select().from(projects).where(eq20(projects.id, projectId)).get();
|
|
13388
|
+
if (!project) {
|
|
13389
|
+
log4.error("project.not-found", { projectId, msg: "skipping scheduled run" });
|
|
13390
|
+
this.remove(projectId);
|
|
13391
|
+
return;
|
|
13392
|
+
}
|
|
13393
|
+
const queueResult = queueRunIfProjectIdle(this.db, {
|
|
13394
|
+
createdAt: now,
|
|
13395
|
+
kind: "answer-visibility",
|
|
13396
|
+
projectId,
|
|
13397
|
+
trigger: "scheduled"
|
|
13398
|
+
});
|
|
13399
|
+
if (queueResult.conflict) {
|
|
13400
|
+
log4.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
13401
|
+
this.db.update(schedules).set({
|
|
13402
|
+
nextRunAt,
|
|
13403
|
+
updatedAt: now
|
|
13404
|
+
}).where(eq20(schedules.id, currentSchedule.id)).run();
|
|
13405
|
+
return;
|
|
13406
|
+
}
|
|
13407
|
+
const runId = queueResult.runId;
|
|
13339
13408
|
this.db.update(schedules).set({
|
|
13409
|
+
lastRunAt: now,
|
|
13340
13410
|
nextRunAt,
|
|
13341
13411
|
updatedAt: now
|
|
13342
13412
|
}).where(eq20(schedules.id, currentSchedule.id)).run();
|
|
13343
|
-
|
|
13413
|
+
const scheduleProviders = JSON.parse(currentSchedule.providers);
|
|
13414
|
+
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
13415
|
+
log4.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
13416
|
+
this.callbacks.onRunCreated(runId, projectId, providers);
|
|
13417
|
+
} catch (err) {
|
|
13418
|
+
log4.error("trigger.error", { scheduleId, projectId, error: err instanceof Error ? err.message : String(err) });
|
|
13344
13419
|
}
|
|
13345
|
-
const runId = queueResult.runId;
|
|
13346
|
-
this.db.update(schedules).set({
|
|
13347
|
-
lastRunAt: now,
|
|
13348
|
-
nextRunAt,
|
|
13349
|
-
updatedAt: now
|
|
13350
|
-
}).where(eq20(schedules.id, currentSchedule.id)).run();
|
|
13351
|
-
const scheduleProviders = JSON.parse(currentSchedule.providers);
|
|
13352
|
-
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
13353
|
-
log4.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
13354
|
-
this.callbacks.onRunCreated(runId, projectId, providers);
|
|
13355
13420
|
}
|
|
13356
13421
|
};
|
|
13357
13422
|
|
package/dist/cli.js
CHANGED
|
@@ -26,7 +26,7 @@ import {
|
|
|
26
26
|
setGoogleAuthConfig,
|
|
27
27
|
showFirstRunNotice,
|
|
28
28
|
trackEvent
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-PZKK53EX.js";
|
|
30
30
|
|
|
31
31
|
// src/cli.ts
|
|
32
32
|
import { pathToFileURL } from "url";
|
|
@@ -670,6 +670,9 @@ var ApiClient = class {
|
|
|
670
670
|
async gaAiReferralHistory(project) {
|
|
671
671
|
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/ai-referral-history`);
|
|
672
672
|
}
|
|
673
|
+
async gaSessionHistory(project) {
|
|
674
|
+
return this.request("GET", `/projects/${encodeURIComponent(project)}/ga/session-history`);
|
|
675
|
+
}
|
|
673
676
|
async wordpressConnect(project, body) {
|
|
674
677
|
return this.request("POST", `/projects/${encodeURIComponent(project)}/wordpress/connect`, body);
|
|
675
678
|
}
|
|
@@ -2631,6 +2634,7 @@ async function importKeywords(project, filePath, format) {
|
|
|
2631
2634
|
throw new CliError({
|
|
2632
2635
|
code: "KEYWORD_IMPORT_FILE_NOT_FOUND",
|
|
2633
2636
|
message: `File not found: ${filePath}`,
|
|
2637
|
+
displayMessage: `Error: file not found: ${filePath}`,
|
|
2634
2638
|
details: {
|
|
2635
2639
|
project,
|
|
2636
2640
|
filePath
|
|
@@ -4852,6 +4856,7 @@ var envSchema = z.object({
|
|
|
4852
4856
|
WORKER_PORT: z.coerce.number().int().positive().default(3001),
|
|
4853
4857
|
WEB_PORT: z.coerce.number().int().positive().default(4173),
|
|
4854
4858
|
BOOTSTRAP_SECRET: z.string().default("change-me"),
|
|
4859
|
+
CANONRY_BASE_PATH: z.string().default("/"),
|
|
4855
4860
|
// Gemini
|
|
4856
4861
|
GEMINI_API_KEY: z.string().optional(),
|
|
4857
4862
|
GEMINI_MODEL: z.string().optional(),
|
package/dist/index.js
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.36.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "The ultimate open-source AEO monitoring tool - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -56,17 +56,17 @@
|
|
|
56
56
|
"tsx": "^4.19.0",
|
|
57
57
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
58
58
|
"@ainyc/canonry-config": "0.0.0",
|
|
59
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
59
60
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
60
61
|
"@ainyc/canonry-db": "0.0.0",
|
|
61
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
62
62
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
63
63
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
64
64
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
65
65
|
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
66
|
-
"@ainyc/canonry-provider-local": "0.0.0",
|
|
67
66
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
67
|
+
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
68
68
|
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
69
|
-
"@ainyc/canonry-provider-
|
|
69
|
+
"@ainyc/canonry-provider-local": "0.0.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "tsup && tsx build-web.ts",
|