fullstackgtm 0.44.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 +185 -1
- package/README.md +19 -7
- 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 +100 -5130
- 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/salesforce.d.ts +3 -1
- package/dist/connectors/salesforce.js +12 -5
- package/dist/connectors/signalSources.js +7 -59
- package/dist/icp.d.ts +15 -11
- package/dist/icp.js +19 -36
- package/dist/judge.d.ts +2 -0
- package/dist/judge.js +6 -0
- 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/schedule.d.ts +80 -2
- package/dist/schedule.js +254 -1
- package/dist/spoolFiles.d.ts +44 -0
- package/dist/spoolFiles.js +114 -0
- package/dist/types.d.ts +11 -0
- package/docs/api.md +73 -7
- package/docs/signal-spool-format.md +13 -0
- package/llms.txt +15 -6
- package/package.json +1 -1
- package/skills/fullstackgtm/SKILL.md +1 -1
- 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 +99 -5829
- package/src/connector.ts +46 -0
- package/src/connectors/hubspot.ts +14 -2
- package/src/connectors/salesforce.ts +18 -2
- package/src/connectors/signalSources.ts +7 -56
- package/src/icp.ts +19 -35
- package/src/judge.ts +7 -0
- package/src/marketClassify.ts +8 -1
- package/src/mcp-bin.ts +2 -2
- package/src/mcp.ts +130 -57
- package/src/schedule.ts +310 -3
- package/src/spoolFiles.ts +116 -0
- package/src/types.ts +12 -0
|
@@ -0,0 +1,391 @@
|
|
|
1
|
+
// Extracted verbatim from src/cli.ts (mechanical split, no behavior change).
|
|
2
|
+
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { resolve } from "node:path";
|
|
4
|
+
import { createFilePlanStore } from "../planStore.js";
|
|
5
|
+
import { captureMarket, computeFrontStates, createFileObservationStore, diffFrontStates, loadCaptureTexts, loadMarketConfig, starterMarketConfig, validateObservationSet, verifyEvidenceSpans } from "../market.js";
|
|
6
|
+
import { assessAxes, axesReportToText } from "../marketAxes.js";
|
|
7
|
+
import { computeDirectives, computeOverlayStats, directivesToPlan, overlayToMarkdown } from "../marketOverlay.js";
|
|
8
|
+
import { computeScaleIndex, scaleReportToText } from "../marketScale.js";
|
|
9
|
+
import { suggestMarketConfig } from "../marketTaxonomy.js";
|
|
10
|
+
import { buildWorksheet, classifyMarket } from "../marketClassify.js";
|
|
11
|
+
import { marketMapToHtml, marketMapToMarkdown } from "../marketReport.js";
|
|
12
|
+
import { isSpoolPath, readSpoolPath } from "../spoolFiles.js";
|
|
13
|
+
import { numericOption, option, repeatedOption, requireLlmCredential, saveRequested } from "./shared.js";
|
|
14
|
+
import { createStatusLine } from "./ui.js";
|
|
15
|
+
import { unknownSubcommandError } from "./suggest.js";
|
|
16
|
+
/**
|
|
17
|
+
* The market map: claim taxonomy in a reviewable config file, page captures
|
|
18
|
+
* and append-only observations under the profile home, deterministic front
|
|
19
|
+
* states and reports computed from the store. Intensity readings enter as
|
|
20
|
+
* proposals through two channels — `classify` (LLM, bring-your-own-key, the
|
|
21
|
+
* call-intelligence pattern) and `worksheet`/`observe` (an agent or human
|
|
22
|
+
* fills the worksheet) — and BOTH pass the same mechanical gate: every quoted
|
|
23
|
+
* span is verified verbatim against the stored capture it cites.
|
|
24
|
+
*/
|
|
25
|
+
const MARKET_SUBCOMMANDS = [
|
|
26
|
+
"init", "capture", "classify", "worksheet", "observe", "fronts", "axes", "overlay", "scale", "report", "refresh",
|
|
27
|
+
];
|
|
28
|
+
export async function marketCommand(args) {
|
|
29
|
+
const [subcommand, ...rest] = args;
|
|
30
|
+
const configPath = () => resolve(process.cwd(), option(rest, "--config") ?? "market.config.json");
|
|
31
|
+
// Catch --help anywhere before loadMarketConfig/credential checks run —
|
|
32
|
+
// several subcommands (capture, refresh) have side effects on bare invocation.
|
|
33
|
+
if (!subcommand || subcommand === "--help" || subcommand === "-h" || rest.includes("--help") || rest.includes("-h")) {
|
|
34
|
+
console.log(`Usage:
|
|
35
|
+
market init --category <name> [--out <path>] write a starter market.config.json
|
|
36
|
+
market init --category <name> --auto --vendor <url> [--vendor <url>...] [--anchor <url>] [--max-claims n]
|
|
37
|
+
LLM-propose vendors + claim taxonomy from seed pages (needs an API key)
|
|
38
|
+
market capture [--config <path>] [--run <label>]
|
|
39
|
+
market classify [--run <label>] [--capture-run <label>] [--vendor <id>] [--model m] [--out <path>]
|
|
40
|
+
market worksheet --vendor <id> [--capture-run <label>] [--out <path>]
|
|
41
|
+
market observe --from <observations.json|sets.jsonl|spool-dir> [--unverified]
|
|
42
|
+
market fronts [--config <path>] [--run <label>] [--diff <prior-run>] [--json]
|
|
43
|
+
market axes [--config <path>] [--run <label>] [--json]
|
|
44
|
+
market overlay --snapshot <crm.json> [--calls <parsed.json|manifest.json>]... [--prior-run <label>]
|
|
45
|
+
[--min-mentions N] [--promote-lift X] [--json] [--save --task-account <id>|--task-deal <id>]
|
|
46
|
+
market scale [--config <path>] [--json]
|
|
47
|
+
market report [--config <path>] [--run <label>] [--format md|html] [--out <path>]
|
|
48
|
+
market refresh [--run <label>] [--model m] capture → classify → fronts drift → HTML report
|
|
49
|
+
|
|
50
|
+
overlay is the directive layer: joins the map to YOUR CRM ground truth and
|
|
51
|
+
emits OCCUPY / PROMOTE / URGENT / RETREAT directives, each carrying ≥1
|
|
52
|
+
observation and ≥1 CRM statistic with its sample size. Claim mentions are
|
|
53
|
+
deterministic word-boundary matches of each claim's "terms" against call
|
|
54
|
+
documents (call parse output); small samples refuse to become strategy
|
|
55
|
+
(--min-mentions, default 3). --save turns directives into approval-gated
|
|
56
|
+
create_task operations through the normal plans → approve → apply gate.
|
|
57
|
+
|
|
58
|
+
scale prints the relative scale index that sizes the report's bubbles when
|
|
59
|
+
vendors carry scaleSignals (citable review counts / headcount / revenue —
|
|
60
|
+
a within-set index, never "market share" unqualified).
|
|
61
|
+
|
|
62
|
+
axes runs the axis-discovery math: PCA over the vendor × claim intensity
|
|
63
|
+
matrix (PC1 = the category's primary axis, PC2 = the max-differentiation
|
|
64
|
+
direction orthogonal to it), triangulation of configured axes against the
|
|
65
|
+
PCs, and an orthogonality screen (|r|>0.75 = one axis twice). Axes live in
|
|
66
|
+
the config as claim-scoring rubrics; the report's strategic map and axis
|
|
67
|
+
lab render from them.
|
|
68
|
+
|
|
69
|
+
classify uses your Anthropic/OpenAI key (like call parse) to read the stored
|
|
70
|
+
captures and propose intensity readings; worksheet is the no-key path (an
|
|
71
|
+
agent or human fills it, submits via observe). Either way, every quoted span
|
|
72
|
+
is verified character-for-character against the capture it cites before the
|
|
73
|
+
observation is accepted — quotes that aren't on the page bounce.
|
|
74
|
+
|
|
75
|
+
The taxonomy (vendors + claims) is config you review and version; captures
|
|
76
|
+
and observations live under ~/.fullstackgtm/market/<category> (profile-scoped,
|
|
77
|
+
one client's category intel never bleeds into another's). Front states are
|
|
78
|
+
recomputed deterministically on every invocation — never stored.`);
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
81
|
+
if (subcommand === "init") {
|
|
82
|
+
const category = option(rest, "--category");
|
|
83
|
+
if (!category)
|
|
84
|
+
throw new Error("market init requires --category <name>");
|
|
85
|
+
const outPath = resolve(process.cwd(), option(rest, "--out") ?? "market.config.json");
|
|
86
|
+
if (existsSync(outPath))
|
|
87
|
+
throw new Error(`${outPath} already exists — refusing to overwrite`);
|
|
88
|
+
if (rest.includes("--auto")) {
|
|
89
|
+
const vendorUrls = repeatedOption(rest, "--vendor");
|
|
90
|
+
if (vendorUrls.length === 0) {
|
|
91
|
+
throw new Error("market init --auto requires at least one --vendor <url> (the competitor homepages to seed from)");
|
|
92
|
+
}
|
|
93
|
+
const anchorUrl = option(rest, "--anchor");
|
|
94
|
+
const credential = await requireLlmCredential("market classify");
|
|
95
|
+
console.error(`Capturing ${vendorUrls.length} seed page(s) and proposing a claim taxonomy with ${credential.provider}…`);
|
|
96
|
+
const { config, unreadableVendorIds, model } = await suggestMarketConfig({
|
|
97
|
+
category,
|
|
98
|
+
vendors: vendorUrls.map((url) => ({ url, anchor: anchorUrl ? url === anchorUrl : false })),
|
|
99
|
+
llm: { ...credential, model: option(rest, "--model") ?? undefined },
|
|
100
|
+
maxClaims: numericOption(rest, "--max-claims"),
|
|
101
|
+
});
|
|
102
|
+
writeFileSync(outPath, `${JSON.stringify(config, null, 2)}\n`);
|
|
103
|
+
if (unreadableVendorIds.length > 0) {
|
|
104
|
+
console.error(`Note: no readable text for ${unreadableVendorIds.join(", ")} — excluded from taxonomy grounding.`);
|
|
105
|
+
}
|
|
106
|
+
console.log(`Wrote ${outPath}: ${config.vendors.length} vendors, ${config.claims.length} proposed claims (${model}). Review it, then: fullstackgtm market refresh`);
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
writeFileSync(outPath, `${JSON.stringify(starterMarketConfig(category), null, 2)}\n`);
|
|
110
|
+
console.log(`Wrote ${outPath}. Fill in vendors and claims, then: fullstackgtm market capture`);
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
// Validate the subcommand BEFORE loading market.config.json — a typo'd
|
|
114
|
+
// subcommand used to die with the config's ENOENT instead of a did-you-mean.
|
|
115
|
+
if (!MARKET_SUBCOMMANDS.includes(subcommand)) {
|
|
116
|
+
throw unknownSubcommandError("market", subcommand, MARKET_SUBCOMMANDS);
|
|
117
|
+
}
|
|
118
|
+
const config = loadMarketConfig(configPath());
|
|
119
|
+
const store = createFileObservationStore(config.category);
|
|
120
|
+
if (subcommand === "capture") {
|
|
121
|
+
const result = await captureMarket(config, { runLabel: option(rest, "--run") ?? "run-1" });
|
|
122
|
+
for (const entry of result.entries) {
|
|
123
|
+
const flag = entry.captureHash && entry.textChars > 500 ? "" : " <-- thin/empty";
|
|
124
|
+
console.log(`${entry.vendorId.padEnd(16)} ${entry.kind.padEnd(8)} ${String(entry.httpStatus ?? "ERR").padEnd(4)} ${String(entry.textChars).padStart(7)} chars ${entry.url}${flag}`);
|
|
125
|
+
}
|
|
126
|
+
console.log(`\nmanifest: ${result.manifestPath}`);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
if (subcommand === "observe") {
|
|
130
|
+
const fromPath = option(rest, "--from");
|
|
131
|
+
if (!fromPath)
|
|
132
|
+
throw new Error("market observe requires --from <observations.json>");
|
|
133
|
+
// Additive spool support (docs/signal-spool-format.md): a *.jsonl file (one
|
|
134
|
+
// ObservationSet envelope per line) or a directory of *.jsonl / *.json spool
|
|
135
|
+
// files, each envelope validated + appended through the SAME gates as the
|
|
136
|
+
// single-file path. A plain .json file parses exactly as before.
|
|
137
|
+
const abs = resolve(process.cwd(), fromPath);
|
|
138
|
+
const sets = isSpoolPath(abs)
|
|
139
|
+
? readSpoolPath(abs, "market observe --from").map((entry) => entry.row)
|
|
140
|
+
: [JSON.parse(readFileSync(abs, "utf8"))];
|
|
141
|
+
for (const set of sets) {
|
|
142
|
+
const problems = validateObservationSet(config, set);
|
|
143
|
+
if (problems.length > 0) {
|
|
144
|
+
console.error(`Rejected: ${problems.length} problem(s)`);
|
|
145
|
+
for (const problem of problems.slice(0, 20))
|
|
146
|
+
console.error(` - ${problem}`);
|
|
147
|
+
process.exitCode = 1;
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!rest.includes("--unverified")) {
|
|
151
|
+
const { textByHash } = loadCaptureTexts(config.category);
|
|
152
|
+
const failures = verifyEvidenceSpans(set.observations, textByHash);
|
|
153
|
+
if (failures.length > 0) {
|
|
154
|
+
console.error(`Rejected: ${failures.length} evidence span(s) failed verification against the stored captures`);
|
|
155
|
+
for (const failure of failures.slice(0, 20)) {
|
|
156
|
+
console.error(` - ${failure.vendorId} × ${failure.claimId}: ${failure.problem}`);
|
|
157
|
+
}
|
|
158
|
+
console.error("Quotes must be copied verbatim from the captured pages. (--unverified skips this gate when the captures genuinely live elsewhere.)");
|
|
159
|
+
process.exitCode = 1;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
await store.append(set);
|
|
164
|
+
console.log(`Appended ${set.runLabel}: ${set.observations.length} observations (${set.extractor})`);
|
|
165
|
+
}
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
if (subcommand === "worksheet") {
|
|
169
|
+
const vendorId = option(rest, "--vendor");
|
|
170
|
+
if (!vendorId)
|
|
171
|
+
throw new Error("market worksheet requires --vendor <id>");
|
|
172
|
+
const worksheet = buildWorksheet(config, vendorId, { captureRun: option(rest, "--capture-run") ?? undefined });
|
|
173
|
+
const outPath = option(rest, "--out");
|
|
174
|
+
const payload = `${JSON.stringify(worksheet, null, 2)}\n`;
|
|
175
|
+
if (outPath) {
|
|
176
|
+
writeFileSync(resolve(process.cwd(), outPath), payload);
|
|
177
|
+
console.log(`Wrote ${outPath} (${worksheet.pages.length} captured pages, ${worksheet.claims.length} claims)`);
|
|
178
|
+
}
|
|
179
|
+
else {
|
|
180
|
+
console.log(payload);
|
|
181
|
+
}
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (subcommand === "classify") {
|
|
185
|
+
const credential = await requireLlmCredential("market classify");
|
|
186
|
+
const vendorFilter = option(rest, "--vendor");
|
|
187
|
+
const outPath = option(rest, "--out");
|
|
188
|
+
if (vendorFilter && !outPath) {
|
|
189
|
+
throw new Error("market classify --vendor produces a partial set (coverage validation would reject it) — pass --out <path> to inspect/merge it by hand");
|
|
190
|
+
}
|
|
191
|
+
const classifyStatus = createStatusLine();
|
|
192
|
+
let result;
|
|
193
|
+
try {
|
|
194
|
+
result = await classifyMarket(config, {
|
|
195
|
+
llm: { ...credential, model: option(rest, "--model") ?? undefined },
|
|
196
|
+
runLabel: option(rest, "--run") ?? option(rest, "--capture-run") ?? "run-1",
|
|
197
|
+
captureRun: option(rest, "--capture-run") ?? undefined,
|
|
198
|
+
vendors: vendorFilter ? [vendorFilter] : undefined,
|
|
199
|
+
onVendor: classifyStatus.active
|
|
200
|
+
? (done, total, vendorId) => classifyStatus.set(`Classifying vendor ${done + 1}/${total} · ${vendorId} · ${credential.provider}`)
|
|
201
|
+
: undefined,
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
classifyStatus.done();
|
|
206
|
+
}
|
|
207
|
+
if (result.retriedVendorIds.length > 0) {
|
|
208
|
+
console.error(`Span verification bounced ${result.retriedVendorIds.join(", ")} once; retry passed.`);
|
|
209
|
+
}
|
|
210
|
+
if (outPath) {
|
|
211
|
+
writeFileSync(resolve(process.cwd(), outPath), `${JSON.stringify(result.set, null, 2)}\n`);
|
|
212
|
+
console.log(`Wrote ${outPath}: ${result.set.observations.length} verified observations (${result.set.extractor})`);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const problems = validateObservationSet(config, result.set);
|
|
216
|
+
if (problems.length > 0) {
|
|
217
|
+
throw new Error(`Classified set failed validation: ${problems.slice(0, 5).join("; ")}`);
|
|
218
|
+
}
|
|
219
|
+
await store.append(result.set);
|
|
220
|
+
console.log(`Appended ${result.set.runLabel}: ${result.set.observations.length} observations, every span verified (${result.set.extractor})`);
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (subcommand === "refresh") {
|
|
224
|
+
const credential = await requireLlmCredential("market classify");
|
|
225
|
+
const runLabel = option(rest, "--run") ?? `run-${new Date().toISOString().slice(0, 10)}`;
|
|
226
|
+
const prior = await store.latest();
|
|
227
|
+
console.log(`Capturing ${config.vendors.length} vendors as ${runLabel}…`);
|
|
228
|
+
const captured = await captureMarket(config, { runLabel });
|
|
229
|
+
const failed = captured.entries.filter((entry) => !entry.captureHash);
|
|
230
|
+
if (failed.length > 0)
|
|
231
|
+
console.log(`${failed.length} page(s) failed/empty — affected cells will verify against remaining pages or read unobservable.`);
|
|
232
|
+
console.log(`Classifying with ${credential.provider}…`);
|
|
233
|
+
const refreshStatus = createStatusLine();
|
|
234
|
+
let result;
|
|
235
|
+
try {
|
|
236
|
+
result = await classifyMarket(config, {
|
|
237
|
+
llm: { ...credential, model: option(rest, "--model") ?? undefined },
|
|
238
|
+
runLabel,
|
|
239
|
+
captureRun: runLabel,
|
|
240
|
+
onVendor: refreshStatus.active
|
|
241
|
+
? (done, total, vendorId) => refreshStatus.set(`Classifying vendor ${done + 1}/${total} · ${vendorId} · ${credential.provider}`)
|
|
242
|
+
: undefined,
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
refreshStatus.done();
|
|
247
|
+
}
|
|
248
|
+
await store.append(result.set);
|
|
249
|
+
const fronts = computeFrontStates(config, result.set);
|
|
250
|
+
if (prior) {
|
|
251
|
+
const drift = diffFrontStates(computeFrontStates(config, prior), fronts);
|
|
252
|
+
if (drift.length === 0)
|
|
253
|
+
console.log(`No front changes since ${prior.runLabel}.`);
|
|
254
|
+
for (const change of drift)
|
|
255
|
+
console.log(`CHANGED ${change.claimId}: ${change.before} → ${change.after}`);
|
|
256
|
+
}
|
|
257
|
+
const outPath = option(rest, "--out") ?? `${config.category}-${runLabel}.html`;
|
|
258
|
+
writeFileSync(resolve(process.cwd(), outPath), marketMapToHtml(config, result.set));
|
|
259
|
+
console.log(`Wrote ${outPath}`);
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const loadSet = async () => {
|
|
263
|
+
const runLabel = option(rest, "--run");
|
|
264
|
+
const set = runLabel ? await store.get(runLabel) : await store.latest();
|
|
265
|
+
if (!set) {
|
|
266
|
+
throw new Error(runLabel
|
|
267
|
+
? `No observation run "${runLabel}" for ${config.category}`
|
|
268
|
+
: `No observations stored for ${config.category} — run market observe --from <file> first`);
|
|
269
|
+
}
|
|
270
|
+
return set;
|
|
271
|
+
};
|
|
272
|
+
if (subcommand === "fronts") {
|
|
273
|
+
const set = await loadSet();
|
|
274
|
+
const fronts = computeFrontStates(config, set);
|
|
275
|
+
const priorLabel = option(rest, "--diff");
|
|
276
|
+
const prior = priorLabel ? await store.get(priorLabel) : null;
|
|
277
|
+
if (priorLabel && !prior)
|
|
278
|
+
throw new Error(`No observation run "${priorLabel}" to diff against`);
|
|
279
|
+
const drift = prior ? diffFrontStates(computeFrontStates(config, prior), fronts) : null;
|
|
280
|
+
if (rest.includes("--json")) {
|
|
281
|
+
console.log(JSON.stringify({ runLabel: set.runLabel, fronts, drift }, null, 2));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
for (const front of fronts) {
|
|
285
|
+
const owner = front.state === "owned" ? ` → ${front.loudVendorIds[0]}` : "";
|
|
286
|
+
console.log(`${front.state.toUpperCase().padEnd(10)} ${front.claimId}${owner}`);
|
|
287
|
+
}
|
|
288
|
+
if (drift) {
|
|
289
|
+
console.log("");
|
|
290
|
+
if (drift.length === 0)
|
|
291
|
+
console.log(`No front changes since ${priorLabel}.`);
|
|
292
|
+
for (const change of drift)
|
|
293
|
+
console.log(`CHANGED ${change.claimId}: ${change.before} → ${change.after}`);
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
if (subcommand === "report") {
|
|
298
|
+
const set = await loadSet();
|
|
299
|
+
const format = option(rest, "--format") ?? "md";
|
|
300
|
+
const output = format === "html" ? marketMapToHtml(config, set) : marketMapToMarkdown(config, set);
|
|
301
|
+
const outPath = option(rest, "--out");
|
|
302
|
+
if (outPath) {
|
|
303
|
+
writeFileSync(resolve(process.cwd(), outPath), output);
|
|
304
|
+
console.log(`Wrote ${outPath}`);
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
console.log(output);
|
|
308
|
+
}
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
if (subcommand === "axes") {
|
|
312
|
+
const set = await loadSet();
|
|
313
|
+
const report = assessAxes(config, set);
|
|
314
|
+
if (rest.includes("--json")) {
|
|
315
|
+
console.log(JSON.stringify(report, null, 2));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
console.log(axesReportToText(report));
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
if (subcommand === "scale") {
|
|
322
|
+
const report = computeScaleIndex(config);
|
|
323
|
+
if (rest.includes("--json")) {
|
|
324
|
+
console.log(JSON.stringify(report, null, 2));
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
console.log(scaleReportToText(config, report));
|
|
328
|
+
return;
|
|
329
|
+
}
|
|
330
|
+
if (subcommand === "overlay") {
|
|
331
|
+
const set = await loadSet();
|
|
332
|
+
const snapshotPath = option(rest, "--snapshot");
|
|
333
|
+
if (!snapshotPath) {
|
|
334
|
+
throw new Error("market overlay requires --snapshot <canonical-snapshot.json> (fullstackgtm snapshot --out it first) — directives need CRM ground truth");
|
|
335
|
+
}
|
|
336
|
+
const snapshot = JSON.parse(readFileSync(resolve(process.cwd(), snapshotPath), "utf8"));
|
|
337
|
+
// --calls accepts ParsedCall JSON files (from `call parse --out`) and/or
|
|
338
|
+
// manifest arrays [{path, dealId?}] linking calls to deals. Repeatable.
|
|
339
|
+
const documents = [];
|
|
340
|
+
const addParsedCall = (parsedPath, dealId) => {
|
|
341
|
+
const parsed = JSON.parse(readFileSync(resolve(process.cwd(), parsedPath), "utf8"));
|
|
342
|
+
const text = [
|
|
343
|
+
...(parsed.segments ?? []).map((segment) => segment.text ?? ""),
|
|
344
|
+
...(parsed.insights ?? []).map((insight) => `${insight.text ?? ""} ${insight.evidence ?? ""}`),
|
|
345
|
+
].join("\n");
|
|
346
|
+
documents.push({ id: parsed.id ?? parsedPath, text, dealId, occurredAt: parsed.evidence?.[0]?.capturedAt });
|
|
347
|
+
};
|
|
348
|
+
for (let i = 0; i < rest.length; i += 1) {
|
|
349
|
+
if (rest[i] !== "--calls")
|
|
350
|
+
continue;
|
|
351
|
+
const callsPath = rest[i + 1];
|
|
352
|
+
if (!callsPath)
|
|
353
|
+
throw new Error("--calls needs a path");
|
|
354
|
+
const raw = JSON.parse(readFileSync(resolve(process.cwd(), callsPath), "utf8"));
|
|
355
|
+
if (Array.isArray(raw)) {
|
|
356
|
+
for (const entry of raw)
|
|
357
|
+
addParsedCall(entry.path, entry.dealId);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
addParsedCall(callsPath);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const priorLabel = option(rest, "--prior-run");
|
|
364
|
+
const priorSet = priorLabel ? await store.get(priorLabel) : null;
|
|
365
|
+
if (priorLabel && !priorSet)
|
|
366
|
+
throw new Error(`No observation run "${priorLabel}" for URGENT drift`);
|
|
367
|
+
const stats = computeOverlayStats(config, snapshot, documents);
|
|
368
|
+
const directives = computeDirectives(config, set, stats, {
|
|
369
|
+
minMentions: numericOption(rest, "--min-mentions") ?? undefined,
|
|
370
|
+
promoteLift: numericOption(rest, "--promote-lift") ?? undefined,
|
|
371
|
+
priorSet: priorSet ?? undefined,
|
|
372
|
+
});
|
|
373
|
+
if (rest.includes("--json")) {
|
|
374
|
+
console.log(JSON.stringify({ stats, directives }, null, 2));
|
|
375
|
+
return;
|
|
376
|
+
}
|
|
377
|
+
console.log(overlayToMarkdown(stats, directives));
|
|
378
|
+
if (saveRequested(rest)) {
|
|
379
|
+
const taskAccount = option(rest, "--task-account");
|
|
380
|
+
const taskDeal = option(rest, "--task-deal");
|
|
381
|
+
if (!taskAccount && !taskDeal) {
|
|
382
|
+
throw new Error("--save needs --task-account <id> or --task-deal <id>: directives become approval-gated create_task operations, and the CRM needs a record to hang them on (your own company's account record works well)");
|
|
383
|
+
}
|
|
384
|
+
const plan = directivesToPlan(config, set, directives, taskDeal ? { objectType: "deal", objectId: taskDeal } : { objectType: "account", objectId: taskAccount });
|
|
385
|
+
const stored = await createFilePlanStore().save(plan);
|
|
386
|
+
console.log(`Saved plan ${stored.plan.id} (${directives.length} directive task(s); approve via \`plans approve\`)`);
|
|
387
|
+
}
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
throw unknownSubcommandError("market", subcommand, MARKET_SUBCOMMANDS);
|
|
391
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function auditLogCommand(args: string[]): Promise<void>;
|
|
2
|
+
export declare function apply(args: string[]): Promise<void>;
|
|
3
|
+
export declare function diffCommand(args: string[]): Promise<void>;
|
|
4
|
+
export declare function mergeCommand(args: string[]): Promise<void>;
|
|
5
|
+
export declare function plansCommand(args: string[]): Promise<void>;
|