fullstackgtm 0.43.0 → 0.45.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/CHANGELOG.md +378 -1
- package/README.md +37 -14
- package/dist/audit.d.ts +3 -1
- package/dist/audit.js +29 -2
- package/dist/cli/audit.d.ts +15 -0
- package/dist/cli/audit.js +392 -0
- package/dist/cli/auth.d.ts +80 -0
- package/dist/cli/auth.js +500 -0
- package/dist/cli/call.d.ts +1 -0
- package/dist/cli/call.js +373 -0
- package/dist/cli/capabilities.d.ts +30 -0
- package/dist/cli/capabilities.js +197 -0
- package/dist/cli/draft.d.ts +7 -0
- package/dist/cli/draft.js +87 -0
- package/dist/cli/enrich.d.ts +8 -0
- package/dist/cli/enrich.js +788 -0
- package/dist/cli/fix.d.ts +33 -0
- package/dist/cli/fix.js +344 -0
- package/dist/cli/help.d.ts +25 -0
- package/dist/cli/help.js +609 -0
- package/dist/cli/icp.d.ts +7 -0
- package/dist/cli/icp.js +229 -0
- package/dist/cli/init.d.ts +7 -0
- package/dist/cli/init.js +58 -0
- package/dist/cli/market.d.ts +1 -0
- package/dist/cli/market.js +391 -0
- package/dist/cli/plans.d.ts +5 -0
- package/dist/cli/plans.js +454 -0
- package/dist/cli/schedule.d.ts +8 -0
- package/dist/cli/schedule.js +479 -0
- package/dist/cli/shared.d.ts +70 -0
- package/dist/cli/shared.js +331 -0
- package/dist/cli/signals.d.ts +7 -0
- package/dist/cli/signals.js +412 -0
- package/dist/cli/suggest.d.ts +49 -0
- package/dist/cli/suggest.js +135 -0
- package/dist/cli/tam.d.ts +9 -0
- package/dist/cli/tam.js +387 -0
- package/dist/cli/ui.d.ts +121 -0
- package/dist/cli/ui.js +375 -0
- package/dist/cli.d.ts +1 -57
- package/dist/cli.js +104 -4556
- package/dist/connector.d.ts +15 -0
- package/dist/connector.js +35 -0
- package/dist/connectors/hubspot.d.ts +3 -1
- package/dist/connectors/hubspot.js +10 -3
- package/dist/connectors/outboxChannel.d.ts +64 -0
- package/dist/connectors/outboxChannel.js +170 -0
- package/dist/connectors/prospectSources.d.ts +22 -0
- package/dist/connectors/prospectSources.js +43 -0
- package/dist/connectors/salesforce.d.ts +3 -1
- package/dist/connectors/salesforce.js +12 -5
- package/dist/connectors/signalSources.d.ts +105 -0
- package/dist/connectors/signalSources.js +264 -0
- package/dist/connectors/theirstack.d.ts +84 -0
- package/dist/connectors/theirstack.js +125 -0
- package/dist/icp.d.ts +52 -4
- package/dist/icp.js +112 -35
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/init.js +3 -0
- package/dist/judge.d.ts +2 -0
- package/dist/judge.js +6 -0
- package/dist/judgeEval.d.ts +7 -0
- package/dist/judgeEval.js +8 -1
- package/dist/marketClassify.d.ts +2 -0
- package/dist/marketClassify.js +7 -1
- package/dist/mcp-bin.js +2 -2
- package/dist/mcp.js +259 -166
- package/dist/runReport.d.ts +26 -0
- package/dist/runReport.js +15 -0
- package/dist/schedule.d.ts +80 -2
- package/dist/schedule.js +272 -5
- package/dist/signals.d.ts +54 -0
- package/dist/signals.js +64 -0
- package/dist/spoolFiles.d.ts +44 -0
- package/dist/spoolFiles.js +114 -0
- package/dist/tam.d.ts +225 -0
- package/dist/tam.js +470 -0
- package/dist/types.d.ts +11 -0
- package/docs/api.md +91 -11
- package/docs/outbox-format.md +92 -0
- package/docs/recipes.md +37 -0
- package/docs/roadmap-to-1.0.md +31 -1
- package/docs/signal-spool-format.md +175 -0
- package/docs/tam.md +195 -0
- package/llms.txt +83 -11
- package/package.json +1 -1
- package/skills/fullstackgtm/SKILL.md +4 -3
- package/src/audit.ts +27 -1
- package/src/cli/audit.ts +447 -0
- package/src/cli/auth.ts +549 -0
- package/src/cli/call.ts +398 -0
- package/src/cli/capabilities.ts +215 -0
- package/src/cli/draft.ts +101 -0
- package/src/cli/enrich.ts +885 -0
- package/src/cli/fix.ts +372 -0
- package/src/cli/help.ts +664 -0
- package/src/cli/icp.ts +265 -0
- package/src/cli/init.ts +65 -0
- package/src/cli/market.ts +423 -0
- package/src/cli/plans.ts +523 -0
- package/src/cli/schedule.ts +526 -0
- package/src/cli/shared.ts +375 -0
- package/src/cli/signals.ts +434 -0
- package/src/cli/suggest.ts +151 -0
- package/src/cli/tam.ts +435 -0
- package/src/cli/ui.ts +426 -0
- package/src/cli.ts +103 -5170
- package/src/connector.ts +46 -0
- package/src/connectors/hubspot.ts +14 -2
- package/src/connectors/outboxChannel.ts +202 -0
- package/src/connectors/prospectSources.ts +57 -0
- package/src/connectors/salesforce.ts +18 -2
- package/src/connectors/signalSources.ts +314 -0
- package/src/connectors/theirstack.ts +170 -0
- package/src/icp.ts +120 -34
- package/src/index.ts +32 -0
- package/src/init.ts +3 -0
- package/src/judge.ts +7 -0
- package/src/judgeEval.ts +8 -1
- package/src/marketClassify.ts +8 -1
- package/src/mcp-bin.ts +2 -2
- package/src/mcp.ts +130 -57
- package/src/runReport.ts +39 -0
- package/src/schedule.ts +330 -7
- package/src/signals.ts +90 -0
- package/src/spoolFiles.ts +116 -0
- package/src/tam.ts +654 -0
- package/src/types.ts +12 -0
package/src/index.ts
CHANGED
|
@@ -107,6 +107,38 @@ export {
|
|
|
107
107
|
type ScaffoldFile,
|
|
108
108
|
type ScaffoldOptions,
|
|
109
109
|
} from "./init.ts";
|
|
110
|
+
export {
|
|
111
|
+
appendCoverage,
|
|
112
|
+
classifyAccount,
|
|
113
|
+
classifyCoverage,
|
|
114
|
+
computeCoverage,
|
|
115
|
+
coverageCountsFromSnapshot,
|
|
116
|
+
coveredAccounts,
|
|
117
|
+
coverageToText,
|
|
118
|
+
crmCheckableCriteria,
|
|
119
|
+
deriveAcvFromClosedWon,
|
|
120
|
+
deriveBuyersPerAccount,
|
|
121
|
+
estimateTam,
|
|
122
|
+
loadTamModel,
|
|
123
|
+
projectEta,
|
|
124
|
+
readCoverageTimeline,
|
|
125
|
+
saveTamModel,
|
|
126
|
+
tamDir,
|
|
127
|
+
tamReportToMarkdown,
|
|
128
|
+
type AccountTamClass,
|
|
129
|
+
type AcvBasis,
|
|
130
|
+
type DerivedAcv,
|
|
131
|
+
type DerivedBuyers,
|
|
132
|
+
type EstimateTamInput,
|
|
133
|
+
type TamClassified,
|
|
134
|
+
type TamCoverage,
|
|
135
|
+
type TamCoverageCounts,
|
|
136
|
+
type TamCrossCheck,
|
|
137
|
+
type TamEta,
|
|
138
|
+
type TamModel,
|
|
139
|
+
type TamTargeting,
|
|
140
|
+
type TamUniverse,
|
|
141
|
+
} from "./tam.ts";
|
|
110
142
|
export {
|
|
111
143
|
apolloPullKeysForAppend,
|
|
112
144
|
apolloPullKeysForRefresh,
|
package/src/init.ts
CHANGED
|
@@ -45,6 +45,9 @@ export function starterIcp(): Icp {
|
|
|
45
45
|
industries: ["software", "saas"],
|
|
46
46
|
employeeBands: ["51-200", "201-500", "501-1000"],
|
|
47
47
|
geos: ["us"],
|
|
48
|
+
// Technographic targeting (the real RevOps signal): companies that USE a
|
|
49
|
+
// CRM/MAP. Drives `tam estimate|accounts --source theirstack`.
|
|
50
|
+
technologies: ["salesforce", "hubspot", "pipedrive"],
|
|
48
51
|
},
|
|
49
52
|
persona: {
|
|
50
53
|
jobLevels: ["vp", "director", "manager"],
|
package/src/judge.ts
CHANGED
|
@@ -612,6 +612,8 @@ export async function judgeSignals(opts: {
|
|
|
612
612
|
promptTemplate?: string;
|
|
613
613
|
llm?: LlmCallOptions;
|
|
614
614
|
now?: Date;
|
|
615
|
+
/** Per-account progress (presentation only — a throwing callback never fails the run). */
|
|
616
|
+
onAccount?: (done: number, total: number, domain: string) => void;
|
|
615
617
|
}): Promise<JudgeDecision[]> {
|
|
616
618
|
const now = opts.now ?? new Date();
|
|
617
619
|
const signalsById = new Map(opts.signals.map((s) => [s.id, s]));
|
|
@@ -629,6 +631,11 @@ export async function judgeSignals(opts: {
|
|
|
629
631
|
|
|
630
632
|
const decisions: JudgeDecision[] = [];
|
|
631
633
|
for (const [domain, signals] of byAccount) {
|
|
634
|
+
try {
|
|
635
|
+
opts.onAccount?.(decisions.length, byAccount.size, domain);
|
|
636
|
+
} catch {
|
|
637
|
+
// progress is presentation-only
|
|
638
|
+
}
|
|
632
639
|
// Memory + fit stay gated on --with-history (scoring semantics unchanged).
|
|
633
640
|
const recentlyTouched =
|
|
634
641
|
opts.withHistory && opts.snapshot ? accountRecentlyTouched(domain, opts.snapshot, now) : false;
|
package/src/judgeEval.ts
CHANGED
|
@@ -205,7 +205,14 @@ export function defaultJudgeFn(opts: { config?: SignalsConfig; icp?: Icp; now?:
|
|
|
205
205
|
// ---------------------------------------------------------------------------
|
|
206
206
|
// The default golden set (ships inline so `icp eval --golden default` is free)
|
|
207
207
|
|
|
208
|
-
|
|
208
|
+
/**
|
|
209
|
+
* The default golden set's clock. Every `firstSeen` below is relative to this
|
|
210
|
+
* instant, so grading MUST pin `now: new Date(DEFAULT_GOLDEN_NOW_ISO)` —
|
|
211
|
+
* grading against wall time lets freshness decay rot the "fresh → send" rows,
|
|
212
|
+
* and the gate starts failing on a calendar date instead of a code change.
|
|
213
|
+
*/
|
|
214
|
+
export const DEFAULT_GOLDEN_NOW_ISO = "2026-06-23T12:00:00.000Z";
|
|
215
|
+
const GOLD_NOW_ISO = DEFAULT_GOLDEN_NOW_ISO;
|
|
209
216
|
|
|
210
217
|
function goldSignal(over: {
|
|
211
218
|
accountDomain: string;
|
package/src/marketClassify.ts
CHANGED
|
@@ -113,6 +113,8 @@ export type ClassifyMarketOptions = {
|
|
|
113
113
|
/** Captures directory override (tests); defaults to the profile market home. */
|
|
114
114
|
capturesDir?: string;
|
|
115
115
|
now?: () => Date;
|
|
116
|
+
/** Per-vendor progress (presentation only — a throwing callback never fails the run). */
|
|
117
|
+
onVendor?: (done: number, total: number, vendorId: string) => void;
|
|
116
118
|
};
|
|
117
119
|
|
|
118
120
|
export type ClassifyMarketResult = {
|
|
@@ -143,7 +145,12 @@ export async function classifyMarket(
|
|
|
143
145
|
const observations: MarketObservation[] = [];
|
|
144
146
|
const retriedVendorIds: string[] = [];
|
|
145
147
|
|
|
146
|
-
for (const vendorId of vendorIds) {
|
|
148
|
+
for (const [vendorIndex, vendorId] of vendorIds.entries()) {
|
|
149
|
+
try {
|
|
150
|
+
options.onVendor?.(vendorIndex, vendorIds.length, vendorId);
|
|
151
|
+
} catch {
|
|
152
|
+
// progress is presentation-only
|
|
153
|
+
}
|
|
147
154
|
const vendor = config.vendors.find((candidate) => candidate.id === vendorId);
|
|
148
155
|
if (!vendor) throw new Error(`Unknown vendor "${vendorId}"`);
|
|
149
156
|
const vendorEntries = runEntries.filter((entry) => entry.vendorId === vendorId);
|
package/src/mcp-bin.ts
CHANGED
|
@@ -9,8 +9,8 @@ const MISSING_PEER_HELP = `The MCP server needs the optional peer dependencies @
|
|
|
9
9
|
In a project:
|
|
10
10
|
npm install fullstackgtm @modelcontextprotocol/sdk zod
|
|
11
11
|
|
|
12
|
-
Zero-install:
|
|
13
|
-
npx -
|
|
12
|
+
Zero-install (the fullstackgtm-mcp wrapper package bundles the peers):
|
|
13
|
+
npx -y fullstackgtm-mcp`;
|
|
14
14
|
|
|
15
15
|
function isMissingPeerError(error: unknown): boolean {
|
|
16
16
|
if (!(error instanceof Error)) return false;
|
package/src/mcp.ts
CHANGED
|
@@ -38,6 +38,8 @@ const { StdioServerTransport } = await importPeer<typeof import("@modelcontextpr
|
|
|
38
38
|
);
|
|
39
39
|
const { z } = await importPeer<typeof import("zod/v4")>("zod/v4");
|
|
40
40
|
import { auditSnapshot, defaultPolicy } from "./audit.ts";
|
|
41
|
+
import { assertCanonicalSnapshot } from "./cli/shared.ts";
|
|
42
|
+
import { nearest } from "./cli/suggest.ts";
|
|
41
43
|
import { loadConfig, mergePolicy, resolveConfiguredRules } from "./config.ts";
|
|
42
44
|
import { applyPatchPlan } from "./connector.ts";
|
|
43
45
|
import { createHubspotConnector } from "./connectors/hubspot.ts";
|
|
@@ -123,8 +125,10 @@ async function connectorFor(provider: string): Promise<GtmConnector> {
|
|
|
123
125
|
}
|
|
124
126
|
return createStripeConnector({ getApiKey: () => key });
|
|
125
127
|
}
|
|
128
|
+
const providerSuggestion = nearest(provider.toLowerCase(), ["hubspot", "salesforce", "stripe"], 3);
|
|
126
129
|
throw new Error(
|
|
127
|
-
`Unknown provider: ${provider}
|
|
130
|
+
`Unknown provider: ${provider}.${providerSuggestion ? ` Did you mean ${providerSuggestion}?` : ""} ` +
|
|
131
|
+
"Supported providers: hubspot, salesforce, stripe",
|
|
128
132
|
);
|
|
129
133
|
}
|
|
130
134
|
|
|
@@ -138,9 +142,10 @@ async function readSnapshot(
|
|
|
138
142
|
return connector.fetchSnapshot();
|
|
139
143
|
}
|
|
140
144
|
if (!inputPath) return sampleSnapshot;
|
|
141
|
-
return
|
|
142
|
-
readFileSync(resolve(process.cwd(), inputPath), "utf8"),
|
|
143
|
-
|
|
145
|
+
return assertCanonicalSnapshot(
|
|
146
|
+
JSON.parse(readFileSync(resolve(process.cwd(), inputPath), "utf8")),
|
|
147
|
+
inputPath,
|
|
148
|
+
);
|
|
144
149
|
}
|
|
145
150
|
|
|
146
151
|
function packageVersion() {
|
|
@@ -152,15 +157,29 @@ function packageVersion() {
|
|
|
152
157
|
}
|
|
153
158
|
}
|
|
154
159
|
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
+
type ToolDefinition = {
|
|
161
|
+
name: string;
|
|
162
|
+
// Whether the tool can write to a CRM. Everything else reads, or writes
|
|
163
|
+
// only local workspace state (market_observe appends to the local
|
|
164
|
+
// observation store).
|
|
165
|
+
writesCrm: boolean;
|
|
166
|
+
config: { title: string; description: string; inputSchema?: Record<string, unknown> };
|
|
167
|
+
// Args are validated by the SDK against config.inputSchema before the
|
|
168
|
+
// handler runs; the loose typing here is what lets one table drive a
|
|
169
|
+
// registration loop instead of per-call generics.
|
|
170
|
+
handler: (args: any) => Promise<ReturnType<typeof content>> | ReturnType<typeof content>;
|
|
171
|
+
};
|
|
160
172
|
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
173
|
+
// Single registration table. startMcpServer() registers exactly this list,
|
|
174
|
+
// and fullstackgtm_capabilities reports its names from the same array — the
|
|
175
|
+
// advertised inventory cannot drift from what is actually registered.
|
|
176
|
+
// (A hand-written parallel tool list was a core defect of the first pass at
|
|
177
|
+
// a capabilities tool, which reported 4 of the 8 registered tools.)
|
|
178
|
+
const toolDefinitions: ToolDefinition[] = [
|
|
179
|
+
{
|
|
180
|
+
name: "fullstackgtm_audit",
|
|
181
|
+
writesCrm: false,
|
|
182
|
+
config: {
|
|
164
183
|
title: "GTM Ops Audit",
|
|
165
184
|
description:
|
|
166
185
|
"Run a dry-run GTM hygiene audit and return a reviewable patch plan. " +
|
|
@@ -176,23 +195,23 @@ export async function startMcpServer() {
|
|
|
176
195
|
staleDealDays: z.number().int().positive().optional(),
|
|
177
196
|
},
|
|
178
197
|
},
|
|
179
|
-
async ({ provider, inputPath, configPath, rules, output, today, staleDealDays }) => {
|
|
198
|
+
handler: async ({ provider, inputPath, configPath, rules, output, today, staleDealDays }) => {
|
|
180
199
|
const loaded = configPath ? loadConfig(configPath) : null;
|
|
181
200
|
const policy = mergePolicy(defaultPolicy(today), loaded?.config);
|
|
182
201
|
if (today) policy.today = today;
|
|
183
202
|
if (staleDealDays !== undefined) policy.staleDealDays = staleDealDays;
|
|
184
203
|
const ruleSet = await resolveConfiguredRules(loaded);
|
|
185
204
|
const selected = rules?.length
|
|
186
|
-
? ruleSet.filter((rule) => rules.includes(rule.id))
|
|
205
|
+
? ruleSet.filter((rule: { id: string }) => rules.includes(rule.id))
|
|
187
206
|
: ruleSet;
|
|
188
207
|
const plan = auditSnapshot(await readSnapshot(provider, inputPath), policy, selected);
|
|
189
208
|
return content(output === "markdown" ? patchPlanToMarkdown(plan) : plan);
|
|
190
209
|
},
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
{
|
|
210
|
+
},
|
|
211
|
+
{
|
|
212
|
+
name: "fullstackgtm_suggest",
|
|
213
|
+
writesCrm: false,
|
|
214
|
+
config: {
|
|
196
215
|
title: "Suggest Placeholder Values",
|
|
197
216
|
description:
|
|
198
217
|
"Derive values for a plan's requires_human_* placeholder operations from snapshot " +
|
|
@@ -204,16 +223,16 @@ export async function startMcpServer() {
|
|
|
204
223
|
inputPath: z.string().optional(),
|
|
205
224
|
},
|
|
206
225
|
},
|
|
207
|
-
async ({ planPath, provider, inputPath }) => {
|
|
226
|
+
handler: async ({ planPath, provider, inputPath }) => {
|
|
208
227
|
const plan = JSON.parse(readFileSync(resolve(process.cwd(), planPath), "utf8")) as PatchPlan;
|
|
209
228
|
const snapshot = await readSnapshot(provider, inputPath);
|
|
210
229
|
return content({ suggestions: suggestValues(plan, snapshot) });
|
|
211
230
|
},
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
{
|
|
231
|
+
},
|
|
232
|
+
{
|
|
233
|
+
name: "fullstackgtm_call_parse",
|
|
234
|
+
writesCrm: false,
|
|
235
|
+
config: {
|
|
217
236
|
title: "Parse Call Transcript",
|
|
218
237
|
description:
|
|
219
238
|
"Parse a call transcript (Speaker:/[Speaker]: lines or Granola utterance JSON) into " +
|
|
@@ -230,7 +249,7 @@ export async function startMcpServer() {
|
|
|
230
249
|
model: z.string().optional(),
|
|
231
250
|
},
|
|
232
251
|
},
|
|
233
|
-
async ({ transcript, transcriptPath, title, source, extractor, model }) => {
|
|
252
|
+
handler: async ({ transcript, transcriptPath, title, source, extractor, model }) => {
|
|
234
253
|
const raw =
|
|
235
254
|
transcript ??
|
|
236
255
|
(transcriptPath ? readFileSync(resolve(process.cwd(), transcriptPath), "utf8") : null);
|
|
@@ -255,11 +274,11 @@ export async function startMcpServer() {
|
|
|
255
274
|
}
|
|
256
275
|
return content(parseCall(raw, { title, sourceSystem: source }));
|
|
257
276
|
},
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
{
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
name: "fullstackgtm_resolve",
|
|
280
|
+
writesCrm: false,
|
|
281
|
+
config: {
|
|
263
282
|
title: "Resolve Record (create gate)",
|
|
264
283
|
description:
|
|
265
284
|
"Before creating a CRM record, check whether it already exists. Returns a verdict " +
|
|
@@ -276,29 +295,29 @@ export async function startMcpServer() {
|
|
|
276
295
|
inputPath: z.string().optional(),
|
|
277
296
|
},
|
|
278
297
|
},
|
|
279
|
-
async ({ objectType, name, domain, email, accountId, provider, inputPath }) => {
|
|
298
|
+
handler: async ({ objectType, name, domain, email, accountId, provider, inputPath }) => {
|
|
280
299
|
const snapshot = await readSnapshot(provider, inputPath);
|
|
281
300
|
return content(resolveRecord(snapshot, { objectType, name, domain, email, accountId }));
|
|
282
301
|
},
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
{
|
|
302
|
+
},
|
|
303
|
+
{
|
|
304
|
+
name: "fullstackgtm_rules",
|
|
305
|
+
writesCrm: false,
|
|
306
|
+
config: {
|
|
288
307
|
title: "List Audit Rules",
|
|
289
308
|
description: "List the built-in deterministic audit rules with ids and descriptions.",
|
|
290
309
|
inputSchema: {},
|
|
291
310
|
},
|
|
292
|
-
async () => {
|
|
311
|
+
handler: async () => {
|
|
293
312
|
return content(
|
|
294
313
|
builtinAuditRules.map(({ id, title, description }) => ({ id, title, description })),
|
|
295
314
|
);
|
|
296
315
|
},
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
{
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
name: "fullstackgtm_apply",
|
|
319
|
+
writesCrm: true,
|
|
320
|
+
config: {
|
|
302
321
|
title: "Apply Approved Patch Operations",
|
|
303
322
|
description:
|
|
304
323
|
"Apply explicitly approved operations from a patch plan through a provider " +
|
|
@@ -312,7 +331,7 @@ export async function startMcpServer() {
|
|
|
312
331
|
output: z.enum(["json", "markdown"]).optional(),
|
|
313
332
|
},
|
|
314
333
|
},
|
|
315
|
-
async ({ provider, planPath, approvedOperationIds, valueOverrides, output }) => {
|
|
334
|
+
handler: async ({ provider, planPath, approvedOperationIds, valueOverrides, output }) => {
|
|
316
335
|
const plan = JSON.parse(
|
|
317
336
|
readFileSync(resolve(process.cwd(), planPath), "utf8"),
|
|
318
337
|
) as PatchPlan;
|
|
@@ -322,11 +341,11 @@ export async function startMcpServer() {
|
|
|
322
341
|
});
|
|
323
342
|
return content(output === "markdown" ? formatPatchPlanRun(run) : run);
|
|
324
343
|
},
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
{
|
|
344
|
+
},
|
|
345
|
+
{
|
|
346
|
+
name: "fullstackgtm_market_worksheet",
|
|
347
|
+
writesCrm: false,
|
|
348
|
+
config: {
|
|
330
349
|
title: "Market Map Classification Worksheet",
|
|
331
350
|
description:
|
|
332
351
|
"Get everything needed to classify ONE vendor's messaging intensity for a market map: " +
|
|
@@ -341,15 +360,15 @@ export async function startMcpServer() {
|
|
|
341
360
|
captureRun: z.string().optional(),
|
|
342
361
|
},
|
|
343
362
|
},
|
|
344
|
-
async ({ vendorId, configPath, captureRun }) => {
|
|
363
|
+
handler: async ({ vendorId, configPath, captureRun }) => {
|
|
345
364
|
const config = loadMarketConfigOrHint(resolve(process.cwd(), configPath ?? "market.config.json"));
|
|
346
365
|
return content(buildWorksheet(config, vendorId, { captureRun }));
|
|
347
366
|
},
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
{
|
|
367
|
+
},
|
|
368
|
+
{
|
|
369
|
+
name: "fullstackgtm_market_observe",
|
|
370
|
+
writesCrm: false,
|
|
371
|
+
config: {
|
|
353
372
|
title: "Submit Market Map Observations",
|
|
354
373
|
description:
|
|
355
374
|
"Submit a complete ObservationSet (every vendor × claim cell) for a market map run. " +
|
|
@@ -361,7 +380,7 @@ export async function startMcpServer() {
|
|
|
361
380
|
configPath: z.string().optional().describe("Path to market.config.json (default ./market.config.json)"),
|
|
362
381
|
},
|
|
363
382
|
},
|
|
364
|
-
async ({ observationsPath, configPath }) => {
|
|
383
|
+
handler: async ({ observationsPath, configPath }) => {
|
|
365
384
|
const config = loadMarketConfigOrHint(resolve(process.cwd(), configPath ?? "market.config.json"));
|
|
366
385
|
const set = JSON.parse(readFileSync(resolve(process.cwd(), observationsPath), "utf8")) as ObservationSet;
|
|
367
386
|
const problems = validateObservationSet(config, set);
|
|
@@ -377,7 +396,61 @@ export async function startMcpServer() {
|
|
|
377
396
|
const fronts = computeFrontStates(config, set);
|
|
378
397
|
return content({ accepted: true, runLabel: set.runLabel, observations: set.observations.length, fronts });
|
|
379
398
|
},
|
|
380
|
-
|
|
399
|
+
},
|
|
400
|
+
];
|
|
401
|
+
|
|
402
|
+
// Registered first so agents that list tools see the contract entry up top.
|
|
403
|
+
// Its payload closes over the registration table, so the reported inventory
|
|
404
|
+
// is derived, not hand-maintained.
|
|
405
|
+
toolDefinitions.unshift({
|
|
406
|
+
name: "fullstackgtm_capabilities",
|
|
407
|
+
writesCrm: false,
|
|
408
|
+
config: {
|
|
409
|
+
title: "FullStackGTM Capabilities",
|
|
410
|
+
description:
|
|
411
|
+
"Machine-readable contract for this MCP server: the tool inventory (derived from the " +
|
|
412
|
+
"server's own registration table, with per-tool CRM-write flags), safety defaults, and " +
|
|
413
|
+
"the CLI entrypoints for everything the tools don't cover.",
|
|
414
|
+
},
|
|
415
|
+
handler: async () =>
|
|
416
|
+
content({
|
|
417
|
+
ok: true,
|
|
418
|
+
tool: "fullstackgtm",
|
|
419
|
+
version: packageVersion(),
|
|
420
|
+
planFirst: true,
|
|
421
|
+
mcpTools: toolDefinitions.map(({ name, writesCrm, config }) => ({
|
|
422
|
+
name,
|
|
423
|
+
title: config.title,
|
|
424
|
+
writesCrm,
|
|
425
|
+
})),
|
|
426
|
+
safety:
|
|
427
|
+
"Only tools flagged writesCrm can reach a CRM; fullstackgtm_apply writes only operations " +
|
|
428
|
+
"listed in approvedOperationIds and never writes requires_human_* placeholders without a value override.",
|
|
429
|
+
server: { command: "npx -y fullstackgtm-mcp" },
|
|
430
|
+
cli: {
|
|
431
|
+
command: "npx fullstackgtm <command> [--json]",
|
|
432
|
+
capabilities: "npx fullstackgtm capabilities --json",
|
|
433
|
+
agentGuide: "npx fullstackgtm robot-docs",
|
|
434
|
+
},
|
|
435
|
+
examples: [
|
|
436
|
+
"npx -y fullstackgtm-mcp",
|
|
437
|
+
"npx fullstackgtm capabilities --json",
|
|
438
|
+
"npx fullstackgtm audit --demo --json",
|
|
439
|
+
],
|
|
440
|
+
}),
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
export async function startMcpServer() {
|
|
444
|
+
const server = new McpServer({
|
|
445
|
+
name: "fullstackgtm",
|
|
446
|
+
version: packageVersion(),
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
for (const tool of toolDefinitions) {
|
|
450
|
+
// The table is loosely typed so it can drive one loop; the SDK still
|
|
451
|
+
// validates every call against the zod inputSchema at runtime.
|
|
452
|
+
server.registerTool(tool.name, tool.config as never, tool.handler as never);
|
|
453
|
+
}
|
|
381
454
|
|
|
382
455
|
const transport = new StdioServerTransport();
|
|
383
456
|
await server.connect(transport);
|
package/src/runReport.ts
CHANGED
|
@@ -11,7 +11,34 @@ import { getCredential } from "./credentials.ts";
|
|
|
11
11
|
|
|
12
12
|
type RunEvent = { ts: number; type: string; detail?: string };
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* Per-row finding for the hosted run timeline. IDs + issue type ONLY — no field
|
|
16
|
+
* values ever leave the CLI, keeping the audit's row data on the operator's side
|
|
17
|
+
* while still giving the dashboard navigable, filterable detail.
|
|
18
|
+
*/
|
|
19
|
+
export type RunFinding = {
|
|
20
|
+
objectType: string;
|
|
21
|
+
objectId: string;
|
|
22
|
+
severity: string;
|
|
23
|
+
ruleId: string;
|
|
24
|
+
field?: string;
|
|
25
|
+
operation?: string;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Where a run's findings live in the source CRM, so the dashboard can deep-link
|
|
30
|
+
* each record. `recordUrlBase` is the provider's record URL prefix; the
|
|
31
|
+
* dashboard appends the per-object path. Just an URL prefix — no record data.
|
|
32
|
+
*/
|
|
33
|
+
export type RunCrm = { provider: string; recordUrlBase: string };
|
|
34
|
+
|
|
35
|
+
// Bound the fire-and-forget POST: a pathological audit could surface thousands
|
|
36
|
+
// of findings; cap what we ship (counts still carry the true total).
|
|
37
|
+
const MAX_REPORTED_FINDINGS = 2000;
|
|
38
|
+
|
|
14
39
|
let counts: Record<string, unknown> | undefined;
|
|
40
|
+
let findings: RunFinding[] | undefined;
|
|
41
|
+
let crm: RunCrm | undefined;
|
|
15
42
|
const events: RunEvent[] = [];
|
|
16
43
|
|
|
17
44
|
/** A command annotates its headline metrics (merged). */
|
|
@@ -19,6 +46,16 @@ export function reportCounts(values: Record<string, unknown>): void {
|
|
|
19
46
|
counts = { ...(counts ?? {}), ...values };
|
|
20
47
|
}
|
|
21
48
|
|
|
49
|
+
/** A command reports per-row findings (IDs + issue type, no values). */
|
|
50
|
+
export function reportFindings(values: RunFinding[]): void {
|
|
51
|
+
findings = values.slice(0, MAX_REPORTED_FINDINGS);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** A command reports the source CRM's record-URL base for dashboard deep-links. */
|
|
55
|
+
export function reportCrm(value: RunCrm): void {
|
|
56
|
+
crm = value;
|
|
57
|
+
}
|
|
58
|
+
|
|
22
59
|
/** A command annotates a structured event (plan saved, meter charged, …). */
|
|
23
60
|
export function reportEvent(type: string, detail?: string): void {
|
|
24
61
|
events.push({ ts: Date.now(), type, detail });
|
|
@@ -65,6 +102,8 @@ export async function flushRunReport(
|
|
|
65
102
|
finishedAt,
|
|
66
103
|
durationMs: finishedAt - startedAt,
|
|
67
104
|
counts,
|
|
105
|
+
findings: findings?.length ? findings : undefined,
|
|
106
|
+
crm,
|
|
68
107
|
events: events.length ? events : undefined,
|
|
69
108
|
error,
|
|
70
109
|
}),
|