@canonry/canonry 7.12.0 → 7.14.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/agent-workspace/skills/canonry/references/canonry-cli.md +14 -7
- package/assets/assets/AuditHistoryPanel-gAkqWz9z.js +1 -0
- package/assets/assets/{BacklinksPage-CbRIZhu5.js → BacklinksPage-C67DtHo8.js} +1 -1
- package/assets/assets/{HistoryPage-Dfq1vK9k.js → HistoryPage-BQuW6Z6m.js} +1 -1
- package/assets/assets/{MeasurementPropertyPage-DcH0zYNN.js → MeasurementPropertyPage-B1XS6DR0.js} +1 -1
- package/assets/assets/ProjectPage-CV_7bOA7.js +8 -0
- package/assets/assets/{RunsPage-aOeJ60pX.js → RunsPage-CJYihZYC.js} +1 -1
- package/assets/assets/SettingsPage-Dd3J2DQE.js +1 -0
- package/assets/assets/{SiteHealthSection-CUOt7jeo.js → SiteHealthSection-iktyuzt2.js} +3 -3
- package/assets/assets/{TrafficPage-C0fVYizx.js → TrafficPage-CWjK7F3j.js} +2 -2
- package/assets/assets/{TrafficSourceDetailPage-LMhXbFlA.js → TrafficSourceDetailPage-D4_AXQNE.js} +1 -1
- package/assets/assets/{extract-error-message-5aHy0fsK.js → extract-error-message-Cxefvthz.js} +1 -1
- package/assets/assets/{index-BJh8cX5E.js → index-BbmiGIM_.js} +27 -27
- package/assets/assets/index-aBS1f2Bj.css +1 -0
- package/assets/assets/{react-sigma_core.esm.min-ldX35rMq.js → react-sigma_core.esm.min-DDEPa7AV.js} +1 -1
- package/assets/assets/{v2-overview-adapter-CaO9vY-l.js → v2-overview-adapter-CLxlMdb2.js} +1 -1
- package/assets/index.html +2 -2
- package/dist/{chunk-BTHEFJCI.js → chunk-4LQ4HKVK.js} +5 -5
- package/dist/{chunk-AU7OUDWR.js → chunk-A25FO3Z5.js} +64 -13
- package/dist/{chunk-GQMSJEMZ.js → chunk-EZRBFH77.js} +233 -54
- package/dist/{chunk-DXI2FTSW.js → chunk-KXHPQMBP.js} +93 -31
- package/dist/{chunk-EPSHIH63.js → chunk-LUQ5G5OO.js} +3 -3
- package/dist/{chunk-7I64KEGM.js → chunk-WHVEDCHY.js} +2 -2
- package/dist/{chunk-SM6ZZAPK.js → chunk-ZHXOR2LJ.js} +2 -2
- package/dist/cli.js +1300 -1244
- package/dist/{demo-server-WJUA4EIG.js → demo-server-SIATTXXS.js} +4 -4
- package/dist/index.js +6 -6
- package/dist/{intelligence-service-DJRH4Z3D.js → intelligence-service-UEEVETII.js} +2 -2
- package/dist/mcp.js +4 -4
- package/package.json +13 -13
- package/assets/assets/AuditHistoryPanel-Bpkuj-iF.js +0 -1
- package/assets/assets/ProjectPage-enF4twWt.js +0 -8
- package/assets/assets/SettingsPage-nA0_ImoQ.js +0 -1
- package/assets/assets/index-BNeiohKS.css +0 -1
package/dist/cli.js
CHANGED
|
@@ -33,11 +33,11 @@ import {
|
|
|
33
33
|
trackCliCommandFinished,
|
|
34
34
|
trackEvent,
|
|
35
35
|
waitForServerRuntimeStartup
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-KXHPQMBP.js";
|
|
37
37
|
import {
|
|
38
38
|
autoSyncSkills,
|
|
39
39
|
formatAutoSyncNotice
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-LUQ5G5OO.js";
|
|
41
41
|
import {
|
|
42
42
|
configExists,
|
|
43
43
|
createApiClient,
|
|
@@ -54,13 +54,13 @@ import {
|
|
|
54
54
|
saveConfig,
|
|
55
55
|
saveConfigPatch,
|
|
56
56
|
shouldPrintServeSkillsNudge
|
|
57
|
-
} from "./chunk-
|
|
57
|
+
} from "./chunk-ZHXOR2LJ.js";
|
|
58
58
|
import {
|
|
59
59
|
DEFAULT_DEMO_TRUSTED_PROXIES,
|
|
60
60
|
trustedProxiesProblem,
|
|
61
61
|
trustedProxyProblem
|
|
62
62
|
} from "./chunk-RB2F4D3Z.js";
|
|
63
|
-
import "./chunk-
|
|
63
|
+
import "./chunk-WHVEDCHY.js";
|
|
64
64
|
import {
|
|
65
65
|
CliError,
|
|
66
66
|
EXIT_SYSTEM_ERROR,
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
runMeasurementDraftAction,
|
|
73
73
|
systemError,
|
|
74
74
|
usageError
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-4LQ4HKVK.js";
|
|
76
76
|
import {
|
|
77
77
|
CLOUDFLARE_WORKER_BINDINGS,
|
|
78
78
|
CLOUDFLARE_WORKER_GENERATED_MARKER,
|
|
@@ -104,7 +104,7 @@ import {
|
|
|
104
104
|
rawEventSamples,
|
|
105
105
|
readMarketCompetitorNames,
|
|
106
106
|
runs
|
|
107
|
-
} from "./chunk-
|
|
107
|
+
} from "./chunk-EZRBFH77.js";
|
|
108
108
|
import {
|
|
109
109
|
AdsDeliverySnapshotStatuses,
|
|
110
110
|
AdsHistoricalCampaignRollupStatuses,
|
|
@@ -234,7 +234,7 @@ import {
|
|
|
234
234
|
visibilityCompareSelectionSchema,
|
|
235
235
|
visibilityReportRequestSchema,
|
|
236
236
|
winnabilityClassSchema
|
|
237
|
-
} from "./chunk-
|
|
237
|
+
} from "./chunk-A25FO3Z5.js";
|
|
238
238
|
|
|
239
239
|
// src/cli.ts
|
|
240
240
|
import { pathToFileURL } from "url";
|
|
@@ -784,7 +784,7 @@ function parseDemoTrustedProxies(values) {
|
|
|
784
784
|
async function demoCommand(options) {
|
|
785
785
|
const { host, port } = parseDemoListenOptions(options);
|
|
786
786
|
const trustProxy = parseDemoTrustedProxies(options.trustProxy);
|
|
787
|
-
const { createDemoServer } = await import("./demo-server-
|
|
787
|
+
const { createDemoServer } = await import("./demo-server-SIATTXXS.js");
|
|
788
788
|
const app = await createDemoServer({ trustProxy });
|
|
789
789
|
let stopping = false;
|
|
790
790
|
const stop = () => {
|
|
@@ -1198,7 +1198,7 @@ No DB writes performed. Re-run without --dry-run to apply.`);
|
|
|
1198
1198
|
}
|
|
1199
1199
|
}
|
|
1200
1200
|
async function backfillInsightsCommand(project2, opts) {
|
|
1201
|
-
const { IntelligenceService } = await import("./intelligence-service-
|
|
1201
|
+
const { IntelligenceService } = await import("./intelligence-service-UEEVETII.js");
|
|
1202
1202
|
const config = loadConfig();
|
|
1203
1203
|
const db = createClient(config.database);
|
|
1204
1204
|
migrate(db);
|
|
@@ -13444,1432 +13444,1485 @@ async function clearResults(project2, opts) {
|
|
|
13444
13444
|
}
|
|
13445
13445
|
}
|
|
13446
13446
|
|
|
13447
|
-
// src/commands/
|
|
13447
|
+
// src/commands/run.ts
|
|
13448
13448
|
function getClient21() {
|
|
13449
13449
|
return createApiClient();
|
|
13450
13450
|
}
|
|
13451
|
-
|
|
13451
|
+
var TERMINAL_STATUSES = /* @__PURE__ */ new Set(["completed", "partial", "failed", "cancelled"]);
|
|
13452
|
+
function shellArg(value) {
|
|
13453
|
+
return /^[\w.-]+$/.test(value) ? value : `'${value.replaceAll("'", `'\\''`)}'`;
|
|
13454
|
+
}
|
|
13455
|
+
function providerAccountStreakLine(provider, streak) {
|
|
13456
|
+
return `${provider} (${streak.code}): failed on its account in each of its last ${streak.consecutiveRuns} runs; called again after ${streak.retryAfter}`;
|
|
13457
|
+
}
|
|
13458
|
+
function providersThatKeepFailing(count) {
|
|
13459
|
+
return count === 1 ? "a provider that keeps failing on its account" : `${count} providers that keep failing on their accounts`;
|
|
13460
|
+
}
|
|
13461
|
+
function providerAccountFixLine(project2) {
|
|
13462
|
+
return `Fix the key, access or billing in the provider's console. Saving a new key, model or endpoint (canonry settings provider <name>) retries at once; canonry run ${shellArg(project2)} --force calls every provider now.`;
|
|
13463
|
+
}
|
|
13464
|
+
function admissionDecisionKey(admission) {
|
|
13465
|
+
return JSON.stringify([
|
|
13466
|
+
admission.refused,
|
|
13467
|
+
admission.retryAfter,
|
|
13468
|
+
Object.entries(admission.providers).sort(([left], [right]) => left.localeCompare(right)).map(([provider, streak]) => [provider, streak.code, streak.consecutiveRuns, streak.since, streak.latestRunId, streak.retryAfter])
|
|
13469
|
+
]);
|
|
13470
|
+
}
|
|
13471
|
+
function admissionScopeLines(admission, label, showAllowed) {
|
|
13472
|
+
const held = Object.entries(admission.providers);
|
|
13473
|
+
if (held.length === 0) return showAllowed ? [`${label}: no providers held back.`] : [];
|
|
13474
|
+
const retry = label === "Manual sweep" ? `it can retry after ${admission.retryAfter}` : `scheduled sweeps skip their slots until ${admission.retryAfter}`;
|
|
13475
|
+
return [
|
|
13476
|
+
admission.refused ? `${label}: refused (PROVIDERS_FAILING). Every provider it would call keeps failing on its account; ${retry}.` : `${label}: skips ${providersThatKeepFailing(held.length)}; the rest run.`,
|
|
13477
|
+
...held.map(([provider, streak]) => ` ${providerAccountStreakLine(provider, streak)}`)
|
|
13478
|
+
];
|
|
13479
|
+
}
|
|
13480
|
+
function runAdmissionLines(project2, admission, manualAdmission) {
|
|
13481
|
+
if (!admission) return [];
|
|
13482
|
+
const differs = manualAdmission !== void 0 && admissionDecisionKey(admission) !== admissionDecisionKey(manualAdmission);
|
|
13483
|
+
const lines = differs ? [...admissionScopeLines(admission, "Scheduled sweep", true), ...admissionScopeLines(manualAdmission, "Manual sweep", true)] : admissionScopeLines(admission, "Next sweep", false);
|
|
13484
|
+
return lines.length > 0 ? [...lines, providerAccountFixLine(project2)] : [];
|
|
13485
|
+
}
|
|
13486
|
+
function printSkippedProviders(project2, skipped) {
|
|
13487
|
+
const held = Object.entries(skipped ?? {});
|
|
13488
|
+
if (held.length === 0) return;
|
|
13489
|
+
console.log(`
|
|
13490
|
+
Not calling ${providersThatKeepFailing(held.length)}:`);
|
|
13491
|
+
for (const [provider, streak] of held) console.log(` ${providerAccountStreakLine(provider, streak)}`);
|
|
13492
|
+
console.log(providerAccountFixLine(project2));
|
|
13493
|
+
}
|
|
13494
|
+
function throwIfWaitedRunFailed(waited) {
|
|
13495
|
+
const failed = waited.filter((r) => r.status === RunStatuses.failed);
|
|
13496
|
+
if (failed.length === 0) return;
|
|
13497
|
+
const reason = (r) => r.error ? formatRunErrorOneLine(r.error) : null;
|
|
13498
|
+
const label = (r) => [r.project, r.location ? `(${r.location})` : null, `run ${r.runId}`].filter(Boolean).join(" ");
|
|
13499
|
+
const message = waited.length === 1 ? `Run ${failed[0].runId} failed${reason(failed[0]) ? `: ${reason(failed[0])}` : ""}` : `${failed.length} of ${waited.length} runs failed: ${failed.map((r) => `${label(r)}${reason(r) ? `: ${reason(r)}` : ""}`).join("; ")}`;
|
|
13500
|
+
throw new CliError({
|
|
13501
|
+
code: "RUN_FAILED",
|
|
13502
|
+
message,
|
|
13503
|
+
exitCode: EXIT_SYSTEM_ERROR,
|
|
13504
|
+
details: {
|
|
13505
|
+
waitedRunCount: waited.length,
|
|
13506
|
+
failedRuns: failed.map((r) => ({
|
|
13507
|
+
runId: r.runId,
|
|
13508
|
+
...r.project ? { project: r.project } : {},
|
|
13509
|
+
...r.location ? { location: r.location } : {},
|
|
13510
|
+
error: reason(r)
|
|
13511
|
+
}))
|
|
13512
|
+
}
|
|
13513
|
+
});
|
|
13514
|
+
}
|
|
13515
|
+
async function triggerRun(project2, opts) {
|
|
13452
13516
|
const client = getClient21();
|
|
13453
|
-
const
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13517
|
+
const body = {};
|
|
13518
|
+
if (opts?.provider) {
|
|
13519
|
+
const providerInputs = opts.provider.split(",").map((s) => s.trim()).filter(Boolean);
|
|
13520
|
+
const resolved = providerInputs.flatMap((p) => resolveProviderInput(p));
|
|
13521
|
+
body.providers = resolved.length > 0 ? resolved : providerInputs;
|
|
13522
|
+
}
|
|
13523
|
+
if (opts?.queries?.length) {
|
|
13524
|
+
body.queries = opts.queries;
|
|
13525
|
+
}
|
|
13526
|
+
if (opts?.groups?.length || opts?.targets?.length) {
|
|
13527
|
+
body.measurementScope = {
|
|
13528
|
+
...opts.groups?.length ? { groups: opts.groups } : {},
|
|
13529
|
+
...opts.targets?.length ? { targets: opts.targets } : {}
|
|
13530
|
+
};
|
|
13531
|
+
}
|
|
13532
|
+
if (opts?.location) {
|
|
13533
|
+
body.location = opts.location;
|
|
13534
|
+
}
|
|
13535
|
+
if (opts?.allLocations) {
|
|
13536
|
+
body.allLocations = true;
|
|
13537
|
+
}
|
|
13538
|
+
if (opts?.noLocation) {
|
|
13539
|
+
body.noLocation = true;
|
|
13540
|
+
}
|
|
13541
|
+
if (opts?.probe) {
|
|
13542
|
+
body.trigger = "probe";
|
|
13543
|
+
}
|
|
13544
|
+
if (opts?.dispatchMode) {
|
|
13545
|
+
body.dispatchMode = opts.dispatchMode;
|
|
13546
|
+
}
|
|
13547
|
+
if (opts?.force) {
|
|
13548
|
+
body.force = true;
|
|
13549
|
+
}
|
|
13550
|
+
const response = await client.triggerRun(project2, body);
|
|
13551
|
+
if (Array.isArray(response)) {
|
|
13552
|
+
const locationRuns = response;
|
|
13553
|
+
if (isMachineFormat(opts?.format)) {
|
|
13554
|
+
if (opts?.wait) {
|
|
13555
|
+
const finals = await Promise.all(
|
|
13556
|
+
locationRuns.map(async (r) => !r.id || r.status === "conflict" ? null : pollRun2(client, r.id, false))
|
|
13557
|
+
);
|
|
13558
|
+
console.log(JSON.stringify(locationRuns.map((r, i) => finals[i] ? { ...r, ...finals[i] } : r), null, 2));
|
|
13559
|
+
throwIfWaitedRunFailed(locationRuns.flatMap((r, i) => {
|
|
13560
|
+
const final = finals[i];
|
|
13561
|
+
return final ? [{ runId: final.id, status: final.status, project: project2, location: r.location ?? null, error: final.error }] : [];
|
|
13562
|
+
}));
|
|
13563
|
+
} else {
|
|
13564
|
+
console.log(JSON.stringify(locationRuns, null, 2));
|
|
13565
|
+
}
|
|
13566
|
+
return;
|
|
13567
|
+
}
|
|
13568
|
+
console.log(`Triggered ${locationRuns.length} location sweep(s) \u2014 ${locationRuns.length}\xD7 API calls:
|
|
13569
|
+
`);
|
|
13570
|
+
console.log(" LOCATION RUN ID STATUS");
|
|
13571
|
+
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");
|
|
13572
|
+
for (const r of locationRuns) {
|
|
13573
|
+
const loc = (r.location ?? "(unknown)").padEnd(15);
|
|
13574
|
+
const id = (r.id ?? "(conflict)").padEnd(36);
|
|
13575
|
+
console.log(` ${loc} ${id} ${r.status}`);
|
|
13576
|
+
}
|
|
13577
|
+
printSkippedProviders(project2, locationRuns.find((r) => r.skippedProviders)?.skippedProviders);
|
|
13578
|
+
if (opts?.wait) {
|
|
13579
|
+
const pending = locationRuns.filter((r) => r.id && r.status !== "conflict" && !TERMINAL_STATUSES.has(r.status));
|
|
13580
|
+
const errors = /* @__PURE__ */ new Map();
|
|
13581
|
+
if (pending.length > 0) {
|
|
13582
|
+
process.stderr.write(`Waiting for ${pending.length} run(s)`);
|
|
13583
|
+
const batchWaits = /* @__PURE__ */ new Map();
|
|
13584
|
+
await Promise.all(
|
|
13585
|
+
pending.map(async (r) => {
|
|
13586
|
+
const final = await pollRun2(client, r.id);
|
|
13587
|
+
r.status = final.status;
|
|
13588
|
+
errors.set(r.id, final.error);
|
|
13589
|
+
const waiting = batchWaitLine(final);
|
|
13590
|
+
if (waiting) batchWaits.set(r.id, waiting);
|
|
13591
|
+
})
|
|
13592
|
+
);
|
|
13593
|
+
process.stderr.write("\n");
|
|
13594
|
+
console.log("\nFinal statuses:");
|
|
13595
|
+
for (const r of locationRuns) {
|
|
13596
|
+
const loc = (r.location ?? "(unknown)").padEnd(15);
|
|
13597
|
+
console.log(` ${loc} ${r.status}`);
|
|
13598
|
+
}
|
|
13599
|
+
if (batchWaits.size > 0) console.log("");
|
|
13600
|
+
for (const r of locationRuns) {
|
|
13601
|
+
const waiting = batchWaits.get(r.id);
|
|
13602
|
+
if (waiting) console.log(` ${r.location ?? "(unknown)"}: ${waiting}`);
|
|
13603
|
+
}
|
|
13604
|
+
}
|
|
13605
|
+
throwIfWaitedRunFailed(locationRuns.filter((r) => r.id && r.status !== "conflict").map((r) => ({ runId: r.id, status: r.status, project: project2, location: r.location ?? null, error: errors.get(r.id) })));
|
|
13460
13606
|
}
|
|
13461
|
-
console.log(JSON.stringify({
|
|
13462
|
-
project: projectData,
|
|
13463
|
-
runs: runs2,
|
|
13464
|
-
latestRun: latest.run,
|
|
13465
|
-
totalRuns: latest.totalRuns
|
|
13466
|
-
}, null, 2));
|
|
13467
13607
|
return;
|
|
13468
13608
|
}
|
|
13469
|
-
|
|
13470
|
-
|
|
13471
|
-
|
|
13472
|
-
|
|
13473
|
-
|
|
13474
|
-
|
|
13475
|
-
|
|
13476
|
-
|
|
13477
|
-
|
|
13478
|
-
|
|
13479
|
-
|
|
13480
|
-
|
|
13481
|
-
|
|
13609
|
+
const run = response;
|
|
13610
|
+
if (opts?.wait && run.id && !TERMINAL_STATUSES.has(run.status)) {
|
|
13611
|
+
const showProgress = !isMachineFormat(opts?.format);
|
|
13612
|
+
if (showProgress) process.stderr.write(`Run ${run.id} started`);
|
|
13613
|
+
const result = await pollRun2(client, run.id, showProgress);
|
|
13614
|
+
if (isMachineFormat(opts?.format)) {
|
|
13615
|
+
console.log(JSON.stringify(result, null, 2));
|
|
13616
|
+
} else {
|
|
13617
|
+
process.stderr.write("\n");
|
|
13618
|
+
printRunDetail(result);
|
|
13619
|
+
const waiting = batchWaitLine(result);
|
|
13620
|
+
if (waiting) console.log(`
|
|
13621
|
+
${waiting}`);
|
|
13482
13622
|
}
|
|
13483
|
-
|
|
13484
|
-
|
|
13485
|
-
} else {
|
|
13486
|
-
const projectArg = /^[\w.-]+$/.test(project2) ? project2 : `'${project2.replaceAll("'", `'\\''`)}'`;
|
|
13487
|
-
console.log(`
|
|
13488
|
-
No runs yet. Capture Page Health with "canonry technical-aeo run ${projectArg}", or run an AI Visibility sweep after adding a provider.`);
|
|
13623
|
+
throwIfWaitedRunFailed([{ runId: result.id, status: result.status, project: project2, error: result.error }]);
|
|
13624
|
+
return;
|
|
13489
13625
|
}
|
|
13490
|
-
|
|
13491
|
-
|
|
13492
|
-
|
|
13493
|
-
|
|
13494
|
-
|
|
13495
|
-
|
|
13496
|
-
const runs2 = await client.listRuns(project2);
|
|
13497
|
-
if (runs2.length === 0) {
|
|
13498
|
-
return { totalRuns: 0, run: null };
|
|
13626
|
+
if (opts?.wait && (TERMINAL_STATUSES.has(run.status) || !run.id)) {
|
|
13627
|
+
const result = run.id ? await client.getRun(run.id) : run;
|
|
13628
|
+
if (isMachineFormat(opts?.format)) {
|
|
13629
|
+
console.log(JSON.stringify(result, null, 2));
|
|
13630
|
+
} else {
|
|
13631
|
+
printRunDetail(result);
|
|
13499
13632
|
}
|
|
13500
|
-
|
|
13501
|
-
|
|
13502
|
-
);
|
|
13503
|
-
return {
|
|
13504
|
-
totalRuns: runs2.length,
|
|
13505
|
-
run: latestRun
|
|
13506
|
-
};
|
|
13633
|
+
throwIfWaitedRunFailed([{ runId: result.id, status: result.status, project: project2, error: result.error }]);
|
|
13634
|
+
return;
|
|
13507
13635
|
}
|
|
13508
|
-
|
|
13509
|
-
|
|
13510
|
-
|
|
13511
|
-
var RESULTS_EXPORT_USAGE = "canonry results export <project> [--format json|csv] [--since <ISO>] [--until <ISO>] [--include-probes] [--output <path>|-]";
|
|
13512
|
-
function parseResultsExportFormat(value) {
|
|
13513
|
-
if (value === void 0 || value === "json") return "json";
|
|
13514
|
-
if (value === "csv") return "csv";
|
|
13515
|
-
throw usageError(`Error: --format must be "json" or "csv"
|
|
13516
|
-
|
|
13517
|
-
Usage: ${RESULTS_EXPORT_USAGE}`);
|
|
13518
|
-
}
|
|
13519
|
-
var RESULTS_CLEAR_USAGE = "canonry results clear <project> [--run <id> ...] [--research-run <id> ...] [--confirm] [--format json|jsonl]";
|
|
13520
|
-
function parseHistoryPageOption(input, key, usage) {
|
|
13521
|
-
const value = getString(input.values, key);
|
|
13522
|
-
if (value === void 0) return void 0;
|
|
13523
|
-
if (!/^\d+$/.test(value)) {
|
|
13524
|
-
throw usageError(`Error: --${key} must be a ${key === "limit" ? "whole number from 1 to 500" : "nonnegative integer"}
|
|
13525
|
-
Usage: ${usage}`);
|
|
13636
|
+
if (isMachineFormat(opts?.format)) {
|
|
13637
|
+
console.log(JSON.stringify(run, null, 2));
|
|
13638
|
+
return;
|
|
13526
13639
|
}
|
|
13527
|
-
|
|
13528
|
-
|
|
13529
|
-
|
|
13530
|
-
|
|
13531
|
-
|
|
13640
|
+
console.log(`Run created: ${run.id}`);
|
|
13641
|
+
console.log(` Kind: ${run.kind}`);
|
|
13642
|
+
console.log(` Status: ${run.status}`);
|
|
13643
|
+
if (opts?.provider) {
|
|
13644
|
+
console.log(` Provider: ${opts.provider}`);
|
|
13532
13645
|
}
|
|
13533
|
-
|
|
13646
|
+
printSkippedProviders(project2, run.skippedProviders);
|
|
13534
13647
|
}
|
|
13535
|
-
|
|
13536
|
-
|
|
13537
|
-
|
|
13538
|
-
|
|
13539
|
-
|
|
13540
|
-
|
|
13541
|
-
|
|
13542
|
-
|
|
13543
|
-
researchRunIds: getStringArray(input.values, "research-run") ?? [],
|
|
13544
|
-
confirm: getBoolean(input.values, "confirm"),
|
|
13545
|
-
format: input.format
|
|
13546
|
-
});
|
|
13648
|
+
async function triggerRunAll(opts) {
|
|
13649
|
+
const client = getClient21();
|
|
13650
|
+
const projects2 = await client.listProjects();
|
|
13651
|
+
if (projects2.length === 0) {
|
|
13652
|
+
if (isMachineFormat(opts?.format)) {
|
|
13653
|
+
console.log("[]");
|
|
13654
|
+
} else {
|
|
13655
|
+
console.log("No projects found.");
|
|
13547
13656
|
}
|
|
13548
|
-
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13657
|
+
return;
|
|
13658
|
+
}
|
|
13659
|
+
const baseBody = {};
|
|
13660
|
+
if (opts?.provider) {
|
|
13661
|
+
const providerInputs = opts.provider.split(",").map((s) => s.trim()).filter(Boolean);
|
|
13662
|
+
const resolved = providerInputs.flatMap((p) => resolveProviderInput(p));
|
|
13663
|
+
baseBody.providers = resolved.length > 0 ? resolved : providerInputs;
|
|
13664
|
+
}
|
|
13665
|
+
if (opts?.allLocations) {
|
|
13666
|
+
baseBody.allLocations = true;
|
|
13667
|
+
}
|
|
13668
|
+
if (opts?.noLocation) {
|
|
13669
|
+
baseBody.noLocation = true;
|
|
13670
|
+
}
|
|
13671
|
+
if (opts?.dispatchMode) {
|
|
13672
|
+
baseBody.dispatchMode = opts.dispatchMode;
|
|
13673
|
+
}
|
|
13674
|
+
if (opts?.force) {
|
|
13675
|
+
baseBody.force = true;
|
|
13676
|
+
}
|
|
13677
|
+
const results = [];
|
|
13678
|
+
for (const p of projects2) {
|
|
13679
|
+
const body = { ...baseBody };
|
|
13680
|
+
if (body.allLocations && p.locations.length === 0) {
|
|
13681
|
+
delete body.allLocations;
|
|
13567
13682
|
}
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13573
|
-
|
|
13574
|
-
|
|
13683
|
+
try {
|
|
13684
|
+
const response = await client.triggerRun(p.name, body);
|
|
13685
|
+
const dispatched = Array.isArray(response) ? response : [response];
|
|
13686
|
+
for (const r of dispatched) {
|
|
13687
|
+
results.push({
|
|
13688
|
+
project: p.name,
|
|
13689
|
+
runId: r.id,
|
|
13690
|
+
status: r.status,
|
|
13691
|
+
location: r.location ?? null
|
|
13692
|
+
});
|
|
13693
|
+
}
|
|
13694
|
+
} catch (err) {
|
|
13695
|
+
const msg = describeError(err);
|
|
13696
|
+
results.push({ project: p.name, runId: "", status: "error", location: null, error: msg });
|
|
13575
13697
|
}
|
|
13576
|
-
}
|
|
13577
|
-
|
|
13578
|
-
|
|
13579
|
-
|
|
13580
|
-
|
|
13581
|
-
|
|
13582
|
-
|
|
13698
|
+
}
|
|
13699
|
+
const errors = /* @__PURE__ */ new Map();
|
|
13700
|
+
const batchWaits = /* @__PURE__ */ new Map();
|
|
13701
|
+
if (opts?.wait) {
|
|
13702
|
+
const pending = results.filter((r) => r.runId && !TERMINAL_STATUSES.has(r.status));
|
|
13703
|
+
if (pending.length > 0) {
|
|
13704
|
+
const showProgress = !isMachineFormat(opts?.format);
|
|
13705
|
+
if (showProgress) process.stderr.write(`Waiting for ${pending.length} run(s)`);
|
|
13706
|
+
await Promise.all(pending.map(async (r) => {
|
|
13707
|
+
const final = await pollRun2(client, r.runId, showProgress);
|
|
13708
|
+
r.status = final.status;
|
|
13709
|
+
errors.set(r.runId, final.error);
|
|
13710
|
+
const waiting = batchWaitLine(final);
|
|
13711
|
+
if (waiting) batchWaits.set(r.runId, waiting);
|
|
13712
|
+
}));
|
|
13713
|
+
if (showProgress) process.stderr.write("\n");
|
|
13583
13714
|
}
|
|
13584
|
-
}
|
|
13585
|
-
{
|
|
13586
|
-
|
|
13587
|
-
|
|
13588
|
-
|
|
13589
|
-
|
|
13590
|
-
|
|
13591
|
-
|
|
13592
|
-
|
|
13593
|
-
|
|
13594
|
-
|
|
13595
|
-
|
|
13596
|
-
|
|
13597
|
-
|
|
13598
|
-
|
|
13599
|
-
const
|
|
13600
|
-
|
|
13601
|
-
|
|
13602
|
-
|
|
13603
|
-
since: getString(input.values, "since"),
|
|
13604
|
-
action: getString(input.values, "action"),
|
|
13605
|
-
actor: getString(input.values, "actor"),
|
|
13606
|
-
entityType: getString(input.values, "entity-type")
|
|
13607
|
-
});
|
|
13715
|
+
}
|
|
13716
|
+
const waited = results.filter((r) => r.runId).map((r) => ({ runId: r.runId, status: r.status, project: r.project, location: r.location, error: errors.get(r.runId) }));
|
|
13717
|
+
if (isMachineFormat(opts?.format)) {
|
|
13718
|
+
console.log(JSON.stringify(results, null, 2));
|
|
13719
|
+
if (opts?.wait) throwIfWaitedRunFailed(waited);
|
|
13720
|
+
return;
|
|
13721
|
+
}
|
|
13722
|
+
const showLocationColumn = results.some((r) => r.location !== null);
|
|
13723
|
+
const projectCount = new Set(results.map((r) => r.project)).size;
|
|
13724
|
+
console.log(`Triggered ${results.length} run(s) across ${projectCount} project(s):
|
|
13725
|
+
`);
|
|
13726
|
+
if (showLocationColumn) {
|
|
13727
|
+
console.log(" PROJECT LOCATION RUN ID STATUS");
|
|
13728
|
+
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");
|
|
13729
|
+
for (const r of results) {
|
|
13730
|
+
const proj = r.project.padEnd(31);
|
|
13731
|
+
const loc = (r.location ?? "\u2014").padEnd(15);
|
|
13732
|
+
const id = (r.runId || "(failed)").padEnd(36);
|
|
13733
|
+
console.log(` ${proj} ${loc} ${id} ${r.status}`);
|
|
13608
13734
|
}
|
|
13609
|
-
}
|
|
13610
|
-
|
|
13611
|
-
|
|
13612
|
-
|
|
13613
|
-
|
|
13614
|
-
|
|
13615
|
-
|
|
13616
|
-
run: async (input) => {
|
|
13617
|
-
const project2 = requireProject(input, "export", "canonry export <project> [--include-results] [--format json]");
|
|
13618
|
-
await exportProject(project2, {
|
|
13619
|
-
includeResults: getBoolean(input.values, "include-results"),
|
|
13620
|
-
format: input.format
|
|
13621
|
-
});
|
|
13735
|
+
} else {
|
|
13736
|
+
console.log(" PROJECT RUN ID STATUS");
|
|
13737
|
+
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");
|
|
13738
|
+
for (const r of results) {
|
|
13739
|
+
const proj = r.project.padEnd(31);
|
|
13740
|
+
const id = (r.runId || "(failed)").padEnd(36);
|
|
13741
|
+
console.log(` ${proj} ${id} ${r.status}`);
|
|
13622
13742
|
}
|
|
13623
|
-
}
|
|
13624
|
-
|
|
13625
|
-
|
|
13626
|
-
|
|
13627
|
-
|
|
13628
|
-
|
|
13629
|
-
|
|
13630
|
-
|
|
13631
|
-
|
|
13632
|
-
|
|
13633
|
-
|
|
13634
|
-
|
|
13635
|
-
|
|
13636
|
-
|
|
13743
|
+
}
|
|
13744
|
+
if (batchWaits.size > 0) console.log("");
|
|
13745
|
+
for (const r of results) {
|
|
13746
|
+
const waiting = batchWaits.get(r.runId);
|
|
13747
|
+
if (waiting) console.log(` ${r.location ? `${r.project} (${r.location})` : r.project}: ${waiting}`);
|
|
13748
|
+
}
|
|
13749
|
+
if (opts?.wait) throwIfWaitedRunFailed(waited);
|
|
13750
|
+
}
|
|
13751
|
+
async function cancelRun(project2, runId, format) {
|
|
13752
|
+
const client = getClient21();
|
|
13753
|
+
let targetId = runId;
|
|
13754
|
+
if (!targetId) {
|
|
13755
|
+
const runs2 = await client.listRuns(project2);
|
|
13756
|
+
const activeRuns = runs2.filter((r) => r.status === "queued" || r.status === "running");
|
|
13757
|
+
if (activeRuns.length > 1) {
|
|
13758
|
+
const candidates = activeRuns.map(({ id, kind, status }) => ({ id, kind, status }));
|
|
13759
|
+
throw new CliError({
|
|
13760
|
+
code: "MULTIPLE_ACTIVE_RUNS",
|
|
13761
|
+
message: `Multiple active runs found for project "${project2}". Specify a run ID.`,
|
|
13762
|
+
displayMessage: `Error: Multiple active runs found for project "${project2}". Specify a run ID.
|
|
13763
|
+
` + candidates.map((r) => ` ${r.id} ${r.kind} ${r.status}`).join("\n") + `
|
|
13764
|
+
To cancel by ID: canonry run cancel ${project2} <run-id>`,
|
|
13765
|
+
details: {
|
|
13766
|
+
project: project2,
|
|
13767
|
+
activeRuns: candidates,
|
|
13768
|
+
suggestedCommands: [`canonry run cancel ${project2} <run-id>`]
|
|
13769
|
+
}
|
|
13637
13770
|
});
|
|
13638
13771
|
}
|
|
13639
|
-
|
|
13640
|
-
|
|
13641
|
-
|
|
13642
|
-
|
|
13643
|
-
|
|
13644
|
-
|
|
13645
|
-
|
|
13646
|
-
|
|
13647
|
-
|
|
13648
|
-
|
|
13649
|
-
|
|
13650
|
-
|
|
13651
|
-
|
|
13652
|
-
|
|
13653
|
-
|
|
13654
|
-
|
|
13655
|
-
rank: getBoolean(input.values, "rank"),
|
|
13656
|
-
byProvider: getBoolean(input.values, "by-provider"),
|
|
13657
|
-
limit: parseIntegerOption(input, "limit", {
|
|
13658
|
-
message: "--limit must be an integer",
|
|
13659
|
-
usage: SOURCES_USAGE,
|
|
13660
|
-
command: "sources"
|
|
13661
|
-
}),
|
|
13662
|
-
window: getString(input.values, "window"),
|
|
13663
|
-
runId: getString(input.values, "run-id"),
|
|
13664
|
-
queryClass: parseSourcesQueryClass(getString(input.values, "query-class")),
|
|
13665
|
-
includeByQuery: parseSourcesIncludeByQuery(getString(input.values, "include-by-query")),
|
|
13666
|
-
format: input.format
|
|
13772
|
+
const active = activeRuns[0];
|
|
13773
|
+
if (!active) {
|
|
13774
|
+
throw new CliError({
|
|
13775
|
+
code: "NO_ACTIVE_RUN",
|
|
13776
|
+
message: `No active run found for project "${project2}"`,
|
|
13777
|
+
displayMessage: `Error: canonry run cancel "${project2}" \u2014 no active run found (status must be queued or running).
|
|
13778
|
+
Check run status : canonry status ${project2}
|
|
13779
|
+
To cancel by ID : canonry run cancel ${project2} <run-id>`,
|
|
13780
|
+
details: {
|
|
13781
|
+
project: project2,
|
|
13782
|
+
allowedStatuses: ["queued", "running"],
|
|
13783
|
+
suggestedCommands: [
|
|
13784
|
+
`canonry status ${project2}`,
|
|
13785
|
+
`canonry run cancel ${project2} <run-id>`
|
|
13786
|
+
]
|
|
13787
|
+
}
|
|
13667
13788
|
});
|
|
13668
13789
|
}
|
|
13669
|
-
|
|
13670
|
-
{
|
|
13671
|
-
path: ["apply"],
|
|
13672
|
-
usage: "canonry apply <file...> [--format json]",
|
|
13673
|
-
run: async (input) => {
|
|
13674
|
-
if (input.positionals.length === 0) {
|
|
13675
|
-
throw usageError("Error: at least one file path is required\nUsage: canonry apply <file...> [--format json]", {
|
|
13676
|
-
message: "at least one file path is required",
|
|
13677
|
-
details: {
|
|
13678
|
-
command: "apply",
|
|
13679
|
-
usage: "canonry apply <file...> [--format json]"
|
|
13680
|
-
}
|
|
13681
|
-
});
|
|
13682
|
-
}
|
|
13683
|
-
await applyConfigs(input.positionals, input.format);
|
|
13684
|
-
}
|
|
13790
|
+
targetId = active.id;
|
|
13685
13791
|
}
|
|
13686
|
-
|
|
13687
|
-
|
|
13688
|
-
|
|
13689
|
-
|
|
13690
|
-
|
|
13792
|
+
const result = await client.cancelRun(targetId);
|
|
13793
|
+
if (isMachineFormat(format)) {
|
|
13794
|
+
console.log(JSON.stringify(result, null, 2));
|
|
13795
|
+
return;
|
|
13796
|
+
}
|
|
13797
|
+
console.log(`Run ${result.id} cancelled.`);
|
|
13691
13798
|
}
|
|
13692
|
-
async function
|
|
13693
|
-
const client =
|
|
13694
|
-
const
|
|
13695
|
-
|
|
13696
|
-
|
|
13697
|
-
ownedDomains: opts.ownedDomains ?? [],
|
|
13698
|
-
aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
|
|
13699
|
-
...opts.qualifiedAliases?.length ? { qualifiedAliases: opts.qualifiedAliases } : {},
|
|
13700
|
-
country: opts.country,
|
|
13701
|
-
language: opts.language,
|
|
13702
|
-
providers: opts.providers ?? [],
|
|
13703
|
-
providerModels: opts.providerModels ?? {},
|
|
13704
|
-
...opts.providerDispatchModes && Object.keys(opts.providerDispatchModes).length > 0 ? { providerDispatchModes: opts.providerDispatchModes } : {},
|
|
13705
|
-
// Sent only when given: this PUT also re-runs over an existing project,
|
|
13706
|
-
// and an omitted budget keeps the stored one.
|
|
13707
|
-
...opts.siteAuditMaxPages !== void 0 ? { siteAuditMaxPages: opts.siteAuditMaxPages } : {}
|
|
13708
|
-
});
|
|
13709
|
-
warnDroppedDispatchModes(opts.providerDispatchModes ?? {}, result);
|
|
13710
|
-
if (isMachineFormat(opts.format)) {
|
|
13711
|
-
console.log(JSON.stringify(result, null, 2));
|
|
13799
|
+
async function showRun(id, format) {
|
|
13800
|
+
const client = getClient21();
|
|
13801
|
+
const run = await client.getRun(id);
|
|
13802
|
+
if (isMachineFormat(format)) {
|
|
13803
|
+
console.log(JSON.stringify(run, null, 2));
|
|
13712
13804
|
return;
|
|
13713
13805
|
}
|
|
13714
|
-
|
|
13715
|
-
}
|
|
13716
|
-
function warnDroppedDispatchModes(sent, result) {
|
|
13717
|
-
const kept = result.providerDispatchModes ?? {};
|
|
13718
|
-
const dropped = Object.keys(sent).filter((provider) => !(provider in kept));
|
|
13719
|
-
if (dropped.length > 0) {
|
|
13720
|
-
console.error(`Warning: Dropped dispatch mode for engine(s) the project does not measure: ${dropped.join(", ")}`);
|
|
13721
|
-
}
|
|
13806
|
+
printRunDetail(run);
|
|
13722
13807
|
}
|
|
13723
|
-
async function
|
|
13724
|
-
const client =
|
|
13725
|
-
const
|
|
13726
|
-
if (format === "json") {
|
|
13727
|
-
console.log(JSON.stringify(
|
|
13808
|
+
async function listRuns(project2, opts) {
|
|
13809
|
+
const client = getClient21();
|
|
13810
|
+
const runs2 = await client.listRuns(project2, opts?.limit, opts?.kind, opts?.status, opts?.excludeKind, opts?.excludeKinds);
|
|
13811
|
+
if (opts?.format === "json") {
|
|
13812
|
+
console.log(JSON.stringify(runs2, null, 2));
|
|
13728
13813
|
return;
|
|
13729
|
-
}
|
|
13730
|
-
|
|
13731
|
-
emitJsonl(projects2);
|
|
13814
|
+
} else if (opts?.format === "jsonl") {
|
|
13815
|
+
emitJsonl(runs2.map((run) => ({ project: project2, ...run })));
|
|
13732
13816
|
return;
|
|
13733
13817
|
}
|
|
13734
|
-
if (
|
|
13735
|
-
console.log(
|
|
13818
|
+
if (runs2.length === 0) {
|
|
13819
|
+
console.log(`No runs found for "${project2}".`);
|
|
13736
13820
|
return;
|
|
13737
13821
|
}
|
|
13738
|
-
console.log("
|
|
13739
|
-
|
|
13740
|
-
|
|
13741
|
-
|
|
13742
|
-
|
|
13743
|
-
};
|
|
13744
|
-
const domainWidth = Math.max(6, ...projects2.map((p) => domainLabel(p).length));
|
|
13745
|
-
console.log(
|
|
13746
|
-
` ${"NAME".padEnd(nameWidth)} ${"DOMAIN".padEnd(domainWidth)} COUNTRY LANGUAGE`
|
|
13747
|
-
);
|
|
13748
|
-
console.log(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(domainWidth)} \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
13749
|
-
for (const p of projects2) {
|
|
13822
|
+
console.log(`Runs for "${project2}" (${runs2.length}):
|
|
13823
|
+
`);
|
|
13824
|
+
console.log(" ID STATUS KIND TRIGGER CREATED");
|
|
13825
|
+
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");
|
|
13826
|
+
for (const run of runs2) {
|
|
13750
13827
|
console.log(
|
|
13751
|
-
` ${
|
|
13828
|
+
` ${run.id} ${run.status.padEnd(10)} ${run.kind.padEnd(18)} ${run.trigger.padEnd(9)} ${run.createdAt}`
|
|
13752
13829
|
);
|
|
13753
13830
|
}
|
|
13754
13831
|
}
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
|
|
13759
|
-
|
|
13760
|
-
|
|
13761
|
-
|
|
13762
|
-
|
|
13763
|
-
|
|
13764
|
-
|
|
13765
|
-
|
|
13766
|
-
|
|
13767
|
-
|
|
13768
|
-
console.log(` ID: ${project2.id}`);
|
|
13769
|
-
console.log(` Domain: ${project2.canonicalDomain}`);
|
|
13770
|
-
const secondaryDomains = effectiveDomains(project2).slice(1);
|
|
13771
|
-
if (secondaryDomains.length > 0) {
|
|
13772
|
-
console.log(` Owned domains: ${secondaryDomains.join(", ")}`);
|
|
13832
|
+
var POLL_TIMEOUT_MS3 = 10 * 60 * 1e3;
|
|
13833
|
+
async function pollRun2(client, runId, showProgress = true) {
|
|
13834
|
+
const deadline = Date.now() + POLL_TIMEOUT_MS3;
|
|
13835
|
+
for (; ; ) {
|
|
13836
|
+
await new Promise((r) => setTimeout(r, 2e3));
|
|
13837
|
+
if (Date.now() > deadline) {
|
|
13838
|
+
throw new Error(`Timed out waiting for run ${runId} after ${POLL_TIMEOUT_MS3 / 1e3}s`);
|
|
13839
|
+
}
|
|
13840
|
+
const run = await client.getRun(runId);
|
|
13841
|
+
if (showProgress) process.stderr.write(".");
|
|
13842
|
+
if (TERMINAL_STATUSES.has(run.status) || outstandingBatches(run).length > 0) {
|
|
13843
|
+
return run;
|
|
13844
|
+
}
|
|
13773
13845
|
}
|
|
13774
|
-
|
|
13775
|
-
|
|
13846
|
+
}
|
|
13847
|
+
function outstandingBatches(run) {
|
|
13848
|
+
if (TERMINAL_STATUSES.has(run.status)) return [];
|
|
13849
|
+
return (run.providerBatches ?? []).filter((batch) => OUTSTANDING_PROVIDER_BATCH_STATUSES.includes(batch.status));
|
|
13850
|
+
}
|
|
13851
|
+
function batchWaitLine(run) {
|
|
13852
|
+
const outstanding = outstandingBatches(run);
|
|
13853
|
+
if (outstanding.length === 0) return null;
|
|
13854
|
+
const batches = outstanding.map((batch) => `${batch.provider} \u2014 ${batch.requestCount} requests, submitted ${batch.submittedAt ?? "unknown"}, deadline ${batch.deadlineAt}`);
|
|
13855
|
+
return `Waiting on provider batch(es): ${batches.join("; ")}; check with canonry run show ${run.id}`;
|
|
13856
|
+
}
|
|
13857
|
+
function printRunDetail(run) {
|
|
13858
|
+
console.log(`Run: ${run.id}`);
|
|
13859
|
+
console.log(` Status: ${run.status}`);
|
|
13860
|
+
console.log(` Kind: ${run.kind}`);
|
|
13861
|
+
if (run.trigger) console.log(` Trigger: ${run.trigger}`);
|
|
13862
|
+
if (run.startedAt) console.log(` Started: ${run.startedAt}`);
|
|
13863
|
+
if (run.finishedAt) console.log(` Finished: ${run.finishedAt}`);
|
|
13864
|
+
if (run.createdAt) console.log(` Created: ${run.createdAt}`);
|
|
13865
|
+
const skipped = Object.entries(run.skippedProviders ?? {});
|
|
13866
|
+
for (const [provider, streak] of skipped) console.log(` Skipped: ${providerAccountStreakLine(provider, streak)}`);
|
|
13867
|
+
if (run.error) {
|
|
13868
|
+
if (run.error.message) console.log(` Error: ${run.error.message}`);
|
|
13869
|
+
if (run.error.providers) {
|
|
13870
|
+
for (const [provider, detail] of Object.entries(run.error.providers)) {
|
|
13871
|
+
if (detail.skipped && run.skippedProviders?.[provider]) continue;
|
|
13872
|
+
console.log(` Error (${provider}): ${detail.message}`);
|
|
13873
|
+
}
|
|
13874
|
+
}
|
|
13776
13875
|
}
|
|
13777
|
-
const
|
|
13778
|
-
if (
|
|
13779
|
-
|
|
13876
|
+
const batched = Object.keys(run.dispatchModes ?? {}).sort();
|
|
13877
|
+
if (batched.length > 0) console.log(` Dispatch: ${batched.map((provider) => `${provider}=batch`).join(", ")} (other providers sync)`);
|
|
13878
|
+
if (run.providerBatches && run.providerBatches.length > 0) {
|
|
13879
|
+
console.log("\n Provider batches:");
|
|
13880
|
+
for (const batch of run.providerBatches) console.log(` ${providerBatchLine(batch)}`);
|
|
13780
13881
|
}
|
|
13781
|
-
|
|
13782
|
-
|
|
13783
|
-
|
|
13784
|
-
|
|
13785
|
-
|
|
13786
|
-
|
|
13787
|
-
|
|
13788
|
-
|
|
13789
|
-
|
|
13790
|
-
|
|
13791
|
-
if (project2.siteAuditMaxPages !== void 0) {
|
|
13792
|
-
console.log(` Site Health page budget: ${formatSiteAuditPageBudget(project2.siteAuditMaxPages)}`);
|
|
13882
|
+
if (run.usage && run.usage.length > 0) printUsageTable(run.usage);
|
|
13883
|
+
if (run.snapshots && run.snapshots.length > 0) {
|
|
13884
|
+
console.log(`
|
|
13885
|
+
Snapshots: ${run.snapshots.length} (cell = [citation][mention]; C=cited c=not, M=mentioned m=not, \u2013=no data)`);
|
|
13886
|
+
for (const s of run.snapshots) {
|
|
13887
|
+
const citationGlyph = s.citationState === CitationStates.cited ? "C" : "c";
|
|
13888
|
+
const mentionGlyph = typeof s.answerMentioned === "boolean" ? s.answerMentioned ? "M" : "m" : "\u2013";
|
|
13889
|
+
const modelLabel = s.model ? ` (${s.model})` : "";
|
|
13890
|
+
console.log(` [${citationGlyph}${mentionGlyph}] ${s.provider}${modelLabel} ${s.query}`);
|
|
13891
|
+
}
|
|
13793
13892
|
}
|
|
13794
|
-
|
|
13795
|
-
|
|
13893
|
+
}
|
|
13894
|
+
function providerBatchLine(batch) {
|
|
13895
|
+
const recorded = `${batch.recordedCount} of ${batch.requestCount} answers recorded`;
|
|
13896
|
+
const withError = (text) => batch.error ? `${text} (${batch.error})` : text;
|
|
13897
|
+
switch (batch.status) {
|
|
13898
|
+
case ProviderBatchStatuses.submitting:
|
|
13899
|
+
return `submitting provider batch: ${batch.provider} \u2014 ${batch.requestCount} requests`;
|
|
13900
|
+
// The two outstanding statuses: the run stays `running` until they clear.
|
|
13901
|
+
case ProviderBatchStatuses.submitted:
|
|
13902
|
+
case ProviderBatchStatuses.ended:
|
|
13903
|
+
return `waiting on provider batch: ${batch.provider} \u2014 ${batch.requestCount} requests, submitted ${batch.submittedAt ?? "unknown"}, deadline ${batch.deadlineAt}`;
|
|
13904
|
+
case ProviderBatchStatuses.ingested:
|
|
13905
|
+
return withError(`provider batch ${batch.provider}: ingested \u2014 ${recorded}`);
|
|
13906
|
+
case ProviderBatchStatuses.cancelled:
|
|
13907
|
+
return withError(`provider batch ${batch.provider}: cancelled \u2014 ${recorded}; the rest are missing and can be filled`);
|
|
13908
|
+
case ProviderBatchStatuses.failed:
|
|
13909
|
+
return withError(`provider batch ${batch.provider}: rejected by the provider \u2014 its ${batch.requestCount} answers ran sync instead`);
|
|
13910
|
+
case ProviderBatchStatuses.unknown:
|
|
13911
|
+
return withError(`provider batch ${batch.provider}: submit outcome unknown \u2014 never resubmitted; its ${batch.requestCount} answers stay missing`);
|
|
13796
13912
|
}
|
|
13797
|
-
console.log(` Tags: ${project2.tags.length > 0 ? project2.tags.join(", ") : "(none)"}`);
|
|
13798
|
-
const labelEntries = Object.entries(project2.labels);
|
|
13799
|
-
console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
|
|
13800
|
-
if (project2.createdAt) console.log(` Created: ${project2.createdAt}`);
|
|
13801
|
-
if (project2.updatedAt) console.log(` Updated: ${project2.updatedAt}`);
|
|
13802
13913
|
}
|
|
13803
|
-
function
|
|
13804
|
-
|
|
13805
|
-
|
|
13806
|
-
|
|
13914
|
+
function printUsageTable(rows) {
|
|
13915
|
+
const cost = (row) => row.estimatedCostMicros === null ? "unpriced" : `${formatMicros(row.estimatedCostMicros, "USD", { fractionDigits: 4, showTinyAsLessThan: true })}${row.unpricedAnswers > 0 ? ` (+${row.unpricedAnswers} unpriced)` : ""}`;
|
|
13916
|
+
const table = rows.map((row) => [
|
|
13917
|
+
row.provider,
|
|
13918
|
+
row.pricingTier,
|
|
13919
|
+
row.answers.toLocaleString("en-US"),
|
|
13920
|
+
row.inputTokens.toLocaleString("en-US"),
|
|
13921
|
+
row.cachedInputTokens.toLocaleString("en-US"),
|
|
13922
|
+
row.cacheWriteTokens.toLocaleString("en-US"),
|
|
13923
|
+
row.outputTokens.toLocaleString("en-US"),
|
|
13924
|
+
row.searchCount.toLocaleString("en-US"),
|
|
13925
|
+
cost(row)
|
|
13926
|
+
]);
|
|
13927
|
+
const header = ["PROVIDER", "TIER", "ANSWERS", "INPUT", "CACHED", "CACHE WRITE", "OUTPUT", "SEARCHES", "EST. COST"];
|
|
13928
|
+
const widths = header.map((title, index) => Math.max(title.length, ...table.map((cells) => cells[index].length)));
|
|
13929
|
+
const line = (cells) => ` ${cells.map((cell2, index) => cell2.padEnd(widths[index])).join(" ").trimEnd()}`;
|
|
13930
|
+
console.log("\n Usage (answers with recorded usage; cost is an estimate):");
|
|
13931
|
+
console.log(line(header));
|
|
13932
|
+
for (const cells of table) console.log(line(cells));
|
|
13807
13933
|
}
|
|
13808
|
-
|
|
13809
|
-
|
|
13810
|
-
|
|
13811
|
-
|
|
13812
|
-
|
|
13813
|
-
|
|
13814
|
-
|
|
13934
|
+
var FILL_POLL_INTERVAL_MS = 3e3;
|
|
13935
|
+
var FILL_POLL_TIMEOUT_MS = 6 * 60 * 60 * 1e3;
|
|
13936
|
+
function missingSummary(completeness) {
|
|
13937
|
+
const parts = Object.entries(completeness.missingByProvider).sort(([left], [right]) => left.localeCompare(right)).map(([provider, count]) => `${provider} ${count}`);
|
|
13938
|
+
return parts.length ? parts.join(", ") : "none";
|
|
13939
|
+
}
|
|
13940
|
+
function printCompleteness(completeness) {
|
|
13941
|
+
console.log(`Run: ${completeness.runId}`);
|
|
13942
|
+
console.log(` Status: ${completeness.status}`);
|
|
13943
|
+
if (!completeness.planned) {
|
|
13944
|
+
console.log(" Not a measurement-plan run: there are no expected answers to count.");
|
|
13945
|
+
return;
|
|
13815
13946
|
}
|
|
13816
|
-
if (
|
|
13817
|
-
|
|
13818
|
-
|
|
13947
|
+
if (!completeness.readable) {
|
|
13948
|
+
console.log(" Answers: unknown (the run's manifest cannot be read)");
|
|
13949
|
+
} else {
|
|
13950
|
+
console.log(` Answers: ${completeness.executed}/${completeness.expected}`);
|
|
13951
|
+
console.log(` Missing: ${completeness.missing} (${missingSummary(completeness)})`);
|
|
13819
13952
|
}
|
|
13820
|
-
|
|
13821
|
-
|
|
13822
|
-
|
|
13823
|
-
|
|
13824
|
-
|
|
13825
|
-
aliases = [...aliases, ...toAdd];
|
|
13953
|
+
if (completeness.refusal) console.log(` Fillable: no. ${completeness.refusal.message}`);
|
|
13954
|
+
else console.log(` Fillable: ${completeness.fillable ? "yes" : "not right now (a sweep or another fill is running)"}`);
|
|
13955
|
+
const fill = completeness.latestFill;
|
|
13956
|
+
if (fill) {
|
|
13957
|
+
console.log(` Last fill: ${fill.id} ${fill.status}, ${fill.filled}/${fill.expected} recorded${fill.error ? `. ${fill.error}` : ""}`);
|
|
13826
13958
|
}
|
|
13827
|
-
|
|
13828
|
-
|
|
13829
|
-
|
|
13959
|
+
}
|
|
13960
|
+
async function showRunCompleteness(runId, format) {
|
|
13961
|
+
const completeness = await getClient21().getRunCompleteness(runId);
|
|
13962
|
+
if (isMachineFormat(format)) {
|
|
13963
|
+
console.log(JSON.stringify(completeness, null, 2));
|
|
13964
|
+
return;
|
|
13830
13965
|
}
|
|
13831
|
-
|
|
13832
|
-
|
|
13833
|
-
|
|
13834
|
-
|
|
13835
|
-
|
|
13836
|
-
|
|
13837
|
-
|
|
13838
|
-
const existingKeys = new Set(qualifiedAliases.map((alias) => alias.toLowerCase()));
|
|
13839
|
-
for (const alias of opts.addQualifiedAlias) {
|
|
13840
|
-
if (existingKeys.has(alias.toLowerCase())) continue;
|
|
13841
|
-
existingKeys.add(alias.toLowerCase());
|
|
13842
|
-
qualifiedAliases = [...qualifiedAliases, alias];
|
|
13843
|
-
}
|
|
13844
|
-
}
|
|
13845
|
-
if (opts.removeQualifiedAlias) {
|
|
13846
|
-
const toRemove = new Set(opts.removeQualifiedAlias.map((alias) => alias.toLowerCase()));
|
|
13847
|
-
qualifiedAliases = qualifiedAliases.filter((alias) => !toRemove.has(alias.toLowerCase()));
|
|
13848
|
-
}
|
|
13849
|
-
const providerModels = { ...project2.providerModels ?? {}, ...opts.providerModels ?? {} };
|
|
13850
|
-
for (const provider of opts.clearProviderModels ?? []) delete providerModels[provider];
|
|
13851
|
-
const nextProviders = opts.providers ?? project2.providers ?? [];
|
|
13852
|
-
if (nextProviders.length > 0) {
|
|
13853
|
-
const requested = Object.keys(opts.providerModels ?? {}).filter((p) => !nextProviders.includes(p));
|
|
13854
|
-
if (requested.length > 0) {
|
|
13855
|
-
throw usageError(
|
|
13856
|
-
`Error: --provider-model set for ${requested.join(", ")}, which ${requested.length > 1 ? "are" : "is"} not a selected engine for "${name}"`,
|
|
13857
|
-
{
|
|
13858
|
-
message: "provider-model set for a provider the project does not run",
|
|
13859
|
-
details: { command: "project.update", project: name, providers: nextProviders, unselected: requested }
|
|
13860
|
-
}
|
|
13861
|
-
);
|
|
13862
|
-
}
|
|
13863
|
-
}
|
|
13864
|
-
const touchesDispatch = Object.keys(opts.dispatchModes ?? {}).length > 0 || (opts.clearDispatchModes ?? []).length > 0;
|
|
13865
|
-
const providerDispatchModes = { ...project2.providerDispatchModes ?? {}, ...opts.dispatchModes ?? {} };
|
|
13866
|
-
for (const provider of opts.clearDispatchModes ?? []) delete providerDispatchModes[provider];
|
|
13867
|
-
const result = await client.putProject(name, {
|
|
13868
|
-
displayName: nextDisplayName,
|
|
13869
|
-
canonicalDomain: opts.domain ?? project2.canonicalDomain,
|
|
13870
|
-
ownedDomains,
|
|
13871
|
-
aliases: nextAliases,
|
|
13872
|
-
...touchesQualified ? { qualifiedAliases } : {},
|
|
13873
|
-
country: opts.country ?? project2.country,
|
|
13874
|
-
language: opts.language ?? project2.language,
|
|
13875
|
-
tags: project2.tags,
|
|
13876
|
-
labels: project2.labels,
|
|
13877
|
-
providers: opts.providers ?? project2.providers,
|
|
13878
|
-
providerModels,
|
|
13879
|
-
...touchesDispatch ? { providerDispatchModes } : {},
|
|
13880
|
-
locations: project2.locations,
|
|
13881
|
-
defaultLocation: project2.defaultLocation,
|
|
13882
|
-
autoExtractBacklinks: project2.autoExtractBacklinks,
|
|
13883
|
-
// Sent only when asked for: the server keeps the stored threshold when the
|
|
13884
|
-
// field is absent, so a stale read cannot overwrite a newer setting.
|
|
13885
|
-
...opts.negativeReviewMaxStars !== void 0 ? { negativeReviewMaxStars: opts.negativeReviewMaxStars } : {},
|
|
13886
|
-
// Same for the page budget: never echoed from the read above, so an edit
|
|
13887
|
-
// that does not touch it cannot overwrite a newer one or send a stray null.
|
|
13888
|
-
...opts.siteAuditMaxPages !== void 0 ? { siteAuditMaxPages: opts.siteAuditMaxPages } : {},
|
|
13889
|
-
// Same for the detection mode.
|
|
13890
|
-
...opts.competitorAutoAliases !== void 0 ? { competitorAutoAliases: opts.competitorAutoAliases } : {}
|
|
13966
|
+
printCompleteness(completeness);
|
|
13967
|
+
}
|
|
13968
|
+
async function fillRun(runId, opts = {}) {
|
|
13969
|
+
const client = getClient21();
|
|
13970
|
+
const response = await client.fillRun(runId, {
|
|
13971
|
+
...opts.providers?.length ? { providers: opts.providers } : {},
|
|
13972
|
+
...opts.dryRun ? { dryRun: true } : {}
|
|
13891
13973
|
});
|
|
13892
|
-
|
|
13893
|
-
|
|
13894
|
-
|
|
13974
|
+
if (!opts.wait || response.outcome !== "queued" || !response.fill) {
|
|
13975
|
+
if (isMachineFormat(opts.format)) {
|
|
13976
|
+
console.log(JSON.stringify(response, null, 2));
|
|
13977
|
+
return;
|
|
13978
|
+
}
|
|
13979
|
+
if (response.outcome === "already-complete") console.log(`Run ${runId} is already complete: nothing to fill.`);
|
|
13980
|
+
else if (response.outcome === "dry-run") console.log("Dry run: nothing was queued.");
|
|
13981
|
+
else if (response.fill) console.log(`Fill ${response.fill.id} queued: ${response.fill.expected} missing answer(s) (${missingSummary(response.completeness)}).`);
|
|
13982
|
+
printCompleteness(response.completeness);
|
|
13895
13983
|
return;
|
|
13896
13984
|
}
|
|
13897
|
-
|
|
13898
|
-
const
|
|
13899
|
-
|
|
13900
|
-
|
|
13985
|
+
const fillId = response.fill.id;
|
|
13986
|
+
const deadline = Date.now() + FILL_POLL_TIMEOUT_MS;
|
|
13987
|
+
let completeness = response.completeness;
|
|
13988
|
+
if (!isMachineFormat(opts.format)) {
|
|
13989
|
+
process.stderr.write(`Filling ${response.fill.expected} missing answer(s) in run ${runId}`);
|
|
13901
13990
|
}
|
|
13902
|
-
|
|
13903
|
-
|
|
13904
|
-
|
|
13905
|
-
|
|
13906
|
-
|
|
13907
|
-
|
|
13908
|
-
|
|
13909
|
-
|
|
13910
|
-
|
|
13991
|
+
for (; ; ) {
|
|
13992
|
+
await new Promise((resolve) => setTimeout(resolve, FILL_POLL_INTERVAL_MS));
|
|
13993
|
+
if (Date.now() > deadline) {
|
|
13994
|
+
throw new CliError({
|
|
13995
|
+
code: "RUN_FILL_INCOMPLETE",
|
|
13996
|
+
message: `Timed out waiting for fill ${fillId}; it may still be running. Check with: canonry run completeness ${runId}`,
|
|
13997
|
+
exitCode: EXIT_SYSTEM_ERROR,
|
|
13998
|
+
details: { runId, fillId }
|
|
13999
|
+
});
|
|
13911
14000
|
}
|
|
13912
|
-
|
|
13913
|
-
|
|
13914
|
-
|
|
13915
|
-
|
|
13916
|
-
|
|
13917
|
-
console.log(` runs: ${cr.runs}`);
|
|
13918
|
-
console.log(` snapshots: ${cr.snapshots}`);
|
|
13919
|
-
console.log(` insights: ${cr.insights}`);
|
|
13920
|
-
console.log(` Detached (project_id set to NULL):`);
|
|
13921
|
-
console.log(` audit_log: ${dr.auditLog}`);
|
|
13922
|
-
console.log(``);
|
|
13923
|
-
console.log(`No DB writes performed. Re-run without --dry-run to delete.`);
|
|
13924
|
-
return;
|
|
14001
|
+
completeness = await client.getRunCompleteness(runId);
|
|
14002
|
+
if (!isMachineFormat(opts.format)) process.stderr.write(".");
|
|
14003
|
+
const latest = completeness.latestFill;
|
|
14004
|
+
const working = latest !== null && (latest.status === "queued" || latest.status === "running");
|
|
14005
|
+
if (completeness.status === "completed" || !working) break;
|
|
13925
14006
|
}
|
|
13926
|
-
|
|
13927
|
-
if (
|
|
13928
|
-
|
|
13929
|
-
|
|
14007
|
+
if (!isMachineFormat(opts.format)) process.stderr.write("\n");
|
|
14008
|
+
if (isMachineFormat(opts.format)) console.log(JSON.stringify(completeness, null, 2));
|
|
14009
|
+
else printCompleteness(completeness);
|
|
14010
|
+
if (completeness.status !== "completed") {
|
|
14011
|
+
throw new CliError({
|
|
14012
|
+
code: "RUN_FILL_INCOMPLETE",
|
|
14013
|
+
message: `Run ${runId} is still ${completeness.status}: ${completeness.missing} answer(s) missing (${missingSummary(completeness)}).`,
|
|
14014
|
+
exitCode: EXIT_SYSTEM_ERROR,
|
|
14015
|
+
details: { runId, fillId, missing: completeness.missing, missingByProvider: completeness.missingByProvider }
|
|
14016
|
+
});
|
|
13930
14017
|
}
|
|
13931
|
-
console.log(`Project deleted: ${name}`);
|
|
13932
14018
|
}
|
|
13933
|
-
|
|
13934
|
-
|
|
13935
|
-
|
|
13936
|
-
|
|
13937
|
-
city: opts.city,
|
|
13938
|
-
region: opts.region,
|
|
13939
|
-
country: opts.country,
|
|
13940
|
-
timezone: opts.timezone
|
|
13941
|
-
});
|
|
13942
|
-
if (isMachineFormat(opts.format)) {
|
|
13943
|
-
console.log(JSON.stringify(location, null, 2));
|
|
13944
|
-
return;
|
|
13945
|
-
}
|
|
13946
|
-
console.log(`Location added: ${opts.label} (${opts.city}, ${opts.region}, ${opts.country})`);
|
|
14019
|
+
|
|
14020
|
+
// src/commands/status.ts
|
|
14021
|
+
function getClient22() {
|
|
14022
|
+
return createApiClient();
|
|
13947
14023
|
}
|
|
13948
|
-
async function
|
|
14024
|
+
async function showStatus(project2, format) {
|
|
13949
14025
|
const client = getClient22();
|
|
13950
|
-
const
|
|
13951
|
-
|
|
13952
|
-
|
|
13953
|
-
|
|
13954
|
-
|
|
13955
|
-
|
|
13956
|
-
|
|
13957
|
-
|
|
13958
|
-
|
|
13959
|
-
|
|
13960
|
-
|
|
13961
|
-
|
|
13962
|
-
|
|
13963
|
-
|
|
13964
|
-
|
|
14026
|
+
const projectData = await client.getProject(project2);
|
|
14027
|
+
const latest = await getLatestRunSummary(client, project2);
|
|
14028
|
+
if (isMachineFormat(format)) {
|
|
14029
|
+
let runs2 = [];
|
|
14030
|
+
try {
|
|
14031
|
+
runs2 = await client.listRuns(project2);
|
|
14032
|
+
} catch {
|
|
14033
|
+
}
|
|
14034
|
+
console.log(JSON.stringify({
|
|
14035
|
+
project: projectData,
|
|
14036
|
+
runs: runs2,
|
|
14037
|
+
latestRun: latest.run,
|
|
14038
|
+
totalRuns: latest.totalRuns,
|
|
14039
|
+
// Absent from a server that predates it.
|
|
14040
|
+
...latest.admission ? { admission: latest.admission } : {},
|
|
14041
|
+
...latest.manualAdmission ? { manualAdmission: latest.manualAdmission } : {}
|
|
14042
|
+
}, null, 2));
|
|
13965
14043
|
return;
|
|
13966
14044
|
}
|
|
13967
|
-
console.log(`
|
|
14045
|
+
console.log(`Status: ${projectData.displayName ?? projectData.name} (${projectData.name})
|
|
13968
14046
|
`);
|
|
13969
|
-
console.log(
|
|
13970
|
-
console.log(
|
|
13971
|
-
|
|
13972
|
-
|
|
13973
|
-
console.log(
|
|
13974
|
-
` ${loc.label.padEnd(15)} ${loc.city.padEnd(19)} ${loc.region.padEnd(19)} ${loc.country.padEnd(7)}${isDefault}`
|
|
13975
|
-
);
|
|
13976
|
-
}
|
|
13977
|
-
if (result.defaultLocation) {
|
|
14047
|
+
console.log(` Domain: ${projectData.canonicalDomain}`);
|
|
14048
|
+
console.log(` Country: ${projectData.country}`);
|
|
14049
|
+
console.log(` Language: ${projectData.language}`);
|
|
14050
|
+
if (latest.run) {
|
|
13978
14051
|
console.log(`
|
|
13979
|
-
|
|
13980
|
-
|
|
13981
|
-
}
|
|
13982
|
-
|
|
13983
|
-
|
|
13984
|
-
|
|
13985
|
-
|
|
13986
|
-
console.log(
|
|
13987
|
-
|
|
14052
|
+
Latest run:`);
|
|
14053
|
+
console.log(` ID: ${latest.run.id}`);
|
|
14054
|
+
console.log(` Status: ${latest.run.status}`);
|
|
14055
|
+
console.log(` Created: ${latest.run.createdAt}`);
|
|
14056
|
+
if (latest.run.finishedAt) {
|
|
14057
|
+
console.log(` Finished: ${latest.run.finishedAt}`);
|
|
14058
|
+
}
|
|
14059
|
+
console.log(`
|
|
14060
|
+
Total runs: ${latest.totalRuns}`);
|
|
14061
|
+
} else {
|
|
14062
|
+
const projectArg = /^[\w.-]+$/.test(project2) ? project2 : `'${project2.replaceAll("'", `'\\''`)}'`;
|
|
14063
|
+
console.log(`
|
|
14064
|
+
No runs yet. Capture Page Health with "canonry technical-aeo run ${projectArg}", or run an AI Visibility sweep after adding a provider.`);
|
|
13988
14065
|
}
|
|
13989
|
-
|
|
14066
|
+
const admission = runAdmissionLines(project2, latest.admission, latest.manualAdmission);
|
|
14067
|
+
if (admission.length > 0) console.log(`
|
|
14068
|
+
${admission.map((line) => ` ${line}`).join("\n")}`);
|
|
13990
14069
|
}
|
|
13991
|
-
async function
|
|
13992
|
-
|
|
13993
|
-
|
|
13994
|
-
|
|
13995
|
-
|
|
13996
|
-
|
|
14070
|
+
async function getLatestRunSummary(client, project2) {
|
|
14071
|
+
try {
|
|
14072
|
+
return await client.getLatestRun(project2);
|
|
14073
|
+
} catch (err) {
|
|
14074
|
+
if (!isEndpointMissing(err)) throw err;
|
|
14075
|
+
const runs2 = await client.listRuns(project2);
|
|
14076
|
+
if (runs2.length === 0) {
|
|
14077
|
+
return { totalRuns: 0, run: null };
|
|
14078
|
+
}
|
|
14079
|
+
const latestRun = runs2.reduce(
|
|
14080
|
+
(current, candidate) => candidate.createdAt > current.createdAt ? candidate : current
|
|
14081
|
+
);
|
|
14082
|
+
return {
|
|
14083
|
+
totalRuns: runs2.length,
|
|
14084
|
+
run: latestRun
|
|
14085
|
+
};
|
|
13997
14086
|
}
|
|
13998
|
-
console.log(`Default location set to: ${label}`);
|
|
13999
14087
|
}
|
|
14000
14088
|
|
|
14001
|
-
// src/cli-commands/
|
|
14002
|
-
var
|
|
14003
|
-
|
|
14004
|
-
|
|
14005
|
-
|
|
14006
|
-
|
|
14007
|
-
|
|
14008
|
-
|
|
14009
|
-
|
|
14010
|
-
|
|
14011
|
-
|
|
14012
|
-
|
|
14013
|
-
|
|
14014
|
-
|
|
14015
|
-
|
|
14016
|
-
|
|
14017
|
-
|
|
14018
|
-
|
|
14019
|
-
|
|
14089
|
+
// src/cli-commands/operator.ts
|
|
14090
|
+
var RESULTS_EXPORT_USAGE = "canonry results export <project> [--format json|csv] [--since <ISO>] [--until <ISO>] [--include-probes] [--output <path>|-]";
|
|
14091
|
+
function parseResultsExportFormat(value) {
|
|
14092
|
+
if (value === void 0 || value === "json") return "json";
|
|
14093
|
+
if (value === "csv") return "csv";
|
|
14094
|
+
throw usageError(`Error: --format must be "json" or "csv"
|
|
14095
|
+
|
|
14096
|
+
Usage: ${RESULTS_EXPORT_USAGE}`);
|
|
14097
|
+
}
|
|
14098
|
+
var RESULTS_CLEAR_USAGE = "canonry results clear <project> [--run <id> ...] [--research-run <id> ...] [--confirm] [--format json|jsonl]";
|
|
14099
|
+
function parseHistoryPageOption(input, key, usage) {
|
|
14100
|
+
const value = getString(input.values, key);
|
|
14101
|
+
if (value === void 0) return void 0;
|
|
14102
|
+
if (!/^\d+$/.test(value)) {
|
|
14103
|
+
throw usageError(`Error: --${key} must be a ${key === "limit" ? "whole number from 1 to 500" : "nonnegative integer"}
|
|
14104
|
+
Usage: ${usage}`);
|
|
14105
|
+
}
|
|
14106
|
+
const parsed = Number(value);
|
|
14107
|
+
const valid = Number.isSafeInteger(parsed) && (key === "limit" ? parsed >= 1 && parsed <= 500 : parsed >= 0);
|
|
14108
|
+
if (!valid) {
|
|
14109
|
+
throw usageError(`Error: --${key} must be a ${key === "limit" ? "whole number from 1 to 500" : "nonnegative integer"}
|
|
14110
|
+
Usage: ${usage}`);
|
|
14111
|
+
}
|
|
14112
|
+
return parsed;
|
|
14113
|
+
}
|
|
14114
|
+
var OPERATOR_CLI_COMMANDS = [
|
|
14115
|
+
{
|
|
14116
|
+
path: ["results", "clear"],
|
|
14117
|
+
usage: RESULTS_CLEAR_USAGE,
|
|
14118
|
+
options: { run: multiStringOption(), "research-run": multiStringOption(), confirm: { type: "boolean", default: false } },
|
|
14020
14119
|
run: async (input) => {
|
|
14021
|
-
|
|
14022
|
-
input,
|
|
14023
|
-
|
|
14024
|
-
|
|
14025
|
-
);
|
|
14026
|
-
await createProject(name, {
|
|
14027
|
-
domain: getString(input.values, "domain") ?? name,
|
|
14028
|
-
ownedDomains: getStringArray(input.values, "owned-domain") ?? [],
|
|
14029
|
-
aliases: getStringArray(input.values, "alias") ?? [],
|
|
14030
|
-
qualifiedAliases: getStringArray(input.values, "qualified-alias") ?? [],
|
|
14031
|
-
country: getString(input.values, "country") ?? "US",
|
|
14032
|
-
language: getString(input.values, "language") ?? "en",
|
|
14033
|
-
displayName: getString(input.values, "display-name") ?? name,
|
|
14034
|
-
providers: getStringArray(input.values, "provider") ?? [],
|
|
14035
|
-
providerModels: parseProviderModelAssignments(getStringArray(input.values, "provider-model")),
|
|
14036
|
-
providerDispatchModes: parseDispatchModeAssignments(getStringArray(input.values, "dispatch-mode")),
|
|
14037
|
-
siteAuditMaxPages: parseSiteAuditMaxPages(getString(input.values, "site-audit-max-pages"), "project.create"),
|
|
14120
|
+
await clearResults(requireProject(input, "results.clear", RESULTS_CLEAR_USAGE), {
|
|
14121
|
+
runIds: getStringArray(input.values, "run") ?? [],
|
|
14122
|
+
researchRunIds: getStringArray(input.values, "research-run") ?? [],
|
|
14123
|
+
confirm: getBoolean(input.values, "confirm"),
|
|
14038
14124
|
format: input.format
|
|
14039
14125
|
});
|
|
14040
14126
|
}
|
|
14041
14127
|
},
|
|
14042
14128
|
{
|
|
14043
|
-
path: ["
|
|
14044
|
-
usage:
|
|
14129
|
+
path: ["results", "export"],
|
|
14130
|
+
usage: RESULTS_EXPORT_USAGE,
|
|
14045
14131
|
options: {
|
|
14046
|
-
|
|
14047
|
-
|
|
14048
|
-
"
|
|
14049
|
-
"
|
|
14050
|
-
alias: multiStringOption(),
|
|
14051
|
-
"add-alias": multiStringOption(),
|
|
14052
|
-
"remove-alias": multiStringOption(),
|
|
14053
|
-
"add-qualified-alias": multiStringOption(),
|
|
14054
|
-
"remove-qualified-alias": multiStringOption(),
|
|
14055
|
-
country: stringOption(),
|
|
14056
|
-
language: stringOption(),
|
|
14057
|
-
"display-name": stringOption(),
|
|
14058
|
-
provider: multiStringOption(),
|
|
14059
|
-
"all-providers": { type: "boolean" },
|
|
14060
|
-
"provider-model": multiStringOption(),
|
|
14061
|
-
"clear-provider-model": multiStringOption(),
|
|
14062
|
-
"dispatch-mode": multiStringOption(),
|
|
14063
|
-
"clear-dispatch-mode": multiStringOption(),
|
|
14064
|
-
"negative-review-max-stars": stringOption(),
|
|
14065
|
-
"site-audit-max-pages": stringOption(),
|
|
14066
|
-
"competitor-auto-aliases": stringOption()
|
|
14132
|
+
since: stringOption(),
|
|
14133
|
+
until: stringOption(),
|
|
14134
|
+
"include-probes": { type: "boolean", default: false },
|
|
14135
|
+
output: { type: "string", short: "o" }
|
|
14067
14136
|
},
|
|
14068
14137
|
run: async (input) => {
|
|
14069
|
-
const
|
|
14070
|
-
|
|
14071
|
-
|
|
14072
|
-
|
|
14073
|
-
|
|
14074
|
-
|
|
14075
|
-
|
|
14076
|
-
if (allProviders && providers?.length) throw usageError("Error: --all-providers conflicts with --provider");
|
|
14077
|
-
const providerModels = parseProviderModelAssignments(getStringArray(input.values, "provider-model"));
|
|
14078
|
-
const clearProviderModels = getStringArray(input.values, "clear-provider-model") ?? [];
|
|
14079
|
-
for (const provider of clearProviderModels) {
|
|
14080
|
-
if (provider in providerModels) throw usageError(`Error: --provider-model and --clear-provider-model conflict for ${provider}`);
|
|
14081
|
-
}
|
|
14082
|
-
const dispatchModes = parseDispatchModeAssignments(getStringArray(input.values, "dispatch-mode"));
|
|
14083
|
-
const clearDispatchModes = getStringArray(input.values, "clear-dispatch-mode") ?? [];
|
|
14084
|
-
for (const provider of clearDispatchModes) {
|
|
14085
|
-
if (provider in dispatchModes) throw usageError(`Error: --dispatch-mode and --clear-dispatch-mode conflict for ${provider}`);
|
|
14086
|
-
}
|
|
14087
|
-
await updateProjectSettings(name, {
|
|
14088
|
-
displayName: getString(input.values, "display-name"),
|
|
14089
|
-
domain: getString(input.values, "domain"),
|
|
14090
|
-
ownedDomains: getStringArray(input.values, "owned-domain"),
|
|
14091
|
-
addOwnedDomain: getStringArray(input.values, "add-domain"),
|
|
14092
|
-
removeOwnedDomain: getStringArray(input.values, "remove-domain"),
|
|
14093
|
-
aliases: getStringArray(input.values, "alias"),
|
|
14094
|
-
addAlias: getStringArray(input.values, "add-alias"),
|
|
14095
|
-
removeAlias: getStringArray(input.values, "remove-alias"),
|
|
14096
|
-
addQualifiedAlias: getStringArray(input.values, "add-qualified-alias"),
|
|
14097
|
-
removeQualifiedAlias: getStringArray(input.values, "remove-qualified-alias"),
|
|
14098
|
-
country: getString(input.values, "country"),
|
|
14099
|
-
language: getString(input.values, "language"),
|
|
14100
|
-
providers: allProviders ? [] : providers,
|
|
14101
|
-
providerModels,
|
|
14102
|
-
clearProviderModels,
|
|
14103
|
-
dispatchModes,
|
|
14104
|
-
clearDispatchModes,
|
|
14105
|
-
negativeReviewMaxStars: parseNegativeReviewMaxStars(getString(input.values, "negative-review-max-stars")),
|
|
14106
|
-
siteAuditMaxPages: parseSiteAuditMaxPages(getString(input.values, "site-audit-max-pages"), "project.update"),
|
|
14107
|
-
competitorAutoAliases: parseCompetitorAutoAliasMode(getString(input.values, "competitor-auto-aliases")),
|
|
14108
|
-
format: input.format
|
|
14138
|
+
const project2 = requireProject(input, "results export", RESULTS_EXPORT_USAGE);
|
|
14139
|
+
await exportResults(project2, {
|
|
14140
|
+
format: parseResultsExportFormat(getString(input.values, "format")),
|
|
14141
|
+
since: getString(input.values, "since"),
|
|
14142
|
+
until: getString(input.values, "until"),
|
|
14143
|
+
includeProbes: getBoolean(input.values, "include-probes"),
|
|
14144
|
+
output: getString(input.values, "output")
|
|
14109
14145
|
});
|
|
14110
14146
|
}
|
|
14111
14147
|
},
|
|
14112
14148
|
{
|
|
14113
|
-
path: ["
|
|
14114
|
-
usage: "canonry project
|
|
14115
|
-
allowPositionals: false,
|
|
14149
|
+
path: ["status"],
|
|
14150
|
+
usage: "canonry status <project> [--format json]",
|
|
14116
14151
|
run: async (input) => {
|
|
14117
|
-
|
|
14152
|
+
const project2 = requireProject(input, "status", "canonry status <project> [--format json]");
|
|
14153
|
+
await showStatus(project2, input.format);
|
|
14118
14154
|
}
|
|
14119
14155
|
},
|
|
14120
14156
|
{
|
|
14121
|
-
path: ["
|
|
14122
|
-
usage: "canonry
|
|
14157
|
+
path: ["evidence"],
|
|
14158
|
+
usage: "canonry evidence <project> [--format json]",
|
|
14123
14159
|
run: async (input) => {
|
|
14124
|
-
const
|
|
14125
|
-
await
|
|
14160
|
+
const project2 = requireProject(input, "evidence", "canonry evidence <project> [--format json]");
|
|
14161
|
+
await showEvidence(project2, input.format);
|
|
14126
14162
|
}
|
|
14127
14163
|
},
|
|
14128
14164
|
{
|
|
14129
|
-
path: ["
|
|
14130
|
-
usage: "canonry project
|
|
14131
|
-
|
|
14165
|
+
path: ["history"],
|
|
14166
|
+
usage: "canonry history <project> [--limit <1..500>] [--offset <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]",
|
|
14167
|
+
options: {
|
|
14168
|
+
all: { type: "boolean", default: false },
|
|
14169
|
+
limit: stringOption(),
|
|
14170
|
+
offset: stringOption(),
|
|
14171
|
+
since: stringOption(),
|
|
14172
|
+
action: stringOption(),
|
|
14173
|
+
actor: stringOption(),
|
|
14174
|
+
"entity-type": stringOption()
|
|
14175
|
+
},
|
|
14132
14176
|
run: async (input) => {
|
|
14133
|
-
const
|
|
14134
|
-
|
|
14177
|
+
const usage = "canonry history <project> [--limit <1..500>] [--offset <n>] [--since <ISO>] [--action <action>] [--actor <actor>] [--entity-type <type>] [--format json|jsonl]\n canonry history --all [same filters]";
|
|
14178
|
+
const project2 = getBoolean(input.values, "all") ? void 0 : requireProject(input, "history", usage);
|
|
14179
|
+
await showHistory(project2, input.format, {
|
|
14180
|
+
limit: parseHistoryPageOption(input, "limit", usage),
|
|
14181
|
+
offset: parseHistoryPageOption(input, "offset", usage),
|
|
14182
|
+
since: getString(input.values, "since"),
|
|
14183
|
+
action: getString(input.values, "action"),
|
|
14184
|
+
actor: getString(input.values, "actor"),
|
|
14185
|
+
entityType: getString(input.values, "entity-type")
|
|
14186
|
+
});
|
|
14135
14187
|
}
|
|
14136
14188
|
},
|
|
14137
14189
|
{
|
|
14138
|
-
path: ["
|
|
14139
|
-
usage: "canonry
|
|
14190
|
+
path: ["export"],
|
|
14191
|
+
usage: "canonry export <project> [--include-results] [--format json]",
|
|
14140
14192
|
options: {
|
|
14141
|
-
|
|
14142
|
-
city: stringOption(),
|
|
14143
|
-
region: stringOption(),
|
|
14144
|
-
country: stringOption(),
|
|
14145
|
-
timezone: stringOption()
|
|
14193
|
+
"include-results": { type: "boolean", default: false }
|
|
14146
14194
|
},
|
|
14147
14195
|
run: async (input) => {
|
|
14148
|
-
const
|
|
14149
|
-
|
|
14150
|
-
"
|
|
14151
|
-
"canonry project add-location <name> --label <label> --city <city> --region <region> --country <country> [--format json]"
|
|
14152
|
-
);
|
|
14153
|
-
const label = getString(input.values, "label");
|
|
14154
|
-
const city = getString(input.values, "city");
|
|
14155
|
-
const region = getString(input.values, "region");
|
|
14156
|
-
const country = getString(input.values, "country");
|
|
14157
|
-
if (!label || !city || !region || !country) {
|
|
14158
|
-
throw usageError("Error: --label, --city, --region, and --country are all required", {
|
|
14159
|
-
message: "location label, city, region, and country are required",
|
|
14160
|
-
details: {
|
|
14161
|
-
command: "project.add-location",
|
|
14162
|
-
usage: "canonry project add-location <name> --label <label> --city <city> --region <region> --country <country> [--format json]",
|
|
14163
|
-
required: ["label", "city", "region", "country"]
|
|
14164
|
-
}
|
|
14165
|
-
});
|
|
14166
|
-
}
|
|
14167
|
-
await addLocation(name, {
|
|
14168
|
-
label,
|
|
14169
|
-
city,
|
|
14170
|
-
region,
|
|
14171
|
-
country,
|
|
14172
|
-
timezone: getString(input.values, "timezone"),
|
|
14196
|
+
const project2 = requireProject(input, "export", "canonry export <project> [--include-results] [--format json]");
|
|
14197
|
+
await exportProject(project2, {
|
|
14198
|
+
includeResults: getBoolean(input.values, "include-results"),
|
|
14173
14199
|
format: input.format
|
|
14174
14200
|
});
|
|
14175
14201
|
}
|
|
14176
14202
|
},
|
|
14177
14203
|
{
|
|
14178
|
-
path: ["
|
|
14179
|
-
usage: "canonry
|
|
14180
|
-
|
|
14181
|
-
|
|
14182
|
-
|
|
14183
|
-
}
|
|
14184
|
-
},
|
|
14185
|
-
{
|
|
14186
|
-
path: ["project", "remove-location"],
|
|
14187
|
-
usage: "canonry project remove-location <name> <label> [--format json]",
|
|
14204
|
+
path: ["analytics"],
|
|
14205
|
+
usage: "canonry analytics <project> [--feature metrics|gaps|sources] [--window 7d|30d|90d|all] [--format json]",
|
|
14206
|
+
options: {
|
|
14207
|
+
feature: stringOption(),
|
|
14208
|
+
window: stringOption()
|
|
14209
|
+
},
|
|
14188
14210
|
run: async (input) => {
|
|
14189
|
-
const
|
|
14190
|
-
|
|
14191
|
-
|
|
14192
|
-
|
|
14193
|
-
|
|
14211
|
+
const project2 = requireProject(input, "analytics", "canonry analytics <project> [--feature metrics|gaps|sources] [--window 7d|30d|90d|all] [--format json]");
|
|
14212
|
+
await showAnalytics(project2, {
|
|
14213
|
+
feature: getString(input.values, "feature"),
|
|
14214
|
+
window: getString(input.values, "window"),
|
|
14215
|
+
format: input.format
|
|
14194
14216
|
});
|
|
14195
|
-
await removeLocation(name, label, input.format);
|
|
14196
14217
|
}
|
|
14197
14218
|
},
|
|
14198
14219
|
{
|
|
14199
|
-
path: ["
|
|
14200
|
-
usage:
|
|
14201
|
-
|
|
14202
|
-
|
|
14203
|
-
|
|
14204
|
-
|
|
14205
|
-
|
|
14206
|
-
|
|
14207
|
-
|
|
14208
|
-
|
|
14209
|
-
}
|
|
14220
|
+
path: ["sources"],
|
|
14221
|
+
usage: SOURCES_USAGE,
|
|
14222
|
+
options: {
|
|
14223
|
+
rank: { type: "boolean", default: false },
|
|
14224
|
+
"by-provider": { type: "boolean", default: false },
|
|
14225
|
+
limit: stringOption(),
|
|
14226
|
+
window: stringOption(),
|
|
14227
|
+
"query-class": stringOption(),
|
|
14228
|
+
"run-id": stringOption(),
|
|
14229
|
+
"include-by-query": stringOption()
|
|
14230
|
+
},
|
|
14231
|
+
run: async (input) => {
|
|
14232
|
+
const project2 = requireProject(input, "sources", SOURCES_USAGE);
|
|
14233
|
+
await showSources(project2, {
|
|
14234
|
+
rank: getBoolean(input.values, "rank"),
|
|
14235
|
+
byProvider: getBoolean(input.values, "by-provider"),
|
|
14236
|
+
limit: parseIntegerOption(input, "limit", {
|
|
14237
|
+
message: "--limit must be an integer",
|
|
14238
|
+
usage: SOURCES_USAGE,
|
|
14239
|
+
command: "sources"
|
|
14240
|
+
}),
|
|
14241
|
+
window: getString(input.values, "window"),
|
|
14242
|
+
runId: getString(input.values, "run-id"),
|
|
14243
|
+
queryClass: parseSourcesQueryClass(getString(input.values, "query-class")),
|
|
14244
|
+
includeByQuery: parseSourcesIncludeByQuery(getString(input.values, "include-by-query")),
|
|
14245
|
+
format: input.format
|
|
14246
|
+
});
|
|
14247
|
+
}
|
|
14210
14248
|
},
|
|
14211
14249
|
{
|
|
14212
|
-
path: ["
|
|
14213
|
-
usage: "canonry
|
|
14250
|
+
path: ["apply"],
|
|
14251
|
+
usage: "canonry apply <file...> [--format json]",
|
|
14214
14252
|
run: async (input) => {
|
|
14215
|
-
|
|
14216
|
-
|
|
14217
|
-
|
|
14218
|
-
|
|
14219
|
-
|
|
14253
|
+
if (input.positionals.length === 0) {
|
|
14254
|
+
throw usageError("Error: at least one file path is required\nUsage: canonry apply <file...> [--format json]", {
|
|
14255
|
+
message: "at least one file path is required",
|
|
14256
|
+
details: {
|
|
14257
|
+
command: "apply",
|
|
14258
|
+
usage: "canonry apply <file...> [--format json]"
|
|
14259
|
+
}
|
|
14260
|
+
});
|
|
14261
|
+
}
|
|
14262
|
+
await applyConfigs(input.positionals, input.format);
|
|
14220
14263
|
}
|
|
14221
14264
|
}
|
|
14222
14265
|
];
|
|
14223
|
-
|
|
14224
|
-
|
|
14225
|
-
|
|
14226
|
-
|
|
14227
|
-
const provider = separator === -1 ? "" : assignment.slice(0, separator).trim();
|
|
14228
|
-
const mode = providerDispatchModeSchema.safeParse(separator === -1 ? "" : assignment.slice(separator + 1).trim().toLowerCase());
|
|
14229
|
-
if (!provider || !mode.success) throw usageError(`Error: --dispatch-mode must use provider=sync or provider=batch (received ${assignment})`);
|
|
14230
|
-
if (provider in result) throw usageError(`Error: duplicate --dispatch-mode assignment for ${provider}`);
|
|
14231
|
-
result[provider] = mode.data;
|
|
14232
|
-
}
|
|
14233
|
-
return result;
|
|
14266
|
+
|
|
14267
|
+
// src/commands/project.ts
|
|
14268
|
+
function getClient23() {
|
|
14269
|
+
return createApiClient();
|
|
14234
14270
|
}
|
|
14235
|
-
function
|
|
14236
|
-
const
|
|
14237
|
-
|
|
14238
|
-
|
|
14239
|
-
|
|
14240
|
-
|
|
14241
|
-
|
|
14242
|
-
|
|
14243
|
-
|
|
14271
|
+
async function createProject(name, opts) {
|
|
14272
|
+
const client = getClient23();
|
|
14273
|
+
const result = await client.putProject(name, {
|
|
14274
|
+
displayName: opts.displayName,
|
|
14275
|
+
canonicalDomain: opts.domain,
|
|
14276
|
+
ownedDomains: opts.ownedDomains ?? [],
|
|
14277
|
+
aliases: normalizeProjectAliases(opts.displayName, opts.aliases ?? []),
|
|
14278
|
+
...opts.qualifiedAliases?.length ? { qualifiedAliases: opts.qualifiedAliases } : {},
|
|
14279
|
+
country: opts.country,
|
|
14280
|
+
language: opts.language,
|
|
14281
|
+
providers: opts.providers ?? [],
|
|
14282
|
+
providerModels: opts.providerModels ?? {},
|
|
14283
|
+
...opts.providerDispatchModes && Object.keys(opts.providerDispatchModes).length > 0 ? { providerDispatchModes: opts.providerDispatchModes } : {},
|
|
14284
|
+
// Sent only when given: this PUT also re-runs over an existing project,
|
|
14285
|
+
// and an omitted budget keeps the stored one.
|
|
14286
|
+
...opts.siteAuditMaxPages !== void 0 ? { siteAuditMaxPages: opts.siteAuditMaxPages } : {}
|
|
14287
|
+
});
|
|
14288
|
+
warnDroppedDispatchModes(opts.providerDispatchModes ?? {}, result);
|
|
14289
|
+
if (isMachineFormat(opts.format)) {
|
|
14290
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14291
|
+
return;
|
|
14244
14292
|
}
|
|
14245
|
-
|
|
14293
|
+
console.log(`Project created: ${result.name} (${result.id})`);
|
|
14246
14294
|
}
|
|
14247
|
-
function
|
|
14248
|
-
|
|
14249
|
-
|
|
14250
|
-
|
|
14251
|
-
|
|
14252
|
-
throw usageError('Error: --negative-review-max-stars must be 1, 2, 3, 4, or "default" (3)', {
|
|
14253
|
-
message: '--negative-review-max-stars must be 1-4 or "default"',
|
|
14254
|
-
details: { command: "project.update", value: raw }
|
|
14255
|
-
});
|
|
14295
|
+
function warnDroppedDispatchModes(sent, result) {
|
|
14296
|
+
const kept = result.providerDispatchModes ?? {};
|
|
14297
|
+
const dropped = Object.keys(sent).filter((provider) => !(provider in kept));
|
|
14298
|
+
if (dropped.length > 0) {
|
|
14299
|
+
console.error(`Warning: Dropped dispatch mode for engine(s) the project does not measure: ${dropped.join(", ")}`);
|
|
14256
14300
|
}
|
|
14257
|
-
return parsed.data;
|
|
14258
14301
|
}
|
|
14259
|
-
function
|
|
14260
|
-
|
|
14261
|
-
const
|
|
14262
|
-
if (
|
|
14263
|
-
|
|
14264
|
-
|
|
14265
|
-
throw usageError(`Error: --site-audit-max-pages must be a whole number of pages from 1 to ${SITE_AUDIT_MAX_PAGE_LIMIT}, or "full" for the full site`, {
|
|
14266
|
-
message: `--site-audit-max-pages must be 1-${SITE_AUDIT_MAX_PAGE_LIMIT} or "full"`,
|
|
14267
|
-
details: { command, option: "site-audit-max-pages", value: raw }
|
|
14268
|
-
});
|
|
14302
|
+
async function listProjects(format) {
|
|
14303
|
+
const client = getClient23();
|
|
14304
|
+
const projects2 = await client.listProjects();
|
|
14305
|
+
if (format === "json") {
|
|
14306
|
+
console.log(JSON.stringify(projects2, null, 2));
|
|
14307
|
+
return;
|
|
14269
14308
|
}
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
if (raw === void 0) return void 0;
|
|
14274
|
-
const parsed = competitorAutoAliasModeSchema.safeParse(raw.trim().toLowerCase());
|
|
14275
|
-
if (!parsed.success) {
|
|
14276
|
-
throw usageError("Error: --competitor-auto-aliases must be off, preview or apply", {
|
|
14277
|
-
message: "--competitor-auto-aliases must be off, preview or apply",
|
|
14278
|
-
details: { command: "project.update", option: "competitor-auto-aliases", value: raw }
|
|
14279
|
-
});
|
|
14309
|
+
if (format === "jsonl") {
|
|
14310
|
+
emitJsonl(projects2);
|
|
14311
|
+
return;
|
|
14280
14312
|
}
|
|
14281
|
-
|
|
14282
|
-
|
|
14283
|
-
|
|
14284
|
-
// src/commands/organic-evidence.ts
|
|
14285
|
-
async function showOrganicEvidence(project2, opts) {
|
|
14286
|
-
const data = await createApiClient().getOrganicEvidence(project2, opts.period);
|
|
14287
|
-
if (isMachineFormat(opts.format)) {
|
|
14288
|
-
console.log(JSON.stringify(data, null, 2));
|
|
14313
|
+
if (projects2.length === 0) {
|
|
14314
|
+
console.log("No projects found.");
|
|
14289
14315
|
return;
|
|
14290
14316
|
}
|
|
14291
|
-
|
|
14317
|
+
console.log("Projects:\n");
|
|
14318
|
+
const nameWidth = Math.max(4, ...projects2.map((p) => p.name.length));
|
|
14319
|
+
const domainLabel = (p) => {
|
|
14320
|
+
const extra = Math.max(0, effectiveDomains(p).length - 1);
|
|
14321
|
+
return extra > 0 ? `${p.canonicalDomain} (+${extra})` : p.canonicalDomain;
|
|
14322
|
+
};
|
|
14323
|
+
const domainWidth = Math.max(6, ...projects2.map((p) => domainLabel(p).length));
|
|
14324
|
+
console.log(
|
|
14325
|
+
` ${"NAME".padEnd(nameWidth)} ${"DOMAIN".padEnd(domainWidth)} COUNTRY LANGUAGE`
|
|
14326
|
+
);
|
|
14327
|
+
console.log(` ${"\u2500".repeat(nameWidth)} ${"\u2500".repeat(domainWidth)} \u2500\u2500\u2500\u2500\u2500\u2500\u2500 \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500`);
|
|
14328
|
+
for (const p of projects2) {
|
|
14329
|
+
console.log(
|
|
14330
|
+
` ${p.name.padEnd(nameWidth)} ${domainLabel(p).padEnd(domainWidth)} ${p.country.padEnd(7)} ${p.language}`
|
|
14331
|
+
);
|
|
14332
|
+
}
|
|
14292
14333
|
}
|
|
14293
|
-
function
|
|
14294
|
-
return
|
|
14334
|
+
function storedQualifiedAliases(project2) {
|
|
14335
|
+
return project2.qualifiedAliases ?? [];
|
|
14295
14336
|
}
|
|
14296
|
-
function
|
|
14297
|
-
const
|
|
14298
|
-
|
|
14299
|
-
|
|
14300
|
-
|
|
14301
|
-
|
|
14337
|
+
async function showProject(name, format) {
|
|
14338
|
+
const client = getClient23();
|
|
14339
|
+
const project2 = await client.getProject(name);
|
|
14340
|
+
if (isMachineFormat(format)) {
|
|
14341
|
+
console.log(JSON.stringify(project2, null, 2));
|
|
14342
|
+
return;
|
|
14302
14343
|
}
|
|
14303
|
-
|
|
14304
|
-
|
|
14305
|
-
|
|
14306
|
-
|
|
14307
|
-
|
|
14308
|
-
|
|
14344
|
+
console.log(`Project: ${project2.displayName ?? project2.name}
|
|
14345
|
+
`);
|
|
14346
|
+
console.log(` Name: ${project2.name}`);
|
|
14347
|
+
console.log(` ID: ${project2.id}`);
|
|
14348
|
+
console.log(` Domain: ${project2.canonicalDomain}`);
|
|
14349
|
+
const secondaryDomains = effectiveDomains(project2).slice(1);
|
|
14350
|
+
if (secondaryDomains.length > 0) {
|
|
14351
|
+
console.log(` Owned domains: ${secondaryDomains.join(", ")}`);
|
|
14309
14352
|
}
|
|
14310
|
-
if (
|
|
14311
|
-
|
|
14312
|
-
console.log(`Latest AI visibility: ${v.mentionedPairs}/${v.answerPairs} mentioned, ${v.citedPairs}/${v.answerPairs} cited (${Math.floor(v.ageDays)}d old)`);
|
|
14353
|
+
if (project2.aliases && project2.aliases.length > 0) {
|
|
14354
|
+
console.log(` Aliases: ${project2.aliases.join(", ")}`);
|
|
14313
14355
|
}
|
|
14314
|
-
|
|
14315
|
-
|
|
14316
|
-
|
|
14356
|
+
const qualifiedAliases = storedQualifiedAliases(project2);
|
|
14357
|
+
if (qualifiedAliases.length > 0) {
|
|
14358
|
+
console.log(` Qualified: ${qualifiedAliases.join(", ")} (sentiment)`);
|
|
14317
14359
|
}
|
|
14318
|
-
|
|
14319
|
-
|
|
14320
|
-
|
|
14360
|
+
console.log(` Country: ${project2.country}`);
|
|
14361
|
+
console.log(` Language: ${project2.language}`);
|
|
14362
|
+
console.log(` Config source: ${project2.configSource}`);
|
|
14363
|
+
console.log(` Config revision: ${project2.configRevision}`);
|
|
14364
|
+
console.log(` Providers: ${(project2.providers ?? []).length > 0 ? project2.providers.join(", ") : "all configured"}`);
|
|
14365
|
+
const providerModels = Object.entries(project2.providerModels ?? {});
|
|
14366
|
+
console.log(` Model overrides: ${providerModels.length > 0 ? providerModels.map(([provider, model]) => `${provider}=${model}`).join(", ") : "(none; instance settings inherited)"}`);
|
|
14367
|
+
const dispatchModes = Object.entries(project2.providerDispatchModes ?? {}).sort(([left], [right]) => left.localeCompare(right));
|
|
14368
|
+
console.log(` Dispatch modes: ${dispatchModes.length > 0 ? `${dispatchModes.map(([provider, mode]) => `${provider}=${mode}`).join(", ")} (scheduled sweeps; others sync)` : "(none; every provider runs sync)"}`);
|
|
14369
|
+
console.log(` Negative reviews: ${resolveNegativeReviewMaxStars(project2.negativeReviewMaxStars)} stars or fewer${project2.negativeReviewMaxStars == null ? " (default)" : ""}`);
|
|
14370
|
+
if (project2.siteAuditMaxPages !== void 0) {
|
|
14371
|
+
console.log(` Site Health page budget: ${formatSiteAuditPageBudget(project2.siteAuditMaxPages)}`);
|
|
14321
14372
|
}
|
|
14322
|
-
|
|
14323
|
-
|
|
14324
|
-
// src/cli-commands/organic-evidence.ts
|
|
14325
|
-
var USAGE3 = "canonry organic-evidence <project> [--period 60|90] [--format json|jsonl]";
|
|
14326
|
-
function parsePeriod(value) {
|
|
14327
|
-
if (value === void 0) return void 0;
|
|
14328
|
-
if (value === "60" || value === "90") return Number(value);
|
|
14329
|
-
throw usageError(`Error: --period must be 60 or 90
|
|
14330
|
-
|
|
14331
|
-
Usage: ${USAGE3}`);
|
|
14332
|
-
}
|
|
14333
|
-
var ORGANIC_EVIDENCE_CLI_COMMANDS = [
|
|
14334
|
-
{
|
|
14335
|
-
path: ["organic-evidence"],
|
|
14336
|
-
usage: USAGE3,
|
|
14337
|
-
options: { period: stringOption() },
|
|
14338
|
-
run: async (input) => {
|
|
14339
|
-
const project2 = requireProject(input, "organic-evidence", USAGE3);
|
|
14340
|
-
await showOrganicEvidence(project2, {
|
|
14341
|
-
period: parsePeriod(getString(input.values, "period")),
|
|
14342
|
-
format: input.format
|
|
14343
|
-
});
|
|
14344
|
-
}
|
|
14373
|
+
if (project2.competitorAutoAliases !== void 0) {
|
|
14374
|
+
console.log(` Competitor auto aliases: ${formatCompetitorAutoAliasMode(project2.competitorAutoAliases)}`);
|
|
14345
14375
|
}
|
|
14346
|
-
|
|
14347
|
-
|
|
14348
|
-
|
|
14349
|
-
|
|
14350
|
-
|
|
14376
|
+
console.log(` Tags: ${project2.tags.length > 0 ? project2.tags.join(", ") : "(none)"}`);
|
|
14377
|
+
const labelEntries = Object.entries(project2.labels);
|
|
14378
|
+
console.log(` Labels: ${labelEntries.length > 0 ? labelEntries.map(([k, v]) => `${k}=${v}`).join(", ") : "(none)"}`);
|
|
14379
|
+
if (project2.createdAt) console.log(` Created: ${project2.createdAt}`);
|
|
14380
|
+
if (project2.updatedAt) console.log(` Updated: ${project2.updatedAt}`);
|
|
14351
14381
|
}
|
|
14352
|
-
|
|
14353
|
-
|
|
14354
|
-
|
|
14355
|
-
|
|
14356
|
-
const reason = (r) => r.error ? formatRunErrorOneLine(r.error) : null;
|
|
14357
|
-
const label = (r) => [r.project, r.location ? `(${r.location})` : null, `run ${r.runId}`].filter(Boolean).join(" ");
|
|
14358
|
-
const message = waited.length === 1 ? `Run ${failed[0].runId} failed${reason(failed[0]) ? `: ${reason(failed[0])}` : ""}` : `${failed.length} of ${waited.length} runs failed: ${failed.map((r) => `${label(r)}${reason(r) ? `: ${reason(r)}` : ""}`).join("; ")}`;
|
|
14359
|
-
throw new CliError({
|
|
14360
|
-
code: "RUN_FAILED",
|
|
14361
|
-
message,
|
|
14362
|
-
exitCode: EXIT_SYSTEM_ERROR,
|
|
14363
|
-
details: {
|
|
14364
|
-
waitedRunCount: waited.length,
|
|
14365
|
-
failedRuns: failed.map((r) => ({
|
|
14366
|
-
runId: r.runId,
|
|
14367
|
-
...r.project ? { project: r.project } : {},
|
|
14368
|
-
...r.location ? { location: r.location } : {},
|
|
14369
|
-
error: reason(r)
|
|
14370
|
-
}))
|
|
14371
|
-
}
|
|
14372
|
-
});
|
|
14382
|
+
function formatCompetitorAutoAliasMode(mode) {
|
|
14383
|
+
if (mode === "apply") return "apply (names detected after each sweep are stored and counted)";
|
|
14384
|
+
if (mode === "off") return "off (no detection after sweeps)";
|
|
14385
|
+
return "preview (detected after each sweep and logged; nothing stored)";
|
|
14373
14386
|
}
|
|
14374
|
-
async function
|
|
14387
|
+
async function updateProjectSettings(name, opts) {
|
|
14375
14388
|
const client = getClient23();
|
|
14376
|
-
const
|
|
14377
|
-
|
|
14378
|
-
|
|
14379
|
-
const
|
|
14380
|
-
|
|
14389
|
+
const project2 = await client.getProject(name);
|
|
14390
|
+
let ownedDomains = opts.ownedDomains ?? project2.ownedDomains ?? [];
|
|
14391
|
+
if (opts.addOwnedDomain) {
|
|
14392
|
+
const toAdd = opts.addOwnedDomain.filter((d) => !ownedDomains.includes(d));
|
|
14393
|
+
ownedDomains = [...ownedDomains, ...toAdd];
|
|
14381
14394
|
}
|
|
14382
|
-
if (opts
|
|
14383
|
-
|
|
14395
|
+
if (opts.removeOwnedDomain) {
|
|
14396
|
+
const toRemove = new Set(opts.removeOwnedDomain);
|
|
14397
|
+
ownedDomains = ownedDomains.filter((d) => !toRemove.has(d));
|
|
14384
14398
|
}
|
|
14385
|
-
|
|
14386
|
-
|
|
14387
|
-
|
|
14388
|
-
|
|
14389
|
-
|
|
14399
|
+
const nextDisplayName = opts.displayName ?? project2.displayName ?? project2.name;
|
|
14400
|
+
let aliases = opts.aliases ?? project2.aliases ?? [];
|
|
14401
|
+
if (opts.addAlias) {
|
|
14402
|
+
const existingKeys = new Set(aliases.map((a) => a.toLowerCase()));
|
|
14403
|
+
const toAdd = opts.addAlias.filter((a) => !existingKeys.has(a.toLowerCase()));
|
|
14404
|
+
aliases = [...aliases, ...toAdd];
|
|
14390
14405
|
}
|
|
14391
|
-
if (opts
|
|
14392
|
-
|
|
14406
|
+
if (opts.removeAlias) {
|
|
14407
|
+
const toRemove = new Set(opts.removeAlias.map((a) => a.toLowerCase()));
|
|
14408
|
+
aliases = aliases.filter((a) => !toRemove.has(a.toLowerCase()));
|
|
14393
14409
|
}
|
|
14394
|
-
|
|
14395
|
-
|
|
14410
|
+
const nextAliases = normalizeProjectAliases(nextDisplayName, aliases);
|
|
14411
|
+
const touchesQualified = (opts.addQualifiedAlias?.length ?? 0) > 0 || (opts.removeQualifiedAlias?.length ?? 0) > 0;
|
|
14412
|
+
let qualifiedAliases = resolveProjectQualifiedAliases(
|
|
14413
|
+
{ displayName: nextDisplayName, aliases: nextAliases },
|
|
14414
|
+
storedQualifiedAliases(project2)
|
|
14415
|
+
).value;
|
|
14416
|
+
if (opts.addQualifiedAlias) {
|
|
14417
|
+
const existingKeys = new Set(qualifiedAliases.map((alias) => alias.toLowerCase()));
|
|
14418
|
+
for (const alias of opts.addQualifiedAlias) {
|
|
14419
|
+
if (existingKeys.has(alias.toLowerCase())) continue;
|
|
14420
|
+
existingKeys.add(alias.toLowerCase());
|
|
14421
|
+
qualifiedAliases = [...qualifiedAliases, alias];
|
|
14422
|
+
}
|
|
14396
14423
|
}
|
|
14397
|
-
if (opts
|
|
14398
|
-
|
|
14424
|
+
if (opts.removeQualifiedAlias) {
|
|
14425
|
+
const toRemove = new Set(opts.removeQualifiedAlias.map((alias) => alias.toLowerCase()));
|
|
14426
|
+
qualifiedAliases = qualifiedAliases.filter((alias) => !toRemove.has(alias.toLowerCase()));
|
|
14399
14427
|
}
|
|
14400
|
-
|
|
14401
|
-
|
|
14428
|
+
const providerModels = { ...project2.providerModels ?? {}, ...opts.providerModels ?? {} };
|
|
14429
|
+
for (const provider of opts.clearProviderModels ?? []) delete providerModels[provider];
|
|
14430
|
+
const nextProviders = opts.providers ?? project2.providers ?? [];
|
|
14431
|
+
if (nextProviders.length > 0) {
|
|
14432
|
+
const requested = Object.keys(opts.providerModels ?? {}).filter((p) => !nextProviders.includes(p));
|
|
14433
|
+
if (requested.length > 0) {
|
|
14434
|
+
throw usageError(
|
|
14435
|
+
`Error: --provider-model set for ${requested.join(", ")}, which ${requested.length > 1 ? "are" : "is"} not a selected engine for "${name}"`,
|
|
14436
|
+
{
|
|
14437
|
+
message: "provider-model set for a provider the project does not run",
|
|
14438
|
+
details: { command: "project.update", project: name, providers: nextProviders, unselected: requested }
|
|
14439
|
+
}
|
|
14440
|
+
);
|
|
14441
|
+
}
|
|
14402
14442
|
}
|
|
14403
|
-
|
|
14404
|
-
|
|
14443
|
+
const touchesDispatch = Object.keys(opts.dispatchModes ?? {}).length > 0 || (opts.clearDispatchModes ?? []).length > 0;
|
|
14444
|
+
const providerDispatchModes = { ...project2.providerDispatchModes ?? {}, ...opts.dispatchModes ?? {} };
|
|
14445
|
+
for (const provider of opts.clearDispatchModes ?? []) delete providerDispatchModes[provider];
|
|
14446
|
+
const result = await client.putProject(name, {
|
|
14447
|
+
displayName: nextDisplayName,
|
|
14448
|
+
canonicalDomain: opts.domain ?? project2.canonicalDomain,
|
|
14449
|
+
ownedDomains,
|
|
14450
|
+
aliases: nextAliases,
|
|
14451
|
+
...touchesQualified ? { qualifiedAliases } : {},
|
|
14452
|
+
country: opts.country ?? project2.country,
|
|
14453
|
+
language: opts.language ?? project2.language,
|
|
14454
|
+
tags: project2.tags,
|
|
14455
|
+
labels: project2.labels,
|
|
14456
|
+
providers: opts.providers ?? project2.providers,
|
|
14457
|
+
providerModels,
|
|
14458
|
+
...touchesDispatch ? { providerDispatchModes } : {},
|
|
14459
|
+
locations: project2.locations,
|
|
14460
|
+
defaultLocation: project2.defaultLocation,
|
|
14461
|
+
autoExtractBacklinks: project2.autoExtractBacklinks,
|
|
14462
|
+
// Sent only when asked for: the server keeps the stored threshold when the
|
|
14463
|
+
// field is absent, so a stale read cannot overwrite a newer setting.
|
|
14464
|
+
...opts.negativeReviewMaxStars !== void 0 ? { negativeReviewMaxStars: opts.negativeReviewMaxStars } : {},
|
|
14465
|
+
// Same for the page budget: never echoed from the read above, so an edit
|
|
14466
|
+
// that does not touch it cannot overwrite a newer one or send a stray null.
|
|
14467
|
+
...opts.siteAuditMaxPages !== void 0 ? { siteAuditMaxPages: opts.siteAuditMaxPages } : {},
|
|
14468
|
+
// Same for the detection mode.
|
|
14469
|
+
...opts.competitorAutoAliases !== void 0 ? { competitorAutoAliases: opts.competitorAutoAliases } : {}
|
|
14470
|
+
});
|
|
14471
|
+
warnDroppedDispatchModes(touchesDispatch ? providerDispatchModes : project2.providerDispatchModes ?? {}, result);
|
|
14472
|
+
if (isMachineFormat(opts.format)) {
|
|
14473
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14474
|
+
return;
|
|
14405
14475
|
}
|
|
14406
|
-
|
|
14407
|
-
|
|
14476
|
+
console.log(`Project updated: ${result.name}`);
|
|
14477
|
+
const dropped = Object.keys(providerModels).filter((provider) => !(provider in result.providerModels));
|
|
14478
|
+
if (dropped.length > 0) {
|
|
14479
|
+
console.log(` Dropped model override for deselected engine(s): ${dropped.join(", ")}`);
|
|
14408
14480
|
}
|
|
14409
|
-
|
|
14410
|
-
|
|
14411
|
-
|
|
14412
|
-
|
|
14413
|
-
|
|
14414
|
-
|
|
14415
|
-
|
|
14416
|
-
|
|
14417
|
-
console.log(JSON.stringify(locationRuns.map((r, i) => finals[i] ? { ...r, ...finals[i] } : r), null, 2));
|
|
14418
|
-
throwIfWaitedRunFailed(locationRuns.flatMap((r, i) => {
|
|
14419
|
-
const final = finals[i];
|
|
14420
|
-
return final ? [{ runId: final.id, status: final.status, project: project2, location: r.location ?? null, error: final.error }] : [];
|
|
14421
|
-
}));
|
|
14422
|
-
} else {
|
|
14423
|
-
console.log(JSON.stringify(locationRuns, null, 2));
|
|
14424
|
-
}
|
|
14481
|
+
}
|
|
14482
|
+
async function deleteProject(name, opts) {
|
|
14483
|
+
const client = getClient23();
|
|
14484
|
+
const isJson = isMachineFormat(opts?.format);
|
|
14485
|
+
if (opts?.dryRun) {
|
|
14486
|
+
const preview = await client.previewProjectDelete(name);
|
|
14487
|
+
if (isJson) {
|
|
14488
|
+
console.log(JSON.stringify({ dryRun: true, ...preview }, null, 2));
|
|
14425
14489
|
return;
|
|
14426
14490
|
}
|
|
14427
|
-
|
|
14428
|
-
`);
|
|
14429
|
-
console.log(
|
|
14430
|
-
console.log(
|
|
14431
|
-
|
|
14432
|
-
|
|
14433
|
-
|
|
14434
|
-
|
|
14435
|
-
|
|
14436
|
-
|
|
14437
|
-
|
|
14438
|
-
|
|
14439
|
-
if (pending.length > 0) {
|
|
14440
|
-
process.stderr.write(`Waiting for ${pending.length} run(s)`);
|
|
14441
|
-
const batchWaits = /* @__PURE__ */ new Map();
|
|
14442
|
-
await Promise.all(
|
|
14443
|
-
pending.map(async (r) => {
|
|
14444
|
-
const final = await pollRun2(client, r.id);
|
|
14445
|
-
r.status = final.status;
|
|
14446
|
-
errors.set(r.id, final.error);
|
|
14447
|
-
const waiting = batchWaitLine(final);
|
|
14448
|
-
if (waiting) batchWaits.set(r.id, waiting);
|
|
14449
|
-
})
|
|
14450
|
-
);
|
|
14451
|
-
process.stderr.write("\n");
|
|
14452
|
-
console.log("\nFinal statuses:");
|
|
14453
|
-
for (const r of locationRuns) {
|
|
14454
|
-
const loc = (r.location ?? "(unknown)").padEnd(15);
|
|
14455
|
-
console.log(` ${loc} ${r.status}`);
|
|
14456
|
-
}
|
|
14457
|
-
if (batchWaits.size > 0) console.log("");
|
|
14458
|
-
for (const r of locationRuns) {
|
|
14459
|
-
const waiting = batchWaits.get(r.id);
|
|
14460
|
-
if (waiting) console.log(` ${r.location ?? "(unknown)"}: ${waiting}`);
|
|
14461
|
-
}
|
|
14462
|
-
}
|
|
14463
|
-
throwIfWaitedRunFailed(locationRuns.filter((r) => r.id && r.status !== "conflict").map((r) => ({ runId: r.id, status: r.status, project: project2, location: r.location ?? null, error: errors.get(r.id) })));
|
|
14464
|
-
}
|
|
14491
|
+
const { cascadeRows: cr, detachedRows: dr } = preview;
|
|
14492
|
+
console.log(`Project delete preview for "${name}":`);
|
|
14493
|
+
console.log(` Cascade-deletes:`);
|
|
14494
|
+
console.log(` queries: ${cr.queries}`);
|
|
14495
|
+
console.log(` competitors: ${cr.competitors}`);
|
|
14496
|
+
console.log(` runs: ${cr.runs}`);
|
|
14497
|
+
console.log(` snapshots: ${cr.snapshots}`);
|
|
14498
|
+
console.log(` insights: ${cr.insights}`);
|
|
14499
|
+
console.log(` Detached (project_id set to NULL):`);
|
|
14500
|
+
console.log(` audit_log: ${dr.auditLog}`);
|
|
14501
|
+
console.log(``);
|
|
14502
|
+
console.log(`No DB writes performed. Re-run without --dry-run to delete.`);
|
|
14465
14503
|
return;
|
|
14466
14504
|
}
|
|
14467
|
-
|
|
14468
|
-
if (
|
|
14469
|
-
|
|
14470
|
-
if (showProgress) process.stderr.write(`Run ${run.id} started`);
|
|
14471
|
-
const result = await pollRun2(client, run.id, showProgress);
|
|
14472
|
-
if (isMachineFormat(opts?.format)) {
|
|
14473
|
-
console.log(JSON.stringify(result, null, 2));
|
|
14474
|
-
} else {
|
|
14475
|
-
process.stderr.write("\n");
|
|
14476
|
-
printRunDetail(result);
|
|
14477
|
-
const waiting = batchWaitLine(result);
|
|
14478
|
-
if (waiting) console.log(`
|
|
14479
|
-
${waiting}`);
|
|
14480
|
-
}
|
|
14481
|
-
throwIfWaitedRunFailed([{ runId: result.id, status: result.status, project: project2, error: result.error }]);
|
|
14505
|
+
await client.deleteProject(name);
|
|
14506
|
+
if (isJson) {
|
|
14507
|
+
console.log(JSON.stringify({ name, deleted: true }, null, 2));
|
|
14482
14508
|
return;
|
|
14483
14509
|
}
|
|
14484
|
-
|
|
14485
|
-
|
|
14486
|
-
|
|
14487
|
-
|
|
14488
|
-
|
|
14489
|
-
|
|
14490
|
-
|
|
14491
|
-
|
|
14510
|
+
console.log(`Project deleted: ${name}`);
|
|
14511
|
+
}
|
|
14512
|
+
async function addLocation(project2, opts) {
|
|
14513
|
+
const client = getClient23();
|
|
14514
|
+
const location = await client.addLocation(project2, {
|
|
14515
|
+
label: opts.label,
|
|
14516
|
+
city: opts.city,
|
|
14517
|
+
region: opts.region,
|
|
14518
|
+
country: opts.country,
|
|
14519
|
+
timezone: opts.timezone
|
|
14520
|
+
});
|
|
14521
|
+
if (isMachineFormat(opts.format)) {
|
|
14522
|
+
console.log(JSON.stringify(location, null, 2));
|
|
14492
14523
|
return;
|
|
14493
14524
|
}
|
|
14494
|
-
|
|
14495
|
-
|
|
14525
|
+
console.log(`Location added: ${opts.label} (${opts.city}, ${opts.region}, ${opts.country})`);
|
|
14526
|
+
}
|
|
14527
|
+
async function listLocations(project2, format) {
|
|
14528
|
+
const client = getClient23();
|
|
14529
|
+
const result = await client.listLocations(project2);
|
|
14530
|
+
if (format === "json") {
|
|
14531
|
+
console.log(JSON.stringify(result, null, 2));
|
|
14496
14532
|
return;
|
|
14497
14533
|
}
|
|
14498
|
-
|
|
14499
|
-
|
|
14500
|
-
|
|
14501
|
-
|
|
14502
|
-
|
|
14503
|
-
|
|
14504
|
-
}
|
|
14505
|
-
async function triggerRunAll(opts) {
|
|
14506
|
-
const client = getClient23();
|
|
14507
|
-
const projects2 = await client.listProjects();
|
|
14508
|
-
if (projects2.length === 0) {
|
|
14509
|
-
if (isMachineFormat(opts?.format)) {
|
|
14510
|
-
console.log("[]");
|
|
14511
|
-
} else {
|
|
14512
|
-
console.log("No projects found.");
|
|
14513
|
-
}
|
|
14534
|
+
if (format === "jsonl") {
|
|
14535
|
+
emitJsonl(result.locations.map((loc) => ({
|
|
14536
|
+
project: project2,
|
|
14537
|
+
isDefault: loc.label === result.defaultLocation,
|
|
14538
|
+
...loc
|
|
14539
|
+
})));
|
|
14514
14540
|
return;
|
|
14515
14541
|
}
|
|
14516
|
-
|
|
14517
|
-
|
|
14518
|
-
const providerInputs = opts.provider.split(",").map((s) => s.trim()).filter(Boolean);
|
|
14519
|
-
const resolved = providerInputs.flatMap((p) => resolveProviderInput(p));
|
|
14520
|
-
baseBody.providers = resolved.length > 0 ? resolved : providerInputs;
|
|
14521
|
-
}
|
|
14522
|
-
if (opts?.allLocations) {
|
|
14523
|
-
baseBody.allLocations = true;
|
|
14524
|
-
}
|
|
14525
|
-
if (opts?.noLocation) {
|
|
14526
|
-
baseBody.noLocation = true;
|
|
14527
|
-
}
|
|
14528
|
-
if (opts?.dispatchMode) {
|
|
14529
|
-
baseBody.dispatchMode = opts.dispatchMode;
|
|
14530
|
-
}
|
|
14531
|
-
if (opts?.force) {
|
|
14532
|
-
baseBody.force = true;
|
|
14533
|
-
}
|
|
14534
|
-
const results = [];
|
|
14535
|
-
for (const p of projects2) {
|
|
14536
|
-
const body = { ...baseBody };
|
|
14537
|
-
if (body.allLocations && p.locations.length === 0) {
|
|
14538
|
-
delete body.allLocations;
|
|
14539
|
-
}
|
|
14540
|
-
try {
|
|
14541
|
-
const response = await client.triggerRun(p.name, body);
|
|
14542
|
-
const dispatched = Array.isArray(response) ? response : [response];
|
|
14543
|
-
for (const r of dispatched) {
|
|
14544
|
-
results.push({
|
|
14545
|
-
project: p.name,
|
|
14546
|
-
runId: r.id,
|
|
14547
|
-
status: r.status,
|
|
14548
|
-
location: r.location ?? null
|
|
14549
|
-
});
|
|
14550
|
-
}
|
|
14551
|
-
} catch (err) {
|
|
14552
|
-
const msg = describeError(err);
|
|
14553
|
-
results.push({ project: p.name, runId: "", status: "error", location: null, error: msg });
|
|
14554
|
-
}
|
|
14555
|
-
}
|
|
14556
|
-
const errors = /* @__PURE__ */ new Map();
|
|
14557
|
-
const batchWaits = /* @__PURE__ */ new Map();
|
|
14558
|
-
if (opts?.wait) {
|
|
14559
|
-
const pending = results.filter((r) => r.runId && !TERMINAL_STATUSES.has(r.status));
|
|
14560
|
-
if (pending.length > 0) {
|
|
14561
|
-
const showProgress = !isMachineFormat(opts?.format);
|
|
14562
|
-
if (showProgress) process.stderr.write(`Waiting for ${pending.length} run(s)`);
|
|
14563
|
-
await Promise.all(pending.map(async (r) => {
|
|
14564
|
-
const final = await pollRun2(client, r.runId, showProgress);
|
|
14565
|
-
r.status = final.status;
|
|
14566
|
-
errors.set(r.runId, final.error);
|
|
14567
|
-
const waiting = batchWaitLine(final);
|
|
14568
|
-
if (waiting) batchWaits.set(r.runId, waiting);
|
|
14569
|
-
}));
|
|
14570
|
-
if (showProgress) process.stderr.write("\n");
|
|
14571
|
-
}
|
|
14572
|
-
}
|
|
14573
|
-
const waited = results.filter((r) => r.runId).map((r) => ({ runId: r.runId, status: r.status, project: r.project, location: r.location, error: errors.get(r.runId) }));
|
|
14574
|
-
if (isMachineFormat(opts?.format)) {
|
|
14575
|
-
console.log(JSON.stringify(results, null, 2));
|
|
14576
|
-
if (opts?.wait) throwIfWaitedRunFailed(waited);
|
|
14542
|
+
if (result.locations.length === 0) {
|
|
14543
|
+
console.log(`No locations configured for "${project2}".`);
|
|
14577
14544
|
return;
|
|
14578
14545
|
}
|
|
14579
|
-
|
|
14580
|
-
const projectCount = new Set(results.map((r) => r.project)).size;
|
|
14581
|
-
console.log(`Triggered ${results.length} run(s) across ${projectCount} project(s):
|
|
14546
|
+
console.log(`Locations for "${project2}" (${result.locations.length}):
|
|
14582
14547
|
`);
|
|
14583
|
-
|
|
14584
|
-
|
|
14585
|
-
|
|
14586
|
-
|
|
14587
|
-
|
|
14588
|
-
|
|
14589
|
-
|
|
14590
|
-
console.log(` ${proj} ${loc} ${id} ${r.status}`);
|
|
14591
|
-
}
|
|
14592
|
-
} else {
|
|
14593
|
-
console.log(" PROJECT RUN ID STATUS");
|
|
14594
|
-
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");
|
|
14595
|
-
for (const r of results) {
|
|
14596
|
-
const proj = r.project.padEnd(31);
|
|
14597
|
-
const id = (r.runId || "(failed)").padEnd(36);
|
|
14598
|
-
console.log(` ${proj} ${id} ${r.status}`);
|
|
14599
|
-
}
|
|
14548
|
+
console.log(" LABEL CITY REGION COUNTRY DEFAULT");
|
|
14549
|
+
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");
|
|
14550
|
+
for (const loc of result.locations) {
|
|
14551
|
+
const isDefault = loc.label === result.defaultLocation ? " *" : "";
|
|
14552
|
+
console.log(
|
|
14553
|
+
` ${loc.label.padEnd(15)} ${loc.city.padEnd(19)} ${loc.region.padEnd(19)} ${loc.country.padEnd(7)}${isDefault}`
|
|
14554
|
+
);
|
|
14600
14555
|
}
|
|
14601
|
-
if (
|
|
14602
|
-
|
|
14603
|
-
|
|
14604
|
-
if (waiting) console.log(` ${r.location ? `${r.project} (${r.location})` : r.project}: ${waiting}`);
|
|
14556
|
+
if (result.defaultLocation) {
|
|
14557
|
+
console.log(`
|
|
14558
|
+
Default: ${result.defaultLocation}`);
|
|
14605
14559
|
}
|
|
14606
|
-
if (opts?.wait) throwIfWaitedRunFailed(waited);
|
|
14607
14560
|
}
|
|
14608
|
-
async function
|
|
14561
|
+
async function removeLocation(project2, label, format) {
|
|
14609
14562
|
const client = getClient23();
|
|
14610
|
-
|
|
14611
|
-
if (!targetId) {
|
|
14612
|
-
const runs2 = await client.listRuns(project2);
|
|
14613
|
-
const activeRuns = runs2.filter((r) => r.status === "queued" || r.status === "running");
|
|
14614
|
-
if (activeRuns.length > 1) {
|
|
14615
|
-
const candidates = activeRuns.map(({ id, kind, status }) => ({ id, kind, status }));
|
|
14616
|
-
throw new CliError({
|
|
14617
|
-
code: "MULTIPLE_ACTIVE_RUNS",
|
|
14618
|
-
message: `Multiple active runs found for project "${project2}". Specify a run ID.`,
|
|
14619
|
-
displayMessage: `Error: Multiple active runs found for project "${project2}". Specify a run ID.
|
|
14620
|
-
` + candidates.map((r) => ` ${r.id} ${r.kind} ${r.status}`).join("\n") + `
|
|
14621
|
-
To cancel by ID: canonry run cancel ${project2} <run-id>`,
|
|
14622
|
-
details: {
|
|
14623
|
-
project: project2,
|
|
14624
|
-
activeRuns: candidates,
|
|
14625
|
-
suggestedCommands: [`canonry run cancel ${project2} <run-id>`]
|
|
14626
|
-
}
|
|
14627
|
-
});
|
|
14628
|
-
}
|
|
14629
|
-
const active = activeRuns[0];
|
|
14630
|
-
if (!active) {
|
|
14631
|
-
throw new CliError({
|
|
14632
|
-
code: "NO_ACTIVE_RUN",
|
|
14633
|
-
message: `No active run found for project "${project2}"`,
|
|
14634
|
-
displayMessage: `Error: canonry run cancel "${project2}" \u2014 no active run found (status must be queued or running).
|
|
14635
|
-
Check run status : canonry status ${project2}
|
|
14636
|
-
To cancel by ID : canonry run cancel ${project2} <run-id>`,
|
|
14637
|
-
details: {
|
|
14638
|
-
project: project2,
|
|
14639
|
-
allowedStatuses: ["queued", "running"],
|
|
14640
|
-
suggestedCommands: [
|
|
14641
|
-
`canonry status ${project2}`,
|
|
14642
|
-
`canonry run cancel ${project2} <run-id>`
|
|
14643
|
-
]
|
|
14644
|
-
}
|
|
14645
|
-
});
|
|
14646
|
-
}
|
|
14647
|
-
targetId = active.id;
|
|
14648
|
-
}
|
|
14649
|
-
const result = await client.cancelRun(targetId);
|
|
14563
|
+
await client.removeLocation(project2, label);
|
|
14650
14564
|
if (isMachineFormat(format)) {
|
|
14651
|
-
console.log(JSON.stringify(
|
|
14565
|
+
console.log(JSON.stringify({ project: project2, label, removed: true }, null, 2));
|
|
14652
14566
|
return;
|
|
14653
14567
|
}
|
|
14654
|
-
console.log(`
|
|
14568
|
+
console.log(`Location removed: ${label}`);
|
|
14655
14569
|
}
|
|
14656
|
-
async function
|
|
14570
|
+
async function setDefaultLocation(project2, label, format) {
|
|
14657
14571
|
const client = getClient23();
|
|
14658
|
-
const
|
|
14572
|
+
const result = await client.setDefaultLocation(project2, label);
|
|
14659
14573
|
if (isMachineFormat(format)) {
|
|
14660
|
-
console.log(JSON.stringify(
|
|
14661
|
-
return;
|
|
14662
|
-
}
|
|
14663
|
-
printRunDetail(run);
|
|
14664
|
-
}
|
|
14665
|
-
async function listRuns(project2, opts) {
|
|
14666
|
-
const client = getClient23();
|
|
14667
|
-
const runs2 = await client.listRuns(project2, opts?.limit, opts?.kind, opts?.status, opts?.excludeKind, opts?.excludeKinds);
|
|
14668
|
-
if (opts?.format === "json") {
|
|
14669
|
-
console.log(JSON.stringify(runs2, null, 2));
|
|
14670
|
-
return;
|
|
14671
|
-
} else if (opts?.format === "jsonl") {
|
|
14672
|
-
emitJsonl(runs2.map((run) => ({ project: project2, ...run })));
|
|
14673
|
-
return;
|
|
14674
|
-
}
|
|
14675
|
-
if (runs2.length === 0) {
|
|
14676
|
-
console.log(`No runs found for "${project2}".`);
|
|
14574
|
+
console.log(JSON.stringify({ project: project2, ...result }, null, 2));
|
|
14677
14575
|
return;
|
|
14678
14576
|
}
|
|
14679
|
-
console.log(`
|
|
14680
|
-
`);
|
|
14681
|
-
console.log(" ID STATUS KIND TRIGGER CREATED");
|
|
14682
|
-
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");
|
|
14683
|
-
for (const run of runs2) {
|
|
14684
|
-
console.log(
|
|
14685
|
-
` ${run.id} ${run.status.padEnd(10)} ${run.kind.padEnd(18)} ${run.trigger.padEnd(9)} ${run.createdAt}`
|
|
14686
|
-
);
|
|
14687
|
-
}
|
|
14577
|
+
console.log(`Default location set to: ${label}`);
|
|
14688
14578
|
}
|
|
14689
|
-
|
|
14690
|
-
|
|
14691
|
-
|
|
14692
|
-
|
|
14693
|
-
|
|
14694
|
-
|
|
14695
|
-
|
|
14579
|
+
|
|
14580
|
+
// src/cli-commands/project.ts
|
|
14581
|
+
var PROJECT_CLI_COMMANDS = [
|
|
14582
|
+
{
|
|
14583
|
+
path: ["project", "create"],
|
|
14584
|
+
usage: "canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--qualified-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--provider-model provider=model...] [--dispatch-mode provider=sync|batch...] [--site-audit-max-pages <1-50000|full>] [--format json]",
|
|
14585
|
+
help: "Create a project. Pass --domain for the public site to scan. Provider credentials are not required for Page Health.",
|
|
14586
|
+
options: {
|
|
14587
|
+
domain: { type: "string", short: "d" },
|
|
14588
|
+
"owned-domain": multiStringOption(),
|
|
14589
|
+
alias: multiStringOption(),
|
|
14590
|
+
"qualified-alias": multiStringOption(),
|
|
14591
|
+
country: stringOption(),
|
|
14592
|
+
language: stringOption(),
|
|
14593
|
+
"display-name": stringOption(),
|
|
14594
|
+
provider: multiStringOption(),
|
|
14595
|
+
"provider-model": multiStringOption(),
|
|
14596
|
+
"dispatch-mode": multiStringOption(),
|
|
14597
|
+
"site-audit-max-pages": stringOption()
|
|
14598
|
+
},
|
|
14599
|
+
run: async (input) => {
|
|
14600
|
+
const name = requireProject(
|
|
14601
|
+
input,
|
|
14602
|
+
"project.create",
|
|
14603
|
+
"canonry project create <name> [--domain <domain>] [--owned-domain <domain>...] [--alias <name>...] [--qualified-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--provider-model provider=model...] [--dispatch-mode provider=sync|batch...] [--site-audit-max-pages <1-50000|full>] [--format json]"
|
|
14604
|
+
);
|
|
14605
|
+
await createProject(name, {
|
|
14606
|
+
domain: getString(input.values, "domain") ?? name,
|
|
14607
|
+
ownedDomains: getStringArray(input.values, "owned-domain") ?? [],
|
|
14608
|
+
aliases: getStringArray(input.values, "alias") ?? [],
|
|
14609
|
+
qualifiedAliases: getStringArray(input.values, "qualified-alias") ?? [],
|
|
14610
|
+
country: getString(input.values, "country") ?? "US",
|
|
14611
|
+
language: getString(input.values, "language") ?? "en",
|
|
14612
|
+
displayName: getString(input.values, "display-name") ?? name,
|
|
14613
|
+
providers: getStringArray(input.values, "provider") ?? [],
|
|
14614
|
+
providerModels: parseProviderModelAssignments(getStringArray(input.values, "provider-model")),
|
|
14615
|
+
providerDispatchModes: parseDispatchModeAssignments(getStringArray(input.values, "dispatch-mode")),
|
|
14616
|
+
siteAuditMaxPages: parseSiteAuditMaxPages(getString(input.values, "site-audit-max-pages"), "project.create"),
|
|
14617
|
+
format: input.format
|
|
14618
|
+
});
|
|
14696
14619
|
}
|
|
14697
|
-
|
|
14698
|
-
|
|
14699
|
-
|
|
14700
|
-
|
|
14620
|
+
},
|
|
14621
|
+
{
|
|
14622
|
+
path: ["project", "update"],
|
|
14623
|
+
usage: "canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--add-qualified-alias <name>...] [--remove-qualified-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--all-providers] [--provider-model provider=model...] [--clear-provider-model <provider>...] [--dispatch-mode provider=sync|batch...] [--clear-dispatch-mode <provider>...] [--negative-review-max-stars <1-4|default>] [--site-audit-max-pages <1-50000|full>] [--competitor-auto-aliases <off|preview|apply>] [--format json]",
|
|
14624
|
+
options: {
|
|
14625
|
+
domain: { type: "string", short: "d" },
|
|
14626
|
+
"owned-domain": multiStringOption(),
|
|
14627
|
+
"add-domain": multiStringOption(),
|
|
14628
|
+
"remove-domain": multiStringOption(),
|
|
14629
|
+
alias: multiStringOption(),
|
|
14630
|
+
"add-alias": multiStringOption(),
|
|
14631
|
+
"remove-alias": multiStringOption(),
|
|
14632
|
+
"add-qualified-alias": multiStringOption(),
|
|
14633
|
+
"remove-qualified-alias": multiStringOption(),
|
|
14634
|
+
country: stringOption(),
|
|
14635
|
+
language: stringOption(),
|
|
14636
|
+
"display-name": stringOption(),
|
|
14637
|
+
provider: multiStringOption(),
|
|
14638
|
+
"all-providers": { type: "boolean" },
|
|
14639
|
+
"provider-model": multiStringOption(),
|
|
14640
|
+
"clear-provider-model": multiStringOption(),
|
|
14641
|
+
"dispatch-mode": multiStringOption(),
|
|
14642
|
+
"clear-dispatch-mode": multiStringOption(),
|
|
14643
|
+
"negative-review-max-stars": stringOption(),
|
|
14644
|
+
"site-audit-max-pages": stringOption(),
|
|
14645
|
+
"competitor-auto-aliases": stringOption()
|
|
14646
|
+
},
|
|
14647
|
+
run: async (input) => {
|
|
14648
|
+
const name = requireProject(
|
|
14649
|
+
input,
|
|
14650
|
+
"project.update",
|
|
14651
|
+
"canonry project update <name> [--domain <domain>] [--owned-domain <domain>...] [--add-domain <domain>...] [--remove-domain <domain>...] [--alias <name>...] [--add-alias <name>...] [--remove-alias <name>...] [--add-qualified-alias <name>...] [--remove-qualified-alias <name>...] [--country <code>] [--language <lang>] [--display-name <name>] [--provider <name>...] [--all-providers] [--provider-model provider=model...] [--clear-provider-model <provider>...] [--dispatch-mode provider=sync|batch...] [--clear-dispatch-mode <provider>...] [--negative-review-max-stars <1-4|default>] [--site-audit-max-pages <1-50000|full>] [--competitor-auto-aliases <off|preview|apply>] [--format json]"
|
|
14652
|
+
);
|
|
14653
|
+
const providers = getStringArray(input.values, "provider");
|
|
14654
|
+
const allProviders = getBoolean(input.values, "all-providers");
|
|
14655
|
+
if (allProviders && providers?.length) throw usageError("Error: --all-providers conflicts with --provider");
|
|
14656
|
+
const providerModels = parseProviderModelAssignments(getStringArray(input.values, "provider-model"));
|
|
14657
|
+
const clearProviderModels = getStringArray(input.values, "clear-provider-model") ?? [];
|
|
14658
|
+
for (const provider of clearProviderModels) {
|
|
14659
|
+
if (provider in providerModels) throw usageError(`Error: --provider-model and --clear-provider-model conflict for ${provider}`);
|
|
14660
|
+
}
|
|
14661
|
+
const dispatchModes = parseDispatchModeAssignments(getStringArray(input.values, "dispatch-mode"));
|
|
14662
|
+
const clearDispatchModes = getStringArray(input.values, "clear-dispatch-mode") ?? [];
|
|
14663
|
+
for (const provider of clearDispatchModes) {
|
|
14664
|
+
if (provider in dispatchModes) throw usageError(`Error: --dispatch-mode and --clear-dispatch-mode conflict for ${provider}`);
|
|
14665
|
+
}
|
|
14666
|
+
await updateProjectSettings(name, {
|
|
14667
|
+
displayName: getString(input.values, "display-name"),
|
|
14668
|
+
domain: getString(input.values, "domain"),
|
|
14669
|
+
ownedDomains: getStringArray(input.values, "owned-domain"),
|
|
14670
|
+
addOwnedDomain: getStringArray(input.values, "add-domain"),
|
|
14671
|
+
removeOwnedDomain: getStringArray(input.values, "remove-domain"),
|
|
14672
|
+
aliases: getStringArray(input.values, "alias"),
|
|
14673
|
+
addAlias: getStringArray(input.values, "add-alias"),
|
|
14674
|
+
removeAlias: getStringArray(input.values, "remove-alias"),
|
|
14675
|
+
addQualifiedAlias: getStringArray(input.values, "add-qualified-alias"),
|
|
14676
|
+
removeQualifiedAlias: getStringArray(input.values, "remove-qualified-alias"),
|
|
14677
|
+
country: getString(input.values, "country"),
|
|
14678
|
+
language: getString(input.values, "language"),
|
|
14679
|
+
providers: allProviders ? [] : providers,
|
|
14680
|
+
providerModels,
|
|
14681
|
+
clearProviderModels,
|
|
14682
|
+
dispatchModes,
|
|
14683
|
+
clearDispatchModes,
|
|
14684
|
+
negativeReviewMaxStars: parseNegativeReviewMaxStars(getString(input.values, "negative-review-max-stars")),
|
|
14685
|
+
siteAuditMaxPages: parseSiteAuditMaxPages(getString(input.values, "site-audit-max-pages"), "project.update"),
|
|
14686
|
+
competitorAutoAliases: parseCompetitorAutoAliasMode(getString(input.values, "competitor-auto-aliases")),
|
|
14687
|
+
format: input.format
|
|
14688
|
+
});
|
|
14701
14689
|
}
|
|
14702
|
-
}
|
|
14703
|
-
|
|
14704
|
-
|
|
14705
|
-
|
|
14706
|
-
|
|
14707
|
-
|
|
14708
|
-
|
|
14709
|
-
|
|
14710
|
-
|
|
14711
|
-
|
|
14712
|
-
|
|
14713
|
-
|
|
14714
|
-
|
|
14715
|
-
|
|
14716
|
-
|
|
14717
|
-
|
|
14718
|
-
|
|
14719
|
-
|
|
14720
|
-
|
|
14721
|
-
|
|
14722
|
-
|
|
14723
|
-
|
|
14724
|
-
|
|
14725
|
-
|
|
14726
|
-
|
|
14690
|
+
},
|
|
14691
|
+
{
|
|
14692
|
+
path: ["project", "list"],
|
|
14693
|
+
usage: "canonry project list [--format json]",
|
|
14694
|
+
allowPositionals: false,
|
|
14695
|
+
run: async (input) => {
|
|
14696
|
+
await listProjects(input.format);
|
|
14697
|
+
}
|
|
14698
|
+
},
|
|
14699
|
+
{
|
|
14700
|
+
path: ["project", "show"],
|
|
14701
|
+
usage: "canonry project show <name> [--format json]",
|
|
14702
|
+
run: async (input) => {
|
|
14703
|
+
const name = requireProject(input, "project.show", "canonry project show <name>");
|
|
14704
|
+
await showProject(name, input.format);
|
|
14705
|
+
}
|
|
14706
|
+
},
|
|
14707
|
+
{
|
|
14708
|
+
path: ["project", "delete"],
|
|
14709
|
+
usage: "canonry project delete <name> [--dry-run] [--format json]",
|
|
14710
|
+
supportsDryRun: true,
|
|
14711
|
+
run: async (input) => {
|
|
14712
|
+
const name = requireProject(input, "project.delete", "canonry project delete <name> [--dry-run] [--format json]");
|
|
14713
|
+
await deleteProject(name, { dryRun: input.dryRun, format: input.format });
|
|
14714
|
+
}
|
|
14715
|
+
},
|
|
14716
|
+
{
|
|
14717
|
+
path: ["project", "add-location"],
|
|
14718
|
+
usage: "canonry project add-location <name> --label <label> --city <city> --region <region> --country <country> [--format json]",
|
|
14719
|
+
options: {
|
|
14720
|
+
label: stringOption(),
|
|
14721
|
+
city: stringOption(),
|
|
14722
|
+
region: stringOption(),
|
|
14723
|
+
country: stringOption(),
|
|
14724
|
+
timezone: stringOption()
|
|
14725
|
+
},
|
|
14726
|
+
run: async (input) => {
|
|
14727
|
+
const name = requireProject(
|
|
14728
|
+
input,
|
|
14729
|
+
"project.add-location",
|
|
14730
|
+
"canonry project add-location <name> --label <label> --city <city> --region <region> --country <country> [--format json]"
|
|
14731
|
+
);
|
|
14732
|
+
const label = getString(input.values, "label");
|
|
14733
|
+
const city = getString(input.values, "city");
|
|
14734
|
+
const region = getString(input.values, "region");
|
|
14735
|
+
const country = getString(input.values, "country");
|
|
14736
|
+
if (!label || !city || !region || !country) {
|
|
14737
|
+
throw usageError("Error: --label, --city, --region, and --country are all required", {
|
|
14738
|
+
message: "location label, city, region, and country are required",
|
|
14739
|
+
details: {
|
|
14740
|
+
command: "project.add-location",
|
|
14741
|
+
usage: "canonry project add-location <name> --label <label> --city <city> --region <region> --country <country> [--format json]",
|
|
14742
|
+
required: ["label", "city", "region", "country"]
|
|
14743
|
+
}
|
|
14744
|
+
});
|
|
14727
14745
|
}
|
|
14746
|
+
await addLocation(name, {
|
|
14747
|
+
label,
|
|
14748
|
+
city,
|
|
14749
|
+
region,
|
|
14750
|
+
country,
|
|
14751
|
+
timezone: getString(input.values, "timezone"),
|
|
14752
|
+
format: input.format
|
|
14753
|
+
});
|
|
14754
|
+
}
|
|
14755
|
+
},
|
|
14756
|
+
{
|
|
14757
|
+
path: ["project", "locations"],
|
|
14758
|
+
usage: "canonry project locations <name> [--format json]",
|
|
14759
|
+
run: async (input) => {
|
|
14760
|
+
const name = requireProject(input, "project.locations", "canonry project locations <name> [--format json]");
|
|
14761
|
+
await listLocations(name, input.format);
|
|
14762
|
+
}
|
|
14763
|
+
},
|
|
14764
|
+
{
|
|
14765
|
+
path: ["project", "remove-location"],
|
|
14766
|
+
usage: "canonry project remove-location <name> <label> [--format json]",
|
|
14767
|
+
run: async (input) => {
|
|
14768
|
+
const name = requireProject(input, "project.remove-location", "canonry project remove-location <name> <label> [--format json]");
|
|
14769
|
+
const label = requirePositional(input, 1, {
|
|
14770
|
+
command: "project.remove-location",
|
|
14771
|
+
usage: "canonry project remove-location <name> <label> [--format json]",
|
|
14772
|
+
message: "project name and location label are required"
|
|
14773
|
+
});
|
|
14774
|
+
await removeLocation(name, label, input.format);
|
|
14775
|
+
}
|
|
14776
|
+
},
|
|
14777
|
+
{
|
|
14778
|
+
path: ["project", "set-default-location"],
|
|
14779
|
+
usage: "canonry project set-default-location <name> <label> [--format json]",
|
|
14780
|
+
run: async (input) => {
|
|
14781
|
+
const name = requireProject(input, "project.set-default-location", "canonry project set-default-location <name> <label> [--format json]");
|
|
14782
|
+
const label = requirePositional(input, 1, {
|
|
14783
|
+
command: "project.set-default-location",
|
|
14784
|
+
usage: "canonry project set-default-location <name> <label> [--format json]",
|
|
14785
|
+
message: "project name and location label are required"
|
|
14786
|
+
});
|
|
14787
|
+
await setDefaultLocation(name, label, input.format);
|
|
14788
|
+
}
|
|
14789
|
+
},
|
|
14790
|
+
{
|
|
14791
|
+
path: ["project"],
|
|
14792
|
+
usage: "canonry project <create|update|list|show|delete|add-location|locations|remove-location|set-default-location> [args]",
|
|
14793
|
+
run: async (input) => {
|
|
14794
|
+
unknownSubcommand(input.positionals[0], {
|
|
14795
|
+
command: "project",
|
|
14796
|
+
usage: "canonry project <create|update|list|show|delete|add-location|locations|remove-location|set-default-location> [args]",
|
|
14797
|
+
available: ["create", "update", "list", "show", "delete", "add-location", "locations", "remove-location", "set-default-location"]
|
|
14798
|
+
});
|
|
14728
14799
|
}
|
|
14729
14800
|
}
|
|
14730
|
-
|
|
14731
|
-
|
|
14732
|
-
|
|
14733
|
-
|
|
14734
|
-
|
|
14801
|
+
];
|
|
14802
|
+
function parseDispatchModeAssignments(assignments) {
|
|
14803
|
+
const result = {};
|
|
14804
|
+
for (const assignment of assignments ?? []) {
|
|
14805
|
+
const separator = assignment.indexOf("=");
|
|
14806
|
+
const provider = separator === -1 ? "" : assignment.slice(0, separator).trim();
|
|
14807
|
+
const mode = providerDispatchModeSchema.safeParse(separator === -1 ? "" : assignment.slice(separator + 1).trim().toLowerCase());
|
|
14808
|
+
if (!provider || !mode.success) throw usageError(`Error: --dispatch-mode must use provider=sync or provider=batch (received ${assignment})`);
|
|
14809
|
+
if (provider in result) throw usageError(`Error: duplicate --dispatch-mode assignment for ${provider}`);
|
|
14810
|
+
result[provider] = mode.data;
|
|
14735
14811
|
}
|
|
14736
|
-
|
|
14737
|
-
|
|
14738
|
-
|
|
14739
|
-
|
|
14740
|
-
|
|
14741
|
-
|
|
14742
|
-
|
|
14743
|
-
|
|
14744
|
-
|
|
14745
|
-
}
|
|
14812
|
+
return result;
|
|
14813
|
+
}
|
|
14814
|
+
function parseProviderModelAssignments(assignments) {
|
|
14815
|
+
const result = {};
|
|
14816
|
+
for (const assignment of assignments ?? []) {
|
|
14817
|
+
const separator = assignment.indexOf("=");
|
|
14818
|
+
const provider = separator === -1 ? "" : assignment.slice(0, separator).trim();
|
|
14819
|
+
const model = separator === -1 ? "" : assignment.slice(separator + 1).trim();
|
|
14820
|
+
if (!provider || !model) throw usageError(`Error: --provider-model must use provider=model (received ${assignment})`);
|
|
14821
|
+
if (provider in result) throw usageError(`Error: duplicate --provider-model assignment for ${provider}`);
|
|
14822
|
+
result[provider] = model;
|
|
14746
14823
|
}
|
|
14824
|
+
return result;
|
|
14747
14825
|
}
|
|
14748
|
-
function
|
|
14749
|
-
|
|
14750
|
-
|
|
14751
|
-
|
|
14752
|
-
|
|
14753
|
-
|
|
14754
|
-
|
|
14755
|
-
|
|
14756
|
-
|
|
14757
|
-
return `waiting on provider batch: ${batch.provider} \u2014 ${batch.requestCount} requests, submitted ${batch.submittedAt ?? "unknown"}, deadline ${batch.deadlineAt}`;
|
|
14758
|
-
case ProviderBatchStatuses.ingested:
|
|
14759
|
-
return withError(`provider batch ${batch.provider}: ingested \u2014 ${recorded}`);
|
|
14760
|
-
case ProviderBatchStatuses.cancelled:
|
|
14761
|
-
return withError(`provider batch ${batch.provider}: cancelled \u2014 ${recorded}; the rest are missing and can be filled`);
|
|
14762
|
-
case ProviderBatchStatuses.failed:
|
|
14763
|
-
return withError(`provider batch ${batch.provider}: rejected by the provider \u2014 its ${batch.requestCount} answers ran sync instead`);
|
|
14764
|
-
case ProviderBatchStatuses.unknown:
|
|
14765
|
-
return withError(`provider batch ${batch.provider}: submit outcome unknown \u2014 never resubmitted; its ${batch.requestCount} answers stay missing`);
|
|
14826
|
+
function parseNegativeReviewMaxStars(raw) {
|
|
14827
|
+
if (raw === void 0) return void 0;
|
|
14828
|
+
if (raw === "default") return null;
|
|
14829
|
+
const parsed = gbpNegativeReviewMaxStarsSchema.safeParse(Number(raw));
|
|
14830
|
+
if (!parsed.success) {
|
|
14831
|
+
throw usageError('Error: --negative-review-max-stars must be 1, 2, 3, 4, or "default" (3)', {
|
|
14832
|
+
message: '--negative-review-max-stars must be 1-4 or "default"',
|
|
14833
|
+
details: { command: "project.update", value: raw }
|
|
14834
|
+
});
|
|
14766
14835
|
}
|
|
14836
|
+
return parsed.data;
|
|
14767
14837
|
}
|
|
14768
|
-
function
|
|
14769
|
-
|
|
14770
|
-
const
|
|
14771
|
-
|
|
14772
|
-
|
|
14773
|
-
|
|
14774
|
-
|
|
14775
|
-
|
|
14776
|
-
|
|
14777
|
-
|
|
14778
|
-
|
|
14779
|
-
|
|
14780
|
-
]);
|
|
14781
|
-
const header = ["PROVIDER", "TIER", "ANSWERS", "INPUT", "CACHED", "CACHE WRITE", "OUTPUT", "SEARCHES", "EST. COST"];
|
|
14782
|
-
const widths = header.map((title, index) => Math.max(title.length, ...table.map((cells) => cells[index].length)));
|
|
14783
|
-
const line = (cells) => ` ${cells.map((cell2, index) => cell2.padEnd(widths[index])).join(" ").trimEnd()}`;
|
|
14784
|
-
console.log("\n Usage (answers with recorded usage; cost is an estimate):");
|
|
14785
|
-
console.log(line(header));
|
|
14786
|
-
for (const cells of table) console.log(line(cells));
|
|
14838
|
+
function parseSiteAuditMaxPages(raw, command) {
|
|
14839
|
+
if (raw === void 0) return void 0;
|
|
14840
|
+
const value = raw.trim();
|
|
14841
|
+
if (value.toLowerCase() === "full") return null;
|
|
14842
|
+
const parsed = /^\d+$/.test(value) ? siteAuditPageBudgetSchema.safeParse(Number(value)) : void 0;
|
|
14843
|
+
if (!parsed?.success) {
|
|
14844
|
+
throw usageError(`Error: --site-audit-max-pages must be a whole number of pages from 1 to ${SITE_AUDIT_MAX_PAGE_LIMIT}, or "full" for the full site`, {
|
|
14845
|
+
message: `--site-audit-max-pages must be 1-${SITE_AUDIT_MAX_PAGE_LIMIT} or "full"`,
|
|
14846
|
+
details: { command, option: "site-audit-max-pages", value: raw }
|
|
14847
|
+
});
|
|
14848
|
+
}
|
|
14849
|
+
return parsed.data;
|
|
14787
14850
|
}
|
|
14788
|
-
|
|
14789
|
-
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14851
|
+
function parseCompetitorAutoAliasMode(raw) {
|
|
14852
|
+
if (raw === void 0) return void 0;
|
|
14853
|
+
const parsed = competitorAutoAliasModeSchema.safeParse(raw.trim().toLowerCase());
|
|
14854
|
+
if (!parsed.success) {
|
|
14855
|
+
throw usageError("Error: --competitor-auto-aliases must be off, preview or apply", {
|
|
14856
|
+
message: "--competitor-auto-aliases must be off, preview or apply",
|
|
14857
|
+
details: { command: "project.update", option: "competitor-auto-aliases", value: raw }
|
|
14858
|
+
});
|
|
14859
|
+
}
|
|
14860
|
+
return parsed.data;
|
|
14793
14861
|
}
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
|
|
14862
|
+
|
|
14863
|
+
// src/commands/organic-evidence.ts
|
|
14864
|
+
async function showOrganicEvidence(project2, opts) {
|
|
14865
|
+
const data = await createApiClient().getOrganicEvidence(project2, opts.period);
|
|
14866
|
+
if (isMachineFormat(opts.format)) {
|
|
14867
|
+
console.log(JSON.stringify(data, null, 2));
|
|
14799
14868
|
return;
|
|
14800
14869
|
}
|
|
14801
|
-
|
|
14802
|
-
|
|
14803
|
-
|
|
14804
|
-
|
|
14805
|
-
|
|
14870
|
+
printOrganicEvidence(project2, data);
|
|
14871
|
+
}
|
|
14872
|
+
function formatCounts(clicks, impressions) {
|
|
14873
|
+
return `${clicks} clicks / ${impressions} impressions`;
|
|
14874
|
+
}
|
|
14875
|
+
function printOrganicEvidence(project2, data) {
|
|
14876
|
+
const asOf = data.asOfDate ? ` through ${data.asOfDate}` : "";
|
|
14877
|
+
console.log(`Organic evidence: ${project2} (${data.periodDays} days${asOf})`);
|
|
14878
|
+
console.log(`Coverage: GSC ${data.coverage.gsc ? "yes" : "no"} \xB7 GA4 ${data.coverage.ga4 ? "yes" : "no"} \xB7 server ${data.coverage.server ? "yes" : "no"} \xB7 AI visibility ${data.coverage.visibility ? "yes" : "no"}`);
|
|
14879
|
+
if (data.gsc) {
|
|
14880
|
+
console.log(`Google Search: ${formatCounts(data.gsc.propertyTotals.clicks, data.gsc.propertyTotals.impressions)} (named non-brand: ${formatCounts(data.gsc.namedNonBrand.clicks, data.gsc.namedNonBrand.impressions)})`);
|
|
14806
14881
|
}
|
|
14807
|
-
if (
|
|
14808
|
-
|
|
14809
|
-
|
|
14810
|
-
|
|
14811
|
-
|
|
14882
|
+
if (data.ga4) console.log(`GA4 organic: ${data.ga4.organicSessions} sessions`);
|
|
14883
|
+
if (data.gaAiReferrals) console.log(`GA4 AI referrals: ${data.gaAiReferrals.organicSessions} organic, ${data.gaAiReferrals.paidSessions} paid sessions`);
|
|
14884
|
+
if (data.server) {
|
|
14885
|
+
const s = data.server;
|
|
14886
|
+
const fetchTotal = s.userFetchHits.verified + s.userFetchHits.claimedUnverified + s.userFetchHits.unknownAiLike;
|
|
14887
|
+
console.log(`Server AI: ${s.crawlerHits.verified} verified + ${s.crawlerHits.claimedUnverified} claimed + ${s.crawlerHits.unknownAiLike} heuristic crawls; ${fetchTotal} user-agent fetches (${s.userFetchHits.verified} verified, ${s.userFetchHits.claimedUnverified} claimed, ${s.userFetchHits.unknownAiLike} heuristic); ${s.referralSessions.organic} organic referrals, ${s.referralSessions.paid} paid, ${s.referralSessions.unknown} unclassified`);
|
|
14812
14888
|
}
|
|
14813
|
-
|
|
14814
|
-
|
|
14815
|
-
|
|
14816
|
-
if (isMachineFormat(format)) {
|
|
14817
|
-
console.log(JSON.stringify(completeness, null, 2));
|
|
14818
|
-
return;
|
|
14889
|
+
if (data.visibility) {
|
|
14890
|
+
const v = data.visibility;
|
|
14891
|
+
console.log(`Latest AI visibility: ${v.mentionedPairs}/${v.answerPairs} mentioned, ${v.citedPairs}/${v.answerPairs} cited (${Math.floor(v.ageDays)}d old)`);
|
|
14819
14892
|
}
|
|
14820
|
-
|
|
14821
|
-
|
|
14822
|
-
|
|
14823
|
-
const client = getClient23();
|
|
14824
|
-
const response = await client.fillRun(runId, {
|
|
14825
|
-
...opts.providers?.length ? { providers: opts.providers } : {},
|
|
14826
|
-
...opts.dryRun ? { dryRun: true } : {}
|
|
14827
|
-
});
|
|
14828
|
-
if (!opts.wait || response.outcome !== "queued" || !response.fill) {
|
|
14829
|
-
if (isMachineFormat(opts.format)) {
|
|
14830
|
-
console.log(JSON.stringify(response, null, 2));
|
|
14831
|
-
return;
|
|
14832
|
-
}
|
|
14833
|
-
if (response.outcome === "already-complete") console.log(`Run ${runId} is already complete: nothing to fill.`);
|
|
14834
|
-
else if (response.outcome === "dry-run") console.log("Dry run: nothing was queued.");
|
|
14835
|
-
else if (response.fill) console.log(`Fill ${response.fill.id} queued: ${response.fill.expected} missing answer(s) (${missingSummary(response.completeness)}).`);
|
|
14836
|
-
printCompleteness(response.completeness);
|
|
14837
|
-
return;
|
|
14893
|
+
if (data.findings.length > 0) {
|
|
14894
|
+
console.log("\nFindings:");
|
|
14895
|
+
for (const finding of data.findings) console.log(`- ${finding.title}: ${finding.detail}`);
|
|
14838
14896
|
}
|
|
14839
|
-
|
|
14840
|
-
|
|
14841
|
-
|
|
14842
|
-
if (!isMachineFormat(opts.format)) {
|
|
14843
|
-
process.stderr.write(`Filling ${response.fill.expected} missing answer(s) in run ${runId}`);
|
|
14897
|
+
if (data.limitations.length > 0) {
|
|
14898
|
+
console.log("\nLimitations:");
|
|
14899
|
+
for (const limitation of data.limitations) console.log(`- ${limitation.detail}`);
|
|
14844
14900
|
}
|
|
14845
|
-
|
|
14846
|
-
|
|
14847
|
-
|
|
14848
|
-
|
|
14849
|
-
|
|
14850
|
-
|
|
14851
|
-
|
|
14852
|
-
|
|
14901
|
+
}
|
|
14902
|
+
|
|
14903
|
+
// src/cli-commands/organic-evidence.ts
|
|
14904
|
+
var USAGE3 = "canonry organic-evidence <project> [--period 60|90] [--format json|jsonl]";
|
|
14905
|
+
function parsePeriod(value) {
|
|
14906
|
+
if (value === void 0) return void 0;
|
|
14907
|
+
if (value === "60" || value === "90") return Number(value);
|
|
14908
|
+
throw usageError(`Error: --period must be 60 or 90
|
|
14909
|
+
|
|
14910
|
+
Usage: ${USAGE3}`);
|
|
14911
|
+
}
|
|
14912
|
+
var ORGANIC_EVIDENCE_CLI_COMMANDS = [
|
|
14913
|
+
{
|
|
14914
|
+
path: ["organic-evidence"],
|
|
14915
|
+
usage: USAGE3,
|
|
14916
|
+
options: { period: stringOption() },
|
|
14917
|
+
run: async (input) => {
|
|
14918
|
+
const project2 = requireProject(input, "organic-evidence", USAGE3);
|
|
14919
|
+
await showOrganicEvidence(project2, {
|
|
14920
|
+
period: parsePeriod(getString(input.values, "period")),
|
|
14921
|
+
format: input.format
|
|
14853
14922
|
});
|
|
14854
14923
|
}
|
|
14855
|
-
completeness = await client.getRunCompleteness(runId);
|
|
14856
|
-
if (!isMachineFormat(opts.format)) process.stderr.write(".");
|
|
14857
|
-
const latest = completeness.latestFill;
|
|
14858
|
-
const working = latest !== null && (latest.status === "queued" || latest.status === "running");
|
|
14859
|
-
if (completeness.status === "completed" || !working) break;
|
|
14860
|
-
}
|
|
14861
|
-
if (!isMachineFormat(opts.format)) process.stderr.write("\n");
|
|
14862
|
-
if (isMachineFormat(opts.format)) console.log(JSON.stringify(completeness, null, 2));
|
|
14863
|
-
else printCompleteness(completeness);
|
|
14864
|
-
if (completeness.status !== "completed") {
|
|
14865
|
-
throw new CliError({
|
|
14866
|
-
code: "RUN_FILL_INCOMPLETE",
|
|
14867
|
-
message: `Run ${runId} is still ${completeness.status}: ${completeness.missing} answer(s) missing (${missingSummary(completeness)}).`,
|
|
14868
|
-
exitCode: EXIT_SYSTEM_ERROR,
|
|
14869
|
-
details: { runId, fillId, missing: completeness.missing, missingByProvider: completeness.missingByProvider }
|
|
14870
|
-
});
|
|
14871
14924
|
}
|
|
14872
|
-
|
|
14925
|
+
];
|
|
14873
14926
|
|
|
14874
14927
|
// src/cli-commands/run.ts
|
|
14875
14928
|
var RUN_TRIGGER_USAGE = "canonry run trigger <project> [--group <key>]... [--target <key>]... [--provider <name>] [--query <q>...] [--location <label>] [--all-locations] [--no-location] [--probe] [--dispatch-mode sync|batch] [--force] [--wait] [--format json]";
|
|
@@ -16364,6 +16417,9 @@ function renderHuman2(overview) {
|
|
|
16364
16417
|
} else {
|
|
16365
16418
|
console.log("\n No runs yet.");
|
|
16366
16419
|
}
|
|
16420
|
+
const admission = runAdmissionLines(meta.name, latestRun.admission, latestRun.manualAdmission);
|
|
16421
|
+
if (admission.length > 0) console.log(`
|
|
16422
|
+
${admission.map((line) => ` ${line}`).join("\n")}`);
|
|
16367
16423
|
console.log("\nScores:");
|
|
16368
16424
|
printScore("Mention ", scores.mention);
|
|
16369
16425
|
printScore("Visibility ", scores.visibility);
|