@ainyc/canonry 4.34.0 → 4.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/README.md +2 -2
- package/assets/assets/index-CAmKaZIt.js +302 -0
- package/assets/assets/index-CTrHzgs-.css +1 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-7AF6B3L6.js → chunk-F2G67CIU.js} +1412 -484
- package/dist/{chunk-7256SFYT.js → chunk-JQQXMCQ7.js} +286 -53
- package/dist/{chunk-5EBN7736.js → chunk-O7S623DL.js} +15 -1
- package/dist/{chunk-XW3F5EEW.js → chunk-XJVYVURK.js} +76 -21
- package/dist/cli.js +157 -586
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-3P2DMYRR.js → intelligence-service-7AWRUNI2.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +7 -7
- package/assets/assets/index-47V0U52s.js +0 -302
- package/assets/assets/index-CNKAwZMB.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
#!/usr/bin/env node --import tsx
|
|
2
2
|
import {
|
|
3
|
+
backfillAiReferralPaths,
|
|
4
|
+
backfillAiReferralPathsCommand,
|
|
5
|
+
backfillAnswerMentionsCommand,
|
|
6
|
+
backfillAnswerVisibilityCommand,
|
|
7
|
+
backfillInsightsCommand,
|
|
8
|
+
backfillNormalizedPaths,
|
|
9
|
+
backfillNormalizedPathsCommand,
|
|
10
|
+
checkLatestVersionForCli,
|
|
3
11
|
coerceAgentProvider,
|
|
4
|
-
computeCompetitorOverlap,
|
|
5
12
|
createServer,
|
|
6
13
|
detectAndTrackUpgrade,
|
|
7
|
-
determineCitationState,
|
|
8
|
-
extractRecommendedCompetitors,
|
|
9
14
|
formatAuditFactorScore,
|
|
10
15
|
getOrCreateAnonymousId,
|
|
11
16
|
isFirstRun,
|
|
12
17
|
isTelemetryEnabled,
|
|
13
18
|
listAgentProviders,
|
|
14
19
|
renderReportHtml,
|
|
15
|
-
reparseStoredResult,
|
|
16
|
-
reparseStoredResult2,
|
|
17
|
-
reparseStoredResult3,
|
|
18
|
-
reparseStoredResult4,
|
|
19
20
|
setGoogleAuthConfig,
|
|
20
21
|
setTelemetrySource,
|
|
21
22
|
showFirstRunNotice,
|
|
22
23
|
trackEvent
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-F2G67CIU.js";
|
|
24
25
|
import {
|
|
25
26
|
CliError,
|
|
26
27
|
EXIT_SYSTEM_ERROR,
|
|
@@ -36,42 +37,33 @@ import {
|
|
|
36
37
|
saveConfig,
|
|
37
38
|
saveConfigPatch,
|
|
38
39
|
usageError
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-O7S623DL.js";
|
|
40
41
|
import {
|
|
41
42
|
apiKeys,
|
|
42
|
-
competitors,
|
|
43
43
|
createClient,
|
|
44
|
-
gaAiReferrals,
|
|
45
|
-
gaTrafficSnapshots,
|
|
46
44
|
migrate,
|
|
47
|
-
parseJsonColumn,
|
|
48
45
|
projects,
|
|
49
|
-
queries
|
|
50
|
-
|
|
51
|
-
runs
|
|
52
|
-
} from "./chunk-7256SFYT.js";
|
|
46
|
+
queries
|
|
47
|
+
} from "./chunk-JQQXMCQ7.js";
|
|
53
48
|
import {
|
|
54
49
|
CcReleaseSyncStatuses,
|
|
55
50
|
CheckScopes,
|
|
56
51
|
CheckStatuses,
|
|
57
52
|
CitationStates,
|
|
58
53
|
CodingAgents,
|
|
59
|
-
ProviderNames,
|
|
60
|
-
RunKinds,
|
|
61
54
|
RunStatuses,
|
|
62
55
|
SkillsClients,
|
|
63
56
|
TrafficEventKinds,
|
|
64
|
-
determineAnswerMentioned,
|
|
65
57
|
discoveryBucketSchema,
|
|
66
58
|
discoveryCompetitorTypeSchema,
|
|
67
59
|
effectiveDomains,
|
|
68
60
|
formatRunErrorOneLine,
|
|
69
|
-
|
|
61
|
+
normalizeProjectAliases,
|
|
70
62
|
notificationEventSchema,
|
|
71
63
|
providerQuotaPolicySchema,
|
|
72
64
|
resolveProviderInput,
|
|
73
65
|
skillsClientSchema
|
|
74
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-XJVYVURK.js";
|
|
75
67
|
|
|
76
68
|
// src/cli.ts
|
|
77
69
|
import { pathToFileURL } from "url";
|
|
@@ -121,17 +113,11 @@ function matchesPath(args, path10) {
|
|
|
121
113
|
if (args.length < path10.length) return false;
|
|
122
114
|
return path10.every((segment, index) => args[index] === segment);
|
|
123
115
|
}
|
|
124
|
-
function
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
}
|
|
130
|
-
if ("format" in options) return options;
|
|
131
|
-
return {
|
|
132
|
-
...options,
|
|
133
|
-
format: { type: "string" }
|
|
134
|
-
};
|
|
116
|
+
function withGlobalOptions(options) {
|
|
117
|
+
const base = options ? { ...options } : {};
|
|
118
|
+
if (!("format" in base)) base.format = { type: "string" };
|
|
119
|
+
if (!("dry-run" in base)) base["dry-run"] = { type: "boolean" };
|
|
120
|
+
return base;
|
|
135
121
|
}
|
|
136
122
|
function toFormat(value, fallbackFormat) {
|
|
137
123
|
return value === "json" ? "json" : fallbackFormat;
|
|
@@ -187,7 +173,7 @@ Usage: ${single.usage}
|
|
|
187
173
|
try {
|
|
188
174
|
const parsed = parseArgs({
|
|
189
175
|
args: remainingArgs,
|
|
190
|
-
options:
|
|
176
|
+
options: withGlobalOptions(spec.options),
|
|
191
177
|
allowPositionals: spec.allowPositionals ?? true
|
|
192
178
|
});
|
|
193
179
|
values = parsed.values;
|
|
@@ -203,509 +189,25 @@ Usage: ${spec.usage}`, {
|
|
|
203
189
|
}
|
|
204
190
|
});
|
|
205
191
|
}
|
|
192
|
+
const dryRunRequested = values["dry-run"] === true;
|
|
193
|
+
if (dryRunRequested && !spec.supportsDryRun) {
|
|
194
|
+
throw usageError(
|
|
195
|
+
`Command "${spec.path.join(" ")}" does not support --dry-run. Either drop the flag, or run a different command that supports preview mode (e.g. \`canonry doctor\` for health checks, \`canonry status <project>\` for read-only state).`,
|
|
196
|
+
{
|
|
197
|
+
message: `Command "${spec.path.join(" ")}" does not support --dry-run`,
|
|
198
|
+
details: { command: commandId(spec), usage: spec.usage }
|
|
199
|
+
}
|
|
200
|
+
);
|
|
201
|
+
}
|
|
206
202
|
await spec.run({
|
|
207
203
|
positionals,
|
|
208
204
|
values,
|
|
209
|
-
format: toFormat(values.format, fallbackFormat)
|
|
205
|
+
format: toFormat(values.format, fallbackFormat),
|
|
206
|
+
dryRun: dryRunRequested
|
|
210
207
|
});
|
|
211
208
|
return true;
|
|
212
209
|
}
|
|
213
210
|
|
|
214
|
-
// src/commands/backfill.ts
|
|
215
|
-
import { and, eq, inArray } from "drizzle-orm";
|
|
216
|
-
var SNAPSHOT_BATCH_SIZE = 500;
|
|
217
|
-
async function backfillAnswerVisibilityCommand(opts) {
|
|
218
|
-
const config = loadConfig();
|
|
219
|
-
const db = createClient(config.database);
|
|
220
|
-
migrate(db);
|
|
221
|
-
const projectFilter = opts?.project?.trim();
|
|
222
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(eq(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
223
|
-
let examined = 0;
|
|
224
|
-
let updated = 0;
|
|
225
|
-
let mentioned = 0;
|
|
226
|
-
let reparsed = 0;
|
|
227
|
-
let providerErrors = 0;
|
|
228
|
-
if (scopedProjects.length > 0) {
|
|
229
|
-
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and(
|
|
230
|
-
eq(runs.kind, RunKinds["answer-visibility"]),
|
|
231
|
-
inArray(runs.projectId, scopedProjects.map((project) => project.id))
|
|
232
|
-
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq(runs.kind, RunKinds["answer-visibility"])).all();
|
|
233
|
-
const runIdsByProject = /* @__PURE__ */ new Map();
|
|
234
|
-
for (const run of runRows) {
|
|
235
|
-
const existing = runIdsByProject.get(run.projectId);
|
|
236
|
-
if (existing) existing.push(run.id);
|
|
237
|
-
else runIdsByProject.set(run.projectId, [run.id]);
|
|
238
|
-
}
|
|
239
|
-
for (const project of scopedProjects) {
|
|
240
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq(competitors.projectId, project.id)).all().map((row) => row.domain);
|
|
241
|
-
const runIds = runIdsByProject.get(project.id) ?? [];
|
|
242
|
-
if (runIds.length === 0) continue;
|
|
243
|
-
const projectDomains = effectiveDomains({
|
|
244
|
-
canonicalDomain: project.canonicalDomain,
|
|
245
|
-
ownedDomains: parseJsonColumn(project.ownedDomains, [])
|
|
246
|
-
});
|
|
247
|
-
for (let offset = 0; offset < runIds.length; offset += SNAPSHOT_BATCH_SIZE) {
|
|
248
|
-
const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
|
|
249
|
-
const snapshotRows = db.select({
|
|
250
|
-
id: querySnapshots.id,
|
|
251
|
-
provider: querySnapshots.provider,
|
|
252
|
-
citationState: querySnapshots.citationState,
|
|
253
|
-
answerMentioned: querySnapshots.answerMentioned,
|
|
254
|
-
answerText: querySnapshots.answerText,
|
|
255
|
-
citedDomains: querySnapshots.citedDomains,
|
|
256
|
-
competitorOverlap: querySnapshots.competitorOverlap,
|
|
257
|
-
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
258
|
-
rawResponse: querySnapshots.rawResponse
|
|
259
|
-
}).from(querySnapshots).where(inArray(querySnapshots.runId, batchRunIds)).all();
|
|
260
|
-
const pendingUpdates = [];
|
|
261
|
-
for (const snapshot of snapshotRows) {
|
|
262
|
-
examined++;
|
|
263
|
-
const reparsedResult = reparseProviderSnapshot(snapshot.provider, snapshot.rawResponse);
|
|
264
|
-
if (reparsedResult) reparsed++;
|
|
265
|
-
if (reparsedResult?.providerError) providerErrors++;
|
|
266
|
-
const answerText = reparsedResult?.answerText ?? snapshot.answerText ?? "";
|
|
267
|
-
const nextValue = determineAnswerMentioned(answerText, project.displayName, projectDomains);
|
|
268
|
-
if (nextValue) mentioned++;
|
|
269
|
-
const nextPatch = {};
|
|
270
|
-
if (snapshot.answerMentioned !== nextValue) {
|
|
271
|
-
nextPatch.answerMentioned = nextValue;
|
|
272
|
-
}
|
|
273
|
-
if ((snapshot.answerText ?? "") !== answerText) {
|
|
274
|
-
nextPatch.answerText = answerText;
|
|
275
|
-
}
|
|
276
|
-
if (reparsedResult) {
|
|
277
|
-
const normalized = {
|
|
278
|
-
provider: snapshot.provider,
|
|
279
|
-
answerText,
|
|
280
|
-
citedDomains: reparsedResult.citedDomains,
|
|
281
|
-
groundingSources: reparsedResult.groundingSources,
|
|
282
|
-
searchQueries: reparsedResult.searchQueries
|
|
283
|
-
};
|
|
284
|
-
const nextCitationState = determineCitationState(normalized, projectDomains);
|
|
285
|
-
const nextCitedDomains = JSON.stringify(reparsedResult.citedDomains);
|
|
286
|
-
const nextCompetitorOverlap = JSON.stringify(
|
|
287
|
-
computeCompetitorOverlap(normalized, competitorDomains)
|
|
288
|
-
);
|
|
289
|
-
const nextRecommendedCompetitors = JSON.stringify(
|
|
290
|
-
extractRecommendedCompetitors(
|
|
291
|
-
normalized.answerText,
|
|
292
|
-
projectDomains,
|
|
293
|
-
normalized.citedDomains,
|
|
294
|
-
competitorDomains
|
|
295
|
-
)
|
|
296
|
-
);
|
|
297
|
-
const nextRawResponse = stringifyStoredSnapshotEnvelope(
|
|
298
|
-
snapshot.rawResponse,
|
|
299
|
-
reparsedResult
|
|
300
|
-
);
|
|
301
|
-
if (snapshot.citationState !== nextCitationState) {
|
|
302
|
-
nextPatch.citationState = nextCitationState;
|
|
303
|
-
}
|
|
304
|
-
if (snapshot.citedDomains !== nextCitedDomains) {
|
|
305
|
-
nextPatch.citedDomains = nextCitedDomains;
|
|
306
|
-
}
|
|
307
|
-
if (snapshot.competitorOverlap !== nextCompetitorOverlap) {
|
|
308
|
-
nextPatch.competitorOverlap = nextCompetitorOverlap;
|
|
309
|
-
}
|
|
310
|
-
if (snapshot.recommendedCompetitors !== nextRecommendedCompetitors) {
|
|
311
|
-
nextPatch.recommendedCompetitors = nextRecommendedCompetitors;
|
|
312
|
-
}
|
|
313
|
-
if (snapshot.rawResponse !== nextRawResponse) {
|
|
314
|
-
nextPatch.rawResponse = nextRawResponse;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
if (Object.keys(nextPatch).length > 0) {
|
|
318
|
-
pendingUpdates.push({ id: snapshot.id, patch: nextPatch });
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
if (pendingUpdates.length > 0) {
|
|
322
|
-
db.transaction((tx) => {
|
|
323
|
-
for (const update of pendingUpdates) {
|
|
324
|
-
tx.update(querySnapshots).set(update.patch).where(eq(querySnapshots.id, update.id)).run();
|
|
325
|
-
}
|
|
326
|
-
});
|
|
327
|
-
updated += pendingUpdates.length;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
const result = {
|
|
333
|
-
project: projectFilter ?? null,
|
|
334
|
-
projects: scopedProjects.length,
|
|
335
|
-
examined,
|
|
336
|
-
updated,
|
|
337
|
-
mentioned,
|
|
338
|
-
reparsed,
|
|
339
|
-
providerErrors
|
|
340
|
-
};
|
|
341
|
-
if (opts?.format === "json") {
|
|
342
|
-
console.log(JSON.stringify(result, null, 2));
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
console.log("Answer visibility backfill complete.\n");
|
|
346
|
-
if (projectFilter) {
|
|
347
|
-
console.log(` Project: ${projectFilter}`);
|
|
348
|
-
}
|
|
349
|
-
console.log(` Projects: ${scopedProjects.length}`);
|
|
350
|
-
console.log(` Examined: ${examined}`);
|
|
351
|
-
console.log(` Updated: ${updated}`);
|
|
352
|
-
console.log(` Mentioned: ${mentioned}`);
|
|
353
|
-
console.log(` Reparsed: ${reparsed}`);
|
|
354
|
-
console.log(` Errors: ${providerErrors}`);
|
|
355
|
-
}
|
|
356
|
-
function backfillNormalizedPaths(db, opts) {
|
|
357
|
-
const baseConditions = [];
|
|
358
|
-
if (opts?.projectId) {
|
|
359
|
-
baseConditions.push(eq(gaTrafficSnapshots.projectId, opts.projectId));
|
|
360
|
-
}
|
|
361
|
-
const rows = db.select({
|
|
362
|
-
id: gaTrafficSnapshots.id,
|
|
363
|
-
landingPage: gaTrafficSnapshots.landingPage,
|
|
364
|
-
landingPageNormalized: gaTrafficSnapshots.landingPageNormalized
|
|
365
|
-
}).from(gaTrafficSnapshots).where(baseConditions.length > 0 ? and(...baseConditions) : void 0).all();
|
|
366
|
-
let updated = 0;
|
|
367
|
-
let unchanged = 0;
|
|
368
|
-
if (rows.length > 0) {
|
|
369
|
-
db.transaction((tx) => {
|
|
370
|
-
for (const row of rows) {
|
|
371
|
-
const next = normalizeUrlPath(row.landingPage);
|
|
372
|
-
if (next === null) {
|
|
373
|
-
unchanged++;
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
if (row.landingPageNormalized === next) {
|
|
377
|
-
unchanged++;
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
tx.update(gaTrafficSnapshots).set({ landingPageNormalized: next }).where(eq(gaTrafficSnapshots.id, row.id)).run();
|
|
381
|
-
updated++;
|
|
382
|
-
}
|
|
383
|
-
});
|
|
384
|
-
}
|
|
385
|
-
return { examined: rows.length, updated, unchanged };
|
|
386
|
-
}
|
|
387
|
-
async function backfillNormalizedPathsCommand(opts) {
|
|
388
|
-
const config = loadConfig();
|
|
389
|
-
const db = createClient(config.database);
|
|
390
|
-
migrate(db);
|
|
391
|
-
const projectFilter = opts?.project?.trim();
|
|
392
|
-
let projectId;
|
|
393
|
-
if (projectFilter) {
|
|
394
|
-
const project = db.select({ id: projects.id }).from(projects).where(eq(projects.name, projectFilter)).get();
|
|
395
|
-
if (!project) {
|
|
396
|
-
const result2 = {
|
|
397
|
-
project: projectFilter,
|
|
398
|
-
examined: 0,
|
|
399
|
-
updated: 0,
|
|
400
|
-
unchanged: 0
|
|
401
|
-
};
|
|
402
|
-
if (opts?.format === "json") {
|
|
403
|
-
console.log(JSON.stringify(result2, null, 2));
|
|
404
|
-
return;
|
|
405
|
-
}
|
|
406
|
-
console.log(`Backfill normalized-paths: project "${projectFilter}" not found.`);
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
projectId = project.id;
|
|
410
|
-
}
|
|
411
|
-
const { examined, updated, unchanged } = backfillNormalizedPaths(db, { projectId });
|
|
412
|
-
const result = {
|
|
413
|
-
project: projectFilter ?? null,
|
|
414
|
-
examined,
|
|
415
|
-
updated,
|
|
416
|
-
unchanged
|
|
417
|
-
};
|
|
418
|
-
if (opts?.format === "json") {
|
|
419
|
-
console.log(JSON.stringify(result, null, 2));
|
|
420
|
-
return;
|
|
421
|
-
}
|
|
422
|
-
console.log("Normalized-path backfill complete.\n");
|
|
423
|
-
if (projectFilter) console.log(` Project: ${projectFilter}`);
|
|
424
|
-
console.log(` Examined: ${examined}`);
|
|
425
|
-
console.log(` Updated: ${updated}`);
|
|
426
|
-
console.log(` Unchanged: ${unchanged}`);
|
|
427
|
-
}
|
|
428
|
-
function backfillAiReferralPaths(db, opts) {
|
|
429
|
-
const baseConditions = [];
|
|
430
|
-
if (opts?.projectId) {
|
|
431
|
-
baseConditions.push(eq(gaAiReferrals.projectId, opts.projectId));
|
|
432
|
-
}
|
|
433
|
-
const rows = db.select({
|
|
434
|
-
id: gaAiReferrals.id,
|
|
435
|
-
landingPage: gaAiReferrals.landingPage,
|
|
436
|
-
landingPageNormalized: gaAiReferrals.landingPageNormalized
|
|
437
|
-
}).from(gaAiReferrals).where(baseConditions.length > 0 ? and(...baseConditions) : void 0).all();
|
|
438
|
-
let updated = 0;
|
|
439
|
-
let unchanged = 0;
|
|
440
|
-
if (rows.length > 0) {
|
|
441
|
-
db.transaction((tx) => {
|
|
442
|
-
for (const row of rows) {
|
|
443
|
-
const next = normalizeUrlPath(row.landingPage);
|
|
444
|
-
if (next === null) {
|
|
445
|
-
unchanged++;
|
|
446
|
-
continue;
|
|
447
|
-
}
|
|
448
|
-
if (row.landingPageNormalized === next) {
|
|
449
|
-
unchanged++;
|
|
450
|
-
continue;
|
|
451
|
-
}
|
|
452
|
-
tx.update(gaAiReferrals).set({ landingPageNormalized: next }).where(eq(gaAiReferrals.id, row.id)).run();
|
|
453
|
-
updated++;
|
|
454
|
-
}
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
return { examined: rows.length, updated, unchanged };
|
|
458
|
-
}
|
|
459
|
-
async function backfillAiReferralPathsCommand(opts) {
|
|
460
|
-
const config = loadConfig();
|
|
461
|
-
const db = createClient(config.database);
|
|
462
|
-
migrate(db);
|
|
463
|
-
const projectFilter = opts?.project?.trim();
|
|
464
|
-
let projectId;
|
|
465
|
-
if (projectFilter) {
|
|
466
|
-
const project = db.select({ id: projects.id }).from(projects).where(eq(projects.name, projectFilter)).get();
|
|
467
|
-
if (!project) {
|
|
468
|
-
const result2 = {
|
|
469
|
-
project: projectFilter,
|
|
470
|
-
examined: 0,
|
|
471
|
-
updated: 0,
|
|
472
|
-
unchanged: 0
|
|
473
|
-
};
|
|
474
|
-
if (opts?.format === "json") {
|
|
475
|
-
console.log(JSON.stringify(result2, null, 2));
|
|
476
|
-
return;
|
|
477
|
-
}
|
|
478
|
-
console.log(`Backfill ai-referral-paths: project "${projectFilter}" not found.`);
|
|
479
|
-
return;
|
|
480
|
-
}
|
|
481
|
-
projectId = project.id;
|
|
482
|
-
}
|
|
483
|
-
const { examined, updated, unchanged } = backfillAiReferralPaths(db, { projectId });
|
|
484
|
-
const result = {
|
|
485
|
-
project: projectFilter ?? null,
|
|
486
|
-
examined,
|
|
487
|
-
updated,
|
|
488
|
-
unchanged
|
|
489
|
-
};
|
|
490
|
-
if (opts?.format === "json") {
|
|
491
|
-
console.log(JSON.stringify(result, null, 2));
|
|
492
|
-
return;
|
|
493
|
-
}
|
|
494
|
-
console.log("AI referral landing-page backfill complete.\n");
|
|
495
|
-
if (projectFilter) console.log(` Project: ${projectFilter}`);
|
|
496
|
-
console.log(` Examined: ${examined}`);
|
|
497
|
-
console.log(` Updated: ${updated}`);
|
|
498
|
-
console.log(` Unchanged: ${unchanged}`);
|
|
499
|
-
}
|
|
500
|
-
async function backfillAnswerMentionsCommand(opts) {
|
|
501
|
-
const config = loadConfig();
|
|
502
|
-
const db = createClient(config.database);
|
|
503
|
-
migrate(db);
|
|
504
|
-
const projectFilter = opts?.project?.trim();
|
|
505
|
-
const scopedProjects = projectFilter ? db.select().from(projects).where(eq(projects.name, projectFilter)).all() : db.select().from(projects).all();
|
|
506
|
-
let examined = 0;
|
|
507
|
-
let updated = 0;
|
|
508
|
-
let mentioned = 0;
|
|
509
|
-
if (scopedProjects.length > 0) {
|
|
510
|
-
const runRows = projectFilter ? db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(and(
|
|
511
|
-
eq(runs.kind, RunKinds["answer-visibility"]),
|
|
512
|
-
inArray(runs.projectId, scopedProjects.map((project) => project.id))
|
|
513
|
-
)).all() : db.select({ id: runs.id, projectId: runs.projectId }).from(runs).where(eq(runs.kind, RunKinds["answer-visibility"])).all();
|
|
514
|
-
const runIdsByProject = /* @__PURE__ */ new Map();
|
|
515
|
-
for (const run of runRows) {
|
|
516
|
-
const existing = runIdsByProject.get(run.projectId);
|
|
517
|
-
if (existing) existing.push(run.id);
|
|
518
|
-
else runIdsByProject.set(run.projectId, [run.id]);
|
|
519
|
-
}
|
|
520
|
-
for (const project of scopedProjects) {
|
|
521
|
-
const competitorDomains = db.select({ domain: competitors.domain }).from(competitors).where(eq(competitors.projectId, project.id)).all().map((row) => row.domain);
|
|
522
|
-
const runIds = runIdsByProject.get(project.id) ?? [];
|
|
523
|
-
if (runIds.length === 0) continue;
|
|
524
|
-
const projectDomains = effectiveDomains({
|
|
525
|
-
canonicalDomain: project.canonicalDomain,
|
|
526
|
-
ownedDomains: parseJsonColumn(project.ownedDomains, [])
|
|
527
|
-
});
|
|
528
|
-
for (let offset = 0; offset < runIds.length; offset += SNAPSHOT_BATCH_SIZE) {
|
|
529
|
-
const batchRunIds = runIds.slice(offset, offset + SNAPSHOT_BATCH_SIZE);
|
|
530
|
-
const snapshotRows = db.select({
|
|
531
|
-
id: querySnapshots.id,
|
|
532
|
-
provider: querySnapshots.provider,
|
|
533
|
-
answerMentioned: querySnapshots.answerMentioned,
|
|
534
|
-
answerText: querySnapshots.answerText,
|
|
535
|
-
citedDomains: querySnapshots.citedDomains,
|
|
536
|
-
competitorOverlap: querySnapshots.competitorOverlap,
|
|
537
|
-
recommendedCompetitors: querySnapshots.recommendedCompetitors,
|
|
538
|
-
rawResponse: querySnapshots.rawResponse
|
|
539
|
-
}).from(querySnapshots).where(inArray(querySnapshots.runId, batchRunIds)).all();
|
|
540
|
-
const pendingUpdates = [];
|
|
541
|
-
for (const snapshot of snapshotRows) {
|
|
542
|
-
examined++;
|
|
543
|
-
const answerText = snapshot.answerText ?? "";
|
|
544
|
-
const nextAnswerMentioned = determineAnswerMentioned(answerText, project.displayName, projectDomains);
|
|
545
|
-
if (nextAnswerMentioned) mentioned++;
|
|
546
|
-
const citedDomains = parseJsonColumn(snapshot.citedDomains, []);
|
|
547
|
-
const groundingSources = readStoredGroundingSources(snapshot.rawResponse);
|
|
548
|
-
const normalized = {
|
|
549
|
-
provider: snapshot.provider,
|
|
550
|
-
answerText,
|
|
551
|
-
citedDomains,
|
|
552
|
-
groundingSources,
|
|
553
|
-
searchQueries: []
|
|
554
|
-
};
|
|
555
|
-
const nextCompetitorOverlap = JSON.stringify(
|
|
556
|
-
computeCompetitorOverlap(normalized, competitorDomains)
|
|
557
|
-
);
|
|
558
|
-
const nextRecommendedCompetitors = JSON.stringify(
|
|
559
|
-
extractRecommendedCompetitors(
|
|
560
|
-
answerText,
|
|
561
|
-
projectDomains,
|
|
562
|
-
citedDomains,
|
|
563
|
-
competitorDomains
|
|
564
|
-
)
|
|
565
|
-
);
|
|
566
|
-
const nextPatch = {};
|
|
567
|
-
if (snapshot.answerMentioned !== nextAnswerMentioned) {
|
|
568
|
-
nextPatch.answerMentioned = nextAnswerMentioned;
|
|
569
|
-
}
|
|
570
|
-
if (snapshot.competitorOverlap !== nextCompetitorOverlap) {
|
|
571
|
-
nextPatch.competitorOverlap = nextCompetitorOverlap;
|
|
572
|
-
}
|
|
573
|
-
if (snapshot.recommendedCompetitors !== nextRecommendedCompetitors) {
|
|
574
|
-
nextPatch.recommendedCompetitors = nextRecommendedCompetitors;
|
|
575
|
-
}
|
|
576
|
-
if (Object.keys(nextPatch).length > 0) {
|
|
577
|
-
pendingUpdates.push({ id: snapshot.id, patch: nextPatch });
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
if (pendingUpdates.length > 0) {
|
|
581
|
-
db.transaction((tx) => {
|
|
582
|
-
for (const update of pendingUpdates) {
|
|
583
|
-
tx.update(querySnapshots).set(update.patch).where(eq(querySnapshots.id, update.id)).run();
|
|
584
|
-
}
|
|
585
|
-
});
|
|
586
|
-
updated += pendingUpdates.length;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
}
|
|
590
|
-
}
|
|
591
|
-
const result = {
|
|
592
|
-
project: projectFilter ?? null,
|
|
593
|
-
projects: scopedProjects.length,
|
|
594
|
-
examined,
|
|
595
|
-
updated,
|
|
596
|
-
mentioned
|
|
597
|
-
};
|
|
598
|
-
if (opts?.format === "json") {
|
|
599
|
-
console.log(JSON.stringify(result, null, 2));
|
|
600
|
-
return;
|
|
601
|
-
}
|
|
602
|
-
console.log("Answer mentions backfill complete.\n");
|
|
603
|
-
if (projectFilter) console.log(` Project: ${projectFilter}`);
|
|
604
|
-
console.log(` Projects: ${scopedProjects.length}`);
|
|
605
|
-
console.log(` Examined: ${examined}`);
|
|
606
|
-
console.log(` Updated: ${updated}`);
|
|
607
|
-
console.log(` Mentioned: ${mentioned}`);
|
|
608
|
-
}
|
|
609
|
-
function readStoredGroundingSources(rawResponse) {
|
|
610
|
-
const envelope = parseJsonColumn(rawResponse, {});
|
|
611
|
-
const sources = envelope.groundingSources;
|
|
612
|
-
if (!Array.isArray(sources)) return [];
|
|
613
|
-
const result = [];
|
|
614
|
-
for (const source of sources) {
|
|
615
|
-
if (source && typeof source === "object") {
|
|
616
|
-
const uri = source.uri;
|
|
617
|
-
const title = source.title;
|
|
618
|
-
if (typeof uri === "string") {
|
|
619
|
-
result.push({ uri, title: typeof title === "string" ? title : "" });
|
|
620
|
-
}
|
|
621
|
-
}
|
|
622
|
-
}
|
|
623
|
-
return result;
|
|
624
|
-
}
|
|
625
|
-
async function backfillInsightsCommand(project, opts) {
|
|
626
|
-
const { IntelligenceService } = await import("./intelligence-service-3P2DMYRR.js");
|
|
627
|
-
const config = loadConfig();
|
|
628
|
-
const db = createClient(config.database);
|
|
629
|
-
migrate(db);
|
|
630
|
-
const service = new IntelligenceService(db);
|
|
631
|
-
const isJson = opts?.format === "json";
|
|
632
|
-
if (!isJson) {
|
|
633
|
-
process.stderr.write(`Backfilling insights for "${project}"...
|
|
634
|
-
`);
|
|
635
|
-
}
|
|
636
|
-
const result = service.backfill(project, {
|
|
637
|
-
fromRunId: opts?.fromRun,
|
|
638
|
-
toRunId: opts?.toRun
|
|
639
|
-
}, (info) => {
|
|
640
|
-
if (!isJson) {
|
|
641
|
-
process.stderr.write(` [${info.index}/${info.total}] ${info.runId} \u2014 ${info.insights} insights
|
|
642
|
-
`);
|
|
643
|
-
}
|
|
644
|
-
});
|
|
645
|
-
const output = {
|
|
646
|
-
project,
|
|
647
|
-
processed: result.processed,
|
|
648
|
-
skipped: result.skipped,
|
|
649
|
-
totalInsights: result.totalInsights
|
|
650
|
-
};
|
|
651
|
-
if (isJson) {
|
|
652
|
-
console.log(JSON.stringify(output, null, 2));
|
|
653
|
-
return;
|
|
654
|
-
}
|
|
655
|
-
console.log(`
|
|
656
|
-
Backfill complete.`);
|
|
657
|
-
console.log(` Processed: ${result.processed}`);
|
|
658
|
-
console.log(` Skipped: ${result.skipped}`);
|
|
659
|
-
console.log(` Insights: ${result.totalInsights}`);
|
|
660
|
-
}
|
|
661
|
-
function reparseProviderSnapshot(provider, rawResponse) {
|
|
662
|
-
const envelope = parseJsonColumn(rawResponse, {});
|
|
663
|
-
const apiResponse = resolveStoredApiResponse(envelope);
|
|
664
|
-
if (!apiResponse) return null;
|
|
665
|
-
switch (provider) {
|
|
666
|
-
case ProviderNames.openai:
|
|
667
|
-
return reparseStoredResult(apiResponse);
|
|
668
|
-
case ProviderNames.claude:
|
|
669
|
-
return reparseStoredResult2(apiResponse);
|
|
670
|
-
case ProviderNames.gemini:
|
|
671
|
-
return reparseStoredResult3(apiResponse);
|
|
672
|
-
case ProviderNames.perplexity:
|
|
673
|
-
return reparseStoredResult4(apiResponse);
|
|
674
|
-
default:
|
|
675
|
-
return null;
|
|
676
|
-
}
|
|
677
|
-
}
|
|
678
|
-
function resolveStoredApiResponse(parsed) {
|
|
679
|
-
const nested = parsed.apiResponse;
|
|
680
|
-
if (nested !== null && typeof nested === "object" && !Array.isArray(nested)) {
|
|
681
|
-
return nested;
|
|
682
|
-
}
|
|
683
|
-
if (looksLikeProviderApiResponse(parsed)) {
|
|
684
|
-
return parsed;
|
|
685
|
-
}
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
function looksLikeProviderApiResponse(value) {
|
|
689
|
-
return Array.isArray(value.output) || Array.isArray(value.content) || Array.isArray(value.candidates) || Array.isArray(value.choices);
|
|
690
|
-
}
|
|
691
|
-
function stringifyStoredSnapshotEnvelope(rawResponse, reparsed) {
|
|
692
|
-
const parsed = parseJsonColumn(rawResponse, {});
|
|
693
|
-
const apiResponse = resolveStoredApiResponse(parsed);
|
|
694
|
-
const envelope = apiResponse === parsed ? {} : { ...parsed };
|
|
695
|
-
delete envelope.answerText;
|
|
696
|
-
delete envelope.citedDomains;
|
|
697
|
-
delete envelope.competitorOverlap;
|
|
698
|
-
delete envelope.recommendedCompetitors;
|
|
699
|
-
delete envelope.providerError;
|
|
700
|
-
return JSON.stringify({
|
|
701
|
-
...envelope,
|
|
702
|
-
groundingSources: reparsed.groundingSources,
|
|
703
|
-
searchQueries: reparsed.searchQueries,
|
|
704
|
-
...reparsed.providerError ? { providerError: reparsed.providerError } : {},
|
|
705
|
-
...apiResponse ? { apiResponse } : {}
|
|
706
|
-
});
|
|
707
|
-
}
|
|
708
|
-
|
|
709
211
|
// src/cli-command-helpers.ts
|
|
710
212
|
function getString(values, key) {
|
|
711
213
|
const value = values[key];
|
|
@@ -818,17 +320,21 @@ var BACKFILL_CLI_COMMANDS = [
|
|
|
818
320
|
},
|
|
819
321
|
{
|
|
820
322
|
path: ["backfill", "insights"],
|
|
821
|
-
usage: "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--format json]",
|
|
323
|
+
usage: "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--since <date>] [--dry-run] [--format json]",
|
|
822
324
|
options: {
|
|
823
325
|
"from-run": stringOption(),
|
|
824
|
-
"to-run": stringOption()
|
|
326
|
+
"to-run": stringOption(),
|
|
327
|
+
"since": stringOption()
|
|
825
328
|
},
|
|
329
|
+
supportsDryRun: true,
|
|
826
330
|
run: async (input) => {
|
|
827
|
-
const usage = "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--format json]";
|
|
331
|
+
const usage = "canonry backfill insights <project> [--from-run <id>] [--to-run <id>] [--since <date>] [--dry-run] [--format json]";
|
|
828
332
|
const project = requireProject(input, "backfill insights", usage);
|
|
829
333
|
await backfillInsightsCommand(project, {
|
|
830
334
|
fromRun: getString(input.values, "from-run"),
|
|
831
335
|
toRun: getString(input.values, "to-run"),
|
|
336
|
+
since: getString(input.values, "since"),
|
|
337
|
+
dryRun: input.dryRun,
|
|
832
338
|
format: input.format
|
|
833
339
|
});
|
|
834
340
|
}
|
|
@@ -1996,18 +1502,18 @@ function errorMessage(err) {
|
|
|
1996
1502
|
async function discoverRun(project, opts) {
|
|
1997
1503
|
const client = getClient4();
|
|
1998
1504
|
const { angles, multiAngle } = resolveIcpAngles(opts);
|
|
1999
|
-
const
|
|
1505
|
+
const runs = [];
|
|
2000
1506
|
for (const angle of angles) {
|
|
2001
1507
|
try {
|
|
2002
1508
|
const start = await client.triggerDiscoveryRun(project, buildRunBody(opts, angle));
|
|
2003
|
-
|
|
1509
|
+
runs.push({ angle, start });
|
|
2004
1510
|
} catch (err) {
|
|
2005
|
-
if (
|
|
1511
|
+
if (runs.length > 0) {
|
|
2006
1512
|
process.stderr.write(
|
|
2007
1513
|
`
|
|
2008
1514
|
Failed to start ${angle ? `angle "${angle}"` : "discovery run"}: ${errorMessage(err)}
|
|
2009
1515
|
Sessions already started (recover with \`canonry discover show ${project} <id>\`):
|
|
2010
|
-
` +
|
|
1516
|
+
` + runs.map((r) => ` ${r.start.sessionId}`).join("\n") + "\n"
|
|
2011
1517
|
);
|
|
2012
1518
|
}
|
|
2013
1519
|
throw err;
|
|
@@ -2015,10 +1521,10 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
|
|
|
2015
1521
|
}
|
|
2016
1522
|
if (!opts.wait) {
|
|
2017
1523
|
if (opts.format === "json") {
|
|
2018
|
-
console.log(JSON.stringify(multiAngle ?
|
|
1524
|
+
console.log(JSON.stringify(multiAngle ? runs.map((r) => r.start) : runs[0].start, null, 2));
|
|
2019
1525
|
return;
|
|
2020
1526
|
}
|
|
2021
|
-
for (const { angle, start } of
|
|
1527
|
+
for (const { angle, start } of runs) {
|
|
2022
1528
|
if (angle) console.log(`[${angle}]`);
|
|
2023
1529
|
if (start.consolidated) {
|
|
2024
1530
|
console.log(`Reusing in-flight discovery session: ${start.sessionId}`);
|
|
@@ -2031,20 +1537,20 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
|
|
|
2031
1537
|
console.log(` Status: ${start.status}`);
|
|
2032
1538
|
console.log(` Tail: canonry discover show ${project} ${start.sessionId}`);
|
|
2033
1539
|
}
|
|
2034
|
-
if (
|
|
1540
|
+
if (runs.length > 1) console.log();
|
|
2035
1541
|
}
|
|
2036
1542
|
return;
|
|
2037
1543
|
}
|
|
2038
|
-
const parallel =
|
|
2039
|
-
if (parallel) process.stderr.write(`Waiting for ${
|
|
1544
|
+
const parallel = runs.length > 1;
|
|
1545
|
+
if (parallel) process.stderr.write(`Waiting for ${runs.length} discovery sessions...
|
|
2040
1546
|
`);
|
|
2041
1547
|
const settled = await Promise.allSettled(
|
|
2042
|
-
|
|
1548
|
+
runs.map((r) => pollSession(client, project, r.start.sessionId, parallel))
|
|
2043
1549
|
);
|
|
2044
1550
|
const results = [];
|
|
2045
1551
|
const failures = [];
|
|
2046
1552
|
settled.forEach((outcome, i) => {
|
|
2047
|
-
const run =
|
|
1553
|
+
const run = runs[i];
|
|
2048
1554
|
if (outcome.status === "fulfilled") {
|
|
2049
1555
|
results.push({ angle: run.angle, session: outcome.value });
|
|
2050
1556
|
} else {
|
|
@@ -2084,7 +1590,7 @@ Sessions already started (recover with \`canonry discover show ${project} <id>\`
|
|
|
2084
1590
|
}
|
|
2085
1591
|
throw new CliError({
|
|
2086
1592
|
code: "DISCOVERY_INCOMPLETE",
|
|
2087
|
-
message: `${failures.length} of ${
|
|
1593
|
+
message: `${failures.length} of ${runs.length} discovery session(s) did not complete`,
|
|
2088
1594
|
details: { failed: failures.map((f) => f.sessionId) }
|
|
2089
1595
|
});
|
|
2090
1596
|
}
|
|
@@ -6069,10 +5575,10 @@ Brand Gap Analysis`);
|
|
|
6069
5575
|
console.log(`
|
|
6070
5576
|
Opportunity Gaps (competitors cited, you're not):`);
|
|
6071
5577
|
for (const q of data.gap) {
|
|
6072
|
-
const
|
|
5578
|
+
const competitors = q.competitorsCiting.join(", ");
|
|
6073
5579
|
const cons = q.consistency.totalRuns > 0 ? ` [cited ${q.consistency.citedRuns}/${q.consistency.totalRuns} runs]` : "";
|
|
6074
5580
|
console.log(` \u2022 ${q.query}${cons}`);
|
|
6075
|
-
console.log(` Competitors: ${
|
|
5581
|
+
console.log(` Competitors: ${competitors}`);
|
|
6076
5582
|
}
|
|
6077
5583
|
}
|
|
6078
5584
|
if (data.cited.length > 0) {
|
|
@@ -6155,9 +5661,9 @@ async function loadLatestRunForExport(client, project) {
|
|
|
6155
5661
|
} catch (err) {
|
|
6156
5662
|
if (!isEndpointMissing(err)) throw err;
|
|
6157
5663
|
}
|
|
6158
|
-
const
|
|
6159
|
-
if (
|
|
6160
|
-
const latestRun =
|
|
5664
|
+
const runs = await client.listRuns(project);
|
|
5665
|
+
if (runs.length === 0) return null;
|
|
5666
|
+
const latestRun = runs.reduce(
|
|
6161
5667
|
(current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
|
|
6162
5668
|
);
|
|
6163
5669
|
return client.getRun(latestRun.id);
|
|
@@ -6211,14 +5717,14 @@ async function showStatus(project, format) {
|
|
|
6211
5717
|
const projectData = await client.getProject(project);
|
|
6212
5718
|
const latest = await getLatestRunSummary(client, project);
|
|
6213
5719
|
if (format === "json") {
|
|
6214
|
-
let
|
|
5720
|
+
let runs = [];
|
|
6215
5721
|
try {
|
|
6216
|
-
|
|
5722
|
+
runs = await client.listRuns(project);
|
|
6217
5723
|
} catch {
|
|
6218
5724
|
}
|
|
6219
5725
|
console.log(JSON.stringify({
|
|
6220
5726
|
project: projectData,
|
|
6221
|
-
runs
|
|
5727
|
+
runs,
|
|
6222
5728
|
latestRun: latest.run,
|
|
6223
5729
|
totalRuns: latest.totalRuns
|
|
6224
5730
|
}, null, 2));
|
|
@@ -6249,15 +5755,15 @@ async function getLatestRunSummary(client, project) {
|
|
|
6249
5755
|
return await client.getLatestRun(project);
|
|
6250
5756
|
} catch (err) {
|
|
6251
5757
|
if (!isEndpointMissing(err)) throw err;
|
|
6252
|
-
const
|
|
6253
|
-
if (
|
|
5758
|
+
const runs = await client.listRuns(project);
|
|
5759
|
+
if (runs.length === 0) {
|
|
6254
5760
|
return { totalRuns: 0, run: null };
|
|
6255
5761
|
}
|
|
6256
|
-
const latestRun =
|
|
5762
|
+
const latestRun = runs.reduce(
|
|
6257
5763
|
(current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
|
|
6258
5764
|
);
|
|
6259
5765
|
return {
|
|
6260
|
-
totalRuns:
|
|
5766
|
+
totalRuns: runs.length,
|
|
6261
5767
|
run: latestRun
|
|
6262
5768
|
};
|
|
6263
5769
|
}
|
|
@@ -6347,6 +5853,7 @@ async function createProject(name, opts) {
|
|
|
6347
5853
|
displayName: opts.displayName,
|
|
6348
5854
|
canonicalDomain: opts.domain,
|
|
6349
5855
|
ownedDomains: opts.ownedDomains ?? [],
|
|
5856
|
+
aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
|
|
6350
5857
|
country: opts.country,
|
|
6351
5858
|
language: opts.language
|
|
6352
5859
|
});
|
|
@@ -6400,6 +5907,9 @@ async function showProject(name, format) {
|
|
|
6400
5907
|
if (secondaryDomains.length > 0) {
|
|
6401
5908
|
console.log(` Owned domains: ${secondaryDomains.join(", ")}`);
|
|
6402
5909
|
}
|
|
5910
|
+
if (project.aliases && project.aliases.length > 0) {
|
|
5911
|
+
console.log(` Aliases: ${project.aliases.join(", ")}`);
|
|
5912
|
+
}
|
|
6403
5913
|
console.log(` Country: ${project.country}`);
|
|
6404
5914
|
console.log(` Language: ${project.language}`);
|
|
6405
5915
|
console.log(` Config source: ${project.configSource}`);
|
|
@@ -6422,10 +5932,22 @@ async function updateProjectSettings(name, opts) {
|
|
|
6422
5932
|
const toRemove = new Set(opts.removeOwnedDomain);
|
|
6423
5933
|
ownedDomains = ownedDomains.filter((d) => !toRemove.has(d));
|
|
6424
5934
|
}
|
|
5935
|
+
const nextDisplayName = opts.displayName ?? project.displayName ?? project.name;
|
|
5936
|
+
let aliases = opts.aliases ?? project.aliases ?? [];
|
|
5937
|
+
if (opts.addAlias) {
|
|
5938
|
+
const existingKeys = new Set(aliases.map((a) => a.toLowerCase()));
|
|
5939
|
+
const toAdd = opts.addAlias.filter((a) => !existingKeys.has(a.toLowerCase()));
|
|
5940
|
+
aliases = [...aliases, ...toAdd];
|
|
5941
|
+
}
|
|
5942
|
+
if (opts.removeAlias) {
|
|
5943
|
+
const toRemove = new Set(opts.removeAlias.map((a) => a.toLowerCase()));
|
|
5944
|
+
aliases = aliases.filter((a) => !toRemove.has(a.toLowerCase()));
|
|
5945
|
+
}
|
|
6425
5946
|
const result = await client.putProject(name, {
|
|
6426
|
-
displayName:
|
|
5947
|
+
displayName: nextDisplayName,
|
|
6427
5948
|
canonicalDomain: opts.domain ?? project.canonicalDomain,
|
|
6428
5949
|
ownedDomains,
|
|
5950
|
+
aliases: normalizeProjectAliases(nextDisplayName, aliases),
|
|
6429
5951
|
country: opts.country ?? project.country,
|
|
6430
5952
|
language: opts.language ?? project.language
|
|
6431
5953
|
});
|
|
@@ -6435,10 +5957,31 @@ async function updateProjectSettings(name, opts) {
|
|
|
6435
5957
|
}
|
|
6436
5958
|
console.log(`Project updated: ${result.name}`);
|
|
6437
5959
|
}
|
|
6438
|
-
async function deleteProject(name,
|
|
5960
|
+
async function deleteProject(name, opts) {
|
|
6439
5961
|
const client = getClient16();
|
|
5962
|
+
const isJson = opts?.format === "json";
|
|
5963
|
+
if (opts?.dryRun) {
|
|
5964
|
+
const preview = await client.previewProjectDelete(name);
|
|
5965
|
+
if (isJson) {
|
|
5966
|
+
console.log(JSON.stringify({ dryRun: true, ...preview }, null, 2));
|
|
5967
|
+
return;
|
|
5968
|
+
}
|
|
5969
|
+
const { cascadeRows: cr, detachedRows: dr } = preview;
|
|
5970
|
+
console.log(`Project delete preview for "${name}":`);
|
|
5971
|
+
console.log(` Cascade-deletes:`);
|
|
5972
|
+
console.log(` queries: ${cr.queries}`);
|
|
5973
|
+
console.log(` competitors: ${cr.competitors}`);
|
|
5974
|
+
console.log(` runs: ${cr.runs}`);
|
|
5975
|
+
console.log(` snapshots: ${cr.snapshots}`);
|
|
5976
|
+
console.log(` insights: ${cr.insights}`);
|
|
5977
|
+
console.log(` Detached (project_id set to NULL):`);
|
|
5978
|
+
console.log(` audit_log: ${dr.auditLog}`);
|
|
5979
|
+
console.log(``);
|
|
5980
|
+
console.log(`No DB writes performed. Re-run without --dry-run to delete.`);
|
|
5981
|
+
return;
|
|
5982
|
+
}
|
|
6440
5983
|
await client.deleteProject(name);
|
|
6441
|
-
if (
|
|
5984
|
+
if (isJson) {
|
|
6442
5985
|
console.log(JSON.stringify({ name, deleted: true }, null, 2));
|
|
6443
5986
|
return;
|
|
6444
5987
|
}
|
|
@@ -6508,10 +6051,11 @@ async function setDefaultLocation(project, label, format) {
|
|
|
6508
6051
|
var PROJECT_CLI_COMMANDS = [
|
|
6509
6052
|
{
|
|
6510
6053
|
path: ["project", "create"],
|
|
6511
|
-
usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
|
|
6054
|
+
usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
|
|
6512
6055
|
options: {
|
|
6513
6056
|
domain: { type: "string", short: "d" },
|
|
6514
6057
|
"owned-domain": multiStringOption(),
|
|
6058
|
+
alias: multiStringOption(),
|
|
6515
6059
|
country: stringOption(),
|
|
6516
6060
|
language: stringOption(),
|
|
6517
6061
|
"display-name": stringOption()
|
|
@@ -6520,11 +6064,12 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
6520
6064
|
const name = requireProject(
|
|
6521
6065
|
input,
|
|
6522
6066
|
"project.create",
|
|
6523
|
-
"canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
|
|
6067
|
+
"canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
|
|
6524
6068
|
);
|
|
6525
6069
|
await createProject(name, {
|
|
6526
6070
|
domain: getString(input.values, "domain") ?? name,
|
|
6527
6071
|
ownedDomains: getStringArray(input.values, "owned-domain") ?? [],
|
|
6072
|
+
aliases: getStringArray(input.values, "alias") ?? [],
|
|
6528
6073
|
country: getString(input.values, "country") ?? "US",
|
|
6529
6074
|
language: getString(input.values, "language") ?? "en",
|
|
6530
6075
|
displayName: getString(input.values, "display-name") ?? name,
|
|
@@ -6534,12 +6079,15 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
6534
6079
|
},
|
|
6535
6080
|
{
|
|
6536
6081
|
path: ["project", "update"],
|
|
6537
|
-
usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
|
|
6082
|
+
usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]",
|
|
6538
6083
|
options: {
|
|
6539
6084
|
domain: { type: "string", short: "d" },
|
|
6540
6085
|
"owned-domain": multiStringOption(),
|
|
6541
6086
|
"add-domain": multiStringOption(),
|
|
6542
6087
|
"remove-domain": multiStringOption(),
|
|
6088
|
+
alias: multiStringOption(),
|
|
6089
|
+
"add-alias": multiStringOption(),
|
|
6090
|
+
"remove-alias": multiStringOption(),
|
|
6543
6091
|
country: stringOption(),
|
|
6544
6092
|
language: stringOption(),
|
|
6545
6093
|
"display-name": stringOption()
|
|
@@ -6548,7 +6096,7 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
6548
6096
|
const name = requireProject(
|
|
6549
6097
|
input,
|
|
6550
6098
|
"project.update",
|
|
6551
|
-
"canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
|
|
6099
|
+
"canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--format json]"
|
|
6552
6100
|
);
|
|
6553
6101
|
await updateProjectSettings(name, {
|
|
6554
6102
|
displayName: getString(input.values, "display-name"),
|
|
@@ -6556,6 +6104,9 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
6556
6104
|
ownedDomains: getStringArray(input.values, "owned-domain"),
|
|
6557
6105
|
addOwnedDomain: getStringArray(input.values, "add-domain"),
|
|
6558
6106
|
removeOwnedDomain: getStringArray(input.values, "remove-domain"),
|
|
6107
|
+
aliases: getStringArray(input.values, "alias"),
|
|
6108
|
+
addAlias: getStringArray(input.values, "add-alias"),
|
|
6109
|
+
removeAlias: getStringArray(input.values, "remove-alias"),
|
|
6559
6110
|
country: getString(input.values, "country"),
|
|
6560
6111
|
language: getString(input.values, "language"),
|
|
6561
6112
|
format: input.format
|
|
@@ -6580,10 +6131,11 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
6580
6131
|
},
|
|
6581
6132
|
{
|
|
6582
6133
|
path: ["project", "delete"],
|
|
6583
|
-
usage: "canonry project delete <name> [--format json]",
|
|
6134
|
+
usage: "canonry project delete <name> [--dry-run] [--format json]",
|
|
6135
|
+
supportsDryRun: true,
|
|
6584
6136
|
run: async (input) => {
|
|
6585
|
-
const name = requireProject(input, "project.delete", "canonry project delete <name> [--format json]");
|
|
6586
|
-
await deleteProject(name, input.format);
|
|
6137
|
+
const name = requireProject(input, "project.delete", "canonry project delete <name> [--dry-run] [--format json]");
|
|
6138
|
+
await deleteProject(name, { dryRun: input.dryRun, format: input.format });
|
|
6587
6139
|
}
|
|
6588
6140
|
},
|
|
6589
6141
|
{
|
|
@@ -6892,8 +6444,8 @@ async function cancelRun(project, runId, format) {
|
|
|
6892
6444
|
const client = getClient17();
|
|
6893
6445
|
let targetId = runId;
|
|
6894
6446
|
if (!targetId) {
|
|
6895
|
-
const
|
|
6896
|
-
const active =
|
|
6447
|
+
const runs = await client.listRuns(project);
|
|
6448
|
+
const active = runs.find((r) => r.status === "queued" || r.status === "running");
|
|
6897
6449
|
if (!active) {
|
|
6898
6450
|
throw new CliError({
|
|
6899
6451
|
code: "NO_ACTIVE_RUN",
|
|
@@ -6931,20 +6483,20 @@ async function showRun(id, format) {
|
|
|
6931
6483
|
}
|
|
6932
6484
|
async function listRuns(project, opts) {
|
|
6933
6485
|
const client = getClient17();
|
|
6934
|
-
const
|
|
6486
|
+
const runs = await client.listRuns(project, opts?.limit);
|
|
6935
6487
|
if (opts?.format === "json") {
|
|
6936
|
-
console.log(JSON.stringify(
|
|
6488
|
+
console.log(JSON.stringify(runs, null, 2));
|
|
6937
6489
|
return;
|
|
6938
6490
|
}
|
|
6939
|
-
if (
|
|
6491
|
+
if (runs.length === 0) {
|
|
6940
6492
|
console.log(`No runs found for "${project}".`);
|
|
6941
6493
|
return;
|
|
6942
6494
|
}
|
|
6943
|
-
console.log(`Runs for "${project}" (${
|
|
6495
|
+
console.log(`Runs for "${project}" (${runs.length}):
|
|
6944
6496
|
`);
|
|
6945
6497
|
console.log(" ID STATUS KIND TRIGGER CREATED");
|
|
6946
6498
|
console.log(" \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
|
|
6947
|
-
for (const run of
|
|
6499
|
+
for (const run of runs) {
|
|
6948
6500
|
console.log(
|
|
6949
6501
|
` ${run.id} ${run.status.padEnd(10)} ${run.kind.padEnd(18)} ${run.trigger.padEnd(9)} ${run.createdAt}`
|
|
6950
6502
|
);
|
|
@@ -7465,6 +7017,7 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
|
|
|
7465
7017
|
|
|
7466
7018
|
// src/commands/skills.ts
|
|
7467
7019
|
import fs7 from "fs";
|
|
7020
|
+
import os2 from "os";
|
|
7468
7021
|
import path4 from "path";
|
|
7469
7022
|
import { fileURLToPath } from "url";
|
|
7470
7023
|
var BUNDLED_SKILL_NAMES = ["canonry", "aero"];
|
|
@@ -7641,7 +7194,7 @@ function buildSummaryMessage(results) {
|
|
|
7641
7194
|
return `Skills install summary: ${parts.join(", ")}.`;
|
|
7642
7195
|
}
|
|
7643
7196
|
async function installSkills(opts = {}) {
|
|
7644
|
-
const targetDir = path4.resolve(opts.dir ?? process.cwd());
|
|
7197
|
+
const targetDir = opts.user ? os2.homedir() : path4.resolve(opts.dir ?? process.cwd());
|
|
7645
7198
|
const client = opts.client ?? SkillsClients.all;
|
|
7646
7199
|
const force = opts.force ?? false;
|
|
7647
7200
|
const allSkills = getBundledSkills();
|
|
@@ -7727,9 +7280,10 @@ var SKILLS_CLI_COMMANDS = [
|
|
|
7727
7280
|
},
|
|
7728
7281
|
{
|
|
7729
7282
|
path: ["skills", "install"],
|
|
7730
|
-
usage: "canonry skills install [skill...] [--dir <path>] [--client claude|codex|all] [--force] [--format json]",
|
|
7283
|
+
usage: "canonry skills install [skill...] [--dir <path> | --user] [--client claude|codex|all] [--force] [--format json]",
|
|
7731
7284
|
options: {
|
|
7732
7285
|
dir: stringOption(),
|
|
7286
|
+
user: { type: "boolean" },
|
|
7733
7287
|
client: stringOption(),
|
|
7734
7288
|
force: { type: "boolean" }
|
|
7735
7289
|
},
|
|
@@ -7737,6 +7291,7 @@ var SKILLS_CLI_COMMANDS = [
|
|
|
7737
7291
|
run: async (input) => {
|
|
7738
7292
|
const summary = await installSkills({
|
|
7739
7293
|
dir: getString(input.values, "dir"),
|
|
7294
|
+
user: getBoolean(input.values, "user"),
|
|
7740
7295
|
skills: input.positionals.length > 0 ? input.positionals : void 0,
|
|
7741
7296
|
client: parseSkillsClient(getString(input.values, "client")),
|
|
7742
7297
|
force: getBoolean(input.values, "force")
|
|
@@ -8042,8 +7597,8 @@ function renderQueries(pdf, report) {
|
|
|
8042
7597
|
for (const result of queryResult.providerResults) {
|
|
8043
7598
|
const status = result.error ? "error" : result.mentioned ? result.cited ? "mentioned and cited" : "mentioned" : "not mentioned";
|
|
8044
7599
|
const accuracy = result.describedAccurately === "not-mentioned" ? "" : `; accuracy: ${result.describedAccurately}`;
|
|
8045
|
-
const
|
|
8046
|
-
const line = `${result.displayName}: ${status}${accuracy}${
|
|
7600
|
+
const competitors = result.recommendedCompetitors.length > 0 ? `; recommended instead: ${result.recommendedCompetitors.join(", ")}` : "";
|
|
7601
|
+
const line = `${result.displayName}: ${status}${accuracy}${competitors}`;
|
|
8047
7602
|
pdf.bullet(line);
|
|
8048
7603
|
if (result.error) {
|
|
8049
7604
|
pdf.paragraph(`Error: ${result.error}`, { size: 9, color: FAIL, lineHeight: 12 });
|
|
@@ -8190,8 +7745,8 @@ function formatSnapshotMarkdown(report) {
|
|
|
8190
7745
|
const mentioned = result.mentioned ? "Yes" : "No";
|
|
8191
7746
|
const cited = result.cited ? "Yes" : "No";
|
|
8192
7747
|
const accuracy = result.describedAccurately === "not-mentioned" ? "-" : result.describedAccurately;
|
|
8193
|
-
const
|
|
8194
|
-
lines.push(`| ${result.displayName} | ${mentioned} | ${cited} | ${accuracy} | ${
|
|
7748
|
+
const competitors = result.recommendedCompetitors.length > 0 ? result.recommendedCompetitors.join(", ") : "-";
|
|
7749
|
+
lines.push(`| ${result.displayName} | ${mentioned} | ${cited} | ${accuracy} | ${competitors} |`);
|
|
8195
7750
|
}
|
|
8196
7751
|
lines.push("");
|
|
8197
7752
|
}
|
|
@@ -8421,7 +7976,7 @@ function renderHuman(overview) {
|
|
|
8421
7976
|
transitions,
|
|
8422
7977
|
scores,
|
|
8423
7978
|
movementSummary,
|
|
8424
|
-
competitors
|
|
7979
|
+
competitors,
|
|
8425
7980
|
providerScores,
|
|
8426
7981
|
attentionItems,
|
|
8427
7982
|
runHistory,
|
|
@@ -8469,9 +8024,9 @@ function renderHuman(overview) {
|
|
|
8469
8024
|
console.log(`
|
|
8470
8025
|
Transitions since ${transitions.since}: +${transitions.gained} gained, -${transitions.lost} lost, ${transitions.emerging} emerging`);
|
|
8471
8026
|
}
|
|
8472
|
-
if (
|
|
8027
|
+
if (competitors.length > 0) {
|
|
8473
8028
|
console.log("\n Competitors:");
|
|
8474
|
-
for (const c of
|
|
8029
|
+
for (const c of competitors) {
|
|
8475
8030
|
console.log(` ${c.domain.padEnd(28)} ${c.citationCount}/${c.totalQueries} ${c.pressureLabel}`);
|
|
8476
8031
|
}
|
|
8477
8032
|
}
|
|
@@ -8860,7 +8415,7 @@ var CONTENT_CLI_COMMANDS = [
|
|
|
8860
8415
|
// src/commands/bootstrap.ts
|
|
8861
8416
|
import crypto from "crypto";
|
|
8862
8417
|
import path7 from "path";
|
|
8863
|
-
import { eq
|
|
8418
|
+
import { eq } from "drizzle-orm";
|
|
8864
8419
|
|
|
8865
8420
|
// ../config/src/index.ts
|
|
8866
8421
|
import { z } from "zod";
|
|
@@ -8898,7 +8453,11 @@ var envSchema = z.object({
|
|
|
8898
8453
|
PERPLEXITY_MODEL: z.string().optional(),
|
|
8899
8454
|
PERPLEXITY_MAX_CONCURRENCY: z.coerce.number().int().positive().default(2),
|
|
8900
8455
|
PERPLEXITY_MAX_REQUESTS_PER_MINUTE: z.coerce.number().int().positive().default(10),
|
|
8901
|
-
PERPLEXITY_MAX_REQUESTS_PER_DAY: z.coerce.number().int().positive().default(1e3)
|
|
8456
|
+
PERPLEXITY_MAX_REQUESTS_PER_DAY: z.coerce.number().int().positive().default(1e3),
|
|
8457
|
+
// Secret for HMAC-signing Google OAuth state parameters. Required for
|
|
8458
|
+
// cloud deployments that mount googleRoutes; the plugin refuses to register
|
|
8459
|
+
// without it (see packages/api-routes/src/google.ts).
|
|
8460
|
+
GOOGLE_STATE_SECRET: z.string().optional()
|
|
8902
8461
|
});
|
|
8903
8462
|
var bootstrapEnvSchema = z.object({
|
|
8904
8463
|
CANONRY_API_KEY: z.string().optional(),
|
|
@@ -9038,7 +8597,7 @@ async function bootstrapCommand(_opts) {
|
|
|
9038
8597
|
const db = createClient(databasePath);
|
|
9039
8598
|
migrate(db);
|
|
9040
8599
|
db.transaction((tx) => {
|
|
9041
|
-
tx.delete(apiKeys).where(
|
|
8600
|
+
tx.delete(apiKeys).where(eq(apiKeys.name, "default")).run();
|
|
9042
8601
|
tx.insert(apiKeys).values({
|
|
9043
8602
|
id: crypto.randomUUID(),
|
|
9044
8603
|
name: "default",
|
|
@@ -11558,6 +11117,18 @@ async function runCli(args = process.argv.slice(2)) {
|
|
|
11558
11117
|
...setupState ? { setup_state: setupState } : {}
|
|
11559
11118
|
});
|
|
11560
11119
|
}
|
|
11120
|
+
if (!isHelpRequest && command !== "telemetry") {
|
|
11121
|
+
void checkLatestVersionForCli().then((update) => {
|
|
11122
|
+
if (!update) return;
|
|
11123
|
+
process.stderr.write(
|
|
11124
|
+
`
|
|
11125
|
+
\u2192 canonry ${update.latest} is available (you have ${update.current}).
|
|
11126
|
+
Upgrade: ${update.upgradeCommand}
|
|
11127
|
+
|
|
11128
|
+
`
|
|
11129
|
+
);
|
|
11130
|
+
});
|
|
11131
|
+
}
|
|
11561
11132
|
try {
|
|
11562
11133
|
if (await dispatchRegisteredCommand(args, format, REGISTERED_CLI_COMMANDS)) {
|
|
11563
11134
|
return 0;
|