@canonry/canonry 4.170.0 → 4.171.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/dist/{chunk-NMCLLN5X.js → chunk-5KGN224W.js} +34 -3
- package/dist/{chunk-BMW3AURU.js → chunk-IIFDS3K2.js} +232 -0
- package/dist/{chunk-CCZNCH7Y.js → chunk-O226BLC7.js} +252 -3
- package/dist/{chunk-HTUI6BSK.js → chunk-RR4KSDOD.js} +1 -1
- package/dist/cli.js +689 -110
- package/dist/index.js +3 -3
- package/dist/{intelligence-service-64IPZ4NU.js → intelligence-service-KT6K4ODY.js} +1 -1
- package/dist/mcp.js +36 -3
- package/package.json +8 -8
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
loadConfig,
|
|
10
10
|
loadConfigRaw,
|
|
11
11
|
saveConfigPatch
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IIFDS3K2.js";
|
|
13
13
|
import {
|
|
14
14
|
CC_CACHE_DIR,
|
|
15
15
|
DUCKDB_SPEC,
|
|
@@ -145,7 +145,7 @@ import {
|
|
|
145
145
|
siteCrawlSnapshots,
|
|
146
146
|
toAlertView,
|
|
147
147
|
usageCounters
|
|
148
|
-
} from "./chunk-
|
|
148
|
+
} from "./chunk-O226BLC7.js";
|
|
149
149
|
import {
|
|
150
150
|
AGENT_MEMORY_VALUE_MAX_BYTES,
|
|
151
151
|
AGENT_PROVIDER_IDS,
|
|
@@ -11829,7 +11829,7 @@ function readStoredGroundingSources(rawResponse) {
|
|
|
11829
11829
|
return result;
|
|
11830
11830
|
}
|
|
11831
11831
|
async function backfillInsightsCommand(project, opts) {
|
|
11832
|
-
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-
|
|
11832
|
+
const { IntelligenceService: IntelligenceService2 } = await import("./intelligence-service-KT6K4ODY.js");
|
|
11833
11833
|
const config = loadConfig();
|
|
11834
11834
|
const db = createClient(config.database);
|
|
11835
11835
|
migrate(db);
|
|
@@ -18003,6 +18003,37 @@ async function createServer(opts) {
|
|
|
18003
18003
|
// Resolved fresh each call so a key added at runtime (settings API) shows
|
|
18004
18004
|
// up immediately in the `config.agent-providers` doctor check.
|
|
18005
18005
|
getAgentProviderSummary: () => buildAgentProvidersResponse(opts.config).providers,
|
|
18006
|
+
getGoogleMarketingDoctorInput: (ctx) => {
|
|
18007
|
+
if (!ctx.project) return null;
|
|
18008
|
+
const googleAds = opts.db.select().from(googleAdsConnections).where(eq25(googleAdsConnections.projectId, ctx.project.id)).get();
|
|
18009
|
+
const gtm = opts.db.select().from(gtmConnections).where(eq25(gtmConnections.projectId, ctx.project.id)).get();
|
|
18010
|
+
const googleAdsCredential = getGoogleAdsConnection(opts.config, ctx.project.id);
|
|
18011
|
+
const gtmCredential = getGtmConnection(opts.config, ctx.project.id);
|
|
18012
|
+
const googleAdsConnected = Boolean(googleAdsCredential?.accessToken);
|
|
18013
|
+
const gtmConnected = Boolean(gtmCredential?.accessToken);
|
|
18014
|
+
return {
|
|
18015
|
+
googleAds: googleAds && googleAdsConnected ? {
|
|
18016
|
+
credentialsPresent: Boolean(
|
|
18017
|
+
googleAdsCredential?.accessToken || googleAdsCredential?.refreshToken
|
|
18018
|
+
),
|
|
18019
|
+
grantedScopes: googleAds.scopes,
|
|
18020
|
+
selectedLoginCustomerId: googleAds.selectedLoginCustomerId,
|
|
18021
|
+
selectedCustomerId: googleAds.selectedCustomerId,
|
|
18022
|
+
latestSnapshotAt: [
|
|
18023
|
+
googleAds.lastInventorySnapshotAt,
|
|
18024
|
+
googleAds.lastMetricsSnapshotAt
|
|
18025
|
+
].filter((value) => Boolean(value)).sort().at(-1) ?? null
|
|
18026
|
+
} : null,
|
|
18027
|
+
gtm: gtm && gtmConnected ? {
|
|
18028
|
+
credentialsPresent: Boolean(gtmCredential?.accessToken || gtmCredential?.refreshToken),
|
|
18029
|
+
grantedScopes: gtm.scopes,
|
|
18030
|
+
selectedAccountId: gtm.selectedAccountId,
|
|
18031
|
+
selectedContainerId: gtm.selectedContainerId,
|
|
18032
|
+
selectedWorkspaceId: gtm.selectedWorkspaceId,
|
|
18033
|
+
latestSnapshotAt: gtm.lastSnapshotAt
|
|
18034
|
+
} : null
|
|
18035
|
+
};
|
|
18036
|
+
},
|
|
18006
18037
|
googleConnectionStore,
|
|
18007
18038
|
googleStateSecret,
|
|
18008
18039
|
publicUrl: googlePublicUrl,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
CodingAgents,
|
|
5
5
|
DISCOVERY_MAX_PROBES_CAP,
|
|
6
6
|
DISCOVERY_PROBE_CONCURRENCY_CAP,
|
|
7
|
+
GOOGLE_MARKETING_STORED_SNAPSHOT_PAGE_MAX,
|
|
7
8
|
SKILL_MANIFEST_FILENAME,
|
|
8
9
|
SkillsClients,
|
|
9
10
|
adsActivateTreeRequestSchema,
|
|
@@ -19,6 +20,8 @@ import {
|
|
|
19
20
|
adsPauseRequestSchema,
|
|
20
21
|
adsUnresolvedOperationListQuerySchema,
|
|
21
22
|
backlinkSourceSchema,
|
|
23
|
+
canonicalizeGtmAccountId,
|
|
24
|
+
canonicalizeGtmResourceSelection,
|
|
22
25
|
classifySkillFile,
|
|
23
26
|
coerceSkillManifest,
|
|
24
27
|
competitorBatchRequestSchema,
|
|
@@ -9350,6 +9353,54 @@ var adsAdPauseInputSchema = z3.object({
|
|
|
9350
9353
|
adId: z3.string().min(1),
|
|
9351
9354
|
request: adsPauseRequestSchema
|
|
9352
9355
|
});
|
|
9356
|
+
var googleMarketingSnapshotPageInputSchema = z3.object({
|
|
9357
|
+
project: projectNameSchema,
|
|
9358
|
+
limit: z3.number().int().min(1).max(GOOGLE_MARKETING_STORED_SNAPSHOT_PAGE_MAX).optional(),
|
|
9359
|
+
cursor: z3.string().trim().min(1).optional()
|
|
9360
|
+
}).strict();
|
|
9361
|
+
var googleMarketingSnapshotInputSchema = z3.object({
|
|
9362
|
+
project: projectNameSchema,
|
|
9363
|
+
snapshotId: z3.string().trim().min(1)
|
|
9364
|
+
}).strict();
|
|
9365
|
+
var gtmAccountInputSchema = z3.object({
|
|
9366
|
+
project: projectNameSchema,
|
|
9367
|
+
accountId: z3.string().trim().min(1)
|
|
9368
|
+
}).strict().superRefine((input, context) => {
|
|
9369
|
+
if (!canonicalizeGtmAccountId(input.accountId)) {
|
|
9370
|
+
context.addIssue({
|
|
9371
|
+
code: z3.ZodIssueCode.custom,
|
|
9372
|
+
path: ["accountId"],
|
|
9373
|
+
message: "Expected a safe GTM account ID or accounts/{id} resource path."
|
|
9374
|
+
});
|
|
9375
|
+
}
|
|
9376
|
+
});
|
|
9377
|
+
var gtmContainerInputSchema = z3.object({
|
|
9378
|
+
project: projectNameSchema,
|
|
9379
|
+
accountId: z3.string().trim().min(1),
|
|
9380
|
+
containerId: z3.string().trim().min(1)
|
|
9381
|
+
}).strict().superRefine((input, context) => {
|
|
9382
|
+
if (!canonicalizeGtmResourceSelection(input)) {
|
|
9383
|
+
context.addIssue({
|
|
9384
|
+
code: z3.ZodIssueCode.custom,
|
|
9385
|
+
path: ["containerId"],
|
|
9386
|
+
message: "Expected matching safe GTM account/container IDs or resource paths."
|
|
9387
|
+
});
|
|
9388
|
+
}
|
|
9389
|
+
});
|
|
9390
|
+
function canonicalGtmMcpAccountId(accountId) {
|
|
9391
|
+
const canonical = canonicalizeGtmAccountId(accountId);
|
|
9392
|
+
if (!canonical) throw new Error("Invalid GTM account input.");
|
|
9393
|
+
return canonical;
|
|
9394
|
+
}
|
|
9395
|
+
function canonicalGtmMcpSelection(accountId, containerId) {
|
|
9396
|
+
const canonical = canonicalizeGtmResourceSelection({ accountId, containerId });
|
|
9397
|
+
if (!canonical) throw new Error("Invalid GTM account/container input.");
|
|
9398
|
+
return canonical;
|
|
9399
|
+
}
|
|
9400
|
+
var conversionTrackingContractInputSchema = z3.object({
|
|
9401
|
+
project: projectNameSchema,
|
|
9402
|
+
contractId: z3.string().trim().min(1)
|
|
9403
|
+
}).strict();
|
|
9353
9404
|
var keywordsInputSchema = z3.object({
|
|
9354
9405
|
project: projectNameSchema,
|
|
9355
9406
|
request: keywordBatchRequestSchema
|
|
@@ -11692,6 +11743,187 @@ var canonryMcpTools = [
|
|
|
11692
11743
|
checkDeadLinks: input.checkDeadLinks
|
|
11693
11744
|
})
|
|
11694
11745
|
}),
|
|
11746
|
+
// ----- Google Marketing: Google Ads + Google Tag Manager -----
|
|
11747
|
+
// OAuth, selection, disconnect, and contract writes deliberately stay on
|
|
11748
|
+
// the operator CLI/dashboard. The agent surface can only inspect bounded
|
|
11749
|
+
// evidence, trigger bounded read-only syncs, and assess stored integrity.
|
|
11750
|
+
defineTool({
|
|
11751
|
+
name: "canonry_google_ads_status",
|
|
11752
|
+
title: "Get Google Ads connection status",
|
|
11753
|
+
description: "Read the project-local Google Ads connection, selected customer, and stored-evidence freshness. This never exposes credentials or changes a Google Ads account.",
|
|
11754
|
+
access: "read",
|
|
11755
|
+
tier: "google-ads",
|
|
11756
|
+
inputSchema: projectInputSchema,
|
|
11757
|
+
annotations: readAnnotations(),
|
|
11758
|
+
openApiOperations: ["GET /api/v1/projects/{name}/google-ads/status"],
|
|
11759
|
+
handler: (client2, input) => client2.getGoogleAdsStatus(input.project)
|
|
11760
|
+
}),
|
|
11761
|
+
defineTool({
|
|
11762
|
+
name: "canonry_google_ads_customers",
|
|
11763
|
+
title: "List live Google Ads customers",
|
|
11764
|
+
description: "Read the bounded set of Google Ads customers available to the project OAuth connection. This makes live provider GET requests and requires google-marketing.read-live; customer selection remains an explicit operator action.",
|
|
11765
|
+
access: "read",
|
|
11766
|
+
tier: "google-ads",
|
|
11767
|
+
inputSchema: projectInputSchema,
|
|
11768
|
+
annotations: readAnnotations(true),
|
|
11769
|
+
openApiOperations: ["GET /api/v1/projects/{name}/google-ads/customers"],
|
|
11770
|
+
handler: (client2, input) => client2.listGoogleAdsCustomers(input.project)
|
|
11771
|
+
}),
|
|
11772
|
+
defineTool({
|
|
11773
|
+
name: "canonry_google_ads_snapshots",
|
|
11774
|
+
title: "List stored Google Ads evidence",
|
|
11775
|
+
description: "List a bounded page of redacted, append-only Google Ads snapshots. Stored reads are quota-free and do not call Google.",
|
|
11776
|
+
access: "read",
|
|
11777
|
+
tier: "google-ads",
|
|
11778
|
+
inputSchema: googleMarketingSnapshotPageInputSchema,
|
|
11779
|
+
annotations: readAnnotations(),
|
|
11780
|
+
openApiOperations: ["GET /api/v1/projects/{name}/google-ads/snapshots"],
|
|
11781
|
+
handler: (client2, input) => client2.listGoogleAdsSnapshots(input.project, {
|
|
11782
|
+
limit: input.limit,
|
|
11783
|
+
cursor: input.cursor
|
|
11784
|
+
})
|
|
11785
|
+
}),
|
|
11786
|
+
defineTool({
|
|
11787
|
+
name: "canonry_google_ads_snapshot_get",
|
|
11788
|
+
title: "Get stored Google Ads evidence",
|
|
11789
|
+
description: "Read one redacted Google Ads evidence snapshot by ID. It can show conversion actions and effective campaign goals, but cannot prove a browser conversion fired.",
|
|
11790
|
+
access: "read",
|
|
11791
|
+
tier: "google-ads",
|
|
11792
|
+
inputSchema: googleMarketingSnapshotInputSchema,
|
|
11793
|
+
annotations: readAnnotations(),
|
|
11794
|
+
openApiOperations: ["GET /api/v1/projects/{name}/google-ads/snapshots/{snapshotId}"],
|
|
11795
|
+
handler: (client2, input) => client2.getGoogleAdsSnapshot(input.project, input.snapshotId)
|
|
11796
|
+
}),
|
|
11797
|
+
defineTool({
|
|
11798
|
+
name: "canonry_google_ads_sync",
|
|
11799
|
+
title: "Sync Google Ads conversion evidence",
|
|
11800
|
+
description: "Queue a bounded read-only Google Ads evidence sync using GETs and SearchStream POSTs. It creates a local run and sanitized snapshots, but never changes campaigns, conversion actions, goals, bids, or budgets. Requires google-marketing.read-live and write authority.",
|
|
11801
|
+
access: "write",
|
|
11802
|
+
tier: "google-ads",
|
|
11803
|
+
inputSchema: projectInputSchema,
|
|
11804
|
+
annotations: writeAnnotations({ idempotentHint: false, openWorldHint: true }),
|
|
11805
|
+
openApiOperations: ["POST /api/v1/projects/{name}/google-ads/sync"],
|
|
11806
|
+
handler: (client2, input) => client2.triggerGoogleAdsSync(input.project)
|
|
11807
|
+
}),
|
|
11808
|
+
defineTool({
|
|
11809
|
+
name: "canonry_gtm_status",
|
|
11810
|
+
title: "Get Google Tag Manager connection status",
|
|
11811
|
+
description: "Read the project-local GTM connection, selected account/container/workspace, and stored-evidence freshness. This never exposes credentials or edits GTM.",
|
|
11812
|
+
access: "read",
|
|
11813
|
+
tier: "gtm",
|
|
11814
|
+
inputSchema: projectInputSchema,
|
|
11815
|
+
annotations: readAnnotations(),
|
|
11816
|
+
openApiOperations: ["GET /api/v1/projects/{name}/gtm/status"],
|
|
11817
|
+
handler: (client2, input) => client2.getGtmStatus(input.project)
|
|
11818
|
+
}),
|
|
11819
|
+
defineTool({
|
|
11820
|
+
name: "canonry_gtm_accounts",
|
|
11821
|
+
title: "List live GTM accounts",
|
|
11822
|
+
description: "Read the bounded set of GTM accounts available to the project OAuth connection. This makes live provider GET requests and requires google-marketing.read-live; resource selection remains operator-only.",
|
|
11823
|
+
access: "read",
|
|
11824
|
+
tier: "gtm",
|
|
11825
|
+
inputSchema: projectInputSchema,
|
|
11826
|
+
annotations: readAnnotations(true),
|
|
11827
|
+
openApiOperations: ["GET /api/v1/projects/{name}/gtm/accounts"],
|
|
11828
|
+
handler: (client2, input) => client2.listGtmAccounts(input.project)
|
|
11829
|
+
}),
|
|
11830
|
+
defineTool({
|
|
11831
|
+
name: "canonry_gtm_containers",
|
|
11832
|
+
title: "List live GTM containers",
|
|
11833
|
+
description: "Read the bounded set of containers in one GTM account. This is a live GET-only provider read; it cannot edit or publish a container.",
|
|
11834
|
+
access: "read",
|
|
11835
|
+
tier: "gtm",
|
|
11836
|
+
inputSchema: gtmAccountInputSchema,
|
|
11837
|
+
annotations: readAnnotations(true),
|
|
11838
|
+
openApiOperations: ["GET /api/v1/projects/{name}/gtm/accounts/{accountId}/containers"],
|
|
11839
|
+
handler: (client2, input) => client2.listGtmContainers(
|
|
11840
|
+
input.project,
|
|
11841
|
+
canonicalGtmMcpAccountId(input.accountId)
|
|
11842
|
+
)
|
|
11843
|
+
}),
|
|
11844
|
+
defineTool({
|
|
11845
|
+
name: "canonry_gtm_workspaces",
|
|
11846
|
+
title: "List live GTM workspaces",
|
|
11847
|
+
description: "Read the bounded set of workspaces in one GTM container. This is a live GET-only provider read; it cannot edit a workspace or publish a version.",
|
|
11848
|
+
access: "read",
|
|
11849
|
+
tier: "gtm",
|
|
11850
|
+
inputSchema: gtmContainerInputSchema,
|
|
11851
|
+
annotations: readAnnotations(true),
|
|
11852
|
+
openApiOperations: ["GET /api/v1/projects/{name}/gtm/accounts/{accountId}/containers/{containerId}/workspaces"],
|
|
11853
|
+
handler: (client2, input) => {
|
|
11854
|
+
const selection = canonicalGtmMcpSelection(input.accountId, input.containerId);
|
|
11855
|
+
return client2.listGtmWorkspaces(input.project, selection.accountId, selection.containerId);
|
|
11856
|
+
}
|
|
11857
|
+
}),
|
|
11858
|
+
defineTool({
|
|
11859
|
+
name: "canonry_gtm_snapshots",
|
|
11860
|
+
title: "List stored GTM evidence",
|
|
11861
|
+
description: "List a bounded page of redacted, append-only GTM live/draft graph snapshots. Stored reads are quota-free and do not call Google.",
|
|
11862
|
+
access: "read",
|
|
11863
|
+
tier: "gtm",
|
|
11864
|
+
inputSchema: googleMarketingSnapshotPageInputSchema,
|
|
11865
|
+
annotations: readAnnotations(),
|
|
11866
|
+
openApiOperations: ["GET /api/v1/projects/{name}/gtm/snapshots"],
|
|
11867
|
+
handler: (client2, input) => client2.listGtmSnapshots(input.project, {
|
|
11868
|
+
limit: input.limit,
|
|
11869
|
+
cursor: input.cursor
|
|
11870
|
+
})
|
|
11871
|
+
}),
|
|
11872
|
+
defineTool({
|
|
11873
|
+
name: "canonry_gtm_snapshot_get",
|
|
11874
|
+
title: "Get stored GTM evidence",
|
|
11875
|
+
description: "Read one redacted GTM live/draft graph snapshot by ID. It can prove static tag and trigger configuration only; it cannot prove a browser event fired.",
|
|
11876
|
+
access: "read",
|
|
11877
|
+
tier: "gtm",
|
|
11878
|
+
inputSchema: googleMarketingSnapshotInputSchema,
|
|
11879
|
+
annotations: readAnnotations(),
|
|
11880
|
+
openApiOperations: ["GET /api/v1/projects/{name}/gtm/snapshots/{snapshotId}"],
|
|
11881
|
+
handler: (client2, input) => client2.getGtmSnapshot(input.project, input.snapshotId)
|
|
11882
|
+
}),
|
|
11883
|
+
defineTool({
|
|
11884
|
+
name: "canonry_gtm_sync",
|
|
11885
|
+
title: "Sync GTM conversion evidence",
|
|
11886
|
+
description: "Queue a bounded GTM GET-only evidence sync. It creates a local run and sanitized live/draft snapshots, but never edits a workspace or publishes a container version. Requires google-marketing.read-live and write authority.",
|
|
11887
|
+
access: "write",
|
|
11888
|
+
tier: "gtm",
|
|
11889
|
+
inputSchema: projectInputSchema,
|
|
11890
|
+
annotations: writeAnnotations({ idempotentHint: false, openWorldHint: true }),
|
|
11891
|
+
openApiOperations: ["POST /api/v1/projects/{name}/gtm/sync"],
|
|
11892
|
+
handler: (client2, input) => client2.triggerGtmSync(input.project)
|
|
11893
|
+
}),
|
|
11894
|
+
defineTool({
|
|
11895
|
+
name: "canonry_conversion_tracking_contracts",
|
|
11896
|
+
title: "List conversion-tracking contracts",
|
|
11897
|
+
description: "List the project\u2019s declared business contracts linking an application event to Google Ads and GTM identifiers. Contract creation and changes remain operator-only.",
|
|
11898
|
+
access: "read",
|
|
11899
|
+
tier: "conversion-tracking",
|
|
11900
|
+
inputSchema: projectInputSchema,
|
|
11901
|
+
annotations: readAnnotations(),
|
|
11902
|
+
openApiOperations: ["GET /api/v1/projects/{name}/conversion-tracking/contracts"],
|
|
11903
|
+
handler: (client2, input) => client2.listConversionTrackingContracts(input.project)
|
|
11904
|
+
}),
|
|
11905
|
+
defineTool({
|
|
11906
|
+
name: "canonry_conversion_tracking_contract_get",
|
|
11907
|
+
title: "Get conversion-tracking contract",
|
|
11908
|
+
description: "Read one declared conversion-tracking contract. It declares intended semantics; use integrity assessment to evaluate stored evidence.",
|
|
11909
|
+
access: "read",
|
|
11910
|
+
tier: "conversion-tracking",
|
|
11911
|
+
inputSchema: conversionTrackingContractInputSchema,
|
|
11912
|
+
annotations: readAnnotations(),
|
|
11913
|
+
openApiOperations: ["GET /api/v1/projects/{name}/conversion-tracking/contracts/{contractId}"],
|
|
11914
|
+
handler: (client2, input) => client2.getConversionTrackingContract(input.project, input.contractId)
|
|
11915
|
+
}),
|
|
11916
|
+
defineTool({
|
|
11917
|
+
name: "canonry_conversion_tracking_integrity",
|
|
11918
|
+
title: "Assess stored conversion integrity",
|
|
11919
|
+
description: "Evaluate a declared contract against stored redacted Google Ads and GTM evidence. This does not call providers and cannot treat static configuration as proof of a browser event or observed Google Ads conversion.",
|
|
11920
|
+
access: "read",
|
|
11921
|
+
tier: "conversion-tracking",
|
|
11922
|
+
inputSchema: conversionTrackingContractInputSchema,
|
|
11923
|
+
annotations: readAnnotations(),
|
|
11924
|
+
openApiOperations: ["GET /api/v1/projects/{name}/conversion-tracking/contracts/{contractId}/integrity"],
|
|
11925
|
+
handler: (client2, input) => client2.getConversionTrackingIntegrity(input.project, input.contractId)
|
|
11926
|
+
}),
|
|
11695
11927
|
// ----- OpenAI ads (ChatGPT ads) -----
|
|
11696
11928
|
defineTool({
|
|
11697
11929
|
name: "canonry_ads_status",
|
|
@@ -61640,6 +61640,251 @@ var WORDPRESS_PUBLISH_CHECKS = [
|
|
|
61640
61640
|
}
|
|
61641
61641
|
];
|
|
61642
61642
|
|
|
61643
|
+
// ../api-routes/src/doctor/checks/google-marketing.ts
|
|
61644
|
+
var GOOGLE_ADS_REQUIRED_OAUTH_SCOPES = [
|
|
61645
|
+
"https://www.googleapis.com/auth/adwords"
|
|
61646
|
+
];
|
|
61647
|
+
var GTM_REQUIRED_OAUTH_SCOPES = [
|
|
61648
|
+
"https://www.googleapis.com/auth/tagmanager.readonly"
|
|
61649
|
+
];
|
|
61650
|
+
var FRESH_SNAPSHOT_WARN_DAYS = 7;
|
|
61651
|
+
var FRESH_SNAPSHOT_FAIL_DAYS = 30;
|
|
61652
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
61653
|
+
function unavailableStatus() {
|
|
61654
|
+
return {
|
|
61655
|
+
status: CheckStatuses.skipped,
|
|
61656
|
+
code: "google-marketing.status-unavailable",
|
|
61657
|
+
summary: "Google marketing status metadata is not configured for this deployment.",
|
|
61658
|
+
remediation: null
|
|
61659
|
+
};
|
|
61660
|
+
}
|
|
61661
|
+
function noProjectStatus() {
|
|
61662
|
+
return {
|
|
61663
|
+
status: CheckStatuses.skipped,
|
|
61664
|
+
code: "google-marketing.no-project",
|
|
61665
|
+
summary: "Project context required.",
|
|
61666
|
+
remediation: null
|
|
61667
|
+
};
|
|
61668
|
+
}
|
|
61669
|
+
function notConnectedStatus(provider, requiredScopes) {
|
|
61670
|
+
return {
|
|
61671
|
+
status: CheckStatuses.skipped,
|
|
61672
|
+
code: provider === "Google Ads" ? "google-ads.auth.not-connected" : "gtm.auth.not-connected",
|
|
61673
|
+
summary: `${provider} is not connected for this project.`,
|
|
61674
|
+
remediation: null,
|
|
61675
|
+
details: { requiredScopes: [...requiredScopes] }
|
|
61676
|
+
};
|
|
61677
|
+
}
|
|
61678
|
+
function credentialsOutput(provider, credentialsPresent) {
|
|
61679
|
+
if (credentialsPresent) {
|
|
61680
|
+
return {
|
|
61681
|
+
status: CheckStatuses.ok,
|
|
61682
|
+
code: `${provider}.auth.credentials-metadata-present`,
|
|
61683
|
+
summary: `${provider === "google-ads" ? "Google Ads" : "Google Tag Manager"} credential metadata is present.`,
|
|
61684
|
+
remediation: null,
|
|
61685
|
+
details: { credentialsPresent }
|
|
61686
|
+
};
|
|
61687
|
+
}
|
|
61688
|
+
return {
|
|
61689
|
+
status: CheckStatuses.fail,
|
|
61690
|
+
code: `${provider}.auth.credentials-metadata-missing`,
|
|
61691
|
+
summary: `${provider === "google-ads" ? "Google Ads" : "Google Tag Manager"} connection metadata exists but credentials are unavailable.`,
|
|
61692
|
+
remediation: "Reconnect this Google integration to restore its credential metadata.",
|
|
61693
|
+
details: { credentialsPresent }
|
|
61694
|
+
};
|
|
61695
|
+
}
|
|
61696
|
+
function scopesOutput(provider, grantedScopes, requiredScopes) {
|
|
61697
|
+
const granted = [...new Set(grantedScopes)];
|
|
61698
|
+
const missing = requiredScopes.filter((scope) => !granted.includes(scope));
|
|
61699
|
+
const label = provider === "google-ads" ? "Google Ads" : "Google Tag Manager";
|
|
61700
|
+
const scopeDescription = provider === "google-ads" ? "OAuth scope" : "read-only OAuth scope";
|
|
61701
|
+
if (missing.length === 0) {
|
|
61702
|
+
return {
|
|
61703
|
+
status: CheckStatuses.ok,
|
|
61704
|
+
code: `${provider}.auth.scopes-ok`,
|
|
61705
|
+
summary: `${label} has the required ${scopeDescription}.`,
|
|
61706
|
+
remediation: null,
|
|
61707
|
+
details: { requiredScopes: [...requiredScopes], grantedScopes: granted, missingScopes: missing }
|
|
61708
|
+
};
|
|
61709
|
+
}
|
|
61710
|
+
return {
|
|
61711
|
+
status: CheckStatuses.fail,
|
|
61712
|
+
code: `${provider}.auth.required-scope-missing`,
|
|
61713
|
+
summary: `${label} is missing a required ${scopeDescription}.`,
|
|
61714
|
+
remediation: `Reconnect the integration and grant every required ${scopeDescription}.`,
|
|
61715
|
+
details: { requiredScopes: [...requiredScopes], grantedScopes: granted, missingScopes: missing }
|
|
61716
|
+
};
|
|
61717
|
+
}
|
|
61718
|
+
function snapshotOutput(provider, latestSnapshotAt, now) {
|
|
61719
|
+
const label = provider === "google-ads" ? "Google Ads" : "Google Tag Manager";
|
|
61720
|
+
if (!latestSnapshotAt) {
|
|
61721
|
+
return {
|
|
61722
|
+
status: CheckStatuses.warn,
|
|
61723
|
+
code: `${provider}.data.snapshot-never-captured`,
|
|
61724
|
+
summary: `${label} is connected but has no stored snapshot.`,
|
|
61725
|
+
remediation: "Run a read-only snapshot sync to establish a fresh configuration record.",
|
|
61726
|
+
details: { latestSnapshotAt: null }
|
|
61727
|
+
};
|
|
61728
|
+
}
|
|
61729
|
+
const snapshotMs = Date.parse(latestSnapshotAt);
|
|
61730
|
+
if (!Number.isFinite(snapshotMs)) {
|
|
61731
|
+
return {
|
|
61732
|
+
status: CheckStatuses.fail,
|
|
61733
|
+
code: `${provider}.data.snapshot-timestamp-invalid`,
|
|
61734
|
+
summary: `${label} has an invalid latest snapshot timestamp.`,
|
|
61735
|
+
remediation: "Run a new snapshot sync to replace the invalid freshness metadata.",
|
|
61736
|
+
details: { latestSnapshotAt }
|
|
61737
|
+
};
|
|
61738
|
+
}
|
|
61739
|
+
const ageDays = (now.getTime() - snapshotMs) / DAY_MS;
|
|
61740
|
+
const details = { latestSnapshotAt, ageDays: Math.max(0, Math.round(ageDays)) };
|
|
61741
|
+
if (ageDays < 0) {
|
|
61742
|
+
return {
|
|
61743
|
+
status: CheckStatuses.warn,
|
|
61744
|
+
code: `${provider}.data.snapshot-in-future`,
|
|
61745
|
+
summary: `${label} latest snapshot timestamp is in the future.`,
|
|
61746
|
+
remediation: "Check the host clock and run a new snapshot sync.",
|
|
61747
|
+
details
|
|
61748
|
+
};
|
|
61749
|
+
}
|
|
61750
|
+
if (ageDays > FRESH_SNAPSHOT_FAIL_DAYS) {
|
|
61751
|
+
return {
|
|
61752
|
+
status: CheckStatuses.fail,
|
|
61753
|
+
code: `${provider}.data.snapshot-stale`,
|
|
61754
|
+
summary: `${label} latest snapshot is ${Math.round(ageDays)} days old (> ${FRESH_SNAPSHOT_FAIL_DAYS}d).`,
|
|
61755
|
+
remediation: "Run a read-only snapshot sync and verify its schedule.",
|
|
61756
|
+
details
|
|
61757
|
+
};
|
|
61758
|
+
}
|
|
61759
|
+
if (ageDays > FRESH_SNAPSHOT_WARN_DAYS) {
|
|
61760
|
+
return {
|
|
61761
|
+
status: CheckStatuses.warn,
|
|
61762
|
+
code: `${provider}.data.snapshot-aging`,
|
|
61763
|
+
summary: `${label} latest snapshot is ${Math.round(ageDays)} days old (> ${FRESH_SNAPSHOT_WARN_DAYS}d).`,
|
|
61764
|
+
remediation: "Schedule a read-only snapshot sync to keep configuration evidence fresh.",
|
|
61765
|
+
details
|
|
61766
|
+
};
|
|
61767
|
+
}
|
|
61768
|
+
return {
|
|
61769
|
+
status: CheckStatuses.ok,
|
|
61770
|
+
code: `${provider}.data.snapshot-fresh`,
|
|
61771
|
+
summary: `${label} latest snapshot is ${Math.round(ageDays)} day(s) old.`,
|
|
61772
|
+
remediation: null,
|
|
61773
|
+
details
|
|
61774
|
+
};
|
|
61775
|
+
}
|
|
61776
|
+
function evaluateGoogleMarketingDoctor(input, now = /* @__PURE__ */ new Date()) {
|
|
61777
|
+
const ads = input.googleAds;
|
|
61778
|
+
const gtm = input.gtm;
|
|
61779
|
+
const adsCredentials = ads ? credentialsOutput("google-ads", ads.credentialsPresent) : notConnectedStatus("Google Ads", GOOGLE_ADS_REQUIRED_OAUTH_SCOPES);
|
|
61780
|
+
const adsScopes = ads ? scopesOutput("google-ads", ads.grantedScopes, GOOGLE_ADS_REQUIRED_OAUTH_SCOPES) : notConnectedStatus("Google Ads", GOOGLE_ADS_REQUIRED_OAUTH_SCOPES);
|
|
61781
|
+
const adsContext = !ads ? notConnectedStatus("Google Ads", GOOGLE_ADS_REQUIRED_OAUTH_SCOPES) : !ads.selectedCustomerId ? {
|
|
61782
|
+
status: CheckStatuses.fail,
|
|
61783
|
+
code: "google-ads.account.customer-not-selected",
|
|
61784
|
+
summary: "Google Ads has no selected customer context for this project.",
|
|
61785
|
+
remediation: "Select the customer whose campaigns and conversion goals this project should read.",
|
|
61786
|
+
details: {
|
|
61787
|
+
loginCustomerId: ads.selectedLoginCustomerId,
|
|
61788
|
+
customerId: ads.selectedCustomerId
|
|
61789
|
+
}
|
|
61790
|
+
} : {
|
|
61791
|
+
status: CheckStatuses.ok,
|
|
61792
|
+
code: "google-ads.account.context-selected",
|
|
61793
|
+
summary: "Google Ads customer context is selected.",
|
|
61794
|
+
remediation: null,
|
|
61795
|
+
details: {
|
|
61796
|
+
loginCustomerId: ads.selectedLoginCustomerId,
|
|
61797
|
+
customerId: ads.selectedCustomerId
|
|
61798
|
+
}
|
|
61799
|
+
};
|
|
61800
|
+
const adsSnapshot = ads ? snapshotOutput("google-ads", ads.latestSnapshotAt, now) : notConnectedStatus("Google Ads", GOOGLE_ADS_REQUIRED_OAUTH_SCOPES);
|
|
61801
|
+
const gtmCredentials = gtm ? credentialsOutput("gtm", gtm.credentialsPresent) : notConnectedStatus("Google Tag Manager", GTM_REQUIRED_OAUTH_SCOPES);
|
|
61802
|
+
const gtmScopes = gtm ? scopesOutput("gtm", gtm.grantedScopes, GTM_REQUIRED_OAUTH_SCOPES) : notConnectedStatus("Google Tag Manager", GTM_REQUIRED_OAUTH_SCOPES);
|
|
61803
|
+
const gtmContext = !gtm ? notConnectedStatus("Google Tag Manager", GTM_REQUIRED_OAUTH_SCOPES) : !gtm.selectedAccountId || !gtm.selectedContainerId ? {
|
|
61804
|
+
status: CheckStatuses.fail,
|
|
61805
|
+
code: "gtm.container.account-or-container-not-selected",
|
|
61806
|
+
summary: "GTM needs both a selected account and container for this project.",
|
|
61807
|
+
remediation: "Select the GTM account and container whose configuration this project should inspect.",
|
|
61808
|
+
details: {
|
|
61809
|
+
accountId: gtm.selectedAccountId,
|
|
61810
|
+
containerId: gtm.selectedContainerId,
|
|
61811
|
+
workspaceId: gtm.selectedWorkspaceId
|
|
61812
|
+
}
|
|
61813
|
+
} : !gtm.selectedWorkspaceId ? {
|
|
61814
|
+
status: CheckStatuses.warn,
|
|
61815
|
+
code: "gtm.container.workspace-not-selected",
|
|
61816
|
+
summary: "GTM account and container are selected, but no draft workspace is selected.",
|
|
61817
|
+
remediation: "Select a draft workspace when this project needs workspace-level configuration evidence.",
|
|
61818
|
+
details: {
|
|
61819
|
+
accountId: gtm.selectedAccountId,
|
|
61820
|
+
containerId: gtm.selectedContainerId,
|
|
61821
|
+
workspaceId: gtm.selectedWorkspaceId
|
|
61822
|
+
}
|
|
61823
|
+
} : {
|
|
61824
|
+
status: CheckStatuses.ok,
|
|
61825
|
+
code: "gtm.container.context-selected",
|
|
61826
|
+
summary: "GTM account, container, and workspace context are selected.",
|
|
61827
|
+
remediation: null,
|
|
61828
|
+
details: {
|
|
61829
|
+
accountId: gtm.selectedAccountId,
|
|
61830
|
+
containerId: gtm.selectedContainerId,
|
|
61831
|
+
workspaceId: gtm.selectedWorkspaceId
|
|
61832
|
+
}
|
|
61833
|
+
};
|
|
61834
|
+
const gtmSnapshot = gtm ? snapshotOutput("gtm", gtm.latestSnapshotAt, now) : notConnectedStatus("Google Tag Manager", GTM_REQUIRED_OAUTH_SCOPES);
|
|
61835
|
+
const gtmRuntime = !gtm ? {
|
|
61836
|
+
status: CheckStatuses.skipped,
|
|
61837
|
+
code: "gtm.runtime.not-connected",
|
|
61838
|
+
summary: "GTM is not connected, so API configuration and runtime firing cannot be assessed.",
|
|
61839
|
+
remediation: null
|
|
61840
|
+
} : {
|
|
61841
|
+
status: CheckStatuses.skipped,
|
|
61842
|
+
code: "gtm.runtime.firing-not-proven",
|
|
61843
|
+
summary: "GTM API configuration does not prove that a tag fired in a real browser session.",
|
|
61844
|
+
remediation: "Verify runtime firing with browser-side evidence or a trusted conversion receipt.",
|
|
61845
|
+
details: { runtimeFiringProven: false }
|
|
61846
|
+
};
|
|
61847
|
+
return [
|
|
61848
|
+
adsCredentials,
|
|
61849
|
+
adsScopes,
|
|
61850
|
+
adsContext,
|
|
61851
|
+
adsSnapshot,
|
|
61852
|
+
gtmCredentials,
|
|
61853
|
+
gtmScopes,
|
|
61854
|
+
gtmContext,
|
|
61855
|
+
gtmSnapshot,
|
|
61856
|
+
gtmRuntime
|
|
61857
|
+
];
|
|
61858
|
+
}
|
|
61859
|
+
var CHECK_METADATA = [
|
|
61860
|
+
["google-ads.auth.connection", CheckCategories.auth, "Google Ads credential metadata"],
|
|
61861
|
+
["google-ads.auth.scopes", CheckCategories.auth, "Google Ads granted scopes"],
|
|
61862
|
+
["google-ads.account.context", CheckCategories.auth, "Google Ads customer context"],
|
|
61863
|
+
["google-ads.data.recent-snapshot", CheckCategories.integrations, "Google Ads latest snapshot"],
|
|
61864
|
+
["gtm.auth.connection", CheckCategories.auth, "GTM credential metadata"],
|
|
61865
|
+
["gtm.auth.scopes", CheckCategories.auth, "GTM granted scopes"],
|
|
61866
|
+
["gtm.container.context", CheckCategories.auth, "GTM account, container, and workspace context"],
|
|
61867
|
+
["gtm.data.recent-snapshot", CheckCategories.integrations, "GTM latest snapshot"],
|
|
61868
|
+
["gtm.runtime.firing", CheckCategories.integrations, "GTM runtime firing evidence"]
|
|
61869
|
+
];
|
|
61870
|
+
function createGoogleMarketingDoctorChecks(resolveInput, now = () => /* @__PURE__ */ new Date()) {
|
|
61871
|
+
return CHECK_METADATA.map(([id, category, title], index2) => ({
|
|
61872
|
+
id,
|
|
61873
|
+
category,
|
|
61874
|
+
scope: CheckScopes.project,
|
|
61875
|
+
title,
|
|
61876
|
+
run: (ctx) => {
|
|
61877
|
+
if (!ctx.project) return noProjectStatus();
|
|
61878
|
+
const input = resolveInput(ctx);
|
|
61879
|
+
if (!input) return unavailableStatus();
|
|
61880
|
+
return evaluateGoogleMarketingDoctor(input, now())[index2];
|
|
61881
|
+
}
|
|
61882
|
+
}));
|
|
61883
|
+
}
|
|
61884
|
+
var GOOGLE_MARKETING_DOCTOR_CHECKS = createGoogleMarketingDoctorChecks(
|
|
61885
|
+
(ctx) => ctx.getGoogleMarketingDoctorInput?.(ctx)
|
|
61886
|
+
);
|
|
61887
|
+
|
|
61643
61888
|
// ../api-routes/src/doctor/registry.ts
|
|
61644
61889
|
var ALL_CHECKS = [
|
|
61645
61890
|
// Runtime-state checks run first so file-system gone errors surface
|
|
@@ -61652,6 +61897,7 @@ var ALL_CHECKS = [
|
|
|
61652
61897
|
...WORDPRESS_PUBLISH_CHECKS,
|
|
61653
61898
|
...GA_AUTH_CHECKS,
|
|
61654
61899
|
...ADS_CHECKS,
|
|
61900
|
+
...GOOGLE_MARKETING_DOCTOR_CHECKS,
|
|
61655
61901
|
...PROVIDERS_CHECKS,
|
|
61656
61902
|
...TRAFFIC_SOURCE_CHECKS,
|
|
61657
61903
|
...BACKLINKS_CHECKS,
|
|
@@ -61748,7 +61994,8 @@ async function doctorRoutes(app, opts) {
|
|
|
61748
61994
|
trafficSourceValidators: opts.trafficSourceValidators,
|
|
61749
61995
|
runtimeStatePaths: opts.runtimeStatePaths,
|
|
61750
61996
|
bundledSkills: opts.bundledSkills,
|
|
61751
|
-
getAgentPluginState: opts.getAgentPluginState
|
|
61997
|
+
getAgentPluginState: opts.getAgentPluginState,
|
|
61998
|
+
getGoogleMarketingDoctorInput: opts.getGoogleMarketingDoctorInput
|
|
61752
61999
|
};
|
|
61753
62000
|
return runChecks(ctx, ALL_CHECKS, { checkIds });
|
|
61754
62001
|
});
|
|
@@ -61776,7 +62023,8 @@ async function doctorRoutes(app, opts) {
|
|
|
61776
62023
|
trafficSourceValidators: opts.trafficSourceValidators,
|
|
61777
62024
|
runtimeStatePaths: opts.runtimeStatePaths,
|
|
61778
62025
|
bundledSkills: opts.bundledSkills,
|
|
61779
|
-
getAgentPluginState: opts.getAgentPluginState
|
|
62026
|
+
getAgentPluginState: opts.getAgentPluginState,
|
|
62027
|
+
getGoogleMarketingDoctorInput: opts.getGoogleMarketingDoctorInput
|
|
61780
62028
|
};
|
|
61781
62029
|
return runChecks(ctx, ALL_CHECKS, { checkIds });
|
|
61782
62030
|
});
|
|
@@ -64704,7 +64952,8 @@ async function apiRoutes(app, opts) {
|
|
|
64704
64952
|
trafficSourceValidators: buildTrafficSourceValidators(opts),
|
|
64705
64953
|
runtimeStatePaths: opts.runtimeStatePaths,
|
|
64706
64954
|
bundledSkills: opts.bundledSkills,
|
|
64707
|
-
getAgentPluginState: opts.getAgentPluginState
|
|
64955
|
+
getAgentPluginState: opts.getAgentPluginState,
|
|
64956
|
+
getGoogleMarketingDoctorInput: opts.getGoogleMarketingDoctorInput
|
|
64708
64957
|
});
|
|
64709
64958
|
if (opts.registerAuthenticatedRoutes) {
|
|
64710
64959
|
await opts.registerAuthenticatedRoutes(api);
|