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.
Files changed (130) hide show
  1. package/CHANGELOG.md +378 -1
  2. package/README.md +37 -14
  3. package/dist/audit.d.ts +3 -1
  4. package/dist/audit.js +29 -2
  5. package/dist/cli/audit.d.ts +15 -0
  6. package/dist/cli/audit.js +392 -0
  7. package/dist/cli/auth.d.ts +80 -0
  8. package/dist/cli/auth.js +500 -0
  9. package/dist/cli/call.d.ts +1 -0
  10. package/dist/cli/call.js +373 -0
  11. package/dist/cli/capabilities.d.ts +30 -0
  12. package/dist/cli/capabilities.js +197 -0
  13. package/dist/cli/draft.d.ts +7 -0
  14. package/dist/cli/draft.js +87 -0
  15. package/dist/cli/enrich.d.ts +8 -0
  16. package/dist/cli/enrich.js +788 -0
  17. package/dist/cli/fix.d.ts +33 -0
  18. package/dist/cli/fix.js +344 -0
  19. package/dist/cli/help.d.ts +25 -0
  20. package/dist/cli/help.js +609 -0
  21. package/dist/cli/icp.d.ts +7 -0
  22. package/dist/cli/icp.js +229 -0
  23. package/dist/cli/init.d.ts +7 -0
  24. package/dist/cli/init.js +58 -0
  25. package/dist/cli/market.d.ts +1 -0
  26. package/dist/cli/market.js +391 -0
  27. package/dist/cli/plans.d.ts +5 -0
  28. package/dist/cli/plans.js +454 -0
  29. package/dist/cli/schedule.d.ts +8 -0
  30. package/dist/cli/schedule.js +479 -0
  31. package/dist/cli/shared.d.ts +70 -0
  32. package/dist/cli/shared.js +331 -0
  33. package/dist/cli/signals.d.ts +7 -0
  34. package/dist/cli/signals.js +412 -0
  35. package/dist/cli/suggest.d.ts +49 -0
  36. package/dist/cli/suggest.js +135 -0
  37. package/dist/cli/tam.d.ts +9 -0
  38. package/dist/cli/tam.js +387 -0
  39. package/dist/cli/ui.d.ts +121 -0
  40. package/dist/cli/ui.js +375 -0
  41. package/dist/cli.d.ts +1 -57
  42. package/dist/cli.js +104 -4556
  43. package/dist/connector.d.ts +15 -0
  44. package/dist/connector.js +35 -0
  45. package/dist/connectors/hubspot.d.ts +3 -1
  46. package/dist/connectors/hubspot.js +10 -3
  47. package/dist/connectors/outboxChannel.d.ts +64 -0
  48. package/dist/connectors/outboxChannel.js +170 -0
  49. package/dist/connectors/prospectSources.d.ts +22 -0
  50. package/dist/connectors/prospectSources.js +43 -0
  51. package/dist/connectors/salesforce.d.ts +3 -1
  52. package/dist/connectors/salesforce.js +12 -5
  53. package/dist/connectors/signalSources.d.ts +105 -0
  54. package/dist/connectors/signalSources.js +264 -0
  55. package/dist/connectors/theirstack.d.ts +84 -0
  56. package/dist/connectors/theirstack.js +125 -0
  57. package/dist/icp.d.ts +52 -4
  58. package/dist/icp.js +112 -35
  59. package/dist/index.d.ts +1 -0
  60. package/dist/index.js +1 -0
  61. package/dist/init.js +3 -0
  62. package/dist/judge.d.ts +2 -0
  63. package/dist/judge.js +6 -0
  64. package/dist/judgeEval.d.ts +7 -0
  65. package/dist/judgeEval.js +8 -1
  66. package/dist/marketClassify.d.ts +2 -0
  67. package/dist/marketClassify.js +7 -1
  68. package/dist/mcp-bin.js +2 -2
  69. package/dist/mcp.js +259 -166
  70. package/dist/runReport.d.ts +26 -0
  71. package/dist/runReport.js +15 -0
  72. package/dist/schedule.d.ts +80 -2
  73. package/dist/schedule.js +272 -5
  74. package/dist/signals.d.ts +54 -0
  75. package/dist/signals.js +64 -0
  76. package/dist/spoolFiles.d.ts +44 -0
  77. package/dist/spoolFiles.js +114 -0
  78. package/dist/tam.d.ts +225 -0
  79. package/dist/tam.js +470 -0
  80. package/dist/types.d.ts +11 -0
  81. package/docs/api.md +91 -11
  82. package/docs/outbox-format.md +92 -0
  83. package/docs/recipes.md +37 -0
  84. package/docs/roadmap-to-1.0.md +31 -1
  85. package/docs/signal-spool-format.md +175 -0
  86. package/docs/tam.md +195 -0
  87. package/llms.txt +83 -11
  88. package/package.json +1 -1
  89. package/skills/fullstackgtm/SKILL.md +4 -3
  90. package/src/audit.ts +27 -1
  91. package/src/cli/audit.ts +447 -0
  92. package/src/cli/auth.ts +549 -0
  93. package/src/cli/call.ts +398 -0
  94. package/src/cli/capabilities.ts +215 -0
  95. package/src/cli/draft.ts +101 -0
  96. package/src/cli/enrich.ts +885 -0
  97. package/src/cli/fix.ts +372 -0
  98. package/src/cli/help.ts +664 -0
  99. package/src/cli/icp.ts +265 -0
  100. package/src/cli/init.ts +65 -0
  101. package/src/cli/market.ts +423 -0
  102. package/src/cli/plans.ts +523 -0
  103. package/src/cli/schedule.ts +526 -0
  104. package/src/cli/shared.ts +375 -0
  105. package/src/cli/signals.ts +434 -0
  106. package/src/cli/suggest.ts +151 -0
  107. package/src/cli/tam.ts +435 -0
  108. package/src/cli/ui.ts +426 -0
  109. package/src/cli.ts +103 -5170
  110. package/src/connector.ts +46 -0
  111. package/src/connectors/hubspot.ts +14 -2
  112. package/src/connectors/outboxChannel.ts +202 -0
  113. package/src/connectors/prospectSources.ts +57 -0
  114. package/src/connectors/salesforce.ts +18 -2
  115. package/src/connectors/signalSources.ts +314 -0
  116. package/src/connectors/theirstack.ts +170 -0
  117. package/src/icp.ts +120 -34
  118. package/src/index.ts +32 -0
  119. package/src/init.ts +3 -0
  120. package/src/judge.ts +7 -0
  121. package/src/judgeEval.ts +8 -1
  122. package/src/marketClassify.ts +8 -1
  123. package/src/mcp-bin.ts +2 -2
  124. package/src/mcp.ts +130 -57
  125. package/src/runReport.ts +39 -0
  126. package/src/schedule.ts +330 -7
  127. package/src/signals.ts +90 -0
  128. package/src/spoolFiles.ts +116 -0
  129. package/src/tam.ts +654 -0
  130. package/src/types.ts +12 -0
@@ -0,0 +1,229 @@
1
+ // Extracted verbatim from src/cli.ts (mechanical split, no behavior change).
2
+ import { readFileSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { resolveLlmCredential } from "../llm.js";
5
+ import { fitThreshold, icpFromAnswers, icpToCrustdataFilters, icpToExploriumFilters, INTERVIEW_SPEC } from "../icp.js";
6
+ import { createFileSignalStore, DEFAULT_SIGNALS_CONFIG } from "../signals.js";
7
+ import { createFileJudgeStore, DEFAULT_JUDGE_PROMPT, judgeRunId, judgeSignals } from "../judge.js";
8
+ import { DEFAULT_GOLDEN_NOW_ISO, DEFAULT_GOLDEN_SET, DEFAULT_MIN_ACCURACY, defaultJudgeFn, gradeAgainstOutcomes, gradeJudge, parseGoldenSet } from "../judgeEval.js";
9
+ import { loadIcp, numericOption, option, readSnapshot, resolveLlmBaseUrls, saveRequested } from "./shared.js";
10
+ import { createStatusLine } from "./ui.js";
11
+ import { unknownSubcommandError } from "./suggest.js";
12
+ /**
13
+ * `icp` — develop and inspect the Ideal Customer Profile that targets acquire.
14
+ * The CLI can't run AskUserQuestion itself; `icp interview` emits the question
15
+ * spec an agent (Claude Code / Codex) drives with its AskUserQuestion tool, then
16
+ * `icp set` writes icp.json from the collected answers.
17
+ */
18
+ export async function icpCommand(args) {
19
+ const [sub, ...rest] = args;
20
+ // Help-before-network: catch --help/-h anywhere in argv (so `icp --help`,
21
+ // `icp judge --help`, `icp eval --help` all print usage before any work).
22
+ if (!sub || args.includes("--help") || args.includes("-h")) {
23
+ console.log(`Usage:
24
+ fullstackgtm icp interview emit the interview spec (an agent drives it with AskUserQuestion)
25
+ fullstackgtm icp set <answers.json> [--name <n>] [--out <path>] write icp.json from interview answers
26
+ fullstackgtm icp show [--icp <path>] render the ICP + the discovery filters it produces
27
+ fullstackgtm icp judge [--signals-from latest|<label>] [--with-history] [--prompt <path>] [--min-score 0] [--save] rank unjudged signals into send/nurture/skip decisions (timing x fit x memory)
28
+ fullstackgtm icp eval [--golden <path>|default] [--against-outcomes] [--min-accuracy 0.8] [--json] grade the judge: golden-set accuracy and/or hot>cold calibration; exits 2 below the bar
29
+
30
+ The ICP makes \`enrich acquire\` targeted, not random: it generates each
31
+ provider's discovery filters (Explorium, pipe0/Crustdata) AND scores every
32
+ discovered prospect for fit — only above-threshold leads become create_record
33
+ ops. Develop one by interview, then \`enrich acquire\` picks up ./icp.json.
34
+ \`icp judge\` ranks fresh signals (from \`signals fetch --save\`) into send/nurture/skip;
35
+ \`icp eval\` is the probabilistic-judgment gate before any apply.`);
36
+ return;
37
+ }
38
+ if (sub === "interview") {
39
+ console.log(JSON.stringify({
40
+ questions: INTERVIEW_SPEC,
41
+ instructions: "Ask each question with AskUserQuestion (multiSelect per .multiSelect). For each answer, collect the chosen options' .value arrays and concat them under the question's .id. Then run `fullstackgtm icp set <answers.json>` with that flattened object.",
42
+ }, null, 2));
43
+ return;
44
+ }
45
+ if (sub === "set") {
46
+ const file = rest.find((a) => !a.startsWith("--"));
47
+ if (!file)
48
+ throw new Error("Usage: fullstackgtm icp set <answers.json> [--name <n>] [--out <path>]");
49
+ const answers = JSON.parse(readFileSync(resolve(process.cwd(), file), "utf8"));
50
+ const icp = icpFromAnswers(option(rest, "--name") ?? "ICP", answers);
51
+ const out = resolve(process.cwd(), option(rest, "--out") ?? "icp.json");
52
+ writeFileSync(out, `${JSON.stringify(icp, null, 2)}\n`);
53
+ console.log(`Wrote ${out} — ${icp.persona.titleKeywords?.length ?? 0} title keyword(s), ` +
54
+ `${icp.firmographics.employeeBands?.length ?? 0} size band(s), fit threshold ${fitThreshold(icp)}.`);
55
+ return;
56
+ }
57
+ if (sub === "show") {
58
+ const icp = loadIcp(rest);
59
+ if (!icp)
60
+ throw new Error("No ICP found (icp.json in cwd, or pass --icp <path>). Build one: fullstackgtm icp interview.");
61
+ console.log(JSON.stringify({
62
+ icp,
63
+ exploriumFilters: icpToExploriumFilters(icp),
64
+ crustdataFilters: icpToCrustdataFilters(icp),
65
+ fitThreshold: fitThreshold(icp),
66
+ }, null, 2));
67
+ return;
68
+ }
69
+ if (sub === "judge") {
70
+ // The parent icpCommand already caught --help/-h before any work, so we can
71
+ // flag-parse then act (no network until judgeSignals, and only with a key).
72
+ const signalsFrom = option(rest, "--signals-from") ?? "latest";
73
+ const withHistory = rest.includes("--with-history");
74
+ const promptPath = option(rest, "--prompt");
75
+ const minScore = numericOption(rest, "--min-score") ?? 0;
76
+ const save = saveRequested(rest);
77
+ // 1) Load signals from the signal store.
78
+ const signalStore = createFileSignalStore();
79
+ const signalRun = signalsFrom === "latest" ? await signalStore.latestRun() : await signalStore.getRun(signalsFrom);
80
+ if (!signalRun) {
81
+ throw new Error(`No signal run "${signalsFrom}" — run \`fullstackgtm signals fetch --save\` first.`);
82
+ }
83
+ const unjudged = signalRun.signals.filter((s) => !s.judgedBy);
84
+ if (unjudged.length === 0)
85
+ throw new Error(`Signal run "${signalRun.runLabel}" has no unjudged signals.`);
86
+ // 2) Optional inputs: ICP (may be undefined), snapshot, outcomes + config.
87
+ // The snapshot is loaded whenever a source is given (--provider/--input/
88
+ // --demo/--sample) — it resolves each decision's CRM target (accountId +
89
+ // best contact) so `draft` can write against a real record. --with-history
90
+ // additionally enables the memory + fit scoring inputs.
91
+ const icp = loadIcp(rest);
92
+ const config = DEFAULT_SIGNALS_CONFIG;
93
+ const outcomes = await signalStore.listOutcomes();
94
+ const hasSnapshotSource = Boolean(option(rest, "--provider")) ||
95
+ Boolean(option(rest, "--input")) ||
96
+ rest.includes("--demo") ||
97
+ rest.includes("--sample");
98
+ const snapshot = withHistory || hasSnapshotSource ? await readSnapshot(rest) : undefined;
99
+ // 3) Resolve the LLM seam ONLY if a key already exists (deterministic baseline
100
+ // otherwise — never PROMPT here; judge must run key-free).
101
+ const cred = resolveLlmCredential();
102
+ const baseUrls = resolveLlmBaseUrls();
103
+ const model = option(rest, "--model") ?? undefined;
104
+ const llm = cred
105
+ ? { ...cred, ...baseUrls, ...(model ? { model } : {}) }
106
+ : undefined;
107
+ const promptTemplate = llm
108
+ ? promptPath
109
+ ? readFileSync(resolve(process.cwd(), promptPath), "utf8")
110
+ : DEFAULT_JUDGE_PROMPT
111
+ : undefined;
112
+ // 4) Judge. Interactive terminals get a per-account stderr status line
113
+ // (LLM judging is the slow path); inert otherwise.
114
+ const judgeStatus = createStatusLine();
115
+ let decisions;
116
+ try {
117
+ decisions = await judgeSignals({
118
+ signals: unjudged,
119
+ outcomes,
120
+ config,
121
+ icp,
122
+ snapshot,
123
+ withHistory,
124
+ promptTemplate,
125
+ llm,
126
+ onAccount: judgeStatus.active
127
+ ? (done, total, domain) => judgeStatus.set(`Judging account ${done + 1}/${total} · ${domain}${llm ? ` · ${llm.provider}` : " · deterministic"}`)
128
+ : undefined,
129
+ });
130
+ }
131
+ finally {
132
+ judgeStatus.done();
133
+ }
134
+ decisions = decisions.filter((d) => d.score >= minScore);
135
+ // 5) Ranked decisions to stdout (JSON), guidance to stderr.
136
+ console.log(JSON.stringify(decisions, null, 2));
137
+ console.error(`Judged ${unjudged.length} signal(s) across ${new Set(decisions.map((d) => d.accountDomain)).size} account(s): ` +
138
+ `${decisions.filter((d) => d.decision === "send").length} send, ` +
139
+ `${decisions.filter((d) => d.decision === "nurture").length} nurture, ` +
140
+ `${decisions.filter((d) => d.decision === "skip").length} skip` +
141
+ `${llm ? "" : " (deterministic — no LLM key; run `login anthropic|openai` for why-now + play)"}.`);
142
+ // 6) --save: persist a JudgeRun + stamp consumed signals judgedBy.
143
+ if (save) {
144
+ const runLabel = option(rest, "--label") ?? `judge-${new Date().toISOString().slice(0, 10)}`;
145
+ const store = createFileJudgeStore();
146
+ await store.appendRun({
147
+ id: judgeRunId(runLabel),
148
+ runLabel,
149
+ signalRunLabel: signalRun.runLabel,
150
+ createdAt: new Date().toISOString(),
151
+ decisions,
152
+ });
153
+ const judgedIds = new Set(decisions.flatMap((d) => d.evidence));
154
+ for (const s of signalRun.signals)
155
+ if (judgedIds.has(s.id))
156
+ s.judgedBy = runLabel;
157
+ await signalStore.updateRun(signalRun);
158
+ console.error(`Saved judge run "${runLabel}" (${decisions.length} decisions); stamped ${judgedIds.size} signals judgedBy. ` +
159
+ `Next: \`fullstackgtm draft --from-judge ${runLabel} --save\`.`);
160
+ }
161
+ else {
162
+ console.error("(not saved — re-run with --save to persist a judge run and stamp the consumed signals)");
163
+ }
164
+ return;
165
+ }
166
+ if (sub === "eval") {
167
+ // The parent icpCommand already caught --help/-h before any work. The whole
168
+ // command is read-only: it grades, never emits a plan, never touches a provider.
169
+ const goldenArg = option(rest, "--golden") ?? "default";
170
+ const againstOutcomes = rest.includes("--against-outcomes");
171
+ const minAccuracy = numericOption(rest, "--min-accuracy") ?? DEFAULT_MIN_ACCURACY;
172
+ const asJson = rest.includes("--json");
173
+ if (againstOutcomes) {
174
+ // The empirical gate: hot decisions must book strictly more than cold.
175
+ const judgeStore = createFileJudgeStore();
176
+ const labelArg = option(rest, "--from-judge");
177
+ const judgeRun = labelArg ? await judgeStore.getRun(labelArg) : await judgeStore.latestRun();
178
+ if (!judgeRun) {
179
+ throw new Error(`No judge run ${labelArg ? `"${labelArg}"` : "to grade"} — run \`fullstackgtm icp judge --save\` first.`);
180
+ }
181
+ const outcomes = await createFileSignalStore().listOutcomes();
182
+ const cal = gradeAgainstOutcomes(judgeRun.decisions, outcomes);
183
+ if (asJson) {
184
+ console.log(JSON.stringify(cal, null, 2));
185
+ }
186
+ else {
187
+ console.log(`Outcome calibration (judge run "${judgeRun.runLabel}"):\n` +
188
+ ` hot book rate: ${(cal.hotBookRate * 100).toFixed(1)}% over ${cal.hotCount} hot account(s)\n` +
189
+ ` cold book rate: ${(cal.coldBookRate * 100).toFixed(1)}% over ${cal.coldCount} cold account(s)\n` +
190
+ ` calibrated: ${cal.calibrated ? "yes (hot > cold)" : "NO (hot did not strictly beat cold)"}`);
191
+ }
192
+ console.error(cal.calibrated
193
+ ? "Calibrated: hot scores book strictly more than cold — the judge earns its interrupt."
194
+ : "NOT calibrated: hot did not strictly beat cold. Tune the rubric before scheduling apply behind this gate.");
195
+ if (!cal.calibrated)
196
+ process.exitCode = 2;
197
+ return;
198
+ }
199
+ // The golden-set gate (default). Resolve "default" BEFORE any file read.
200
+ // The default set is graded on its own pinned clock (its firstSeen stamps
201
+ // are relative to DEFAULT_GOLDEN_NOW_ISO) — wall time would let freshness
202
+ // decay flip the "fresh → send" rows as the calendar advances.
203
+ const rows = goldenArg === "default"
204
+ ? DEFAULT_GOLDEN_SET
205
+ : parseGoldenSet(readFileSync(resolve(process.cwd(), goldenArg), "utf8"));
206
+ const gradeNow = goldenArg === "default" ? new Date(DEFAULT_GOLDEN_NOW_ISO) : new Date();
207
+ const result = await gradeJudge(rows, defaultJudgeFn({ icp: loadIcp(rest), now: gradeNow }));
208
+ if (asJson) {
209
+ console.log(JSON.stringify(result, null, 2));
210
+ }
211
+ else {
212
+ const c = result.confusion;
213
+ console.log(`Golden-set grade (${rows.length} row(s)):\n` +
214
+ ` accuracy: ${(result.accuracy * 100).toFixed(1)}% (bar ${(minAccuracy * 100).toFixed(0)}%)\n` +
215
+ ` precision: ${(result.precision * 100).toFixed(1)}%\n` +
216
+ ` recall: ${(result.recall * 100).toFixed(1)}%\n` +
217
+ ` confusion: tp=${c.truePositive} fp=${c.falsePositive} tn=${c.trueNegative} fn=${c.falseNegative}`);
218
+ }
219
+ if (result.accuracy < minAccuracy) {
220
+ console.error(`Judge accuracy ${(result.accuracy * 100).toFixed(1)}% is below the ${(minAccuracy * 100).toFixed(0)}% bar — exit 2.`);
221
+ process.exitCode = 2;
222
+ }
223
+ else {
224
+ console.error(`Judge passes the golden-set gate (${(result.accuracy * 100).toFixed(1)}% >= ${(minAccuracy * 100).toFixed(0)}%).`);
225
+ }
226
+ return;
227
+ }
228
+ throw unknownSubcommandError("icp", sub, ["interview", "set", "show", "judge", "eval"]);
229
+ }
@@ -0,0 +1,7 @@
1
+ /**
2
+ * `init` — scaffold a GTM workspace from cold scratch: a starter icp.json, an
3
+ * enrich.config.json (acquire preset + a visible assign seam), and a PLAYBOOK
4
+ * wired with the chosen source/provider that points at the recipes. Pure
5
+ * file-writer: no network, never overwrites without --force.
6
+ */
7
+ export declare function initCommand(args: string[]): void;
@@ -0,0 +1,58 @@
1
+ // Extracted verbatim from src/cli.ts (mechanical split, no behavior change).
2
+ import { existsSync, writeFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ import { activeProfile, DEFAULT_PROFILE } from "../credentials.js";
5
+ import { scaffoldWorkspace } from "../init.js";
6
+ import { option } from "./shared.js";
7
+ /**
8
+ * `init` — scaffold a GTM workspace from cold scratch: a starter icp.json, an
9
+ * enrich.config.json (acquire preset + a visible assign seam), and a PLAYBOOK
10
+ * wired with the chosen source/provider that points at the recipes. Pure
11
+ * file-writer: no network, never overwrites without --force.
12
+ */
13
+ export function initCommand(args) {
14
+ if (args.includes("--help") || args.includes("-h")) {
15
+ console.log(`Usage:
16
+ fullstackgtm init [--source pipe0|explorium|linkedin] [--provider hubspot|salesforce] [--out <dir>] [--force]
17
+
18
+ Scaffolds a workspace so the first acquire/signals/judge/draft commands work:
19
+ icp.json starter ICP (edit, or rebuild with \`icp interview\`)
20
+ enrich.config.json acquire preset for --source + a placeholder assign policy
21
+ PLAYBOOK.md the cold-start + outbound-loop recipes wired for this workspace
22
+
23
+ The CLI ships governed primitives; your coding agent is the orchestrator. See
24
+ docs/recipes.md for the full play set. Existing files are kept unless --force.`);
25
+ return;
26
+ }
27
+ const source = (option(args, "--source") ?? "pipe0");
28
+ if (!["pipe0", "explorium", "linkedin"].includes(source)) {
29
+ throw new Error(`init: --source must be pipe0|explorium|linkedin (got "${source}")`);
30
+ }
31
+ const provider = (option(args, "--provider") ?? "hubspot");
32
+ if (!["hubspot", "salesforce"].includes(provider)) {
33
+ throw new Error(`init: --provider must be hubspot|salesforce (got "${provider}")`);
34
+ }
35
+ const outDir = resolve(process.cwd(), option(args, "--out") ?? ".");
36
+ const force = args.includes("--force");
37
+ const current = activeProfile();
38
+ const profile = current === DEFAULT_PROFILE ? undefined : current;
39
+ const files = scaffoldWorkspace({ source, provider, profile });
40
+ const wrote = [];
41
+ const kept = [];
42
+ for (const file of files) {
43
+ const path = resolve(outDir, file.path);
44
+ if (existsSync(path) && !force) {
45
+ kept.push(file.path);
46
+ continue;
47
+ }
48
+ writeFileSync(path, file.content);
49
+ wrote.push(file.path);
50
+ }
51
+ console.log(`Scaffolded workspace (${provider}, discovery via ${source}${profile ? `, profile ${profile}` : ""}).`);
52
+ if (wrote.length)
53
+ console.log(` wrote: ${wrote.join(", ")}`);
54
+ if (kept.length)
55
+ console.log(` kept (use --force to overwrite): ${kept.join(", ")}`);
56
+ console.log("\nNext: edit icp.json + set acquire.assign.ownerId in enrich.config.json, then follow PLAYBOOK.md\n" +
57
+ "(full recipe set: docs/recipes.md).");
58
+ }
@@ -0,0 +1 @@
1
+ export declare function marketCommand(args: string[]): Promise<void>;