fullstackgtm 0.31.0 → 0.33.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 +54 -0
- package/README.md +7 -3
- package/dist/callTypes.d.ts +72 -0
- package/dist/callTypes.js +690 -0
- package/dist/cli.js +93 -12
- package/dist/index.d.ts +3 -1
- package/dist/index.js +3 -1
- package/dist/llm.d.ts +50 -5
- package/dist/llm.js +60 -2
- package/dist/marketReport.js +29 -8
- package/dist/marketSourcing.d.ts +66 -0
- package/dist/marketSourcing.js +405 -0
- package/package.json +1 -1
- package/src/callTypes.ts +752 -0
- package/src/cli.ts +109 -11
- package/src/index.ts +30 -0
- package/src/llm.ts +100 -3
- package/src/marketReport.ts +31 -7
- package/src/marketSourcing.ts +405 -0
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,60 @@ The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
|
5
5
|
and the project adheres to [Semantic Versioning](https://semver.org/).
|
|
6
6
|
The path to 1.0 is planned in [docs/roadmap-to-1.0.md](./docs/roadmap-to-1.0.md).
|
|
7
7
|
|
|
8
|
+
## [0.33.0] — 2026-06-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- **Market sourcing helpers (`marketSourcing.ts`)** — find the right page to
|
|
13
|
+
capture per vendor, detect acquired/redirected vendors, and extract brand logos,
|
|
14
|
+
with zero coupling to any transport:
|
|
15
|
+
- `pickCategoryPage(html, baseUrl, keywords)` — follow a vendor's own nav to its
|
|
16
|
+
category page (so multi-product companies like SAP/Salesforce are captured on
|
|
17
|
+
the product page, not the corporate homepage). Pure.
|
|
18
|
+
- `findCategoryPageInSitemap(rootUrl, keywords, fetchText?)` — sitemap fallback
|
|
19
|
+
for JS-mega-menu sites whose product links aren't in the rendered homepage.
|
|
20
|
+
- `findCategoryPage(...)` — nav-scan then sitemap, combined.
|
|
21
|
+
- `detectDrift(url, srcHost, resolve?)` / `resolveFinalUrl(url)` — skip vendors
|
|
22
|
+
whose site redirects to a different company (an acquired/defunct product).
|
|
23
|
+
- `extractLogoUrl(html, baseUrl)` + `fetchLogoDataUri(homepageUrl, html?, …)` —
|
|
24
|
+
a vendor logo as a self-contained `data:` URI for `MarketVendor.logo`.
|
|
25
|
+
- `categoryKeywords()` + `registrableDomain()` utilities.
|
|
26
|
+
|
|
27
|
+
Pure functions operate on already-fetched HTML; fetching helpers default to the
|
|
28
|
+
package's SSRF-guarded `assertPublicUrl` + `fetch` but accept an injectable
|
|
29
|
+
fetcher (testable offline, browser-render-friendly). 8 tests.
|
|
30
|
+
|
|
31
|
+
## [0.32.0] — 2026-06-18
|
|
32
|
+
|
|
33
|
+
### Added
|
|
34
|
+
|
|
35
|
+
- **Brand logos plot inside the market-map scatter bubbles.** When a vendor has
|
|
36
|
+
a `data:image/…` logo and its dot is big enough to read it (r ≥ 12), the logo
|
|
37
|
+
becomes the in-bubble label: a white disc clipped to the circle carries the
|
|
38
|
+
logo, a colored rim still ties the dot to its legend color, and the legend
|
|
39
|
+
number moves just above so the cross-reference holds. Smaller or logo-less
|
|
40
|
+
dots keep the numbered-bubble treatment. Same `img-src data:` safety as the
|
|
41
|
+
legend/header logos — only `data:` URIs are honored; anything else is refused
|
|
42
|
+
and never emitted as an `<image>`. Render stays byte-deterministic.
|
|
43
|
+
|
|
44
|
+
- **Call-type classification + type-specific coaching rubrics.** New `call
|
|
45
|
+
classify` verb runs a deterministic, key-free signal classifier over a
|
|
46
|
+
transcript (12 call types: prospecting, discovery, demo, technical_validation,
|
|
47
|
+
negotiation, closing, onboarding, check_in, renewal_expansion, qbr, internal,
|
|
48
|
+
other), returning the type with a confidence and a written reason; `--llm`
|
|
49
|
+
adds a model tiebreak for the ambiguous middle, `--list` prints the taxonomy.
|
|
50
|
+
`call score` now **auto-selects the type-specific rubric** from that
|
|
51
|
+
classification instead of applying one generic discovery rubric to every call
|
|
52
|
+
— override with `--call-type <type>` or `--rubric`. The `Rubric` type gains
|
|
53
|
+
optional `name`, `callType`, `bands`, and per-dimension `anchorsHigh` /
|
|
54
|
+
`anchorsLow` / `evidenceCues` / `coachingPrompts`; anchored examples are
|
|
55
|
+
threaded into the scoring prompt (cuts variance) and the weighted overall is
|
|
56
|
+
mapped to a qualitative band, all computed client-side. Ten built-in presets
|
|
57
|
+
(`call score --list-rubrics`). New exports: `classifyCall`, `classifyCallLlm`,
|
|
58
|
+
`rubricForCallType`, `rubricPresets`, `bandForScore`, `CALL_TYPES`,
|
|
59
|
+
`CALL_TYPE_IDS`, `BANDS_5`, and the `CallType` / `CallTypeDef` /
|
|
60
|
+
`CallClassification` / `RubricDimension` / `ScoreBand` types.
|
|
61
|
+
|
|
8
62
|
## [0.31.0] — 2026-06-18
|
|
9
63
|
|
|
10
64
|
### Added
|
package/README.md
CHANGED
|
@@ -59,14 +59,18 @@ Calls are where pipeline truth lives. `call parse` normalizes any transcript dia
|
|
|
59
59
|
|
|
60
60
|
**Extraction is LLM-powered by default, with your own key.** The first time you run `call parse` or `call score`, the CLI asks for an Anthropic or OpenAI API key (auto-detected from the prefix), validates it against the provider, and stores it in the 0600 credential store — same treatment as CRM logins. Or skip the prompt: set `ANTHROPIC_API_KEY`/`OPENAI_API_KEY`, or `echo "$KEY" | fullstackgtm login anthropic` (or `openai`). The key talks directly to your provider — raw fetch, no SDK, no middleman. `--model` overrides the defaults (claude-haiku-4-5 / gpt-4o-mini). LLM insights carry verbatim-quote evidence and, for next steps, owner/deadline/commitment. Pass `--deterministic` for the free, instant, byte-stable keyword baseline (no key needed — right for CI and warehouse bulk loads). Every insight is provenance-marked (`extractor: "llm:anthropic:…"` vs `"deterministic"`).
|
|
61
61
|
|
|
62
|
-
**`call
|
|
62
|
+
**`call classify`** picks the call type before scoring, because a renewal scored on "Depth of Discovery" is just wrong. It runs a **deterministic, key-free** signal classifier over the transcript — 12 call types (prospecting, discovery, demo, technical_validation, negotiation, closing, onboarding, check_in, renewal_expansion, qbr, internal, other) — returning the type with a confidence and a written reason; pass `--llm` for a model tiebreak on the ambiguous middle. It needs no API key, so it runs free in CI and on warehouse bulk loads (`--list` prints the taxonomy).
|
|
63
|
+
|
|
64
|
+
**`call score`** rates the call against a coaching rubric. By default it **auto-selects the type-specific rubric** from the classification — each call type ships its own dimensions with anchored high/low examples and evidence cues (cuts scoring variance). Override with `--call-type <type>` to force a preset, or `--rubric rubric.json` for your own (`{ scale, name?, callType?, bands?, dimensions: [{ name, weight, rubric, anchorsHigh?, anchorsLow?, evidenceCues?, coachingPrompts? }] }`). The weighted overall is computed deterministically client-side, mapped to a qualitative band, every dimension score is evidence-quoted, and the rubric file is where your client-specific coaching framework lives (`--list-rubrics` prints the built-in presets).
|
|
63
65
|
|
|
64
66
|
`call link` answers "which deal was this call about" from attendee domains (account domain or contact emails → open deals, most recent activity first, with confidence + reason). `call plan` turns next-step insights into the same governed plan lifecycle as everything else.
|
|
65
67
|
|
|
66
68
|
```bash
|
|
67
|
-
# Coaching pipeline (Slack + CRM): parse → score → link → govern the writeback
|
|
69
|
+
# Coaching pipeline (Slack + CRM): parse → classify → score → link → govern the writeback
|
|
68
70
|
fullstackgtm call parse --transcript call.txt --title "Acme disco" --out parsed.json # LLM extraction
|
|
69
|
-
fullstackgtm call
|
|
71
|
+
fullstackgtm call classify --call parsed.json # deterministic call type
|
|
72
|
+
fullstackgtm call score --call parsed.json # auto-selects the type's rubric
|
|
73
|
+
fullstackgtm call score --call parsed.json --rubric team-rubric.json # …or bring your own framework
|
|
70
74
|
fullstackgtm call link --attendees jane@acme.com --provider hubspot # → deal id + reason
|
|
71
75
|
fullstackgtm call plan --call parsed.json --deal 123 --provider hubspot --save
|
|
72
76
|
# review → plans approve → apply: deal.next_step + follow-up tasks, compare-and-set protected
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import type { Rubric, ScoreBand } from "./llm.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Call-type taxonomy, a deterministic signal-based classifier, and one
|
|
4
|
+
* type-specific coaching rubric per call type.
|
|
5
|
+
*
|
|
6
|
+
* The design mirrors the rest of the package: deterministic by default,
|
|
7
|
+
* explainable, and byte-stable. `classifyCall` reads a transcript and returns
|
|
8
|
+
* a ranked classification with a written reason — no LLM, no key, no network —
|
|
9
|
+
* so it runs free in CI and on warehouse bulk loads. The LLM tiebreak
|
|
10
|
+
* (`classifyCallLlm` in llm.ts) is opt-in, for the ambiguous middle.
|
|
11
|
+
*
|
|
12
|
+
* Scoring a renewal against a discovery rubric is simply wrong, so each call
|
|
13
|
+
* type carries its own rubric: the dimensions that matter for *that*
|
|
14
|
+
* conversation, with anchored high/low examples and evidence cues that sharpen
|
|
15
|
+
* the verbatim-quote grounding the scorer already requires.
|
|
16
|
+
*/
|
|
17
|
+
export type CallType = "prospecting" | "discovery" | "demo" | "technical_validation" | "negotiation" | "closing" | "onboarding" | "check_in" | "renewal_expansion" | "qbr" | "internal" | "other";
|
|
18
|
+
export declare const CALL_TYPE_IDS: CallType[];
|
|
19
|
+
export type CallTypeDef = {
|
|
20
|
+
id: CallType;
|
|
21
|
+
name: string;
|
|
22
|
+
phase: "pre_sale" | "post_sale" | "neither";
|
|
23
|
+
definition: string;
|
|
24
|
+
/** Phrases that, when present, argue *for* this type (lowercased, substring match). */
|
|
25
|
+
signals: string[];
|
|
26
|
+
/** Phrases that argue *against* this type even if a signal matched. */
|
|
27
|
+
negativeSignals?: string[];
|
|
28
|
+
/** Types this is commonly confused with — surfaced in the classifier reason. */
|
|
29
|
+
confusedWith?: CallType[];
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Our own taxonomy. Signals are authored phrases, not lifted from any third
|
|
33
|
+
* party; they are intentionally conservative (high-precision verbs and nouns
|
|
34
|
+
* that rarely appear off-topic) so the deterministic pass is trustworthy and
|
|
35
|
+
* the ambiguous remainder is handed to the LLM tiebreak rather than guessed.
|
|
36
|
+
*/
|
|
37
|
+
export declare const CALL_TYPES: CallTypeDef[];
|
|
38
|
+
export type CallClassification = {
|
|
39
|
+
type: CallType;
|
|
40
|
+
confidence: "high" | "low" | "none";
|
|
41
|
+
reason: string;
|
|
42
|
+
/** All types that matched at least one signal, strongest first. */
|
|
43
|
+
candidates: Array<{
|
|
44
|
+
type: CallType;
|
|
45
|
+
score: number;
|
|
46
|
+
matched: string[];
|
|
47
|
+
}>;
|
|
48
|
+
method: "deterministic";
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* Classify a call from its transcript using authored phrase signals. Pure,
|
|
52
|
+
* deterministic, key-free. Score = distinct positive signals matched minus
|
|
53
|
+
* distinct negative signals; the strongest type wins. Confidence is `high`
|
|
54
|
+
* only when one type clearly leads (>=2 signals and a >=2 margin over the
|
|
55
|
+
* runner-up); a single weak signal yields `low`; no signal yields `other`/none.
|
|
56
|
+
*/
|
|
57
|
+
export declare function classifyCall(transcript: string): CallClassification;
|
|
58
|
+
/** Shared qualitative bands over a 1-5 weighted overall. */
|
|
59
|
+
export declare const BANDS_5: ScoreBand[];
|
|
60
|
+
/**
|
|
61
|
+
* Pick the qualitative band for a weighted overall: the highest band whose
|
|
62
|
+
* `min` the score reaches. Deterministic, client-side — same input, same band.
|
|
63
|
+
*/
|
|
64
|
+
export declare function bandForScore(score: number, bands: ScoreBand[]): ScoreBand | undefined;
|
|
65
|
+
/**
|
|
66
|
+
* The type-specific rubric for a call type, or the generic fallback. The
|
|
67
|
+
* generic rubric is intentionally discovery-leaning (the most common scored
|
|
68
|
+
* call) and is what `other`/`internal`/unknown resolve to.
|
|
69
|
+
*/
|
|
70
|
+
export declare function rubricForCallType(type: CallType, fallback: Rubric): Rubric;
|
|
71
|
+
/** All authored presets, for `--list` and docs. */
|
|
72
|
+
export declare function rubricPresets(): Rubric[];
|