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/dist/mcp.js
CHANGED
|
@@ -42,6 +42,8 @@ const { McpServer } = await importPeer("@modelcontextprotocol/sdk/server/mcp.js"
|
|
|
42
42
|
const { StdioServerTransport } = await importPeer("@modelcontextprotocol/sdk/server/stdio.js");
|
|
43
43
|
const { z } = await importPeer("zod/v4");
|
|
44
44
|
import { auditSnapshot, defaultPolicy } from "./audit.js";
|
|
45
|
+
import { assertCanonicalSnapshot } from "./cli/shared.js";
|
|
46
|
+
import { nearest } from "./cli/suggest.js";
|
|
45
47
|
import { loadConfig, mergePolicy, resolveConfiguredRules } from "./config.js";
|
|
46
48
|
import { applyPatchPlan } from "./connector.js";
|
|
47
49
|
import { createHubspotConnector } from "./connectors/hubspot.js";
|
|
@@ -101,7 +103,9 @@ async function connectorFor(provider) {
|
|
|
101
103
|
}
|
|
102
104
|
return createStripeConnector({ getApiKey: () => key });
|
|
103
105
|
}
|
|
104
|
-
|
|
106
|
+
const providerSuggestion = nearest(provider.toLowerCase(), ["hubspot", "salesforce", "stripe"], 3);
|
|
107
|
+
throw new Error(`Unknown provider: ${provider}.${providerSuggestion ? ` Did you mean ${providerSuggestion}?` : ""} ` +
|
|
108
|
+
"Supported providers: hubspot, salesforce, stripe");
|
|
105
109
|
}
|
|
106
110
|
async function readSnapshot(provider, inputPath) {
|
|
107
111
|
if (provider === "demo")
|
|
@@ -112,7 +116,7 @@ async function readSnapshot(provider, inputPath) {
|
|
|
112
116
|
}
|
|
113
117
|
if (!inputPath)
|
|
114
118
|
return sampleSnapshot;
|
|
115
|
-
return JSON.parse(readFileSync(resolve(process.cwd(), inputPath), "utf8"));
|
|
119
|
+
return assertCanonicalSnapshot(JSON.parse(readFileSync(resolve(process.cwd(), inputPath), "utf8")), inputPath);
|
|
116
120
|
}
|
|
117
121
|
function packageVersion() {
|
|
118
122
|
try {
|
|
@@ -123,179 +127,268 @@ function packageVersion() {
|
|
|
123
127
|
return "0.0.0";
|
|
124
128
|
}
|
|
125
129
|
}
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
130
|
+
// Single registration table. startMcpServer() registers exactly this list,
|
|
131
|
+
// and fullstackgtm_capabilities reports its names from the same array — the
|
|
132
|
+
// advertised inventory cannot drift from what is actually registered.
|
|
133
|
+
// (A hand-written parallel tool list was a core defect of the first pass at
|
|
134
|
+
// a capabilities tool, which reported 4 of the 8 registered tools.)
|
|
135
|
+
const toolDefinitions = [
|
|
136
|
+
{
|
|
137
|
+
name: "fullstackgtm_audit",
|
|
138
|
+
writesCrm: false,
|
|
139
|
+
config: {
|
|
140
|
+
title: "GTM Ops Audit",
|
|
141
|
+
description: "Run a dry-run GTM hygiene audit and return a reviewable patch plan. " +
|
|
142
|
+
"Sources: the realistic zero-credential demo CRM (provider: \"demo\" — richest test data), " +
|
|
143
|
+
"the minimal sample dataset, a snapshot file, or a live provider.",
|
|
144
|
+
inputSchema: {
|
|
145
|
+
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
146
|
+
inputPath: z.string().optional(),
|
|
147
|
+
configPath: z.string().optional(),
|
|
148
|
+
rules: z.array(z.string()).optional(),
|
|
149
|
+
output: z.enum(["json", "markdown"]).optional(),
|
|
150
|
+
today: z.string().optional(),
|
|
151
|
+
staleDealDays: z.number().int().positive().optional(),
|
|
152
|
+
},
|
|
144
153
|
},
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
});
|
|
159
|
-
server.registerTool("fullstackgtm_suggest", {
|
|
160
|
-
title: "Suggest Placeholder Values",
|
|
161
|
-
description: "Derive values for a plan's requires_human_* placeholder operations from snapshot " +
|
|
162
|
-
"evidence (account-name matching, contact associations), with confidence levels and " +
|
|
163
|
-
"reasons. Read-only; feed accepted values into fullstackgtm_apply's valueOverrides.",
|
|
164
|
-
inputSchema: {
|
|
165
|
-
planPath: z.string(),
|
|
166
|
-
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
167
|
-
inputPath: z.string().optional(),
|
|
154
|
+
handler: async ({ provider, inputPath, configPath, rules, output, today, staleDealDays }) => {
|
|
155
|
+
const loaded = configPath ? loadConfig(configPath) : null;
|
|
156
|
+
const policy = mergePolicy(defaultPolicy(today), loaded?.config);
|
|
157
|
+
if (today)
|
|
158
|
+
policy.today = today;
|
|
159
|
+
if (staleDealDays !== undefined)
|
|
160
|
+
policy.staleDealDays = staleDealDays;
|
|
161
|
+
const ruleSet = await resolveConfiguredRules(loaded);
|
|
162
|
+
const selected = rules?.length
|
|
163
|
+
? ruleSet.filter((rule) => rules.includes(rule.id))
|
|
164
|
+
: ruleSet;
|
|
165
|
+
const plan = auditSnapshot(await readSnapshot(provider, inputPath), policy, selected);
|
|
166
|
+
return content(output === "markdown" ? patchPlanToMarkdown(plan) : plan);
|
|
168
167
|
},
|
|
169
|
-
},
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
transcriptPath: z.string().optional(),
|
|
184
|
-
title: z.string().optional(),
|
|
185
|
-
source: z.enum(["gong", "chorus", "fathom", "manual", "csv", "unknown"]).optional(),
|
|
186
|
-
extractor: z.enum(["auto", "llm", "deterministic"]).optional(),
|
|
187
|
-
model: z.string().optional(),
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
name: "fullstackgtm_suggest",
|
|
171
|
+
writesCrm: false,
|
|
172
|
+
config: {
|
|
173
|
+
title: "Suggest Placeholder Values",
|
|
174
|
+
description: "Derive values for a plan's requires_human_* placeholder operations from snapshot " +
|
|
175
|
+
"evidence (account-name matching, contact associations), with confidence levels and " +
|
|
176
|
+
"reasons. Read-only; feed accepted values into fullstackgtm_apply's valueOverrides.",
|
|
177
|
+
inputSchema: {
|
|
178
|
+
planPath: z.string(),
|
|
179
|
+
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
180
|
+
inputPath: z.string().optional(),
|
|
181
|
+
},
|
|
188
182
|
},
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
throw new Error("Provide transcript (text) or transcriptPath (file).");
|
|
194
|
-
const mode = extractor ?? "auto";
|
|
195
|
-
const credential = mode === "deterministic" ? null : resolveLlmCredential();
|
|
196
|
-
if (mode === "llm" && !credential) {
|
|
197
|
-
throw new Error("extractor 'llm' needs an API key: set ANTHROPIC_API_KEY or OPENAI_API_KEY in the MCP server environment, or store one with `fullstackgtm login anthropic|openai`.");
|
|
198
|
-
}
|
|
199
|
-
if (credential) {
|
|
200
|
-
const normalized = normalizeTranscript(raw);
|
|
201
|
-
const { insights, model: used } = await extractInsightsLlm(normalized, {
|
|
202
|
-
...credential,
|
|
203
|
-
model,
|
|
204
|
-
title,
|
|
205
|
-
});
|
|
206
|
-
return content(parseCall(raw, { title, sourceSystem: source, insights, extractor: `llm:${credential.provider}:${used}` }));
|
|
207
|
-
}
|
|
208
|
-
return content(parseCall(raw, { title, sourceSystem: source }));
|
|
209
|
-
});
|
|
210
|
-
server.registerTool("fullstackgtm_resolve", {
|
|
211
|
-
title: "Resolve Record (create gate)",
|
|
212
|
-
description: "Before creating a CRM record, check whether it already exists. Returns a verdict " +
|
|
213
|
-
"(exists | ambiguous | safe_to_create) with matches and a reason, using the same " +
|
|
214
|
-
"identity keys as the audit/merge engines (account domain, contact email, open-deal " +
|
|
215
|
-
"key). Read-only. Never create on 'exists' or 'ambiguous'.",
|
|
216
|
-
inputSchema: {
|
|
217
|
-
objectType: z.enum(["account", "contact", "deal"]),
|
|
218
|
-
name: z.string().optional(),
|
|
219
|
-
domain: z.string().optional(),
|
|
220
|
-
email: z.string().optional(),
|
|
221
|
-
accountId: z.string().optional(),
|
|
222
|
-
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
223
|
-
inputPath: z.string().optional(),
|
|
183
|
+
handler: async ({ planPath, provider, inputPath }) => {
|
|
184
|
+
const plan = JSON.parse(readFileSync(resolve(process.cwd(), planPath), "utf8"));
|
|
185
|
+
const snapshot = await readSnapshot(provider, inputPath);
|
|
186
|
+
return content({ suggestions: suggestValues(plan, snapshot) });
|
|
224
187
|
},
|
|
225
|
-
},
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
approvedOperationIds: z.array(z.string()).min(1),
|
|
245
|
-
valueOverrides: z.record(z.string(), z.string()).optional(),
|
|
246
|
-
output: z.enum(["json", "markdown"]).optional(),
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
name: "fullstackgtm_call_parse",
|
|
191
|
+
writesCrm: false,
|
|
192
|
+
config: {
|
|
193
|
+
title: "Parse Call Transcript",
|
|
194
|
+
description: "Parse a call transcript (Speaker:/[Speaker]: lines or Granola utterance JSON) into " +
|
|
195
|
+
"canonical segments, insights, and GtmEvidence records. extractor: 'auto' (default) " +
|
|
196
|
+
"uses LLM extraction when an Anthropic/OpenAI key is configured in the server " +
|
|
197
|
+
"environment or credential store, else the free deterministic keyword baseline; " +
|
|
198
|
+
"'llm' and 'deterministic' force either. Read-only; every insight is provenance-marked.",
|
|
199
|
+
inputSchema: {
|
|
200
|
+
transcript: z.string().optional(),
|
|
201
|
+
transcriptPath: z.string().optional(),
|
|
202
|
+
title: z.string().optional(),
|
|
203
|
+
source: z.enum(["gong", "chorus", "fathom", "manual", "csv", "unknown"]).optional(),
|
|
204
|
+
extractor: z.enum(["auto", "llm", "deterministic"]).optional(),
|
|
205
|
+
model: z.string().optional(),
|
|
206
|
+
},
|
|
247
207
|
},
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
208
|
+
handler: async ({ transcript, transcriptPath, title, source, extractor, model }) => {
|
|
209
|
+
const raw = transcript ??
|
|
210
|
+
(transcriptPath ? readFileSync(resolve(process.cwd(), transcriptPath), "utf8") : null);
|
|
211
|
+
if (!raw)
|
|
212
|
+
throw new Error("Provide transcript (text) or transcriptPath (file).");
|
|
213
|
+
const mode = extractor ?? "auto";
|
|
214
|
+
const credential = mode === "deterministic" ? null : resolveLlmCredential();
|
|
215
|
+
if (mode === "llm" && !credential) {
|
|
216
|
+
throw new Error("extractor 'llm' needs an API key: set ANTHROPIC_API_KEY or OPENAI_API_KEY in the MCP server environment, or store one with `fullstackgtm login anthropic|openai`.");
|
|
217
|
+
}
|
|
218
|
+
if (credential) {
|
|
219
|
+
const normalized = normalizeTranscript(raw);
|
|
220
|
+
const { insights, model: used } = await extractInsightsLlm(normalized, {
|
|
221
|
+
...credential,
|
|
222
|
+
model,
|
|
223
|
+
title,
|
|
224
|
+
});
|
|
225
|
+
return content(parseCall(raw, { title, sourceSystem: source, insights, extractor: `llm:${credential.provider}:${used}` }));
|
|
226
|
+
}
|
|
227
|
+
return content(parseCall(raw, { title, sourceSystem: source }));
|
|
268
228
|
},
|
|
269
|
-
},
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
name: "fullstackgtm_resolve",
|
|
232
|
+
writesCrm: false,
|
|
233
|
+
config: {
|
|
234
|
+
title: "Resolve Record (create gate)",
|
|
235
|
+
description: "Before creating a CRM record, check whether it already exists. Returns a verdict " +
|
|
236
|
+
"(exists | ambiguous | safe_to_create) with matches and a reason, using the same " +
|
|
237
|
+
"identity keys as the audit/merge engines (account domain, contact email, open-deal " +
|
|
238
|
+
"key). Read-only. Never create on 'exists' or 'ambiguous'.",
|
|
239
|
+
inputSchema: {
|
|
240
|
+
objectType: z.enum(["account", "contact", "deal"]),
|
|
241
|
+
name: z.string().optional(),
|
|
242
|
+
domain: z.string().optional(),
|
|
243
|
+
email: z.string().optional(),
|
|
244
|
+
accountId: z.string().optional(),
|
|
245
|
+
provider: z.enum(["sample", "demo", "hubspot", "salesforce", "stripe"]).optional(),
|
|
246
|
+
inputPath: z.string().optional(),
|
|
247
|
+
},
|
|
282
248
|
},
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
249
|
+
handler: async ({ objectType, name, domain, email, accountId, provider, inputPath }) => {
|
|
250
|
+
const snapshot = await readSnapshot(provider, inputPath);
|
|
251
|
+
return content(resolveRecord(snapshot, { objectType, name, domain, email, accountId }));
|
|
252
|
+
},
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
name: "fullstackgtm_rules",
|
|
256
|
+
writesCrm: false,
|
|
257
|
+
config: {
|
|
258
|
+
title: "List Audit Rules",
|
|
259
|
+
description: "List the built-in deterministic audit rules with ids and descriptions.",
|
|
260
|
+
inputSchema: {},
|
|
261
|
+
},
|
|
262
|
+
handler: async () => {
|
|
263
|
+
return content(builtinAuditRules.map(({ id, title, description }) => ({ id, title, description })));
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
name: "fullstackgtm_apply",
|
|
268
|
+
writesCrm: true,
|
|
269
|
+
config: {
|
|
270
|
+
title: "Apply Approved Patch Operations",
|
|
271
|
+
description: "Apply explicitly approved operations from a patch plan through a provider " +
|
|
272
|
+
"connector. Operations not listed in approvedOperationIds are never written, " +
|
|
273
|
+
"and requires_human_* placeholders need a value override.",
|
|
274
|
+
inputSchema: {
|
|
275
|
+
provider: z.enum(["hubspot", "salesforce"]),
|
|
276
|
+
planPath: z.string(),
|
|
277
|
+
approvedOperationIds: z.array(z.string()).min(1),
|
|
278
|
+
valueOverrides: z.record(z.string(), z.string()).optional(),
|
|
279
|
+
output: z.enum(["json", "markdown"]).optional(),
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
handler: async ({ provider, planPath, approvedOperationIds, valueOverrides, output }) => {
|
|
283
|
+
const plan = JSON.parse(readFileSync(resolve(process.cwd(), planPath), "utf8"));
|
|
284
|
+
const run = await applyPatchPlan(await connectorFor(provider), plan, {
|
|
285
|
+
approvedOperationIds,
|
|
286
|
+
valueOverrides,
|
|
293
287
|
});
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
288
|
+
return content(output === "markdown" ? formatPatchPlanRun(run) : run);
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
name: "fullstackgtm_market_worksheet",
|
|
293
|
+
writesCrm: false,
|
|
294
|
+
config: {
|
|
295
|
+
title: "Market Map Classification Worksheet",
|
|
296
|
+
description: "Get everything needed to classify ONE vendor's messaging intensity for a market map: " +
|
|
297
|
+
"the claim taxonomy with judging definitions, the surface rule, and the captured page " +
|
|
298
|
+
"texts. Read each claim's definition, judge loud/quiet/absent from the page texts only, " +
|
|
299
|
+
"and quote verbatim spans (≤300 chars) for every loud/quiet reading. Submit the full " +
|
|
300
|
+
"ObservationSet via fullstackgtm_market_observe — quotes are verified character-for-" +
|
|
301
|
+
"character against the captures, so never paraphrase.",
|
|
302
|
+
inputSchema: {
|
|
303
|
+
vendorId: z.string(),
|
|
304
|
+
configPath: z.string().optional().describe("Path to market.config.json (default ./market.config.json)"),
|
|
305
|
+
captureRun: z.string().optional(),
|
|
306
|
+
},
|
|
307
|
+
},
|
|
308
|
+
handler: async ({ vendorId, configPath, captureRun }) => {
|
|
309
|
+
const config = loadMarketConfigOrHint(resolve(process.cwd(), configPath ?? "market.config.json"));
|
|
310
|
+
return content(buildWorksheet(config, vendorId, { captureRun }));
|
|
311
|
+
},
|
|
312
|
+
},
|
|
313
|
+
{
|
|
314
|
+
name: "fullstackgtm_market_observe",
|
|
315
|
+
writesCrm: false,
|
|
316
|
+
config: {
|
|
317
|
+
title: "Submit Market Map Observations",
|
|
318
|
+
description: "Submit a complete ObservationSet (every vendor × claim cell) for a market map run. " +
|
|
319
|
+
"Validates coverage, the verbatim-evidence rule, and mechanically verifies every quoted " +
|
|
320
|
+
"span against the stored capture it cites. Returns problems if rejected; nothing is " +
|
|
321
|
+
"stored unless the whole set passes. Observations are append-only — use a new runLabel.",
|
|
322
|
+
inputSchema: {
|
|
323
|
+
observationsPath: z.string().describe("Path to the ObservationSet JSON file"),
|
|
324
|
+
configPath: z.string().optional().describe("Path to market.config.json (default ./market.config.json)"),
|
|
325
|
+
},
|
|
326
|
+
},
|
|
327
|
+
handler: async ({ observationsPath, configPath }) => {
|
|
328
|
+
const config = loadMarketConfigOrHint(resolve(process.cwd(), configPath ?? "market.config.json"));
|
|
329
|
+
const set = JSON.parse(readFileSync(resolve(process.cwd(), observationsPath), "utf8"));
|
|
330
|
+
const problems = validateObservationSet(config, set);
|
|
331
|
+
const failures = verifyEvidenceSpans(set.observations, loadCaptureTexts(config.category).textByHash);
|
|
332
|
+
if (problems.length > 0 || failures.length > 0) {
|
|
333
|
+
return content({
|
|
334
|
+
accepted: false,
|
|
335
|
+
problems,
|
|
336
|
+
spanFailures: failures.map((failure) => `${failure.vendorId} × ${failure.claimId}: ${failure.problem}`),
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
await createFileObservationStore(config.category).append(set);
|
|
340
|
+
const fronts = computeFrontStates(config, set);
|
|
341
|
+
return content({ accepted: true, runLabel: set.runLabel, observations: set.observations.length, fronts });
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
];
|
|
345
|
+
// Registered first so agents that list tools see the contract entry up top.
|
|
346
|
+
// Its payload closes over the registration table, so the reported inventory
|
|
347
|
+
// is derived, not hand-maintained.
|
|
348
|
+
toolDefinitions.unshift({
|
|
349
|
+
name: "fullstackgtm_capabilities",
|
|
350
|
+
writesCrm: false,
|
|
351
|
+
config: {
|
|
352
|
+
title: "FullStackGTM Capabilities",
|
|
353
|
+
description: "Machine-readable contract for this MCP server: the tool inventory (derived from the " +
|
|
354
|
+
"server's own registration table, with per-tool CRM-write flags), safety defaults, and " +
|
|
355
|
+
"the CLI entrypoints for everything the tools don't cover.",
|
|
356
|
+
},
|
|
357
|
+
handler: async () => content({
|
|
358
|
+
ok: true,
|
|
359
|
+
tool: "fullstackgtm",
|
|
360
|
+
version: packageVersion(),
|
|
361
|
+
planFirst: true,
|
|
362
|
+
mcpTools: toolDefinitions.map(({ name, writesCrm, config }) => ({
|
|
363
|
+
name,
|
|
364
|
+
title: config.title,
|
|
365
|
+
writesCrm,
|
|
366
|
+
})),
|
|
367
|
+
safety: "Only tools flagged writesCrm can reach a CRM; fullstackgtm_apply writes only operations " +
|
|
368
|
+
"listed in approvedOperationIds and never writes requires_human_* placeholders without a value override.",
|
|
369
|
+
server: { command: "npx -y fullstackgtm-mcp" },
|
|
370
|
+
cli: {
|
|
371
|
+
command: "npx fullstackgtm <command> [--json]",
|
|
372
|
+
capabilities: "npx fullstackgtm capabilities --json",
|
|
373
|
+
agentGuide: "npx fullstackgtm robot-docs",
|
|
374
|
+
},
|
|
375
|
+
examples: [
|
|
376
|
+
"npx -y fullstackgtm-mcp",
|
|
377
|
+
"npx fullstackgtm capabilities --json",
|
|
378
|
+
"npx fullstackgtm audit --demo --json",
|
|
379
|
+
],
|
|
380
|
+
}),
|
|
381
|
+
});
|
|
382
|
+
export async function startMcpServer() {
|
|
383
|
+
const server = new McpServer({
|
|
384
|
+
name: "fullstackgtm",
|
|
385
|
+
version: packageVersion(),
|
|
298
386
|
});
|
|
387
|
+
for (const tool of toolDefinitions) {
|
|
388
|
+
// The table is loosely typed so it can drive one loop; the SDK still
|
|
389
|
+
// validates every call against the zod inputSchema at runtime.
|
|
390
|
+
server.registerTool(tool.name, tool.config, tool.handler);
|
|
391
|
+
}
|
|
299
392
|
const transport = new StdioServerTransport();
|
|
300
393
|
await server.connect(transport);
|
|
301
394
|
}
|
package/dist/runReport.d.ts
CHANGED
|
@@ -1,5 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-row finding for the hosted run timeline. IDs + issue type ONLY — no field
|
|
3
|
+
* values ever leave the CLI, keeping the audit's row data on the operator's side
|
|
4
|
+
* while still giving the dashboard navigable, filterable detail.
|
|
5
|
+
*/
|
|
6
|
+
export type RunFinding = {
|
|
7
|
+
objectType: string;
|
|
8
|
+
objectId: string;
|
|
9
|
+
severity: string;
|
|
10
|
+
ruleId: string;
|
|
11
|
+
field?: string;
|
|
12
|
+
operation?: string;
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* Where a run's findings live in the source CRM, so the dashboard can deep-link
|
|
16
|
+
* each record. `recordUrlBase` is the provider's record URL prefix; the
|
|
17
|
+
* dashboard appends the per-object path. Just an URL prefix — no record data.
|
|
18
|
+
*/
|
|
19
|
+
export type RunCrm = {
|
|
20
|
+
provider: string;
|
|
21
|
+
recordUrlBase: string;
|
|
22
|
+
};
|
|
1
23
|
/** A command annotates its headline metrics (merged). */
|
|
2
24
|
export declare function reportCounts(values: Record<string, unknown>): void;
|
|
25
|
+
/** A command reports per-row findings (IDs + issue type, no values). */
|
|
26
|
+
export declare function reportFindings(values: RunFinding[]): void;
|
|
27
|
+
/** A command reports the source CRM's record-URL base for dashboard deep-links. */
|
|
28
|
+
export declare function reportCrm(value: RunCrm): void;
|
|
3
29
|
/** A command annotates a structured event (plan saved, meter charged, …). */
|
|
4
30
|
export declare function reportEvent(type: string, detail?: string): void;
|
|
5
31
|
/**
|
package/dist/runReport.js
CHANGED
|
@@ -8,12 +8,25 @@
|
|
|
8
8
|
* pure added value for users who granted it.
|
|
9
9
|
*/
|
|
10
10
|
import { getCredential } from "./credentials.js";
|
|
11
|
+
// Bound the fire-and-forget POST: a pathological audit could surface thousands
|
|
12
|
+
// of findings; cap what we ship (counts still carry the true total).
|
|
13
|
+
const MAX_REPORTED_FINDINGS = 2000;
|
|
11
14
|
let counts;
|
|
15
|
+
let findings;
|
|
16
|
+
let crm;
|
|
12
17
|
const events = [];
|
|
13
18
|
/** A command annotates its headline metrics (merged). */
|
|
14
19
|
export function reportCounts(values) {
|
|
15
20
|
counts = { ...(counts ?? {}), ...values };
|
|
16
21
|
}
|
|
22
|
+
/** A command reports per-row findings (IDs + issue type, no values). */
|
|
23
|
+
export function reportFindings(values) {
|
|
24
|
+
findings = values.slice(0, MAX_REPORTED_FINDINGS);
|
|
25
|
+
}
|
|
26
|
+
/** A command reports the source CRM's record-URL base for dashboard deep-links. */
|
|
27
|
+
export function reportCrm(value) {
|
|
28
|
+
crm = value;
|
|
29
|
+
}
|
|
17
30
|
/** A command annotates a structured event (plan saved, meter charged, …). */
|
|
18
31
|
export function reportEvent(type, detail) {
|
|
19
32
|
events.push({ ts: Date.now(), type, detail });
|
|
@@ -54,6 +67,8 @@ export async function flushRunReport(args, status, startedAt, error) {
|
|
|
54
67
|
finishedAt,
|
|
55
68
|
durationMs: finishedAt - startedAt,
|
|
56
69
|
counts,
|
|
70
|
+
findings: findings?.length ? findings : undefined,
|
|
71
|
+
crm,
|
|
57
72
|
events: events.length ? events : undefined,
|
|
58
73
|
error,
|
|
59
74
|
}),
|
package/dist/schedule.d.ts
CHANGED
|
@@ -40,9 +40,11 @@ export type ScheduleRunRecord = {
|
|
|
40
40
|
* recorded for visibility but nothing ran. Reasons: "plan_not_approved"
|
|
41
41
|
* (scheduled apply against a plan that is not approved — there is no flag
|
|
42
42
|
* that relaxes this), "not_schedulable" (the stored argv no longer passes
|
|
43
|
-
* the allowlist, e.g. after a hand edit of schedules.json)
|
|
43
|
+
* the allowlist, e.g. after a hand edit of schedules.json),
|
|
44
|
+
* "duplicate_firing" (a cron-triggered run already recorded in this minute
|
|
45
|
+
* — launchd can double-trigger the Vixie dom+dow OR corner).
|
|
44
46
|
*/
|
|
45
|
-
noopReason?: "plan_not_approved" | "not_schedulable";
|
|
47
|
+
noopReason?: "plan_not_approved" | "not_schedulable" | "duplicate_firing";
|
|
46
48
|
};
|
|
47
49
|
export declare function scheduleId(label: string, cron: string, argv: string[], createdAt: string): string;
|
|
48
50
|
/**
|
|
@@ -158,3 +160,79 @@ export declare function renderManagedBlock(profile: string, entries: ScheduleEnt
|
|
|
158
160
|
* Idempotent: re-applying the same block yields identical content.
|
|
159
161
|
*/
|
|
160
162
|
export declare function replaceManagedBlock(existing: string, profile: string, block: string | null): string;
|
|
163
|
+
/**
|
|
164
|
+
* One launchd StartCalendarInterval dict: every present key must match (AND),
|
|
165
|
+
* a missing key is a wildcard, and an array of dicts fires when ANY dict
|
|
166
|
+
* matches. Weekday 0 is Sunday, as in cron.
|
|
167
|
+
*/
|
|
168
|
+
export type LaunchdCalendarInterval = {
|
|
169
|
+
Minute?: number;
|
|
170
|
+
Hour?: number;
|
|
171
|
+
Day?: number;
|
|
172
|
+
Weekday?: number;
|
|
173
|
+
Month?: number;
|
|
174
|
+
};
|
|
175
|
+
/**
|
|
176
|
+
* Compile a cron expression to StartCalendarInterval dicts. Full-range fields
|
|
177
|
+
* (`*`, but also spellings like `0-59`) are omitted (launchd wildcard); the
|
|
178
|
+
* rest cross-product, capped so a dense expression cannot render a megabyte
|
|
179
|
+
* plist. Vixie day semantics: when day-of-month AND day-of-week are both
|
|
180
|
+
* restricted, either may match — launchd ANDs keys within one dict, so that
|
|
181
|
+
* becomes the union of a Day dict set and a Weekday dict set. A date matching
|
|
182
|
+
* both sets makes launchd trigger twice in the same minute; the run entry
|
|
183
|
+
* point drops the second via isDuplicateCronFiring.
|
|
184
|
+
*/
|
|
185
|
+
export declare function cronToCalendarIntervals(cron: CronExpression, cap?: number): LaunchdCalendarInterval[];
|
|
186
|
+
/**
|
|
187
|
+
* True when a cron-triggered run was already recorded in `firedAt`'s minute.
|
|
188
|
+
* The `schedule run` entry point checks this for trigger:cron so a launchd
|
|
189
|
+
* double-trigger (dom+dow OR corner above) records a duplicate_firing no-op
|
|
190
|
+
* instead of running the command twice. Manual runs are never deduplicated.
|
|
191
|
+
*/
|
|
192
|
+
export declare function isDuplicateCronFiring(runs: ScheduleRunRecord[], firedAt: string): boolean;
|
|
193
|
+
/** Reverse-DNS namespace for one profile's agents; install/uninstall own this prefix wholesale. */
|
|
194
|
+
export declare function launchdAgentPrefix(profile: string): string;
|
|
195
|
+
export declare function launchdLabel(profile: string, scheduleEntryId: string): string;
|
|
196
|
+
/**
|
|
197
|
+
* Render one LaunchAgent plist. ProgramArguments is an argv array — launchd
|
|
198
|
+
* execs it directly, no shell, so there is no quoting/injection surface at
|
|
199
|
+
* all (values are XML-escaped for the document, nothing more).
|
|
200
|
+
*/
|
|
201
|
+
export declare function renderLaunchdPlist(options: {
|
|
202
|
+
label: string;
|
|
203
|
+
programArguments: string[];
|
|
204
|
+
calendarIntervals: LaunchdCalendarInterval[];
|
|
205
|
+
environment?: Record<string, string>;
|
|
206
|
+
standardOutPath?: string;
|
|
207
|
+
standardErrorPath?: string;
|
|
208
|
+
}): string;
|
|
209
|
+
export type LaunchdIo = {
|
|
210
|
+
/** Plist file names currently in the agents directory. */
|
|
211
|
+
list(): string[];
|
|
212
|
+
write(fileName: string, content: string): void;
|
|
213
|
+
remove(fileName: string): void;
|
|
214
|
+
/** Load one agent plist into the user's gui domain; throws on failure. */
|
|
215
|
+
bootstrap(fileName: string): void;
|
|
216
|
+
/** Best-effort unload by label; a label that is not loaded is not an error. */
|
|
217
|
+
bootout(label: string): void;
|
|
218
|
+
};
|
|
219
|
+
/**
|
|
220
|
+
* The real ~/Library/LaunchAgents + launchctl. Everything above takes a
|
|
221
|
+
* LaunchdIo so tests inject fakes and never touch the user's agents.
|
|
222
|
+
*/
|
|
223
|
+
export declare function systemLaunchdIo(agentsDirOverride?: string): LaunchdIo;
|
|
224
|
+
/**
|
|
225
|
+
* Materialize enabled entries as one LaunchAgent each, replacing the
|
|
226
|
+
* profile's com.fullstackgtm.<profile>.* fleet wholesale (stale agents are
|
|
227
|
+
* booted out and deleted; plists outside the prefix are never touched).
|
|
228
|
+
* `cliArgv` is the argv-array analog of the crontab invocation line.
|
|
229
|
+
*/
|
|
230
|
+
export declare function installLaunchdAgents(profile: string, entries: ScheduleEntry[], cliArgv: string[], io: LaunchdIo, options?: {
|
|
231
|
+
environment?: Record<string, string>;
|
|
232
|
+
logDir?: string;
|
|
233
|
+
}): {
|
|
234
|
+
installed: string[];
|
|
235
|
+
removed: string[];
|
|
236
|
+
};
|
|
237
|
+
/** Boot out and delete every agent in the profile's prefix; returns the removed labels. */
|
|
238
|
+
export declare function uninstallLaunchdAgents(profile: string, io: LaunchdIo): string[];
|