@ainyc/canonry 4.190.1 → 4.192.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/SKILL.md +124 -216
- package/assets/assets/{AuditHistoryPanel-Dob_85ay.js → AuditHistoryPanel-C7MwfCl9.js} +1 -1
- package/assets/assets/{BacklinksPage-BDm4gQsa.js → BacklinksPage-1Nq1zbRk.js} +1 -1
- package/assets/assets/{ChartPrimitives-DLEICrrL.js → ChartPrimitives-Bxa_vFmq.js} +1 -1
- package/assets/assets/{HistoryPage-BUGJKNWJ.js → HistoryPage-BNACyEiz.js} +1 -1
- package/assets/assets/MeasurementPropertyPage-BqjpIYAw.js +1 -0
- package/assets/assets/ProjectPage-BhBzznlv.js +8 -0
- package/assets/assets/{RunRow-Cja3vahD.js → RunRow-DINPo5ES.js} +1 -1
- package/assets/assets/{RunsPage-BdVfLW9Z.js → RunsPage-BFuDLm7p.js} +1 -1
- package/assets/assets/{SettingsPage-Xw9rdDPd.js → SettingsPage-CkqmyTSK.js} +1 -1
- package/assets/assets/SiteHealthSection-DzFKj9lX.js +4 -0
- package/assets/assets/{TrafficPage-D5ZqJrJm.js → TrafficPage-CkIGVLET.js} +1 -1
- package/assets/assets/{TrafficSourceDetailPage-DB5V8cdH.js → TrafficSourceDetailPage-CTmn_FmZ.js} +1 -1
- package/assets/assets/{extract-error-message-lzuarNEg.js → extract-error-message-CC2kxd3e.js} +1 -1
- package/assets/assets/{index-BjjM151x.css → index-BtTEcbcB.css} +1 -1
- package/assets/assets/{index-YVHujfy1.js → index-CCPMPGar.js} +26 -26
- package/assets/assets/{react-sigma_core.esm.min-Ddu-c40B.js → react-sigma_core.esm.min-DkpQa1sf.js} +1 -1
- package/assets/assets/{vendor-lucide-dNHw1I5G.js → vendor-lucide-CW2qe0uH.js} +1 -1
- package/assets/assets/{vendor-markdown-9tTeP8v7.js → vendor-markdown-B-Oq2NYq.js} +1 -1
- package/assets/assets/{vendor-radix-BpmvLvpp.js → vendor-radix-twS1M3FF.js} +1 -1
- package/assets/assets/{vendor-recharts-FA0uySU7.js → vendor-recharts-D-cUuLZo.js} +1 -1
- package/assets/assets/{vendor-tanstack-CYBjbqVr.js → vendor-tanstack-ZXXkndVj.js} +1 -1
- package/assets/index.html +7 -7
- package/dist/{chunk-2II4ZMMH.js → chunk-32Q2NZ4S.js} +2 -2
- package/dist/{chunk-32QBVRMN.js → chunk-36RKISAI.js} +213 -32
- package/dist/{chunk-ROPJUNQN.js → chunk-BULKBROE.js} +1368 -1223
- package/dist/{chunk-ECPN4OUR.js → chunk-FTKZAOFD.js} +649 -249
- package/dist/{chunk-BE366UCM.js → chunk-JOSSB4QC.js} +73 -22
- package/dist/cli.js +65 -10
- package/dist/index.js +4 -4
- package/dist/{intelligence-service-QCKX2W4G.js → intelligence-service-MEQ5VX5L.js} +2 -2
- package/dist/mcp.js +13 -8
- package/package.json +11 -11
- package/assets/assets/MeasurementPropertyPage-H2g4VdcA.js +0 -1
- package/assets/assets/ProjectPage-d7iOSkrd.js +0 -8
- package/assets/assets/SiteHealthSection-CjioXMWy.js +0 -4
|
@@ -14,7 +14,7 @@ import {
|
|
|
14
14
|
loadConfig,
|
|
15
15
|
loadConfigRaw,
|
|
16
16
|
saveConfigPatch
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-36RKISAI.js";
|
|
18
18
|
import {
|
|
19
19
|
CC_CACHE_DIR,
|
|
20
20
|
DUCKDB_SPEC,
|
|
@@ -162,7 +162,7 @@ import {
|
|
|
162
162
|
siteCrawlSnapshots,
|
|
163
163
|
toAlertView,
|
|
164
164
|
usageCounters
|
|
165
|
-
} from "./chunk-
|
|
165
|
+
} from "./chunk-FTKZAOFD.js";
|
|
166
166
|
import {
|
|
167
167
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
168
168
|
AGENT_PROVIDER_IDS,
|
|
@@ -305,7 +305,7 @@ import {
|
|
|
305
305
|
validationError,
|
|
306
306
|
winnabilityClassLabel,
|
|
307
307
|
withRetry
|
|
308
|
-
} from "./chunk-
|
|
308
|
+
} from "./chunk-BULKBROE.js";
|
|
309
309
|
|
|
310
310
|
// src/telemetry.ts
|
|
311
311
|
import crypto from "crypto";
|
|
@@ -12041,7 +12041,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
12041
12041
|
return result;
|
|
12042
12042
|
}
|
|
12043
12043
|
async function backfillInsightsCommand(project, opts) {
|
|
12044
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
12044
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-MEQ5VX5L.js");
|
|
12045
12045
|
const config = loadConfig();
|
|
12046
12046
|
const db = createClient(config.database);
|
|
12047
12047
|
migrate(db);
|
|
@@ -12623,7 +12623,7 @@ var RETRY_MS = 60 * 1e3;
|
|
|
12623
12623
|
var TIMEOUT_MS2 = 3e3;
|
|
12624
12624
|
function createProviderModelCatalog(registry) {
|
|
12625
12625
|
const cache = /* @__PURE__ */ new Map();
|
|
12626
|
-
|
|
12626
|
+
const read = async (name) => {
|
|
12627
12627
|
const provider = registry.get(name);
|
|
12628
12628
|
if (!provider) {
|
|
12629
12629
|
cache.delete(name);
|
|
@@ -12666,6 +12666,14 @@ function createProviderModelCatalog(registry) {
|
|
|
12666
12666
|
});
|
|
12667
12667
|
return current.pending;
|
|
12668
12668
|
};
|
|
12669
|
+
const cached = (name) => {
|
|
12670
|
+
const provider = registry.get(name);
|
|
12671
|
+
if (!provider) return [];
|
|
12672
|
+
const entry = cache.get(name);
|
|
12673
|
+
const identity = createHash("sha256").update(JSON.stringify(provider.config)).digest("hex");
|
|
12674
|
+
return (entry?.identity === identity ? entry.models : void 0) ?? provider.adapter.modelRegistry.knownModels;
|
|
12675
|
+
};
|
|
12676
|
+
return Object.assign(read, { cached });
|
|
12669
12677
|
}
|
|
12670
12678
|
|
|
12671
12679
|
// src/scheduler.ts
|
|
@@ -15703,6 +15711,7 @@ ${lines.join("\n")}
|
|
|
15703
15711
|
import crypto26 from "crypto";
|
|
15704
15712
|
import { eq as eq22 } from "drizzle-orm";
|
|
15705
15713
|
import { StreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/streamableHttp.js";
|
|
15714
|
+
import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
|
|
15706
15715
|
var SESSION_IDLE_MS = 10 * 60 * 1e3;
|
|
15707
15716
|
var SWEEP_INTERVAL_MS = 60 * 1e3;
|
|
15708
15717
|
function mintSessionKey(db, scopes, userId) {
|
|
@@ -15714,6 +15723,7 @@ function mintSessionKey(db, scopes, userId) {
|
|
|
15714
15723
|
keyHash: hashApiKey(raw),
|
|
15715
15724
|
keyPrefix: raw.slice(0, 9),
|
|
15716
15725
|
scopes: [...scopes],
|
|
15726
|
+
delegatedUserId: userId,
|
|
15717
15727
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
15718
15728
|
}).run();
|
|
15719
15729
|
return { id, raw };
|
|
@@ -15777,7 +15787,7 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
15777
15787
|
if (trimmed.slice(0, prefix.length).toLowerCase() !== prefix) return null;
|
|
15778
15788
|
return trimmed.slice(prefix.length).trim() || null;
|
|
15779
15789
|
}
|
|
15780
|
-
async function openSession(request,
|
|
15790
|
+
async function openSession(request, authorizationId, segment) {
|
|
15781
15791
|
const bearer = callerBearer(request);
|
|
15782
15792
|
if (!bearer) return null;
|
|
15783
15793
|
const scopes = request.principal?.scopes ?? request.apiKey?.scopes ?? [];
|
|
@@ -15786,40 +15796,64 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
15786
15796
|
sessionKey = mintSessionKey(opts.db, scopes, request.principal.id);
|
|
15787
15797
|
}
|
|
15788
15798
|
const client = new ApiClient(opts.selfApiUrl, sessionKey?.raw ?? bearer, { skipProbe: true });
|
|
15789
|
-
|
|
15790
|
-
|
|
15791
|
-
|
|
15792
|
-
|
|
15793
|
-
|
|
15799
|
+
let server;
|
|
15800
|
+
try {
|
|
15801
|
+
server = createCanonryMcpServer({
|
|
15802
|
+
scope: segment.readOnly || isReadOnlyKey(scopes) ? "read-only" : "all",
|
|
15803
|
+
credentialScopes: scopes,
|
|
15804
|
+
tiers: segment.tiers,
|
|
15805
|
+
clientFactory: () => client
|
|
15806
|
+
});
|
|
15807
|
+
} catch (error) {
|
|
15808
|
+
if (sessionKey) revokeSessionKey(opts.db, sessionKey.id);
|
|
15809
|
+
throw error;
|
|
15810
|
+
}
|
|
15794
15811
|
const transport = new StreamableHTTPServerTransport({
|
|
15795
15812
|
sessionIdGenerator: () => crypto26.randomUUID(),
|
|
15796
15813
|
onsessioninitialized: (id) => {
|
|
15797
|
-
sessions.set(id, { transport, close,
|
|
15814
|
+
sessions.set(id, { transport, close, authorizationId, segmentId: segment.id, lastSeenAt: now() });
|
|
15798
15815
|
}
|
|
15799
15816
|
});
|
|
15800
15817
|
async function close() {
|
|
15801
|
-
await transport.close();
|
|
15802
|
-
await server.close();
|
|
15803
15818
|
if (sessionKey) revokeSessionKey(opts.db, sessionKey.id);
|
|
15819
|
+
try {
|
|
15820
|
+
await transport.close();
|
|
15821
|
+
} finally {
|
|
15822
|
+
await server.close();
|
|
15823
|
+
}
|
|
15804
15824
|
}
|
|
15805
15825
|
transport.onclose = () => {
|
|
15806
15826
|
const id = transport.sessionId;
|
|
15807
15827
|
if (id) sessions.delete(id);
|
|
15808
15828
|
if (sessionKey) revokeSessionKey(opts.db, sessionKey.id);
|
|
15809
15829
|
};
|
|
15810
|
-
|
|
15811
|
-
|
|
15830
|
+
try {
|
|
15831
|
+
await server.connect(transport);
|
|
15832
|
+
} catch (error) {
|
|
15833
|
+
await close().catch(() => {
|
|
15834
|
+
});
|
|
15835
|
+
throw error;
|
|
15836
|
+
}
|
|
15837
|
+
return { transport, close, authorizationId, segmentId: segment.id, lastSeenAt: now() };
|
|
15812
15838
|
}
|
|
15813
15839
|
async function handle(request, reply, segment) {
|
|
15814
|
-
const
|
|
15815
|
-
|
|
15840
|
+
const principal = request.principal ?? request.apiKey;
|
|
15841
|
+
const bearer = callerBearer(request);
|
|
15842
|
+
if (!principal || !bearer) {
|
|
15816
15843
|
await reply.status(401).send({ error: { code: "AUTH_REQUIRED", message: "Authentication required." } });
|
|
15817
15844
|
return;
|
|
15818
15845
|
}
|
|
15846
|
+
const authorizationId = hashApiKey(JSON.stringify({
|
|
15847
|
+
bearerHash: hashApiKey(bearer),
|
|
15848
|
+
kind: request.principal?.kind ?? "api-key",
|
|
15849
|
+
id: principal.id,
|
|
15850
|
+
scopes: [...new Set(principal.scopes)].sort(),
|
|
15851
|
+
projectId: principal.projectId ?? null
|
|
15852
|
+
}));
|
|
15819
15853
|
const sessionId = request.headers["mcp-session-id"];
|
|
15820
15854
|
const existing = typeof sessionId === "string" ? sessions.get(sessionId) : void 0;
|
|
15821
15855
|
if (existing) {
|
|
15822
|
-
if (existing.
|
|
15856
|
+
if (existing.authorizationId !== authorizationId || existing.segmentId !== segment.id) {
|
|
15823
15857
|
await reply.status(404).send({ error: { code: "NOT_FOUND", message: "Unknown MCP session." } });
|
|
15824
15858
|
return;
|
|
15825
15859
|
}
|
|
@@ -15831,12 +15865,27 @@ function registerMcpHttpRoutes(scope, opts) {
|
|
|
15831
15865
|
await reply.status(404).send({ error: { code: "NOT_FOUND", message: "Unknown MCP session." } });
|
|
15832
15866
|
return;
|
|
15833
15867
|
}
|
|
15834
|
-
|
|
15868
|
+
if (request.method !== "POST" || !isInitializeRequest(request.body)) {
|
|
15869
|
+
await reply.status(400).send({ jsonrpc: "2.0", error: { code: -32e3, message: "Bad Request: Expected an initialize request." }, id: null });
|
|
15870
|
+
return;
|
|
15871
|
+
}
|
|
15872
|
+
const opened = await openSession(request, authorizationId, segment);
|
|
15835
15873
|
if (!opened) {
|
|
15836
15874
|
await reply.status(401).send({ error: { code: "AUTH_REQUIRED", message: "Authentication required." } });
|
|
15837
15875
|
return;
|
|
15838
15876
|
}
|
|
15839
|
-
|
|
15877
|
+
let handled = false;
|
|
15878
|
+
try {
|
|
15879
|
+
await opened.transport.handleRequest(request.raw, reply.raw, request.body);
|
|
15880
|
+
handled = true;
|
|
15881
|
+
} finally {
|
|
15882
|
+
const openedId = opened.transport.sessionId;
|
|
15883
|
+
if (!handled || reply.raw.statusCode >= 400 || openedId === void 0 || !sessions.has(openedId)) {
|
|
15884
|
+
if (openedId !== void 0) sessions.delete(openedId);
|
|
15885
|
+
await opened.close().catch(() => {
|
|
15886
|
+
});
|
|
15887
|
+
}
|
|
15888
|
+
}
|
|
15840
15889
|
}
|
|
15841
15890
|
const config = { transportEnvelope: true };
|
|
15842
15891
|
function mount(pathname, segment) {
|
|
@@ -18657,6 +18706,7 @@ async function createServer(opts) {
|
|
|
18657
18706
|
app.log.error({ runId, err }, "Research run failed");
|
|
18658
18707
|
});
|
|
18659
18708
|
};
|
|
18709
|
+
const providerModelCatalog = createProviderModelCatalog(registry);
|
|
18660
18710
|
await app.register(apiRoutes, {
|
|
18661
18711
|
db: opts.db,
|
|
18662
18712
|
routePrefix: apiPrefix,
|
|
@@ -18974,7 +19024,8 @@ async function createServer(opts) {
|
|
|
18974
19024
|
includeCanonryLocal: true
|
|
18975
19025
|
},
|
|
18976
19026
|
providerSummary,
|
|
18977
|
-
getProviderModels:
|
|
19027
|
+
getProviderModels: providerModelCatalog,
|
|
19028
|
+
getCachedProviderModels: providerModelCatalog.cached,
|
|
18978
19029
|
providerAdapters: [...API_ADAPTERS, ...BROWSER_ADAPTERS].map((a) => ({
|
|
18979
19030
|
name: a.name,
|
|
18980
19031
|
displayName: a.displayName,
|
package/dist/cli.js
CHANGED
|
@@ -28,11 +28,11 @@ import {
|
|
|
28
28
|
trackCliCommandFinished,
|
|
29
29
|
trackEvent,
|
|
30
30
|
waitForServerRuntimeStartup
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JOSSB4QC.js";
|
|
32
32
|
import {
|
|
33
33
|
autoSyncSkills,
|
|
34
34
|
formatAutoSyncNotice
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-32Q2NZ4S.js";
|
|
36
36
|
import {
|
|
37
37
|
CliError,
|
|
38
38
|
EXIT_SYSTEM_ERROR,
|
|
@@ -58,7 +58,7 @@ import {
|
|
|
58
58
|
saveConfigPatch,
|
|
59
59
|
systemError,
|
|
60
60
|
usageError
|
|
61
|
-
} from "./chunk-
|
|
61
|
+
} from "./chunk-36RKISAI.js";
|
|
62
62
|
import {
|
|
63
63
|
CLOUDFLARE_WORKER_BINDINGS,
|
|
64
64
|
CLOUDFLARE_WORKER_GENERATED_MARKER,
|
|
@@ -72,7 +72,7 @@ import {
|
|
|
72
72
|
projects,
|
|
73
73
|
queries,
|
|
74
74
|
renderReportHtml
|
|
75
|
-
} from "./chunk-
|
|
75
|
+
} from "./chunk-FTKZAOFD.js";
|
|
76
76
|
import {
|
|
77
77
|
AdsDeliverySnapshotStatuses,
|
|
78
78
|
AdsHistoricalCampaignRollupStatuses,
|
|
@@ -141,13 +141,14 @@ import {
|
|
|
141
141
|
notificationEventSchema,
|
|
142
142
|
queryTrackingCommitRequestSchema,
|
|
143
143
|
queryTrackingPreviewRequestSchema,
|
|
144
|
+
researchBatchCreateSchema,
|
|
144
145
|
resolveProviderInput,
|
|
145
146
|
resultsClearRequestSchema,
|
|
146
147
|
shareOfVoiceLabel,
|
|
147
148
|
shareOfVoiceReason,
|
|
148
149
|
visibilityReportRequestSchema,
|
|
149
150
|
winnabilityClassSchema
|
|
150
|
-
} from "./chunk-
|
|
151
|
+
} from "./chunk-BULKBROE.js";
|
|
151
152
|
|
|
152
153
|
// src/cli.ts
|
|
153
154
|
import { pathToFileURL } from "url";
|
|
@@ -2599,6 +2600,7 @@ var DISCOVER_CLI_COMMANDS = [
|
|
|
2599
2600
|
];
|
|
2600
2601
|
|
|
2601
2602
|
// src/commands/research.ts
|
|
2603
|
+
import { readFileSync } from "fs";
|
|
2602
2604
|
var TERMINAL_RESEARCH_STATUSES = /* @__PURE__ */ new Set([
|
|
2603
2605
|
ResearchRunStatuses.completed,
|
|
2604
2606
|
ResearchRunStatuses.partial,
|
|
@@ -2636,11 +2638,45 @@ async function researchRun(project, opts) {
|
|
|
2636
2638
|
const detail = await pollResearchRun(client, project, started.id, isMachineFormat(opts.format));
|
|
2637
2639
|
printDetail(project, detail, opts.format);
|
|
2638
2640
|
}
|
|
2641
|
+
async function researchBatch(project, source, opts) {
|
|
2642
|
+
let raw;
|
|
2643
|
+
try {
|
|
2644
|
+
raw = JSON.parse(readFileSync(source === "-" ? 0 : source, "utf8"));
|
|
2645
|
+
} catch {
|
|
2646
|
+
throw usageError("Research batch input must be a readable JSON file (or - for stdin).", {
|
|
2647
|
+
message: "Research batch input must be a readable JSON file (or - for stdin).",
|
|
2648
|
+
details: { command: "research.batch" }
|
|
2649
|
+
});
|
|
2650
|
+
}
|
|
2651
|
+
const parsed = researchBatchCreateSchema.safeParse(raw);
|
|
2652
|
+
if (!parsed.success) {
|
|
2653
|
+
const message = parsed.error.issues.map((issue) => `${issue.path.join(".")}: ${issue.message}`).join("; ");
|
|
2654
|
+
throw usageError(`Invalid research batch: ${message}`, {
|
|
2655
|
+
message: `Invalid research batch: ${message}`,
|
|
2656
|
+
details: { command: "research.batch" }
|
|
2657
|
+
});
|
|
2658
|
+
}
|
|
2659
|
+
const client = getClient5();
|
|
2660
|
+
const started = await client.startResearchBatch(project, parsed.data);
|
|
2661
|
+
const runs = opts.wait ? await Promise.all(started.runs.map((run) => TERMINAL_RESEARCH_STATUSES.has(run.status) ? Promise.resolve(run) : pollResearchRun(client, project, run.id, true))) : started.runs;
|
|
2662
|
+
if (opts.format === "jsonl") {
|
|
2663
|
+
emitJsonl(runs.map((run) => ({ project, ...run })));
|
|
2664
|
+
} else if (opts.format === "json") {
|
|
2665
|
+
console.log(JSON.stringify({ runs }, null, 2));
|
|
2666
|
+
} else {
|
|
2667
|
+
console.log(`Research saved for ${runs.length} destinations (${runs.reduce((count, run) => count + run.totalQueries, 0)} queries).`);
|
|
2668
|
+
for (const run of runs) {
|
|
2669
|
+
if (opts.wait) printDetail(project, run);
|
|
2670
|
+
else printStarted(project, run);
|
|
2671
|
+
}
|
|
2672
|
+
}
|
|
2673
|
+
}
|
|
2639
2674
|
async function researchList(project, opts) {
|
|
2640
2675
|
const client = getClient5();
|
|
2641
|
-
const
|
|
2676
|
+
const result = await client.listResearchRuns(project, opts.limit === void 0 && opts.cursor === void 0 ? void 0 : { limit: opts.limit, cursor: opts.cursor });
|
|
2677
|
+
const { runs } = result;
|
|
2642
2678
|
if (opts.format === "json") {
|
|
2643
|
-
console.log(JSON.stringify(
|
|
2679
|
+
console.log(JSON.stringify(result, null, 2));
|
|
2644
2680
|
return;
|
|
2645
2681
|
}
|
|
2646
2682
|
if (opts.format === "jsonl") {
|
|
@@ -2659,6 +2695,8 @@ async function researchList(project, opts) {
|
|
|
2659
2695
|
const model = run.resolvedModel ? `${run.provider} / ${run.resolvedModel}` : run.provider;
|
|
2660
2696
|
console.log(` ${run.id.padEnd(36)} ${run.status.padEnd(10)} ${String(run.totalQueries).padStart(7)} ${model.slice(0, 32).padEnd(32)} ${formatScope(run.scope).slice(0, 24).padEnd(24)} ${run.createdAt}`);
|
|
2661
2697
|
}
|
|
2698
|
+
if (result.nextCursor) console.log(`
|
|
2699
|
+
Next page: canonry research list ${project} --cursor ${result.nextCursor}`);
|
|
2662
2700
|
}
|
|
2663
2701
|
async function researchShow(project, runId, opts) {
|
|
2664
2702
|
const detail = await getClient5().getResearchRun(project, runId);
|
|
@@ -2688,6 +2726,7 @@ function printStarted(project, run) {
|
|
|
2688
2726
|
console.log(` Status: ${run.status}`);
|
|
2689
2727
|
console.log(` Queries: ${run.totalQueries}`);
|
|
2690
2728
|
console.log(` Scope: ${formatScope(run.scope)}`);
|
|
2729
|
+
console.log(` Location: ${run.location?.label ?? "No location context"}`);
|
|
2691
2730
|
console.log(` Provider: ${run.provider}${run.resolvedModel ? ` / ${run.resolvedModel}` : ""}`);
|
|
2692
2731
|
console.log(` Inspect: canonry research show ${project} ${run.id}`);
|
|
2693
2732
|
console.log(" Nothing was added to tracked queries.");
|
|
@@ -2737,6 +2776,7 @@ function formatScope(scope) {
|
|
|
2737
2776
|
|
|
2738
2777
|
// src/cli-commands/research.ts
|
|
2739
2778
|
var RUN_USAGE = "canonry research run <project> <query...> [--query <text>] [--provider <name>] [--model <id>] [--market <key>|--property <key>] [--template-id <id> --template-version <version>] [--location <label>|--no-location] [--idempotency-key <key>] [--wait] [--format json|jsonl]";
|
|
2779
|
+
var BATCH_USAGE = "canonry research batch <project> <json-file|-> [--wait] [--format json|jsonl]";
|
|
2740
2780
|
function normalizeQueries(input, usage) {
|
|
2741
2781
|
const positional = input.positionals.slice(1);
|
|
2742
2782
|
const flagged = getStringArray(input.values, "query") ?? [];
|
|
@@ -2814,6 +2854,20 @@ Usage: ${usage}`, {
|
|
|
2814
2854
|
return templateId && templateVersion ? { templateId, templateVersion } : void 0;
|
|
2815
2855
|
}
|
|
2816
2856
|
var RESEARCH_CLI_COMMANDS = [
|
|
2857
|
+
{
|
|
2858
|
+
path: ["research", "batch"],
|
|
2859
|
+
usage: BATCH_USAGE,
|
|
2860
|
+
options: { wait: { type: "boolean", default: false } },
|
|
2861
|
+
run: async (input) => {
|
|
2862
|
+
const project = requireProject(input, "research.batch", BATCH_USAGE);
|
|
2863
|
+
const source = requirePositional(input, 1, {
|
|
2864
|
+
command: "research.batch",
|
|
2865
|
+
usage: BATCH_USAGE,
|
|
2866
|
+
message: "reviewed research batch JSON file is required"
|
|
2867
|
+
});
|
|
2868
|
+
await researchBatch(project, source, { wait: getBoolean(input.values, "wait"), format: input.format });
|
|
2869
|
+
}
|
|
2870
|
+
},
|
|
2817
2871
|
{
|
|
2818
2872
|
path: ["research", "run"],
|
|
2819
2873
|
usage: RUN_USAGE,
|
|
@@ -2856,13 +2910,14 @@ Usage: ${RUN_USAGE}`, {
|
|
|
2856
2910
|
},
|
|
2857
2911
|
{
|
|
2858
2912
|
path: ["research", "list"],
|
|
2859
|
-
usage: "canonry research list <project> [--limit <n>] [--format json|jsonl]",
|
|
2860
|
-
options: { limit: stringOption() },
|
|
2913
|
+
usage: "canonry research list <project> [--limit <n>] [--cursor <cursor>] [--format json|jsonl]",
|
|
2914
|
+
options: { limit: stringOption(), cursor: stringOption() },
|
|
2861
2915
|
run: async (input) => {
|
|
2862
|
-
const usage = "canonry research list <project> [--limit <n>] [--format json|jsonl]";
|
|
2916
|
+
const usage = "canonry research list <project> [--limit <n>] [--cursor <cursor>] [--format json|jsonl]";
|
|
2863
2917
|
const project = requireProject(input, "research.list", usage);
|
|
2864
2918
|
await researchList(project, {
|
|
2865
2919
|
limit: parseResearchLimit(input, usage),
|
|
2920
|
+
cursor: getString(input.values, "cursor"),
|
|
2866
2921
|
format: input.format
|
|
2867
2922
|
});
|
|
2868
2923
|
}
|
package/dist/index.js
CHANGED
|
@@ -3,12 +3,12 @@ import {
|
|
|
3
3
|
createGoogleMarketingCredentialStore,
|
|
4
4
|
createGoogleMarketingRuntime,
|
|
5
5
|
createServer
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-JOSSB4QC.js";
|
|
7
7
|
import {
|
|
8
8
|
loadConfig
|
|
9
|
-
} from "./chunk-
|
|
10
|
-
import "./chunk-
|
|
11
|
-
import "./chunk-
|
|
9
|
+
} from "./chunk-36RKISAI.js";
|
|
10
|
+
import "./chunk-FTKZAOFD.js";
|
|
11
|
+
import "./chunk-BULKBROE.js";
|
|
12
12
|
export {
|
|
13
13
|
GoogleMarketingRuntimeError,
|
|
14
14
|
createGoogleMarketingCredentialStore,
|
package/dist/mcp.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import {
|
|
2
2
|
autoSyncSkills
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-32Q2NZ4S.js";
|
|
4
4
|
import {
|
|
5
5
|
createApiClient,
|
|
6
6
|
createCanonryMcpServer
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-36RKISAI.js";
|
|
8
8
|
import {
|
|
9
9
|
isReadOnlyKey
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-BULKBROE.js";
|
|
11
11
|
|
|
12
12
|
// src/mcp/cli.ts
|
|
13
13
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -46,23 +46,27 @@ async function main(argv = process.argv.slice(2)) {
|
|
|
46
46
|
}
|
|
47
47
|
void autoSyncSkills();
|
|
48
48
|
const client = createApiClient();
|
|
49
|
-
const
|
|
50
|
-
const server = createCanonryMcpServer({
|
|
49
|
+
const authorization = await resolveEffectiveAuthorization(client, options.scope);
|
|
50
|
+
const server = createCanonryMcpServer({ ...authorization, eager: options.eager, clientFactory: () => client });
|
|
51
51
|
await server.connect(new StdioServerTransport());
|
|
52
52
|
}
|
|
53
53
|
async function resolveEffectiveScope(client, flagScope) {
|
|
54
|
-
|
|
54
|
+
return (await resolveEffectiveAuthorization(client, flagScope)).scope;
|
|
55
|
+
}
|
|
56
|
+
async function resolveEffectiveAuthorization(client, flagScope) {
|
|
57
|
+
if (flagScope === "read-only") return { scope: "read-only" };
|
|
55
58
|
try {
|
|
56
59
|
const self = await client.getApiKeySelf();
|
|
57
60
|
if (isReadOnlyKey(self.scopes)) {
|
|
58
61
|
process.stderr.write(
|
|
59
62
|
"canonry-mcp: configured API key is read-only \u2014 restricting to read tools.\n"
|
|
60
63
|
);
|
|
61
|
-
return "read-only";
|
|
64
|
+
return { scope: "read-only", credentialScopes: self.scopes };
|
|
62
65
|
}
|
|
66
|
+
return { scope: flagScope, credentialScopes: self.scopes };
|
|
63
67
|
} catch {
|
|
64
68
|
}
|
|
65
|
-
return flagScope;
|
|
69
|
+
return { scope: flagScope };
|
|
66
70
|
}
|
|
67
71
|
function parseCliOptions(argv, env = process.env) {
|
|
68
72
|
if (argv.includes("--help") || argv.includes("-h")) {
|
|
@@ -118,5 +122,6 @@ export {
|
|
|
118
122
|
HelpRequested,
|
|
119
123
|
main,
|
|
120
124
|
parseCliOptions,
|
|
125
|
+
resolveEffectiveAuthorization,
|
|
121
126
|
resolveEffectiveScope
|
|
122
127
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ainyc/canonry",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.192.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Self-hosted AI visibility (AEO) platform: track how ChatGPT, Claude, Gemini, and Perplexity cite your domain, join it with Search Console, GA4, server-side traffic, and paid media, and fix what you find through agent tools (CLI, REST, MCP). Local SQLite.",
|
|
6
6
|
"license": "FSL-1.1-ALv2",
|
|
@@ -70,32 +70,32 @@
|
|
|
70
70
|
"@types/node-cron": "^3.0.11",
|
|
71
71
|
"tsup": "^8.5.1",
|
|
72
72
|
"tsx": "^4.19.0",
|
|
73
|
-
"@ainyc/canonry-web": "0.0.0",
|
|
74
73
|
"@ainyc/canonry-api-client": "0.0.0",
|
|
75
|
-
"@ainyc/canonry-
|
|
74
|
+
"@ainyc/canonry-web": "0.0.0",
|
|
76
75
|
"@ainyc/canonry-api-routes": "0.0.0",
|
|
77
76
|
"@ainyc/canonry-contracts": "0.0.0",
|
|
77
|
+
"@ainyc/canonry-config": "0.0.0",
|
|
78
78
|
"@ainyc/canonry-db": "0.0.0",
|
|
79
79
|
"@ainyc/canonry-integration-bing": "0.0.0",
|
|
80
80
|
"@ainyc/canonry-integration-cloud-run": "0.0.0",
|
|
81
|
-
"@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
|
|
82
81
|
"@ainyc/canonry-integration-cloudflare-worker": "0.0.0",
|
|
82
|
+
"@ainyc/canonry-integration-cloudflare-queue": "0.0.0",
|
|
83
83
|
"@ainyc/canonry-integration-commoncrawl": "0.0.0",
|
|
84
84
|
"@ainyc/canonry-integration-google": "0.0.0",
|
|
85
|
-
"@ainyc/canonry-integration-google-ads": "0.0.0",
|
|
86
85
|
"@ainyc/canonry-integration-google-business-profile": "0.0.0",
|
|
87
86
|
"@ainyc/canonry-integration-google-places": "0.0.0",
|
|
88
87
|
"@ainyc/canonry-integration-google-tag-manager": "0.0.0",
|
|
89
88
|
"@ainyc/canonry-integration-openai-ads": "0.0.0",
|
|
90
|
-
"@ainyc/canonry-integration-
|
|
89
|
+
"@ainyc/canonry-integration-google-ads": "0.0.0",
|
|
91
90
|
"@ainyc/canonry-integration-traffic": "0.0.0",
|
|
92
|
-
"@ainyc/canonry-
|
|
93
|
-
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
91
|
+
"@ainyc/canonry-integration-wordpress": "0.0.0",
|
|
94
92
|
"@ainyc/canonry-intelligence": "0.0.0",
|
|
95
|
-
"@ainyc/canonry-provider-
|
|
96
|
-
"@ainyc/canonry-provider-
|
|
93
|
+
"@ainyc/canonry-provider-cdp": "0.0.0",
|
|
94
|
+
"@ainyc/canonry-provider-claude": "0.0.0",
|
|
95
|
+
"@ainyc/canonry-provider-perplexity": "0.0.0",
|
|
97
96
|
"@ainyc/canonry-provider-openai": "0.0.0",
|
|
98
|
-
"@ainyc/canonry-provider-
|
|
97
|
+
"@ainyc/canonry-provider-local": "0.0.0",
|
|
98
|
+
"@ainyc/canonry-provider-gemini": "0.0.0"
|
|
99
99
|
},
|
|
100
100
|
"scripts": {
|
|
101
101
|
"build": "pnpm run build:cli && pnpm run build:web",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
import{C as le,r as w,u as E,D as de,j as e,L as A}from"./vendor-tanstack-CYBjbqVr.js";import{aB as ce,cq as he,dA as me,i as L,dB as xe,cy as pe,c9 as ue,B as j,T as N,av as K,aw as D,I as P,dC as je,dD as ye,dE as be}from"./index-YVHujfy1.js";import{j as ve,o as ge}from"./ChartPrimitives-DLEICrrL.js";import{m as Ne}from"./v2-overview-adapter-COMP5UGt.js";import{I as fe}from"./vendor-lucide-dNHw1I5G.js";import"./vendor-radix-BpmvLvpp.js";import"./vendor-recharts-FA0uySU7.js";import"./vendor-markdown-9tTeP8v7.js";import"./vendor-yaml-GWJz5H2G.js";const W=50,h={branded:{headline:"When they know your name",technical:"Branded queries"},"non-brand":{headline:"When they don't",technical:"Non-brand queries"}},we={plan_v1:"Setup update required",no_completed_run:"No completed measurement yet",no_population:"No queries of this type are assigned",evidence_incomplete:"Source evidence is incomplete",not_applicable:"Not applicable for this Property"},H={complete:{label:"Measured",tone:"positive"},partial:{label:"Partly measured",tone:"caution"},running:{label:"Measuring now",tone:"neutral"},queued:{label:"Measurement queued",tone:"neutral"},failed:{label:"Measurement failed",tone:"negative"},not_measured:{label:"Not measured",tone:"neutral"}},G={assigned:{label:"Matches this Property",tone:"positive"},sibling:{label:"Matches another Property",tone:"caution"},ownedUnmapped:{label:"Site URL not in a Property",tone:"caution"},external:{label:"External URL",tone:"neutral"},ambiguous:{label:"Matches multiple Properties",tone:"caution"},invalid:{label:"Invalid URL",tone:"negative"}};function Pe(s){return[...s].sort((t,a)=>+(a.classification==="assigned")-+(t.classification==="assigned"))}function Ce(s){return`${s.expectedSlotId}:${s.usageEdgeId}`}function Ee({row:s}){return s.mentioned===null?e.jsxs("span",{className:"inline-flex flex-col items-start gap-0.5",children:[e.jsx(N,{tone:"neutral",children:"Not measured"}),e.jsx("span",{className:"text-xs text-muted",children:"No mention signal for this Property"})]}):e.jsx(N,{tone:s.mentioned?"positive":"neutral",children:s.mentioned?"Mentioned":"Not mentioned"})}const Le="—";function Me({row:s}){return s.cited===null?e.jsxs("span",{className:"inline-flex flex-col items-start gap-0.5",children:[e.jsx(N,{tone:"neutral",children:"Not measured"}),e.jsx("span",{className:"text-xs text-muted",children:"Sources were not fully captured"})]}):e.jsx(N,{tone:s.cited?"positive":"neutral",children:s.cited?"Cited":"Not cited"})}function Re({row:s}){return s.cited===null&&s.sources.length===0?e.jsx("p",{className:"py-2 text-sm text-secondary",children:"The sources for this answer were not fully captured, so none can be shown."}):s.sources.length===0?e.jsx("p",{className:"py-2 text-sm text-secondary",children:"This answer returned no source URLs at all."}):e.jsx("div",{className:"overflow-x-auto",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsxs("caption",{className:"sr-only",children:["Source URLs for ",s.queryText]}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Match"}),e.jsx("th",{children:"URL"})]})}),e.jsx("tbody",{children:Pe(s.sources).map(t=>e.jsxs("tr",{children:[e.jsx("td",{children:e.jsx(N,{tone:G[t.classification].tone,children:G[t.classification].label})}),e.jsx("td",{className:"break-all text-secondary",children:t.sourceUrl})]},t.sourceUrl))})]})})}function Se({project:s,targetKey:t,row:a}){const n=E({...be({client:L,path:{name:s},query:{targetKey:t,resultId:a.observationId}})});if(n.isPending)return e.jsx("p",{className:"py-2 text-sm text-secondary",role:"status","aria-live":"polite",children:"Loading the answer…"});if(n.isError)return e.jsx("p",{className:"py-2 text-sm text-secondary",role:"alert",children:"Could not load this answer."});const o=n.data?.answer;return o==null||o.trim()===""?e.jsx("p",{className:"py-2 text-sm text-secondary",children:"This answer was measured, but its text was not stored."}):e.jsxs("div",{className:"py-2",children:[e.jsxs("h4",{className:"mb-1 text-xs font-medium tracking-wide text-muted uppercase",children:["What ",a.provider," answered"]}),e.jsx("p",{className:"max-h-64 overflow-y-auto whitespace-pre-wrap text-sm leading-relaxed text-secondary",children:o})]})}function q(s){return we[s.reason]??"Not measured"}function T({metric:s,emphasis:t=!1}){if(s.state==="unavailable")return e.jsxs("span",{className:"inline-flex flex-col gap-0.5",children:[e.jsx("span",{className:t?"text-lg font-semibold text-secondary":"text-sm font-medium text-secondary",children:"Not measured"}),e.jsx("span",{className:"text-sm text-secondary",children:q(s)})]});const a=`${Math.round(s.value*100)}%`,n=s.numerator===void 0||s.denominator===void 0?null:`${s.numerator} of ${s.denominator}`;return e.jsxs("span",{className:"inline-flex flex-col gap-0.5 tabular-nums",children:[e.jsx("span",{className:t?"text-lg font-semibold text-heading":"text-sm font-medium text-primary",children:a}),n?e.jsx("span",{className:"text-xs text-muted",children:n}):null]})}function Y(s,t,a){return je({client:L,path:{name:s},query:{scope:"property",targetKey:t,queryClass:a}})}function Z(s){return s?.properties.items.at(0)}function Ae({measuredAt:s,unmeasuredReason:t,queryClass:a}){const n=s===void 0?null:s===null?"No completed sweep yet":`Measured ${ve(ge(s))}`;return n===null&&t===void 0?null:e.jsxs("p",{className:"supporting-copy",children:[[n,t].filter(Boolean).join(" · "),n!==null&&t===void 0?` · ${h[a].technical.toLocaleLowerCase()} only`:""]})}function Te({project:s,groups:t}){return t.length===0?null:e.jsxs("section",{"aria-labelledby":"property-market",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Competitive comparison"}),e.jsxs("h2",{id:"property-market",className:"text-base font-semibold text-heading",children:["Measured at the market level",e.jsx(P,{text:"Competitors are attached to a market rather than to a single Property, because one building has nobody to be compared against. Share of voice and competitor pressure are reported for the market this Property sits in."})]})]}),e.jsx(j,{asChild:!0,type:"button",size:"sm",variant:"outline",children:e.jsx(A,{to:"/projects/$projectName",params:{projectName:s},children:"Open measurement overview"})})]}),e.jsx("ul",{className:"flex flex-wrap gap-2",children:t.map(a=>e.jsxs("li",{className:"rounded-md border border-default px-3 py-1.5 text-sm text-strong",children:[a.label,e.jsxs("span",{className:"ml-2 text-xs text-muted",children:[a.competitors.length," ",a.competitors.length===1?"competitor":"competitors"]})]},a.stableKey))})]})}function S({label:s,metric:t,failed:a=!1}){if(t===void 0)return e.jsxs("div",{className:"aeo-hero-row",children:[e.jsx("p",{className:"aeo-hero-row-label",children:s}),e.jsx("p",{className:"aeo-hero-row-value text-base font-semibold text-secondary",children:a?"Unavailable":"…"}),a?e.jsx("div",{}):e.jsx("div",{className:"aeo-hero-row-bar","aria-hidden":"true"}),e.jsx("p",{className:"aeo-hero-row-detail",children:a?"Could not be loaded":"Loading"})]});if(t.state==="unavailable"){const m=q(t);return e.jsxs("div",{className:"aeo-hero-row",children:[e.jsx("p",{className:"aeo-hero-row-label",children:s}),e.jsx("p",{className:"aeo-hero-row-value text-base font-semibold text-secondary",children:"Not measured"}),e.jsx("div",{}),e.jsx("p",{className:"aeo-hero-row-detail",children:m==="Not measured"?"":m})]})}const n=Math.round(t.value*100),o=t.numerator===void 0||t.denominator===void 0?null:`${t.numerator} of ${t.denominator}`;return e.jsxs("div",{className:"aeo-hero-row",children:[e.jsx("p",{className:"aeo-hero-row-label",children:s}),e.jsxs("p",{className:"aeo-hero-row-value text-heading",children:[n,e.jsx("span",{className:"text-faint",children:"%"})]}),e.jsx("div",{className:"aeo-hero-row-bar","aria-hidden":"true",children:e.jsx("div",{className:"metric-card-bar-fill progress-fill-neutral",style:{width:`${n}%`}})}),e.jsx("p",{className:"aeo-hero-row-detail tabular-nums",children:o??""})]})}function qe({branded:s,nonBrand:t,brandedFailed:a=!1,nonBrandFailed:n=!1}){return e.jsxs("section",{"aria-labelledby":"property-coverage-hero",children:[e.jsx("h2",{id:"property-coverage-hero",className:"sr-only",children:"Coverage for this Property"}),e.jsxs("div",{className:"space-y-5",children:[e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Non-brand · the demand to earn"}),e.jsx(S,{label:"Mentioned",metric:t?.mentionCoverage,failed:n}),e.jsx(S,{label:"Cited",metric:t?.citationCoverage,failed:n})]}),e.jsxs("div",{className:"space-y-2",children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"Branded · already named"}),e.jsx(S,{label:"Mentioned",metric:s?.mentionCoverage,failed:a}),e.jsx(S,{label:"Cited",metric:s?.citationCoverage,failed:a})]})]})]})}function ke({branded:s,nonBrand:t,brandedError:a,nonBrandError:n,onRetry:o}){const m=[{queryClass:"branded",row:s,isError:a},{queryClass:"non-brand",row:t,isError:n}];return e.jsxs("section",{"aria-labelledby":"property-brand-contrast",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsxs("div",{children:[e.jsx("p",{className:"eyebrow eyebrow-soft",children:"The gap"}),e.jsxs("h2",{id:"property-brand-contrast",className:"text-base font-semibold text-heading",children:["Named versus not named",e.jsx(P,{text:"Branded queries already contain your name, so an answer engine has an easy path back to you. Non-brand queries describe the need instead, and that is the demand you have to earn. Each row is measured only over the queries assigned to this Property in that class; a class with no assigned query reads Not measured rather than 0%."})]})]})}),e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[560px]",children:[e.jsx("caption",{className:"sr-only",children:"Mention and citation coverage for this Property, split by query class"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Query type"}),e.jsx("th",{children:"Mentioned in the answer"}),e.jsx("th",{children:"Cited as a source"})]})}),e.jsx("tbody",{children:m.map(({queryClass:r,row:l,isError:y})=>e.jsxs("tr",{children:[e.jsxs("td",{children:[e.jsx("span",{className:"block font-medium text-heading",children:h[r].headline}),e.jsx("span",{className:"mt-0.5 block text-xs text-muted",children:h[r].technical}),l&&y?e.jsxs("span",{className:"mt-2 flex flex-wrap items-center gap-2 text-sm text-caution",children:[e.jsx("span",{role:"status",children:"Refresh failed."}),e.jsxs(j,{type:"button",size:"sm",variant:"ghost",className:"h-11 px-3 text-sm md:h-11",onClick:()=>o(r),children:["Retry ",h[r].technical.toLocaleLowerCase()]})]}):null]}),l?e.jsxs(e.Fragment,{children:[e.jsx("td",{children:e.jsx(T,{metric:l.mentionCoverage,emphasis:!0})}),e.jsx("td",{children:e.jsx(T,{metric:l.citationCoverage,emphasis:!0})})]}):y?e.jsx("td",{colSpan:2,children:e.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-sm text-secondary",children:[e.jsxs("span",{role:"alert",children:["Could not load ",h[r].technical.toLocaleLowerCase(),"."]}),e.jsxs(j,{type:"button",size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",onClick:()=>o(r),children:["Retry ",h[r].technical.toLocaleLowerCase()]})]})}):e.jsxs(e.Fragment,{children:[e.jsx("td",{children:e.jsx("span",{className:"text-sm text-secondary",children:"Loading…"})}),e.jsx("td",{children:e.jsx("span",{className:"text-sm text-secondary",children:"Loading…"})})]})]},r))})]})})]})}function Be({row:s,queryClass:t,isError:a}){return e.jsxs("section",{"aria-labelledby":"property-providers",className:"page-section-divider",children:[e.jsx("div",{className:"section-head section-head-inline",children:e.jsx("div",{children:e.jsxs("h2",{id:"property-providers",className:"text-base font-semibold text-heading",children:["Which engines answer for this Property",e.jsx(P,{text:"Each row is measured over the queries that engine actually answered for this Property, so the rows are a split of the same population rather than parts that add up to the Property total. An engine that answered nothing for this Property is absent instead of shown at 0%."})]})})}),s===void 0&&a?e.jsxs("p",{className:"text-sm text-secondary",children:["Details for ",h[t].technical.toLocaleLowerCase()," are unavailable. Retry that query type above."]}):s===void 0?e.jsx("p",{className:"text-sm text-secondary",children:"Loading…"}):s.providers.length===0?e.jsxs("p",{className:"text-sm text-secondary",children:["No answer engine has measured ",h[t].technical.toLocaleLowerCase()," for this Property.",s.mentionCoverage.state==="unavailable"?` ${q(s.mentionCoverage)}.`:""]}):e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[520px]",children:[e.jsx("caption",{className:"sr-only",children:"Per-engine mention and citation coverage"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Engine"}),e.jsx("th",{children:"Mentioned"}),e.jsx("th",{children:"Cited"})]})}),e.jsx("tbody",{children:s.providers.map(n=>e.jsxs("tr",{children:[e.jsx("td",{className:"font-medium text-heading",children:n.provider}),e.jsx("td",{children:e.jsx(T,{metric:n.mentionCoverage})}),e.jsx("td",{children:e.jsx(T,{metric:n.citationCoverage})})]},n.provider))})]})})]})}function Ue(s){const t=s.questions.join(" · ");return s.questionsTruncated?`${t} +${s.questionTotal-s.questions.length} more`:t}function Ie({project:s,targetKey:t,queryClass:a}){const n=E({...ye({client:L,path:{name:s},query:{targetKey:t,queryClass:a}})});if(n.isPending||n.isError)return null;const o=n.data?.competitors??[],m=n.data?.basis;return e.jsxs("section",{"aria-labelledby":"property-named-instead",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-named-instead",className:"text-base font-semibold text-heading",children:["Named instead of this Property",e.jsx(P,{text:"Counted from the answers that did not name this Property, so a name here is one an engine recommended in its place. Occurrences count answers, not positions: an engine naming the same rival in two answers counts twice, and one naming it twice in a single answer counts once."})]})}),o.length>0?e.jsxs("p",{className:"supporting-copy",children:[n.data?.total??o.length," named"]}):null]}),o.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"No rival was named in the answers this Property missed. Nothing to compete with here yet."}):e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsx("caption",{className:"sr-only",children:"Names the engines gave instead of this Property"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Named"}),e.jsx("th",{children:"Answers"}),e.jsx("th",{children:"Engines"}),e.jsx("th",{children:"Queries"})]})}),e.jsx("tbody",{children:o.map(r=>e.jsxs("tr",{children:[e.jsx("td",{className:"text-strong",children:r.name}),e.jsx("td",{className:"tabular-nums text-secondary",children:r.occurrences}),e.jsxs("td",{className:"text-secondary",children:[r.providers.join(", "),r.providersTruncated?` +${r.providerTotal-r.providers.length}`:""]}),e.jsx("td",{className:"text-right",children:e.jsxs("span",{className:"inline-flex items-center justify-end gap-1",children:[e.jsx("span",{className:"tabular-nums text-secondary",children:r.questionTotal}),e.jsx(P,{text:Ue(r)})]})})]},r.name))})]})}),m?.state==="available"?e.jsxs("p",{className:"supporting-copy mt-2",children:[m.targetMissResults," of ",m.answeredResults," answers to ",h[a].technical.toLocaleLowerCase()," did not name this Property."]}):null]})]})}function $e({questions:s,queryClass:t}){return e.jsxs("section",{"aria-labelledby":"property-questions",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-questions",className:"text-base font-semibold text-heading",children:[h[t].technical," assigned to this Property"]})}),e.jsxs("p",{className:"supporting-copy",children:[s.length," assigned"]})]}),s.length===0?e.jsxs("p",{className:"text-sm text-secondary",children:["No ",h[t].technical.toLocaleLowerCase()," are assigned. Add one in advanced measurement setup to measure this class."]}):e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsx("caption",{className:"sr-only",children:"Queries assigned to this Property"}),e.jsx("thead",{children:e.jsx("tr",{children:e.jsx("th",{children:"Query"})})}),e.jsx("tbody",{children:s.map(a=>e.jsx("tr",{children:e.jsx("td",{className:"text-secondary",children:a})},a))})]})})]})}function _e({urls:s}){return e.jsxs("section",{"aria-labelledby":"property-urls",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-urls",className:"text-base font-semibold text-heading",children:["URLs that count as this Property",e.jsx(P,{text:"A cited source URL is credited to this Property when it matches one of these. The most specific matcher wins, so a URL covered by two Properties at the same specificity is flagged for review instead of being credited to either."})]})}),e.jsxs("p",{className:"supporting-copy",children:[s.length," configured"]})]}),s.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"No URLs are configured for this Property."}):e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[420px]",children:[e.jsx("caption",{className:"sr-only",children:"URL matchers configured for this Property"}),e.jsx("thead",{children:e.jsx("tr",{children:e.jsx("th",{children:"URL"})})}),e.jsx("tbody",{children:s.map(t=>e.jsx("tr",{children:e.jsx("td",{className:"break-all text-secondary",children:t})},t))})]})})]})}function Ye(){const{projectName:s,targetKey:t}=le({strict:!1}),[a,n]=w.useState("non-brand"),[o,m]=w.useState(new Set),r=s??"",l=t??"",y=!!r&&!!l,{canWrite:k}=ce(),C=E({...he({client:L,path:{name:r}}),enabled:y}),b=E({...Y(r,l,"branded"),enabled:y}),v=E({...Y(r,l,"non-brand"),enabled:y}),B=Z(b.data),U=Z(v.data),x=a==="branded"?b.data:v.data,f=a==="branded"?B:U,O=(a==="branded"?b.isError:v.isError)&&f===void 0,Q=x?.measurement.displayedRunId,J=f?.mentionCoverage.state==="unavailable"?q(f.mentionCoverage):void 0,X=x===void 0?void 0:x.measurement.completedAt??null,M={client:L,path:{name:r},query:{targetKey:l,queryClass:a,shape:me.answers,limit:W,...Q?{runId:Q}:{}}},d=de({...xe(M),enabled:y&&x!==void 0,initialPageParam:M,getNextPageParam:i=>i.answers?.nextCursor?{path:M.path,query:{...M.query,cursor:i.answers.nextCursor}}:void 0}),I=C.data?.active??null,p=I?.plan.schemaVersion===2?I.plan:null,ee=I!==null&&p===null,R=p?.targets.find(i=>i.stableKey===l)??null,se=w.useMemo(()=>{if(!p)return[];const i=new Map(p.querySnapshots.map(c=>[c.queryId,c.queryText]));return[...new Set(p.assignments.filter(c=>c.targetKey===l&&c.queryClass===a).flatMap(c=>{const u=i.get(c.queryId);return u===void 0?[]:[u]}))].sort((c,u)=>c.localeCompare(u))},[p,l,a]),te=w.useMemo(()=>R?.urlMatchers.map(Ne)??[],[R]),ae=w.useMemo(()=>p?.groups.filter(i=>i.targetKeys.includes(l))??[],[p,l]),g=w.useMemo(()=>(d.data?.pages??[]).flatMap(u=>u.answers?.items??[]),[d.data]),V=d.data?.pages[0]?.answers?.totalEstimate??g.length,ne=(d.data?.pages??[]).length>0&&(d.data?.pages??[]).every(i=>i.answers===void 0),re=d.data?.pages[0]?.measurement.state,$=e.jsxs(A,{to:"/projects/$projectName",params:{projectName:r},className:"inline-flex items-center gap-1 text-xs text-muted hover:text-strong",children:[e.jsx(fe,{className:"size-3.5","aria-hidden":"true"}),"Back to measurement overview"]});if(!y)return e.jsx("div",{className:"page-container",children:e.jsx("p",{className:"text-sm text-muted",children:"Missing project name or Property key in URL."})});if(!pe("portfolio",ue()?.projectTabs))return e.jsx("div",{className:"page-container",children:e.jsx("p",{className:"text-sm text-muted",children:"This view is not available here."})});if(C.isPending&&!C.isError)return e.jsx("div",{className:"page-container",children:e.jsxs("div",{role:"status","aria-live":"polite",children:[e.jsx("span",{className:"sr-only",children:"Loading Property"}),e.jsx("div",{className:"h-32 animate-pulse rounded-md bg-surface-subtle","aria-hidden":"true"})]})});const ie=C.isError&&C.data===void 0,F=b.isError&&b.data===void 0,z=v.isError&&v.data===void 0;return ie||F&&z?e.jsxs("div",{className:"page-container space-y-3",children:[$,e.jsx("p",{role:"alert",className:"text-sm text-negative",children:"Could not load this Property."}),e.jsx(j,{type:"button",size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",onClick:()=>{C.refetch(),b.refetch(),v.refetch()},children:"Try again"})]}):!p||!R?e.jsxs("div",{className:"page-container space-y-3",children:[$,e.jsx("p",{role:"status",className:"text-sm text-secondary",children:p?"This Property is not in the published setup. It may have been renamed or removed.":"A Property page needs a published advanced measurement setup. Republish setup from the project Portfolio tab."}),e.jsx(j,{asChild:!0,type:"button",variant:"outline",className:"h-11 px-4 text-sm md:h-11",children:e.jsx(A,{to:"/projects/$projectName/portfolio",params:{projectName:r},children:k?ee?"Republish setup":"Open measurement setup":"View measurement setup"})})]}):e.jsxs("div",{className:"page-container space-y-8",children:[e.jsxs("div",{className:"page-header",children:[e.jsxs("div",{className:"page-header-left",children:[$,e.jsx("h1",{className:"page-title mt-2",children:R.label}),e.jsxs("p",{className:"page-subtitle",children:["Property in ",r]})]}),e.jsxs("div",{className:"page-header-right",children:[x?e.jsx(N,{tone:H[x.measurement.state].tone,children:H[x.measurement.state].label}):null,f&&f.flags>0?e.jsxs(N,{tone:"caution",children:[f.flags," flagged"]}):null]})]}),e.jsx(qe,{branded:B,nonBrand:U,brandedFailed:F,nonBrandFailed:z}),e.jsx(ke,{branded:B,nonBrand:U,brandedError:b.isError,nonBrandError:v.isError,onRetry:i=>{i==="branded"?b.refetch():v.refetch()}}),x&&(x.measurement.state==="not_measured"||x.nextAction.kind==="run_measurement")?e.jsxs("section",{className:"flex flex-wrap items-center justify-between gap-3 border-y border-default py-4","aria-label":"Measurement next step",children:[e.jsx("p",{className:"text-sm text-secondary",children:D()?K:k?"Run a measurement from the project overview to collect this Property’s coverage and source evidence.":"This Property needs a new measurement before coverage and source evidence are available."}),e.jsx(j,{asChild:!0,type:"button",className:"h-11 px-4 text-sm md:h-11",children:e.jsx(A,{to:"/projects/$projectName",params:{projectName:r},children:k?"Go to measurement overview":"View measurement overview"})})]}):null,e.jsxs("div",{className:"flex flex-wrap items-end gap-4 border-y border-default py-4",children:[e.jsxs("div",{className:"space-y-1",children:[e.jsx("label",{htmlFor:"property-query-class",className:"block text-sm font-medium text-heading",children:"Query type"}),e.jsxs("select",{id:"property-query-class",value:a,onChange:i=>n(i.target.value==="branded"?"branded":"non-brand"),className:"h-11 rounded-md border border-default bg-surface px-3 text-sm text-primary focus:outline-none focus:ring-2 focus:ring-mono-400",children:[e.jsx("option",{value:"non-brand",children:h["non-brand"].technical}),e.jsx("option",{value:"branded",children:h.branded.technical})]})]}),e.jsx(Ae,{measuredAt:X,unmeasuredReason:J,queryClass:a})]}),e.jsx(Be,{row:f,queryClass:a,isError:O}),e.jsx(Ie,{project:r,targetKey:l,queryClass:a}),e.jsx($e,{questions:se,queryClass:a}),e.jsx(_e,{urls:te}),e.jsx(Te,{project:r,groups:ae}),e.jsxs("section",{"aria-labelledby":"property-evidence",className:"page-section-divider",children:[e.jsxs("div",{className:"section-head section-head-inline",children:[e.jsx("div",{children:e.jsxs("h2",{id:"property-evidence",className:"text-base font-semibold text-heading",children:["Answers the engines gave",e.jsx(P,{text:"One row per answer an engine gave for the queries assigned to this Property in the displayed measurement. Mentioned and cited are independent: an answer can name this Property without linking it, or link it without naming it. Answers that did neither are listed first, because those are what a gap is made of. Where the answer text was not stored the mention reads Not measured, never a zero. Open a row to read what the engine actually said, followed by the source URLs it returned, this Property's own first. The answer is where a miss becomes actionable: a query this Property was not named in may still have named a sibling building."})]})}),g.length>0?e.jsxs("p",{className:"supporting-copy",children:[g.length," of ",V]}):null]}),O?e.jsxs("p",{className:"text-sm text-secondary",children:["Evidence for ",h[a].technical.toLocaleLowerCase()," is unavailable. Retry that query type above."]}):d.isPending&&g.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"Loading evidence…"}):d.isError&&g.length===0?e.jsxs("div",{className:"flex flex-wrap items-center gap-3 text-sm text-secondary",children:[e.jsx("span",{role:"alert",children:"Evidence could not be loaded."}),e.jsx(j,{type:"button",size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",onClick:()=>{d.refetch()},children:"Retry evidence"})]}):re==="not_measured"?e.jsx("p",{className:"text-sm text-secondary",children:D()?K:"Not measured yet. Run a measurement to collect the answers for this Property."}):ne?e.jsx("p",{role:"alert",className:"text-sm text-caution",children:"This measurement was returned in an older format, so the answers cannot be shown here. The numbers above are unaffected."}):g.length===0?e.jsx("p",{className:"text-sm text-secondary",children:"No answers matched this Property in the displayed measurement."}):e.jsxs(e.Fragment,{children:[e.jsx("div",{className:"overflow-x-auto rounded-md border border-default",children:e.jsxs("table",{className:"evidence-table min-w-[720px]",children:[e.jsx("caption",{className:"sr-only",children:"Answers measured for this Property"}),e.jsx("thead",{children:e.jsxs("tr",{children:[e.jsx("th",{children:"Query"}),e.jsx("th",{children:"Mentioned in the answer"}),e.jsx("th",{children:"Cited as a source"}),e.jsx("th",{children:"Sources"}),e.jsx("th",{children:e.jsx("span",{className:"sr-only",children:"Sources detail"})})]})}),e.jsx("tbody",{children:g.map(i=>{const c=Ce(i),u=o.has(c);return e.jsxs(w.Fragment,{children:[e.jsxs("tr",{children:[e.jsxs("td",{className:"text-secondary",children:[e.jsx("span",{className:"block",children:i.queryText}),e.jsx("span",{className:"mt-1 block text-xs text-muted",children:[i.provider,i.location].filter(Boolean).join(" · ")}),i.historical||i.bridged?e.jsx("span",{className:"mt-1 flex",children:e.jsx(N,{tone:"caution",children:"Historical"})}):null]}),e.jsx("td",{children:e.jsx(Ee,{row:i})}),e.jsx("td",{children:e.jsx(Me,{row:i})}),e.jsx("td",{className:"tabular-nums text-secondary",children:i.cited===null?Le:i.sources.length}),e.jsx("td",{className:"text-right",children:e.jsx(j,{type:"button",size:"sm",variant:"ghost","aria-expanded":u,onClick:()=>m(oe=>{const _=new Set(oe);return _.delete(c)||_.add(c),_}),children:u?`Hide the answer for ${i.queryText}`:`Read the answer for ${i.queryText}`})})]}),u?e.jsx("tr",{children:e.jsxs("td",{colSpan:5,className:"bg-surface-subtle px-4",children:[e.jsx(Se,{project:r,targetKey:l,row:i}),e.jsx(Re,{row:i})]})}):null]},c)})})]})}),d.hasNextPage?e.jsxs("div",{className:"mt-2 flex flex-wrap items-center gap-3 text-sm text-secondary",children:[e.jsxs("span",{children:["Showing ",g.length," of ",V]}),d.isFetchNextPageError?e.jsx("span",{role:"alert",children:"Could not load more evidence."}):null,e.jsx(j,{size:"sm",variant:"outline",className:"h-11 px-4 text-sm md:h-11",disabled:d.isFetchingNextPage,onClick:()=>{d.fetchNextPage()},children:d.isFetchingNextPage?"Loading…":d.isFetchNextPageError?"Retry more evidence":`Show ${W} more`})]}):null]})]})]})}export{Ye as MeasurementPropertyPage};
|