@ainyc/canonry 4.35.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/assets/assets/{index-u7ZXZ5mA.js → index-CAmKaZIt.js} +72 -72
- package/assets/assets/index-CTrHzgs-.css +1 -0
- package/assets/index.html +2 -2
- package/dist/{chunk-NLV4MZZF.js → chunk-F2G67CIU.js} +359 -236
- package/dist/{chunk-MLS5KJWK.js → chunk-JQQXMCQ7.js} +132 -16
- package/dist/{chunk-B3FBOECD.js → chunk-O7S623DL.js} +15 -1
- package/dist/{chunk-EM5GVF3C.js → chunk-XJVYVURK.js} +3 -1
- package/dist/cli.js +61 -27
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-WAJOEOJV.js → intelligence-service-7AWRUNI2.js} +2 -2
- package/dist/mcp.js +2 -2
- package/package.json +4 -4
- package/assets/assets/index-Cfv0_lwq.css +0 -1
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
categoryLabel,
|
|
9
9
|
determineAnswerMentioned,
|
|
10
10
|
normalizeProjectDomain
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-XJVYVURK.js";
|
|
12
12
|
|
|
13
13
|
// src/intelligence-service.ts
|
|
14
14
|
import { eq, desc, asc, and, or, inArray } from "drizzle-orm";
|
|
@@ -3303,14 +3303,14 @@ function gapTone(gapCount, totalCount) {
|
|
|
3303
3303
|
|
|
3304
3304
|
// ../intelligence/src/visibility-score.ts
|
|
3305
3305
|
function buildVisibilityScore(snapshots, options) {
|
|
3306
|
-
const tooltip =
|
|
3306
|
+
const tooltip = "An LLM used a page on your domain as a source for its answer.";
|
|
3307
3307
|
if (snapshots.length === 0) {
|
|
3308
3308
|
return {
|
|
3309
|
-
label: "
|
|
3309
|
+
label: "Citation Coverage",
|
|
3310
3310
|
value: "No data",
|
|
3311
3311
|
delta: "Run a sweep first",
|
|
3312
3312
|
tone: "neutral",
|
|
3313
|
-
description: "No
|
|
3313
|
+
description: "No citation data yet. Trigger a run to start tracking.",
|
|
3314
3314
|
tooltip,
|
|
3315
3315
|
trend: []
|
|
3316
3316
|
};
|
|
@@ -3327,9 +3327,9 @@ function buildVisibilityScore(snapshots, options) {
|
|
|
3327
3327
|
const runApiProviderCount = options.configuredApiProviders.filter((p) => runProviders.has(p)).length;
|
|
3328
3328
|
const isPartialProviderRun = options.configuredApiProviders.length > 1 && runApiProviderCount < options.configuredApiProviders.length;
|
|
3329
3329
|
return {
|
|
3330
|
-
label: "
|
|
3330
|
+
label: "Citation Coverage",
|
|
3331
3331
|
value: `${score}`,
|
|
3332
|
-
delta: `${citedCount} of ${totalCount} queries
|
|
3332
|
+
delta: `${citedCount} of ${totalCount} queries cited`,
|
|
3333
3333
|
tone: isPartialProviderRun ? "caution" : scoreTone(score),
|
|
3334
3334
|
description: `${citedCount} of ${totalCount} tracked queries found your domain in at least one AI answer engine.`,
|
|
3335
3335
|
tooltip,
|
|
@@ -3339,6 +3339,44 @@ function buildVisibilityScore(snapshots, options) {
|
|
|
3339
3339
|
};
|
|
3340
3340
|
}
|
|
3341
3341
|
|
|
3342
|
+
// ../intelligence/src/mention-coverage.ts
|
|
3343
|
+
function buildMentionCoverage(snapshots, options) {
|
|
3344
|
+
const tooltip = "Your domain or company name was in the answer returned by the LLM.";
|
|
3345
|
+
if (snapshots.length === 0) {
|
|
3346
|
+
return {
|
|
3347
|
+
label: "Mention Coverage",
|
|
3348
|
+
value: "No data",
|
|
3349
|
+
delta: "Run a sweep first",
|
|
3350
|
+
tone: "neutral",
|
|
3351
|
+
description: "No mention data yet. Trigger a run to start tracking.",
|
|
3352
|
+
tooltip,
|
|
3353
|
+
trend: []
|
|
3354
|
+
};
|
|
3355
|
+
}
|
|
3356
|
+
const queryMentioned = /* @__PURE__ */ new Map();
|
|
3357
|
+
for (const snap of snapshots) {
|
|
3358
|
+
if (!queryMentioned.has(snap.queryId)) queryMentioned.set(snap.queryId, false);
|
|
3359
|
+
if (snap.answerMentioned === true) queryMentioned.set(snap.queryId, true);
|
|
3360
|
+
}
|
|
3361
|
+
const totalCount = queryMentioned.size;
|
|
3362
|
+
const mentionedCount = [...queryMentioned.values()].filter(Boolean).length;
|
|
3363
|
+
const score = totalCount > 0 ? Math.round(mentionedCount / totalCount * 100) : 0;
|
|
3364
|
+
const runProviders = new Set(snapshots.map((s) => s.provider));
|
|
3365
|
+
const runApiProviderCount = options.configuredApiProviders.filter((p) => runProviders.has(p)).length;
|
|
3366
|
+
const isPartialProviderRun = options.configuredApiProviders.length > 1 && runApiProviderCount < options.configuredApiProviders.length;
|
|
3367
|
+
return {
|
|
3368
|
+
label: "Mention Coverage",
|
|
3369
|
+
value: `${score}`,
|
|
3370
|
+
delta: `${mentionedCount} of ${totalCount} queries mentioned`,
|
|
3371
|
+
tone: isPartialProviderRun ? "caution" : scoreTone(score),
|
|
3372
|
+
description: `${mentionedCount} of ${totalCount} tracked queries had your brand or domain in the AI answer text.`,
|
|
3373
|
+
tooltip,
|
|
3374
|
+
trend: [],
|
|
3375
|
+
progress: score,
|
|
3376
|
+
providerCoverage: isPartialProviderRun ? `${runApiProviderCount} of ${options.configuredApiProviders.length} providers` : void 0
|
|
3377
|
+
};
|
|
3378
|
+
}
|
|
3379
|
+
|
|
3342
3380
|
// ../intelligence/src/gap-query-score.ts
|
|
3343
3381
|
function buildGapQueryScore(snapshots) {
|
|
3344
3382
|
const tooltip = "Tracked queries where a competitor is cited in the latest run but your domain is not.";
|
|
@@ -3611,8 +3649,13 @@ var IntelligenceService = class {
|
|
|
3611
3649
|
/**
|
|
3612
3650
|
* Analyze a single run given an explicit previous run (or null for first run).
|
|
3613
3651
|
* Used by backfill where we control the run ordering.
|
|
3652
|
+
*
|
|
3653
|
+
* `dryRun: true` skips the DB write — `persistResult` is not called and
|
|
3654
|
+
* dismissed flags / health rows are untouched. Callers receive the same
|
|
3655
|
+
* AnalysisResult they would have, suitable for previewing what a write
|
|
3656
|
+
* would have produced.
|
|
3614
3657
|
*/
|
|
3615
|
-
analyzeRunWithPrevious(runRecord, previousRunRecord, historyRecords) {
|
|
3658
|
+
analyzeRunWithPrevious(runRecord, previousRunRecord, historyRecords, opts) {
|
|
3616
3659
|
const currentRun = this.buildRunData(
|
|
3617
3660
|
runRecord.id,
|
|
3618
3661
|
runRecord.projectId,
|
|
@@ -3632,23 +3675,44 @@ var IntelligenceService = class {
|
|
|
3632
3675
|
const history = (historyRecords ?? []).map((r) => r.id === runRecord.id ? currentRun : this.buildRunData(r.id, r.projectId, r.finishedAt ?? r.createdAt, r.location ?? null));
|
|
3633
3676
|
if (!previousRun) {
|
|
3634
3677
|
const result2 = analyzeRuns(currentRun, currentRun, { trackedCompetitors, history });
|
|
3635
|
-
this.
|
|
3678
|
+
const emptyResult = this.emptyAnalysisResult(result2);
|
|
3679
|
+
if (!opts?.dryRun) this.persistResult(emptyResult, runRecord.id, runRecord.projectId);
|
|
3636
3680
|
return result2;
|
|
3637
3681
|
}
|
|
3638
3682
|
const result = analyzeRuns(currentRun, previousRun, { trackedCompetitors, history });
|
|
3639
3683
|
const tieredResult = this.tierResult(result, runRecord.id, runRecord.projectId);
|
|
3640
|
-
this.persistResult(tieredResult, runRecord.id, runRecord.projectId);
|
|
3684
|
+
if (!opts?.dryRun) this.persistResult(tieredResult, runRecord.id, runRecord.projectId);
|
|
3641
3685
|
return tieredResult;
|
|
3642
3686
|
}
|
|
3643
3687
|
/**
|
|
3644
3688
|
* Backfill intelligence for all completed/partial runs of a project.
|
|
3645
3689
|
* Processes runs in chronological order so each run compares against its predecessor.
|
|
3690
|
+
*
|
|
3691
|
+
* Scoping options:
|
|
3692
|
+
* - `fromRunId` / `toRunId`: bound the target range by exact run ID.
|
|
3693
|
+
* - `since`: bound the target range by `finishedAt >= <date>`. Accepts
|
|
3694
|
+
* any string that `Date.parse` understands (ISO 8601, `YYYY-MM-DD`,
|
|
3695
|
+
* etc.). Runs before the cutoff are *not* re-processed but stay
|
|
3696
|
+
* available for predecessor lookup, so transition detection at the
|
|
3697
|
+
* boundary stays correct. Composes with `fromRunId` / `toRunId` —
|
|
3698
|
+
* all three filters intersect.
|
|
3699
|
+
* - `dryRun`: compute the analysis without writing. The return value
|
|
3700
|
+
* includes a `delta` describing what would change (rows to delete vs
|
|
3701
|
+
* create per run + aggregate). DB is left untouched.
|
|
3646
3702
|
*/
|
|
3647
3703
|
backfill(projectName, opts, onProgress) {
|
|
3648
3704
|
const project = this.db.select().from(projects).where(eq(projects.name, projectName)).get();
|
|
3649
3705
|
if (!project) {
|
|
3650
3706
|
throw new Error(`Project "${projectName}" not found`);
|
|
3651
3707
|
}
|
|
3708
|
+
let sinceTimestamp = null;
|
|
3709
|
+
if (opts?.since !== void 0) {
|
|
3710
|
+
const parsed = Date.parse(opts.since);
|
|
3711
|
+
if (Number.isNaN(parsed)) {
|
|
3712
|
+
throw new Error(`Invalid --since value "${opts.since}": expected a parseable date (ISO 8601 or YYYY-MM-DD)`);
|
|
3713
|
+
}
|
|
3714
|
+
sinceTimestamp = parsed;
|
|
3715
|
+
}
|
|
3652
3716
|
const allRuns = this.db.select().from(runs).where(
|
|
3653
3717
|
and(
|
|
3654
3718
|
eq(runs.projectId, project.id),
|
|
@@ -3667,10 +3731,28 @@ var IntelligenceService = class {
|
|
|
3667
3731
|
if (idx === -1) throw new Error(`Run "${opts.toRunId}" not found in project`);
|
|
3668
3732
|
endIdx = idx + 1;
|
|
3669
3733
|
}
|
|
3670
|
-
|
|
3734
|
+
let targetRuns = allRuns.slice(startIdx, endIdx);
|
|
3735
|
+
if (sinceTimestamp !== null) {
|
|
3736
|
+
targetRuns = targetRuns.filter((r) => {
|
|
3737
|
+
const ts = r.finishedAt ?? r.createdAt;
|
|
3738
|
+
const t = Date.parse(ts);
|
|
3739
|
+
return !Number.isNaN(t) && t >= sinceTimestamp;
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3671
3742
|
let processed = 0;
|
|
3672
3743
|
let skipped = 0;
|
|
3673
3744
|
let totalInsights = 0;
|
|
3745
|
+
const isDryRun = opts?.dryRun === true;
|
|
3746
|
+
const perRunDelta = [];
|
|
3747
|
+
let wouldDeleteTotal = 0;
|
|
3748
|
+
const existingByRunId = /* @__PURE__ */ new Map();
|
|
3749
|
+
if (isDryRun && targetRuns.length > 0) {
|
|
3750
|
+
const rows = this.db.select({ runId: insights.runId }).from(insights).where(inArray(insights.runId, targetRuns.map((r) => r.id))).all();
|
|
3751
|
+
for (const r of rows) {
|
|
3752
|
+
if (r.runId == null) continue;
|
|
3753
|
+
existingByRunId.set(r.runId, (existingByRunId.get(r.runId) ?? 0) + 1);
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3674
3756
|
for (let i = 0; i < targetRuns.length; i++) {
|
|
3675
3757
|
const run = targetRuns[i];
|
|
3676
3758
|
const runLocation = run.location ?? null;
|
|
@@ -3679,16 +3761,35 @@ var IntelligenceService = class {
|
|
|
3679
3761
|
const previousRun = sameLocIdx > 0 ? sameLocationRuns[sameLocIdx - 1] : null;
|
|
3680
3762
|
const historyStart = Math.max(0, sameLocIdx - (HISTORY_WINDOW_RUNS - 1));
|
|
3681
3763
|
const historyRecords = sameLocationRuns.slice(historyStart, sameLocIdx + 1);
|
|
3682
|
-
const result = this.analyzeRunWithPrevious(run, previousRun, historyRecords);
|
|
3764
|
+
const result = this.analyzeRunWithPrevious(run, previousRun, historyRecords, { dryRun: isDryRun });
|
|
3683
3765
|
if (result) {
|
|
3684
3766
|
processed++;
|
|
3685
3767
|
totalInsights += result.insights.length;
|
|
3768
|
+
if (isDryRun) {
|
|
3769
|
+
const existing = existingByRunId.get(run.id) ?? 0;
|
|
3770
|
+
wouldDeleteTotal += existing;
|
|
3771
|
+
perRunDelta.push({ runId: run.id, existingInsights: existing, newInsights: result.insights.length });
|
|
3772
|
+
}
|
|
3686
3773
|
onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: result.insights.length });
|
|
3687
3774
|
} else {
|
|
3688
3775
|
skipped++;
|
|
3689
3776
|
onProgress?.({ runId: run.id, index: i + 1, total: targetRuns.length, insights: 0 });
|
|
3690
3777
|
}
|
|
3691
3778
|
}
|
|
3779
|
+
if (isDryRun) {
|
|
3780
|
+
return {
|
|
3781
|
+
processed,
|
|
3782
|
+
skipped,
|
|
3783
|
+
totalInsights,
|
|
3784
|
+
dryRun: true,
|
|
3785
|
+
delta: {
|
|
3786
|
+
wouldDelete: wouldDeleteTotal,
|
|
3787
|
+
wouldCreate: totalInsights,
|
|
3788
|
+
netChange: totalInsights - wouldDeleteTotal,
|
|
3789
|
+
perRun: perRunDelta
|
|
3790
|
+
}
|
|
3791
|
+
};
|
|
3792
|
+
}
|
|
3692
3793
|
return { processed, skipped, totalInsights };
|
|
3693
3794
|
}
|
|
3694
3795
|
loadTrackedCompetitors(projectId) {
|
|
@@ -3838,17 +3939,28 @@ var IntelligenceService = class {
|
|
|
3838
3939
|
buildRunData(runId, projectId, completedAt, location = null) {
|
|
3839
3940
|
const rows = this.db.select({
|
|
3840
3941
|
query: queries.query,
|
|
3942
|
+
// Denormalized query text persisted by v58 — the fallback when the
|
|
3943
|
+
// joined queries.query has been hard-deleted (or the query_id was
|
|
3944
|
+
// nulled by the v58 dangling-FK cleanup).
|
|
3945
|
+
queryText: querySnapshots.queryText,
|
|
3841
3946
|
provider: querySnapshots.provider,
|
|
3842
3947
|
citationState: querySnapshots.citationState,
|
|
3843
3948
|
citedDomains: querySnapshots.citedDomains,
|
|
3844
3949
|
competitorOverlap: querySnapshots.competitorOverlap,
|
|
3845
3950
|
snapshotLocation: querySnapshots.location
|
|
3846
3951
|
}).from(querySnapshots).leftJoin(queries, eq(querySnapshots.queryId, queries.id)).where(eq(querySnapshots.runId, runId)).all();
|
|
3847
|
-
const snapshots =
|
|
3952
|
+
const snapshots = [];
|
|
3953
|
+
let orphanCount = 0;
|
|
3954
|
+
for (const r of rows) {
|
|
3955
|
+
const resolvedQuery = r.query ?? r.queryText ?? null;
|
|
3956
|
+
if (!resolvedQuery) {
|
|
3957
|
+
orphanCount++;
|
|
3958
|
+
continue;
|
|
3959
|
+
}
|
|
3848
3960
|
const domains = parseJsonColumn(r.citedDomains, []);
|
|
3849
3961
|
const competitors2 = parseJsonColumn(r.competitorOverlap, []);
|
|
3850
|
-
|
|
3851
|
-
query:
|
|
3962
|
+
snapshots.push({
|
|
3963
|
+
query: resolvedQuery,
|
|
3852
3964
|
provider: r.provider,
|
|
3853
3965
|
cited: r.citationState === CitationStates.cited,
|
|
3854
3966
|
citationUrl: domains[0] ?? void 0,
|
|
@@ -3863,8 +3975,11 @@ var IntelligenceService = class {
|
|
|
3863
3975
|
// sources). Cause analysis uses it to name the displacing source
|
|
3864
3976
|
// when no tracked competitor appears in the response.
|
|
3865
3977
|
citedDomains: domains
|
|
3866
|
-
};
|
|
3867
|
-
}
|
|
3978
|
+
});
|
|
3979
|
+
}
|
|
3980
|
+
if (orphanCount > 0) {
|
|
3981
|
+
log.warn("snapshot.orphan-skip", { runId, projectId, orphanCount });
|
|
3982
|
+
}
|
|
3868
3983
|
return { runId, projectId, completedAt, location, snapshots };
|
|
3869
3984
|
}
|
|
3870
3985
|
};
|
|
@@ -3928,6 +4043,7 @@ export {
|
|
|
3928
4043
|
buildAiSourceOrigin,
|
|
3929
4044
|
buildMovementSummary,
|
|
3930
4045
|
buildVisibilityScore,
|
|
4046
|
+
buildMentionCoverage,
|
|
3931
4047
|
buildGapQueryScore,
|
|
3932
4048
|
buildCompetitorPressureScore,
|
|
3933
4049
|
buildOverviewCompetitors,
|
|
@@ -22,7 +22,7 @@ import {
|
|
|
22
22
|
trafficConnectVercelRequestSchema,
|
|
23
23
|
trafficConnectWordpressRequestSchema,
|
|
24
24
|
trafficEventKindSchema
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-XJVYVURK.js";
|
|
26
26
|
|
|
27
27
|
// src/config.ts
|
|
28
28
|
import fs from "fs";
|
|
@@ -466,6 +466,9 @@ var ApiClient = class {
|
|
|
466
466
|
async deleteProject(name) {
|
|
467
467
|
await this.request("DELETE", `/projects/${encodeURIComponent(name)}`);
|
|
468
468
|
}
|
|
469
|
+
async previewProjectDelete(name) {
|
|
470
|
+
return this.request("GET", `/projects/${encodeURIComponent(name)}/delete-preview`);
|
|
471
|
+
}
|
|
469
472
|
async putQueries(project, queries) {
|
|
470
473
|
await this.request("PUT", `/projects/${encodeURIComponent(project)}/queries`, { queries });
|
|
471
474
|
}
|
|
@@ -1333,6 +1336,17 @@ var canonryMcpTools = [
|
|
|
1333
1336
|
openApiOperations: ["GET /api/v1/projects/{name}"],
|
|
1334
1337
|
handler: (client, input) => client.getProject(input.project)
|
|
1335
1338
|
}),
|
|
1339
|
+
defineTool({
|
|
1340
|
+
name: "canonry_project_delete_preview",
|
|
1341
|
+
title: "Preview project delete impact",
|
|
1342
|
+
description: "Returns the cascade impact of deleting a project \u2014 how many queries, competitors, runs, snapshots, and insights would be removed, plus how many audit_log rows would be detached (project_id set NULL). Read-only. Use this BEFORE invoking project delete on any project you didn't create yourself; the underlying delete is irreversible.",
|
|
1343
|
+
access: "read",
|
|
1344
|
+
tier: "setup",
|
|
1345
|
+
inputSchema: projectInputSchema,
|
|
1346
|
+
annotations: readAnnotations(),
|
|
1347
|
+
openApiOperations: ["GET /api/v1/projects/{name}/delete-preview"],
|
|
1348
|
+
handler: (client, input) => client.previewProjectDelete(input.project)
|
|
1349
|
+
}),
|
|
1336
1350
|
defineTool({
|
|
1337
1351
|
name: "canonry_project_overview",
|
|
1338
1352
|
title: "Get project overview (composite)",
|
|
@@ -1858,7 +1858,9 @@ var checkCategorySchema = z17.enum([
|
|
|
1858
1858
|
"providers",
|
|
1859
1859
|
"integrations",
|
|
1860
1860
|
"database",
|
|
1861
|
-
"schedules"
|
|
1861
|
+
"schedules",
|
|
1862
|
+
/** Discoverability checks for agent integrations (skills installed, MCP setup). */
|
|
1863
|
+
"agent"
|
|
1862
1864
|
]);
|
|
1863
1865
|
var CheckCategories = checkCategorySchema.enum;
|
|
1864
1866
|
var checkResultSchema = z17.object({
|
package/dist/cli.js
CHANGED
|
@@ -21,7 +21,7 @@ import {
|
|
|
21
21
|
setTelemetrySource,
|
|
22
22
|
showFirstRunNotice,
|
|
23
23
|
trackEvent
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-F2G67CIU.js";
|
|
25
25
|
import {
|
|
26
26
|
CliError,
|
|
27
27
|
EXIT_SYSTEM_ERROR,
|
|
@@ -37,14 +37,14 @@ import {
|
|
|
37
37
|
saveConfig,
|
|
38
38
|
saveConfigPatch,
|
|
39
39
|
usageError
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-O7S623DL.js";
|
|
41
41
|
import {
|
|
42
42
|
apiKeys,
|
|
43
43
|
createClient,
|
|
44
44
|
migrate,
|
|
45
45
|
projects,
|
|
46
46
|
queries
|
|
47
|
-
} from "./chunk-
|
|
47
|
+
} from "./chunk-JQQXMCQ7.js";
|
|
48
48
|
import {
|
|
49
49
|
CcReleaseSyncStatuses,
|
|
50
50
|
CheckScopes,
|
|
@@ -63,7 +63,7 @@ import {
|
|
|
63
63
|
providerQuotaPolicySchema,
|
|
64
64
|
resolveProviderInput,
|
|
65
65
|
skillsClientSchema
|
|
66
|
-
} from "./chunk-
|
|
66
|
+
} from "./chunk-XJVYVURK.js";
|
|
67
67
|
|
|
68
68
|
// src/cli.ts
|
|
69
69
|
import { pathToFileURL } from "url";
|
|
@@ -113,17 +113,11 @@ function matchesPath(args, path10) {
|
|
|
113
113
|
if (args.length < path10.length) return false;
|
|
114
114
|
return path10.every((segment, index) => args[index] === segment);
|
|
115
115
|
}
|
|
116
|
-
function
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
}
|
|
122
|
-
if ("format" in options) return options;
|
|
123
|
-
return {
|
|
124
|
-
...options,
|
|
125
|
-
format: { type: "string" }
|
|
126
|
-
};
|
|
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;
|
|
127
121
|
}
|
|
128
122
|
function toFormat(value, fallbackFormat) {
|
|
129
123
|
return value === "json" ? "json" : fallbackFormat;
|
|
@@ -179,7 +173,7 @@ Usage: ${single.usage}
|
|
|
179
173
|
try {
|
|
180
174
|
const parsed = parseArgs({
|
|
181
175
|
args: remainingArgs,
|
|
182
|
-
options:
|
|
176
|
+
options: withGlobalOptions(spec.options),
|
|
183
177
|
allowPositionals: spec.allowPositionals ?? true
|
|
184
178
|
});
|
|
185
179
|
values = parsed.values;
|
|
@@ -195,10 +189,21 @@ Usage: ${spec.usage}`, {
|
|
|
195
189
|
}
|
|
196
190
|
});
|
|
197
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
|
+
}
|
|
198
202
|
await spec.run({
|
|
199
203
|
positionals,
|
|
200
204
|
values,
|
|
201
|
-
format: toFormat(values.format, fallbackFormat)
|
|
205
|
+
format: toFormat(values.format, fallbackFormat),
|
|
206
|
+
dryRun: dryRunRequested
|
|
202
207
|
});
|
|
203
208
|
return true;
|
|
204
209
|
}
|
|
@@ -315,17 +320,21 @@ var BACKFILL_CLI_COMMANDS = [
|
|
|
315
320
|
},
|
|
316
321
|
{
|
|
317
322
|
path: ["backfill", "insights"],
|
|
318
|
-
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]",
|
|
319
324
|
options: {
|
|
320
325
|
"from-run": stringOption(),
|
|
321
|
-
"to-run": stringOption()
|
|
326
|
+
"to-run": stringOption(),
|
|
327
|
+
"since": stringOption()
|
|
322
328
|
},
|
|
329
|
+
supportsDryRun: true,
|
|
323
330
|
run: async (input) => {
|
|
324
|
-
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]";
|
|
325
332
|
const project = requireProject(input, "backfill insights", usage);
|
|
326
333
|
await backfillInsightsCommand(project, {
|
|
327
334
|
fromRun: getString(input.values, "from-run"),
|
|
328
335
|
toRun: getString(input.values, "to-run"),
|
|
336
|
+
since: getString(input.values, "since"),
|
|
337
|
+
dryRun: input.dryRun,
|
|
329
338
|
format: input.format
|
|
330
339
|
});
|
|
331
340
|
}
|
|
@@ -5948,10 +5957,31 @@ async function updateProjectSettings(name, opts) {
|
|
|
5948
5957
|
}
|
|
5949
5958
|
console.log(`Project updated: ${result.name}`);
|
|
5950
5959
|
}
|
|
5951
|
-
async function deleteProject(name,
|
|
5960
|
+
async function deleteProject(name, opts) {
|
|
5952
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
|
+
}
|
|
5953
5983
|
await client.deleteProject(name);
|
|
5954
|
-
if (
|
|
5984
|
+
if (isJson) {
|
|
5955
5985
|
console.log(JSON.stringify({ name, deleted: true }, null, 2));
|
|
5956
5986
|
return;
|
|
5957
5987
|
}
|
|
@@ -6101,10 +6131,11 @@ var PROJECT_CLI_COMMANDS = [
|
|
|
6101
6131
|
},
|
|
6102
6132
|
{
|
|
6103
6133
|
path: ["project", "delete"],
|
|
6104
|
-
usage: "canonry project delete <name> [--format json]",
|
|
6134
|
+
usage: "canonry project delete <name> [--dry-run] [--format json]",
|
|
6135
|
+
supportsDryRun: true,
|
|
6105
6136
|
run: async (input) => {
|
|
6106
|
-
const name = requireProject(input, "project.delete", "canonry project delete <name> [--format json]");
|
|
6107
|
-
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 });
|
|
6108
6139
|
}
|
|
6109
6140
|
},
|
|
6110
6141
|
{
|
|
@@ -6986,6 +7017,7 @@ Usage: canonry settings provider ${name} --api-key <key> [--model <model>] [--ma
|
|
|
6986
7017
|
|
|
6987
7018
|
// src/commands/skills.ts
|
|
6988
7019
|
import fs7 from "fs";
|
|
7020
|
+
import os2 from "os";
|
|
6989
7021
|
import path4 from "path";
|
|
6990
7022
|
import { fileURLToPath } from "url";
|
|
6991
7023
|
var BUNDLED_SKILL_NAMES = ["canonry", "aero"];
|
|
@@ -7162,7 +7194,7 @@ function buildSummaryMessage(results) {
|
|
|
7162
7194
|
return `Skills install summary: ${parts.join(", ")}.`;
|
|
7163
7195
|
}
|
|
7164
7196
|
async function installSkills(opts = {}) {
|
|
7165
|
-
const targetDir = path4.resolve(opts.dir ?? process.cwd());
|
|
7197
|
+
const targetDir = opts.user ? os2.homedir() : path4.resolve(opts.dir ?? process.cwd());
|
|
7166
7198
|
const client = opts.client ?? SkillsClients.all;
|
|
7167
7199
|
const force = opts.force ?? false;
|
|
7168
7200
|
const allSkills = getBundledSkills();
|
|
@@ -7248,9 +7280,10 @@ var SKILLS_CLI_COMMANDS = [
|
|
|
7248
7280
|
},
|
|
7249
7281
|
{
|
|
7250
7282
|
path: ["skills", "install"],
|
|
7251
|
-
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]",
|
|
7252
7284
|
options: {
|
|
7253
7285
|
dir: stringOption(),
|
|
7286
|
+
user: { type: "boolean" },
|
|
7254
7287
|
client: stringOption(),
|
|
7255
7288
|
force: { type: "boolean" }
|
|
7256
7289
|
},
|
|
@@ -7258,6 +7291,7 @@ var SKILLS_CLI_COMMANDS = [
|
|
|
7258
7291
|
run: async (input) => {
|
|
7259
7292
|
const summary = await installSkills({
|
|
7260
7293
|
dir: getString(input.values, "dir"),
|
|
7294
|
+
user: getBoolean(input.values, "user"),
|
|
7261
7295
|
skills: input.positionals.length > 0 ? input.positionals : void 0,
|
|
7262
7296
|
client: parseSkillsClient(getString(input.values, "client")),
|
|
7263
7297
|
force: getBoolean(input.values, "force")
|
package/dist/index.js
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createServer
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-F2G67CIU.js";
|
|
4
4
|
import {
|
|
5
5
|
loadConfig
|
|
6
|
-
} from "./chunk-
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
6
|
+
} from "./chunk-O7S623DL.js";
|
|
7
|
+
import "./chunk-JQQXMCQ7.js";
|
|
8
|
+
import "./chunk-XJVYVURK.js";
|
|
9
9
|
export {
|
|
10
10
|
createServer,
|
|
11
11
|
loadConfig
|
package/dist/mcp.js
CHANGED
|
@@ -2,8 +2,8 @@ import {
|
|
|
2
2
|
CliError,
|
|
3
3
|
canonryMcpTools,
|
|
4
4
|
createApiClient
|
|
5
|
-
} from "./chunk-
|
|
6
|
-
import "./chunk-
|
|
5
|
+
} from "./chunk-O7S623DL.js";
|
|
6
|
+
import "./chunk-XJVYVURK.js";
|
|
7
7
|
|
|
8
8
|
// src/mcp/cli.ts
|
|
9
9
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.36.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Agent-first open-source AEO operating platform - track how answer engines cite your domain",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -62,18 +62,18 @@
|
|
|
62
62
|
"tsx": "^4.19.0",
|
|
63
63
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
64
64
|
"@ainyc/canonry-config": "0.0.0",
|
|
65
|
-
"@ainyc/canonry-contracts": "0.0.0",
|
|
66
65
|
"@ainyc/canonry-db": "0.0.0",
|
|
67
66
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
68
67
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
69
68
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
70
|
-
"@ainyc/canonry-
|
|
69
|
+
"@ainyc/canonry-contracts": "0.0.0",
|
|
71
70
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
72
71
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
72
|
+
"@ainyc/canonry-integration-google": "0.0.0",
|
|
73
73
|
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
74
74
|
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
76
75
|
"@ainyc/canonry-provider-gemini": "0.0.0",
|
|
76
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
77
77
|
"@ainyc/canonry-provider-local": "0.0.0",
|
|
78
78
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
79
79
|
"@ainyc/canonry-provider-perplexity": "0.0.0"
|