@ainyc/canonry 1.35.0 → 1.37.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-BZX6FEZ4.css} +1 -1
- package/assets/assets/index-DR4zYsnw.js +281 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-ETP5IOHC.js → chunk-5MOWJJND.js} +221 -95
- package/dist/cli.js +20 -7
- package/dist/index.js +1 -1
- package/package.json +6 -6
- 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-DR4zYsnw.js"></script>
|
|
16
|
+
<link rel="stylesheet" crossorigin href="./assets/index-BZX6FEZ4.css">
|
|
17
17
|
</head>
|
|
18
18
|
<body>
|
|
19
19
|
<div id="root"></div>
|
|
@@ -1055,11 +1055,13 @@ var ga4TrafficSnapshotDtoSchema = z11.object({
|
|
|
1055
1055
|
organicSessions: z11.number(),
|
|
1056
1056
|
users: z11.number()
|
|
1057
1057
|
});
|
|
1058
|
+
var ga4SourceDimensionSchema = z11.enum(["session", "first_user", "manual_utm"]);
|
|
1058
1059
|
var ga4AiReferralDtoSchema = z11.object({
|
|
1059
1060
|
source: z11.string(),
|
|
1060
1061
|
medium: z11.string(),
|
|
1061
1062
|
sessions: z11.number(),
|
|
1062
|
-
users: z11.number()
|
|
1063
|
+
users: z11.number(),
|
|
1064
|
+
sourceDimension: ga4SourceDimensionSchema
|
|
1063
1065
|
});
|
|
1064
1066
|
var ga4TrafficSummaryDtoSchema = z11.object({
|
|
1065
1067
|
totalSessions: z11.number(),
|
|
@@ -1072,6 +1074,10 @@ var ga4TrafficSummaryDtoSchema = z11.object({
|
|
|
1072
1074
|
users: z11.number()
|
|
1073
1075
|
})),
|
|
1074
1076
|
aiReferrals: z11.array(ga4AiReferralDtoSchema),
|
|
1077
|
+
/** Deduped AI session total: MAX(sessions) per date+source+medium across attribution dimensions, then summed. */
|
|
1078
|
+
aiSessionsDeduped: z11.number(),
|
|
1079
|
+
/** Deduped AI user total: MAX(users) per date+source+medium across attribution dimensions, then summed. */
|
|
1080
|
+
aiUsersDeduped: z11.number(),
|
|
1075
1081
|
lastSyncedAt: z11.string().nullable()
|
|
1076
1082
|
});
|
|
1077
1083
|
var ga4AiReferralHistoryEntrySchema = z11.object({
|
|
@@ -1079,6 +1085,14 @@ var ga4AiReferralHistoryEntrySchema = z11.object({
|
|
|
1079
1085
|
source: z11.string(),
|
|
1080
1086
|
medium: z11.string(),
|
|
1081
1087
|
sessions: z11.number(),
|
|
1088
|
+
users: z11.number(),
|
|
1089
|
+
/** Which GA4 dimension this row came from: session (sessionSource), first_user (firstUserSource), or manual_utm (utm_source parameter) */
|
|
1090
|
+
sourceDimension: ga4SourceDimensionSchema
|
|
1091
|
+
});
|
|
1092
|
+
var ga4SessionHistoryEntrySchema = z11.object({
|
|
1093
|
+
date: z11.string(),
|
|
1094
|
+
sessions: z11.number(),
|
|
1095
|
+
organicSessions: z11.number(),
|
|
1082
1096
|
users: z11.number()
|
|
1083
1097
|
});
|
|
1084
1098
|
|
|
@@ -1481,13 +1495,15 @@ var gaAiReferrals = sqliteTable("ga_ai_referrals", {
|
|
|
1481
1495
|
date: text("date").notNull(),
|
|
1482
1496
|
source: text("source").notNull(),
|
|
1483
1497
|
medium: text("medium").notNull(),
|
|
1498
|
+
/** Which GA4 dimension produced this row: 'session' | 'first_user' | 'manual_utm' */
|
|
1499
|
+
sourceDimension: text("source_dimension").notNull().default("session"),
|
|
1484
1500
|
sessions: integer("sessions").notNull().default(0),
|
|
1485
1501
|
users: integer("users").notNull().default(0),
|
|
1486
1502
|
syncedAt: text("synced_at").notNull()
|
|
1487
1503
|
}, (table) => [
|
|
1488
1504
|
index("idx_ga_ai_ref_project_date").on(table.projectId, table.date),
|
|
1489
1505
|
index("idx_ga_ai_ref_source").on(table.source),
|
|
1490
|
-
uniqueIndex("
|
|
1506
|
+
uniqueIndex("idx_ga_ai_ref_unique_v2").on(table.projectId, table.date, table.source, table.medium, table.sourceDimension)
|
|
1491
1507
|
]);
|
|
1492
1508
|
var gaTrafficSummaries = sqliteTable("ga_traffic_summaries", {
|
|
1493
1509
|
id: text("id").primaryKey(),
|
|
@@ -1519,6 +1535,7 @@ function createClient(databasePath) {
|
|
|
1519
1535
|
const sqlite = new Database(databasePath);
|
|
1520
1536
|
sqlite.pragma("journal_mode = WAL");
|
|
1521
1537
|
sqlite.pragma("foreign_keys = ON");
|
|
1538
|
+
sqlite.pragma("busy_timeout = 5000");
|
|
1522
1539
|
return drizzle(sqlite, { schema: schema_exports });
|
|
1523
1540
|
}
|
|
1524
1541
|
|
|
@@ -1846,7 +1863,20 @@ var MIGRATIONS = [
|
|
|
1846
1863
|
`CREATE INDEX IF NOT EXISTS idx_ga_ai_ref_source ON ga_ai_referrals(source)`,
|
|
1847
1864
|
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique ON ga_ai_referrals(project_id, date, source, medium)`,
|
|
1848
1865
|
// v18: Answer-level visibility derived from answer text
|
|
1849
|
-
`ALTER TABLE query_snapshots ADD COLUMN answer_mentioned INTEGER
|
|
1866
|
+
`ALTER TABLE query_snapshots ADD COLUMN answer_mentioned INTEGER`,
|
|
1867
|
+
// v19: Add named unique indexes and missing columns from early tables
|
|
1868
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_keywords_project_keyword ON keywords(project_id, keyword)`,
|
|
1869
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_competitors_project_domain ON competitors(project_id, domain)`,
|
|
1870
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_schedules_project ON schedules(project_id)`,
|
|
1871
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_usage_scope_period_metric ON usage_counters(scope, period, metric)`,
|
|
1872
|
+
`ALTER TABLE projects ADD COLUMN config_source TEXT NOT NULL DEFAULT 'cli'`,
|
|
1873
|
+
`ALTER TABLE projects ADD COLUMN config_revision INTEGER NOT NULL DEFAULT 1`,
|
|
1874
|
+
// v20: Track which GA4 dimension produced each AI referral row
|
|
1875
|
+
// Values: 'session' (sessionSource), 'first_user' (firstUserSource), 'manual_utm' (manualSource/utm_source)
|
|
1876
|
+
`ALTER TABLE ga_ai_referrals ADD COLUMN source_dimension TEXT NOT NULL DEFAULT 'session'`,
|
|
1877
|
+
// Replace old unique index with one that includes source_dimension
|
|
1878
|
+
`DROP INDEX IF EXISTS idx_ga_ai_ref_unique`,
|
|
1879
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS idx_ga_ai_ref_unique_v2 ON ga_ai_referrals(project_id, date, source, medium, source_dimension)`
|
|
1850
1880
|
];
|
|
1851
1881
|
function migrate(db) {
|
|
1852
1882
|
const statements = MIGRATION_SQL.split(";").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
@@ -5773,6 +5803,18 @@ var routeCatalog = [
|
|
|
5773
5803
|
404: { description: "Project not found." }
|
|
5774
5804
|
}
|
|
5775
5805
|
},
|
|
5806
|
+
{
|
|
5807
|
+
method: "get",
|
|
5808
|
+
path: "/api/v1/projects/{name}/ga/session-history",
|
|
5809
|
+
summary: "Get total sessions per day for the project",
|
|
5810
|
+
tags: ["ga4"],
|
|
5811
|
+
parameters: [nameParameter],
|
|
5812
|
+
responses: {
|
|
5813
|
+
200: { description: "Session history returned." },
|
|
5814
|
+
400: { description: "GA4 is not connected." },
|
|
5815
|
+
404: { description: "Project not found." }
|
|
5816
|
+
}
|
|
5817
|
+
},
|
|
5776
5818
|
{
|
|
5777
5819
|
method: "get",
|
|
5778
5820
|
path: "/api/v1/projects/{name}/ga/coverage",
|
|
@@ -6608,7 +6650,10 @@ var AI_REFERRAL_SOURCE_FILTERS = [
|
|
|
6608
6650
|
{ matchType: "CONTAINS", value: "openai" },
|
|
6609
6651
|
{ matchType: "CONTAINS", value: "claude" },
|
|
6610
6652
|
{ matchType: "CONTAINS", value: "anthropic" },
|
|
6611
|
-
{ matchType: "CONTAINS", value: "copilot" }
|
|
6653
|
+
{ matchType: "CONTAINS", value: "copilot" },
|
|
6654
|
+
{ matchType: "CONTAINS", value: "phind" },
|
|
6655
|
+
{ matchType: "EXACT", value: "you.com" },
|
|
6656
|
+
{ matchType: "CONTAINS", value: "meta.ai" }
|
|
6612
6657
|
];
|
|
6613
6658
|
async function fetchTrafficByLandingPage(accessToken, propertyId, days) {
|
|
6614
6659
|
const syncDays = Math.min(Math.max(1, days ?? GA4_DEFAULT_SYNC_DAYS), GA4_MAX_SYNC_DAYS);
|
|
@@ -6747,52 +6792,75 @@ async function fetchAiReferrals(accessToken, propertyId, days) {
|
|
|
6747
6792
|
ga4Log("info", "fetch-ai-referrals.start", { propertyId, days: syncDays });
|
|
6748
6793
|
const PAGE_SIZE = 1e3;
|
|
6749
6794
|
const rows = [];
|
|
6750
|
-
|
|
6751
|
-
|
|
6752
|
-
|
|
6753
|
-
|
|
6754
|
-
|
|
6755
|
-
|
|
6756
|
-
|
|
6757
|
-
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6767
|
-
|
|
6768
|
-
|
|
6769
|
-
|
|
6770
|
-
|
|
6771
|
-
|
|
6772
|
-
|
|
6773
|
-
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6795
|
+
const dimensionPairs = [
|
|
6796
|
+
["sessionSource", "sessionMedium", "session"],
|
|
6797
|
+
["firstUserSource", "firstUserMedium", "first_user"],
|
|
6798
|
+
["manualSource", "manualMedium", "manual_utm"]
|
|
6799
|
+
];
|
|
6800
|
+
for (const [sourceDim, mediumDim, dimLabel] of dimensionPairs) {
|
|
6801
|
+
let offset = 0;
|
|
6802
|
+
while (true) {
|
|
6803
|
+
const request = {
|
|
6804
|
+
dateRanges: [{ startDate: formatDate(startDate), endDate: formatDate(endDate) }],
|
|
6805
|
+
dimensions: [
|
|
6806
|
+
{ name: "date" },
|
|
6807
|
+
{ name: sourceDim },
|
|
6808
|
+
{ name: mediumDim }
|
|
6809
|
+
],
|
|
6810
|
+
metrics: [
|
|
6811
|
+
{ name: "sessions" },
|
|
6812
|
+
{ name: "totalUsers" }
|
|
6813
|
+
],
|
|
6814
|
+
dimensionFilter: {
|
|
6815
|
+
orGroup: {
|
|
6816
|
+
expressions: AI_REFERRAL_SOURCE_FILTERS.map(({ matchType, value }) => ({
|
|
6817
|
+
filter: {
|
|
6818
|
+
fieldName: sourceDim,
|
|
6819
|
+
stringFilter: { matchType, value }
|
|
6820
|
+
}
|
|
6821
|
+
}))
|
|
6822
|
+
}
|
|
6823
|
+
},
|
|
6824
|
+
limit: PAGE_SIZE,
|
|
6825
|
+
offset
|
|
6826
|
+
};
|
|
6827
|
+
const response = await runReport(accessToken, propertyId, request);
|
|
6828
|
+
const pageRows = (response.rows ?? []).map((row) => ({
|
|
6829
|
+
date: row.dimensionValues[0].value,
|
|
6830
|
+
source: row.dimensionValues[1].value,
|
|
6831
|
+
medium: row.dimensionValues[2].value,
|
|
6832
|
+
sessions: parseInt(row.metricValues[0].value, 10) || 0,
|
|
6833
|
+
users: parseInt(row.metricValues[1].value, 10) || 0,
|
|
6834
|
+
sourceDimension: dimLabel
|
|
6835
|
+
}));
|
|
6836
|
+
rows.push(...pageRows);
|
|
6837
|
+
const totalRows = response.rowCount ?? 0;
|
|
6838
|
+
offset += pageRows.length;
|
|
6839
|
+
if (pageRows.length < PAGE_SIZE || offset >= totalRows) break;
|
|
6840
|
+
}
|
|
6788
6841
|
}
|
|
6842
|
+
const deduped = /* @__PURE__ */ new Map();
|
|
6789
6843
|
for (const row of rows) {
|
|
6844
|
+
const key = `${row.date}::${row.source}::${row.medium}::${row.sourceDimension}`;
|
|
6845
|
+
const existing = deduped.get(key);
|
|
6846
|
+
if (!existing) {
|
|
6847
|
+
deduped.set(key, row);
|
|
6848
|
+
} else {
|
|
6849
|
+
deduped.set(key, {
|
|
6850
|
+
...existing,
|
|
6851
|
+
sessions: Math.max(existing.sessions, row.sessions),
|
|
6852
|
+
users: Math.max(existing.users, row.users)
|
|
6853
|
+
});
|
|
6854
|
+
}
|
|
6855
|
+
}
|
|
6856
|
+
const dedupedRows = [...deduped.values()];
|
|
6857
|
+
for (const row of dedupedRows) {
|
|
6790
6858
|
if (row.date.length === 8 && !row.date.includes("-")) {
|
|
6791
6859
|
row.date = `${row.date.slice(0, 4)}-${row.date.slice(4, 6)}-${row.date.slice(6, 8)}`;
|
|
6792
6860
|
}
|
|
6793
6861
|
}
|
|
6794
|
-
ga4Log("info", "fetch-ai-referrals.done", { propertyId, rowCount:
|
|
6795
|
-
return
|
|
6862
|
+
ga4Log("info", "fetch-ai-referrals.done", { propertyId, rowCount: dedupedRows.length });
|
|
6863
|
+
return dedupedRows;
|
|
6796
6864
|
}
|
|
6797
6865
|
|
|
6798
6866
|
// ../api-routes/src/google.ts
|
|
@@ -8418,6 +8486,7 @@ async function ga4Routes(app, opts) {
|
|
|
8418
8486
|
date: row.date,
|
|
8419
8487
|
source: row.source,
|
|
8420
8488
|
medium: row.medium,
|
|
8489
|
+
sourceDimension: row.sourceDimension,
|
|
8421
8490
|
sessions: row.sessions,
|
|
8422
8491
|
users: row.users,
|
|
8423
8492
|
syncedAt: now
|
|
@@ -8469,9 +8538,23 @@ async function ga4Routes(app, opts) {
|
|
|
8469
8538
|
const aiReferrals = app.db.select({
|
|
8470
8539
|
source: gaAiReferrals.source,
|
|
8471
8540
|
medium: gaAiReferrals.medium,
|
|
8541
|
+
sourceDimension: gaAiReferrals.sourceDimension,
|
|
8472
8542
|
sessions: sql4`SUM(${gaAiReferrals.sessions})`,
|
|
8473
8543
|
users: sql4`SUM(${gaAiReferrals.users})`
|
|
8474
|
-
}).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).groupBy(gaAiReferrals.source, gaAiReferrals.medium).orderBy(sql4`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
8544
|
+
}).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).groupBy(gaAiReferrals.source, gaAiReferrals.medium, gaAiReferrals.sourceDimension).orderBy(sql4`SUM(${gaAiReferrals.sessions}) DESC`).all();
|
|
8545
|
+
const aiDeduped = app.db.select({
|
|
8546
|
+
sessions: sql4`SUM(max_sessions)`,
|
|
8547
|
+
users: sql4`SUM(max_users)`
|
|
8548
|
+
}).from(
|
|
8549
|
+
sql4`(
|
|
8550
|
+
SELECT date, source, medium,
|
|
8551
|
+
MAX(sessions) AS max_sessions,
|
|
8552
|
+
MAX(users) AS max_users
|
|
8553
|
+
FROM ga_ai_referrals
|
|
8554
|
+
WHERE project_id = ${project.id}
|
|
8555
|
+
GROUP BY date, source, medium
|
|
8556
|
+
)`
|
|
8557
|
+
).get();
|
|
8475
8558
|
const latestSync = app.db.select({ syncedAt: gaTrafficSummaries.syncedAt }).from(gaTrafficSummaries).where(eq16(gaTrafficSummaries.projectId, project.id)).orderBy(desc6(gaTrafficSummaries.syncedAt)).limit(1).get();
|
|
8476
8559
|
return {
|
|
8477
8560
|
totalSessions: summary?.totalSessions ?? 0,
|
|
@@ -8486,9 +8569,12 @@ async function ga4Routes(app, opts) {
|
|
|
8486
8569
|
aiReferrals: aiReferrals.map((r) => ({
|
|
8487
8570
|
source: r.source,
|
|
8488
8571
|
medium: r.medium,
|
|
8572
|
+
sourceDimension: r.sourceDimension,
|
|
8489
8573
|
sessions: r.sessions ?? 0,
|
|
8490
8574
|
users: r.users ?? 0
|
|
8491
8575
|
})),
|
|
8576
|
+
aiSessionsDeduped: aiDeduped?.sessions ?? 0,
|
|
8577
|
+
aiUsersDeduped: aiDeduped?.users ?? 0,
|
|
8492
8578
|
lastSyncedAt: latestSync?.syncedAt ?? null
|
|
8493
8579
|
};
|
|
8494
8580
|
});
|
|
@@ -8499,11 +8585,28 @@ async function ga4Routes(app, opts) {
|
|
|
8499
8585
|
date: gaAiReferrals.date,
|
|
8500
8586
|
source: gaAiReferrals.source,
|
|
8501
8587
|
medium: gaAiReferrals.medium,
|
|
8588
|
+
sourceDimension: gaAiReferrals.sourceDimension,
|
|
8502
8589
|
sessions: gaAiReferrals.sessions,
|
|
8503
8590
|
users: gaAiReferrals.users
|
|
8504
8591
|
}).from(gaAiReferrals).where(eq16(gaAiReferrals.projectId, project.id)).orderBy(gaAiReferrals.date).all();
|
|
8505
8592
|
return rows;
|
|
8506
8593
|
});
|
|
8594
|
+
app.get("/projects/:name/ga/session-history", async (request, _reply) => {
|
|
8595
|
+
const project = resolveProject(app.db, request.params.name);
|
|
8596
|
+
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
8597
|
+
const rows = app.db.select({
|
|
8598
|
+
date: gaTrafficSnapshots.date,
|
|
8599
|
+
sessions: sql4`SUM(${gaTrafficSnapshots.sessions})`,
|
|
8600
|
+
organicSessions: sql4`SUM(${gaTrafficSnapshots.organicSessions})`,
|
|
8601
|
+
users: sql4`SUM(${gaTrafficSnapshots.users})`
|
|
8602
|
+
}).from(gaTrafficSnapshots).where(eq16(gaTrafficSnapshots.projectId, project.id)).groupBy(gaTrafficSnapshots.date).orderBy(gaTrafficSnapshots.date).all();
|
|
8603
|
+
return rows.map((r) => ({
|
|
8604
|
+
date: r.date,
|
|
8605
|
+
sessions: r.sessions ?? 0,
|
|
8606
|
+
organicSessions: r.organicSessions ?? 0,
|
|
8607
|
+
users: r.users ?? 0
|
|
8608
|
+
}));
|
|
8609
|
+
});
|
|
8507
8610
|
app.get("/projects/:name/ga/coverage", async (request, _reply) => {
|
|
8508
8611
|
const project = resolveProject(app.db, request.params.name);
|
|
8509
8612
|
requireGa4Connection(opts, project.name, project.canonicalDomain);
|
|
@@ -8930,12 +9033,20 @@ async function getSiteStatus(connection, env) {
|
|
|
8930
9033
|
async function listActivePlugins(connection, env) {
|
|
8931
9034
|
const site = resolveEnvironment(connection, env);
|
|
8932
9035
|
try {
|
|
8933
|
-
const
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
|
|
8938
|
-
|
|
9036
|
+
const allPlugins = [];
|
|
9037
|
+
let page = 1;
|
|
9038
|
+
let totalPages = 1;
|
|
9039
|
+
while (page <= totalPages) {
|
|
9040
|
+
const { body, response } = await fetchJson(
|
|
9041
|
+
connection,
|
|
9042
|
+
site.siteUrl,
|
|
9043
|
+
`/wp-json/wp/v2/plugins?per_page=100&page=${page}&_fields=plugin,status`
|
|
9044
|
+
);
|
|
9045
|
+
totalPages = Number.parseInt(response.headers.get("x-wp-totalpages") ?? "1", 10) || 1;
|
|
9046
|
+
allPlugins.push(...body);
|
|
9047
|
+
page += 1;
|
|
9048
|
+
}
|
|
9049
|
+
return allPlugins.filter((plugin) => plugin.status === "active").map((plugin) => plugin.plugin).sort();
|
|
8939
9050
|
} catch (error) {
|
|
8940
9051
|
if (error instanceof WordpressApiError && (error.statusCode === 403 || error.statusCode === 404)) {
|
|
8941
9052
|
return null;
|
|
@@ -10743,7 +10854,7 @@ function extractCitedDomains2(raw) {
|
|
|
10743
10854
|
function extractDomainFromUri2(uri) {
|
|
10744
10855
|
try {
|
|
10745
10856
|
const url = new URL(uri);
|
|
10746
|
-
return url.hostname.replace(/^www\./, "");
|
|
10857
|
+
return url.hostname.replace(/^www\./, "").toLowerCase();
|
|
10747
10858
|
} catch {
|
|
10748
10859
|
return null;
|
|
10749
10860
|
}
|
|
@@ -10751,11 +10862,11 @@ function extractDomainFromUri2(uri) {
|
|
|
10751
10862
|
async function generateText2(prompt, config) {
|
|
10752
10863
|
const model = config.model ?? DEFAULT_MODEL2;
|
|
10753
10864
|
const client = new OpenAI({ apiKey: config.apiKey });
|
|
10754
|
-
const response = await client.
|
|
10865
|
+
const response = await client.responses.create({
|
|
10755
10866
|
model,
|
|
10756
|
-
|
|
10867
|
+
input: prompt
|
|
10757
10868
|
});
|
|
10758
|
-
return response
|
|
10869
|
+
return extractResponseText(response);
|
|
10759
10870
|
}
|
|
10760
10871
|
function responseToRecord2(response) {
|
|
10761
10872
|
try {
|
|
@@ -11004,7 +11115,7 @@ function extractCitedDomains3(raw) {
|
|
|
11004
11115
|
function extractDomainFromUri3(uri) {
|
|
11005
11116
|
try {
|
|
11006
11117
|
const url = new URL(uri);
|
|
11007
|
-
return url.hostname.replace(/^www\./, "");
|
|
11118
|
+
return url.hostname.replace(/^www\./, "").toLowerCase();
|
|
11008
11119
|
} catch {
|
|
11009
11120
|
return null;
|
|
11010
11121
|
}
|
|
@@ -11170,7 +11281,7 @@ async function executeTrackedQuery4(input) {
|
|
|
11170
11281
|
});
|
|
11171
11282
|
return {
|
|
11172
11283
|
provider: "local",
|
|
11173
|
-
rawResponse:
|
|
11284
|
+
rawResponse: responseToRecord4(response),
|
|
11174
11285
|
model,
|
|
11175
11286
|
groundingSources: [],
|
|
11176
11287
|
searchQueries: []
|
|
@@ -11225,6 +11336,13 @@ function extractDomainMentions(text2) {
|
|
|
11225
11336
|
}
|
|
11226
11337
|
return [...domains];
|
|
11227
11338
|
}
|
|
11339
|
+
function responseToRecord4(response) {
|
|
11340
|
+
try {
|
|
11341
|
+
return JSON.parse(JSON.stringify(response));
|
|
11342
|
+
} catch {
|
|
11343
|
+
return { error: "failed to serialize response" };
|
|
11344
|
+
}
|
|
11345
|
+
}
|
|
11228
11346
|
|
|
11229
11347
|
// ../provider-local/src/adapter.ts
|
|
11230
11348
|
function toLocalConfig(config) {
|
|
@@ -11754,6 +11872,10 @@ function getConnection(config) {
|
|
|
11754
11872
|
if (parts.length >= 1 && parts[0]) host = parts[0];
|
|
11755
11873
|
if (parts.length >= 2 && parts[1]) port = parseInt(parts[1], 10) || 9222;
|
|
11756
11874
|
if (!sharedConnection || sharedConnection.endpoint !== `${host}:${port}`) {
|
|
11875
|
+
if (sharedConnection) {
|
|
11876
|
+
sharedConnection.disconnect().catch(() => {
|
|
11877
|
+
});
|
|
11878
|
+
}
|
|
11757
11879
|
sharedConnection = new CDPConnectionManager(host, port);
|
|
11758
11880
|
}
|
|
11759
11881
|
return sharedConnection;
|
|
@@ -11903,7 +12025,7 @@ async function executeTrackedQuery5(input) {
|
|
|
11903
12025
|
{ role: "user", content: prompt }
|
|
11904
12026
|
]
|
|
11905
12027
|
});
|
|
11906
|
-
const rawResponse =
|
|
12028
|
+
const rawResponse = responseToRecord5(response);
|
|
11907
12029
|
const citations = extractCitations(rawResponse);
|
|
11908
12030
|
const groundingSources = citations.map((url) => ({
|
|
11909
12031
|
uri: url,
|
|
@@ -11967,7 +12089,7 @@ function extractCitedDomains5(groundingSources) {
|
|
|
11967
12089
|
function extractDomainFromUri4(uri) {
|
|
11968
12090
|
try {
|
|
11969
12091
|
const url = new URL(uri);
|
|
11970
|
-
return url.hostname.replace(/^www\./, "");
|
|
12092
|
+
return url.hostname.replace(/^www\./, "").toLowerCase();
|
|
11971
12093
|
} catch {
|
|
11972
12094
|
return null;
|
|
11973
12095
|
}
|
|
@@ -11981,7 +12103,7 @@ async function generateText5(prompt, config) {
|
|
|
11981
12103
|
});
|
|
11982
12104
|
return response.choices[0]?.message?.content ?? "";
|
|
11983
12105
|
}
|
|
11984
|
-
function
|
|
12106
|
+
function responseToRecord5(response) {
|
|
11985
12107
|
try {
|
|
11986
12108
|
return JSON.parse(JSON.stringify(response));
|
|
11987
12109
|
} catch {
|
|
@@ -12613,7 +12735,7 @@ var JobRunner = class {
|
|
|
12613
12735
|
});
|
|
12614
12736
|
if (this.onRunCompleted) {
|
|
12615
12737
|
this.onRunCompleted(runId, projectId).catch((notifErr) => {
|
|
12616
|
-
|
|
12738
|
+
log.error("notification.callback-failed", { runId, error: notifErr instanceof Error ? notifErr.message : String(notifErr) });
|
|
12617
12739
|
});
|
|
12618
12740
|
}
|
|
12619
12741
|
}
|
|
@@ -13313,45 +13435,49 @@ var Scheduler = class {
|
|
|
13313
13435
|
log4.info("cron.registered", { projectId, schedule: label, timezone });
|
|
13314
13436
|
}
|
|
13315
13437
|
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
|
-
|
|
13438
|
+
try {
|
|
13439
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
13440
|
+
const currentSchedule = this.db.select().from(schedules).where(eq20(schedules.id, scheduleId)).get();
|
|
13441
|
+
if (!currentSchedule || currentSchedule.enabled !== 1) {
|
|
13442
|
+
log4.warn("schedule.stale", { scheduleId, projectId, msg: "schedule no longer exists or is disabled" });
|
|
13443
|
+
this.remove(projectId);
|
|
13444
|
+
return;
|
|
13445
|
+
}
|
|
13446
|
+
const task = this.tasks.get(projectId);
|
|
13447
|
+
const nextRunAt = task?.getNextRun()?.toISOString() ?? null;
|
|
13448
|
+
const project = this.db.select().from(projects).where(eq20(projects.id, projectId)).get();
|
|
13449
|
+
if (!project) {
|
|
13450
|
+
log4.error("project.not-found", { projectId, msg: "skipping scheduled run" });
|
|
13451
|
+
this.remove(projectId);
|
|
13452
|
+
return;
|
|
13453
|
+
}
|
|
13454
|
+
const queueResult = queueRunIfProjectIdle(this.db, {
|
|
13455
|
+
createdAt: now,
|
|
13456
|
+
kind: "answer-visibility",
|
|
13457
|
+
projectId,
|
|
13458
|
+
trigger: "scheduled"
|
|
13459
|
+
});
|
|
13460
|
+
if (queueResult.conflict) {
|
|
13461
|
+
log4.info("run.skipped-active", { projectName: project.name, activeRunId: queueResult.activeRunId });
|
|
13462
|
+
this.db.update(schedules).set({
|
|
13463
|
+
nextRunAt,
|
|
13464
|
+
updatedAt: now
|
|
13465
|
+
}).where(eq20(schedules.id, currentSchedule.id)).run();
|
|
13466
|
+
return;
|
|
13467
|
+
}
|
|
13468
|
+
const runId = queueResult.runId;
|
|
13339
13469
|
this.db.update(schedules).set({
|
|
13470
|
+
lastRunAt: now,
|
|
13340
13471
|
nextRunAt,
|
|
13341
13472
|
updatedAt: now
|
|
13342
13473
|
}).where(eq20(schedules.id, currentSchedule.id)).run();
|
|
13343
|
-
|
|
13474
|
+
const scheduleProviders = JSON.parse(currentSchedule.providers);
|
|
13475
|
+
const providers = scheduleProviders.length > 0 ? scheduleProviders : void 0;
|
|
13476
|
+
log4.info("run.triggered", { runId, projectName: project.name, providers: providers ?? "all" });
|
|
13477
|
+
this.callbacks.onRunCreated(runId, projectId, providers);
|
|
13478
|
+
} catch (err) {
|
|
13479
|
+
log4.error("trigger.error", { scheduleId, projectId, error: err instanceof Error ? err.message : String(err) });
|
|
13344
13480
|
}
|
|
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
13481
|
}
|
|
13356
13482
|
};
|
|
13357
13483
|
|
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-5MOWJJND.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
|
}
|
|
@@ -1490,14 +1493,20 @@ async function gaTraffic(project, opts) {
|
|
|
1490
1493
|
console.log(` Total Sessions: ${result.totalSessions}`);
|
|
1491
1494
|
console.log(` Organic Sessions: ${result.totalOrganicSessions}`);
|
|
1492
1495
|
console.log(` Total Users: ${result.totalUsers}`);
|
|
1496
|
+
if (result.aiSessionsDeduped > 0) {
|
|
1497
|
+
const share = result.totalSessions > 0 ? Math.round(result.aiSessionsDeduped / result.totalSessions * 100) : 0;
|
|
1498
|
+
console.log(` AI Sessions (deduped): ${result.aiSessionsDeduped} (${share}% of total)`);
|
|
1499
|
+
}
|
|
1493
1500
|
console.log();
|
|
1494
1501
|
if (result.aiReferrals.length > 0) {
|
|
1502
|
+
const attrWidth = 12;
|
|
1495
1503
|
console.log(" AI REFERRAL SOURCES");
|
|
1496
|
-
console.log(` ${"SOURCE".padEnd(25)} ${"MEDIUM".padEnd(15)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1497
|
-
console.log(` ${"\u2500".repeat(25)} ${"\u2500".repeat(15)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1504
|
+
console.log(` ${"SOURCE".padEnd(25)} ${"MEDIUM".padEnd(15)} ${"ATTRIBUTION".padEnd(attrWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1505
|
+
console.log(` ${"\u2500".repeat(25)} ${"\u2500".repeat(15)} ${"\u2500".repeat(attrWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1498
1506
|
for (const ref of result.aiReferrals) {
|
|
1507
|
+
const dimLabel = ref.sourceDimension === "first_user" ? "first-visit" : ref.sourceDimension === "manual_utm" ? "utm" : "session";
|
|
1499
1508
|
console.log(
|
|
1500
|
-
` ${ref.source.padEnd(25)} ${ref.medium.padEnd(15)} ${String(ref.sessions).padEnd(10)}${String(ref.users).padEnd(8)}`
|
|
1509
|
+
` ${ref.source.padEnd(25)} ${ref.medium.padEnd(15)} ${dimLabel.padEnd(attrWidth)} ${String(ref.sessions).padEnd(10)}${String(ref.users).padEnd(8)}`
|
|
1501
1510
|
);
|
|
1502
1511
|
}
|
|
1503
1512
|
console.log();
|
|
@@ -1532,13 +1541,15 @@ async function gaAiReferralHistory(project, format) {
|
|
|
1532
1541
|
}
|
|
1533
1542
|
const dateWidth = 12;
|
|
1534
1543
|
const sourceWidth = Math.min(30, Math.max(10, ...result.map((r) => r.source.length)));
|
|
1544
|
+
const attrWidth = 12;
|
|
1535
1545
|
console.log(`GA4 AI Referral History for "${project}":
|
|
1536
1546
|
`);
|
|
1537
|
-
console.log(` ${"DATE".padEnd(dateWidth)} ${"SOURCE".padEnd(sourceWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1538
|
-
console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(sourceWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1547
|
+
console.log(` ${"DATE".padEnd(dateWidth)} ${"SOURCE".padEnd(sourceWidth)} ${"ATTRIBUTION".padEnd(attrWidth)} ${"SESSIONS".padEnd(10)}${"USERS".padEnd(8)}`);
|
|
1548
|
+
console.log(` ${"\u2500".repeat(dateWidth)} ${"\u2500".repeat(sourceWidth)} ${"\u2500".repeat(attrWidth)} ${"\u2500".repeat(10)}${"\u2500".repeat(8)}`);
|
|
1539
1549
|
for (const row of result) {
|
|
1550
|
+
const dimLabel = row.sourceDimension === "first_user" ? "first-visit" : row.sourceDimension === "manual_utm" ? "utm" : "session";
|
|
1540
1551
|
console.log(
|
|
1541
|
-
` ${row.date.padEnd(dateWidth)} ${row.source.padEnd(sourceWidth)} ${String(row.sessions).padEnd(10)}${String(row.users).padEnd(8)}`
|
|
1552
|
+
` ${row.date.padEnd(dateWidth)} ${row.source.padEnd(sourceWidth)} ${dimLabel.padEnd(attrWidth)} ${String(row.sessions).padEnd(10)}${String(row.users).padEnd(8)}`
|
|
1542
1553
|
);
|
|
1543
1554
|
}
|
|
1544
1555
|
}
|
|
@@ -2631,6 +2642,7 @@ async function importKeywords(project, filePath, format) {
|
|
|
2631
2642
|
throw new CliError({
|
|
2632
2643
|
code: "KEYWORD_IMPORT_FILE_NOT_FOUND",
|
|
2633
2644
|
message: `File not found: ${filePath}`,
|
|
2645
|
+
displayMessage: `Error: file not found: ${filePath}`,
|
|
2634
2646
|
details: {
|
|
2635
2647
|
project,
|
|
2636
2648
|
filePath
|
|
@@ -4852,6 +4864,7 @@ var envSchema = z.object({
|
|
|
4852
4864
|
WORKER_PORT: z.coerce.number().int().positive().default(3001),
|
|
4853
4865
|
WEB_PORT: z.coerce.number().int().positive().default(4173),
|
|
4854
4866
|
BOOTSTRAP_SECRET: z.string().default("change-me"),
|
|
4867
|
+
CANONRY_BASE_PATH: z.string().default("/"),
|
|
4855
4868
|
// Gemini
|
|
4856
4869
|
GEMINI_API_KEY: z.string().optional(),
|
|
4857
4870
|
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.37.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-integration-bing": "0.0.0",
|
|
60
|
-
"@ainyc/canonry-db": "0.0.0",
|
|
61
59
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
60
|
+
"@ainyc/canonry-db": "0.0.0",
|
|
61
|
+
"@ainyc/canonry-integration-bing": "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-
|
|
66
|
+
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
67
67
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
68
|
-
"@ainyc/canonry-provider-
|
|
69
|
-
"@ainyc/canonry-provider-
|
|
68
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
69
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0"
|
|
70
70
|
},
|
|
71
71
|
"scripts": {
|
|
72
72
|
"build": "tsup && tsx build-web.ts",
|