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,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The resolve gate: exit 0 = safe to create, exit 2 = match found (exists or
|
|
3
|
+
* ambiguous — do NOT blind-create), exit 1 = error. Built for sync jobs and
|
|
4
|
+
* webhook handlers to call before any record creation.
|
|
5
|
+
*/
|
|
6
|
+
export declare function resolveCommand(args: string[]): Promise<void>;
|
|
7
|
+
/**
|
|
8
|
+
* Governed generic writes: build a dry-run patch plan from a snapshot filter
|
|
9
|
+
* plus field assignments (or --archive). Never writes — approve and apply the
|
|
10
|
+
* plan like any audit plan; compare-and-set protects every operation.
|
|
11
|
+
*/
|
|
12
|
+
export declare function bulkUpdateCommand(args: string[]): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Governed duplicate cleanup: group by a normalized identity key, propose one
|
|
15
|
+
* merge_records per duplicate group with a deterministic survivor. Never
|
|
16
|
+
* writes — approve and apply the plan like any audit plan.
|
|
17
|
+
*/
|
|
18
|
+
export declare function dedupeCommand(args: string[]): Promise<void>;
|
|
19
|
+
/**
|
|
20
|
+
* Ownership handoff playbook: compile one bulk-update-style plan per object
|
|
21
|
+
* type. Each plan carries its full filter, so eligibility (including the
|
|
22
|
+
* --except-deal-stage exclusion) is re-verified per record at apply time.
|
|
23
|
+
*/
|
|
24
|
+
export declare function reassignCommand(args: string[]): Promise<void>;
|
|
25
|
+
/**
|
|
26
|
+
* One-shot composite for a single audit rule: audit → save → suggest →
|
|
27
|
+
* approve only suggestion-backed operations meeting the confidence bar (plus
|
|
28
|
+
* operations that carry concrete values and need no human input) → apply
|
|
29
|
+
* (only with --yes). Every stage goes through the same gates as the manual
|
|
30
|
+
* chain; placeholder values below the bar stay unapproved.
|
|
31
|
+
*/
|
|
32
|
+
export declare function fixCommand(args: string[]): Promise<void>;
|
|
33
|
+
export declare function suggest(args: string[]): Promise<void>;
|
package/dist/cli/fix.js
ADDED
|
@@ -0,0 +1,344 @@
|
|
|
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 { auditSnapshot, defaultPolicy } from "../audit.js";
|
|
5
|
+
import { loadConfig, mergePolicy, resolveConfiguredRules } from "../config.js";
|
|
6
|
+
import { applyPatchPlan } from "../connector.js";
|
|
7
|
+
import { patchPlanToMarkdown } from "../format.js";
|
|
8
|
+
import { createFilePlanStore } from "../planStore.js";
|
|
9
|
+
import { resolveRecord } from "../resolve.js";
|
|
10
|
+
import { buildBulkUpdatePlan } from "../bulkUpdate.js";
|
|
11
|
+
import { buildDedupePlan } from "../dedupe.js";
|
|
12
|
+
import { buildReassignPlans } from "../reassign.js";
|
|
13
|
+
import { suggestValues } from "../suggest.js";
|
|
14
|
+
import { confirmRequested, connectorFor, isOptionValue, numericOption, option, readSnapshot, repeatedOption, saveRequested } from "./shared.js";
|
|
15
|
+
import { box, colorEnabled, createStatusLine, paint } from "./ui.js";
|
|
16
|
+
/**
|
|
17
|
+
* The resolve gate: exit 0 = safe to create, exit 2 = match found (exists or
|
|
18
|
+
* ambiguous — do NOT blind-create), exit 1 = error. Built for sync jobs and
|
|
19
|
+
* webhook handlers to call before any record creation.
|
|
20
|
+
*/
|
|
21
|
+
export async function resolveCommand(args) {
|
|
22
|
+
const [objectType, ...rest] = args;
|
|
23
|
+
if (!objectType || !["account", "contact", "deal"].includes(objectType)) {
|
|
24
|
+
throw new Error("Usage: fullstackgtm resolve <account|contact|deal> [--name N] [--domain D] [--email E] [--account-id A] [source options] [--json]");
|
|
25
|
+
}
|
|
26
|
+
const candidate = {
|
|
27
|
+
objectType: objectType,
|
|
28
|
+
name: option(rest, "--name") ?? undefined,
|
|
29
|
+
domain: option(rest, "--domain") ?? undefined,
|
|
30
|
+
email: option(rest, "--email") ?? undefined,
|
|
31
|
+
accountId: option(rest, "--account-id") ?? undefined,
|
|
32
|
+
};
|
|
33
|
+
const snapshot = await readSnapshot(rest);
|
|
34
|
+
const result = resolveRecord(snapshot, candidate);
|
|
35
|
+
if (rest.includes("--json")) {
|
|
36
|
+
console.log(JSON.stringify(result, null, 2));
|
|
37
|
+
}
|
|
38
|
+
else {
|
|
39
|
+
const marker = result.verdict === "safe_to_create" ? "✓" : result.verdict === "exists" ? "=" : "?";
|
|
40
|
+
console.log(`${marker} [${result.verdict}] ${result.reason}`);
|
|
41
|
+
for (const m of result.matches) {
|
|
42
|
+
console.log(` ${m.id} "${m.name}" — matched by ${m.matchedBy}: ${m.detail}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
if (result.verdict !== "safe_to_create")
|
|
46
|
+
process.exitCode = 2;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Governed generic writes: build a dry-run patch plan from a snapshot filter
|
|
50
|
+
* plus field assignments (or --archive). Never writes — approve and apply the
|
|
51
|
+
* plan like any audit plan; compare-and-set protects every operation.
|
|
52
|
+
*/
|
|
53
|
+
export async function bulkUpdateCommand(args) {
|
|
54
|
+
const [objectType, ...rest] = args;
|
|
55
|
+
if (!objectType || !["account", "contact", "deal"].includes(objectType)) {
|
|
56
|
+
throw new Error("Usage: fullstackgtm bulk-update <account|contact|deal> --where <field=value|field!=value|field~substr|field:empty|field:notempty> [--where …] (--set <field>=<value> [--set …] | --archive) [source options] [--reason <text>] [--max-operations <n>] [--save] [--out <path>] [--json]");
|
|
57
|
+
}
|
|
58
|
+
const where = repeatedOption(rest, "--where");
|
|
59
|
+
const set = {};
|
|
60
|
+
for (const pair of repeatedOption(rest, "--set")) {
|
|
61
|
+
const separator = pair.indexOf("=");
|
|
62
|
+
if (separator === -1)
|
|
63
|
+
throw new Error(`--set must look like <field>=<value>, got "${pair}"`);
|
|
64
|
+
set[pair.slice(0, separator)] = pair.slice(separator + 1);
|
|
65
|
+
}
|
|
66
|
+
const snapshot = await readSnapshot(rest);
|
|
67
|
+
const plan = buildBulkUpdatePlan(snapshot, {
|
|
68
|
+
objectType: objectType,
|
|
69
|
+
where,
|
|
70
|
+
set: Object.keys(set).length > 0 ? set : undefined,
|
|
71
|
+
archive: rest.includes("--archive"),
|
|
72
|
+
forceArchiveDuplicates: rest.includes("--force-archive-duplicates"),
|
|
73
|
+
createTask: option(rest, "--create-task") ?? undefined,
|
|
74
|
+
require: repeatedOption(rest, "--require"),
|
|
75
|
+
guard: repeatedOption(rest, "--guard"),
|
|
76
|
+
reason: option(rest, "--reason") ?? undefined,
|
|
77
|
+
maxOperations: numericOption(rest, "--max-operations"),
|
|
78
|
+
// --today resolves the comparison `today` literal (e.g. closeDate<today);
|
|
79
|
+
// defaults to the system date inside buildBulkUpdatePlan when omitted.
|
|
80
|
+
today: option(rest, "--today") ?? undefined,
|
|
81
|
+
});
|
|
82
|
+
await emitPlan(plan, rest);
|
|
83
|
+
}
|
|
84
|
+
/** Shared plan output plumbing: --out, --save (with the approve/apply hint), --json or markdown. */
|
|
85
|
+
async function emitPlan(plan, args) {
|
|
86
|
+
const out = option(args, "--out");
|
|
87
|
+
if (out) {
|
|
88
|
+
writeFileSync(resolve(process.cwd(), out), `${JSON.stringify(plan, null, 2)}\n`);
|
|
89
|
+
}
|
|
90
|
+
if (saveRequested(args)) {
|
|
91
|
+
await createFilePlanStore().save(plan);
|
|
92
|
+
console.error(`Saved plan ${plan.id} (${plan.operations.length} operations). Review with \`fullstackgtm plans show ${plan.id}\`, approve with \`fullstackgtm plans approve ${plan.id} --operations <ids|all>\`, then \`fullstackgtm apply --plan-id ${plan.id} --provider <name>\`.`);
|
|
93
|
+
}
|
|
94
|
+
if (args.includes("--json")) {
|
|
95
|
+
console.log(JSON.stringify(plan, null, 2));
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
console.log(patchPlanToMarkdown(plan));
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Governed duplicate cleanup: group by a normalized identity key, propose one
|
|
103
|
+
* merge_records per duplicate group with a deterministic survivor. Never
|
|
104
|
+
* writes — approve and apply the plan like any audit plan.
|
|
105
|
+
*/
|
|
106
|
+
export async function dedupeCommand(args) {
|
|
107
|
+
const [objectType, ...rest] = args;
|
|
108
|
+
if (!objectType || !["account", "contact", "deal"].includes(objectType)) {
|
|
109
|
+
throw new Error("Usage: fullstackgtm dedupe <account|contact|deal> --key <domain|email|name> [--keep richest|oldest] [source options] [--reason <text>] [--max-operations <n>] [--save] [--out <path>] [--json]");
|
|
110
|
+
}
|
|
111
|
+
const key = option(rest, "--key");
|
|
112
|
+
if (!key || !["domain", "email", "name"].includes(key)) {
|
|
113
|
+
throw new Error("dedupe requires --key <domain|email|name> (the identity field duplicates share).");
|
|
114
|
+
}
|
|
115
|
+
const keep = option(rest, "--keep") ?? undefined;
|
|
116
|
+
const snapshot = await readSnapshot(rest);
|
|
117
|
+
const plan = buildDedupePlan(snapshot, {
|
|
118
|
+
objectType: objectType,
|
|
119
|
+
key: key,
|
|
120
|
+
keep: (keep ?? undefined),
|
|
121
|
+
reason: option(rest, "--reason") ?? undefined,
|
|
122
|
+
maxOperations: numericOption(rest, "--max-operations"),
|
|
123
|
+
});
|
|
124
|
+
await emitPlan(plan, rest);
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Ownership handoff playbook: compile one bulk-update-style plan per object
|
|
128
|
+
* type. Each plan carries its full filter, so eligibility (including the
|
|
129
|
+
* --except-deal-stage exclusion) is re-verified per record at apply time.
|
|
130
|
+
*/
|
|
131
|
+
export async function reassignCommand(args) {
|
|
132
|
+
const from = option(args, "--from");
|
|
133
|
+
const to = option(args, "--to");
|
|
134
|
+
const assignUnowned = args.includes("--assign-unowned");
|
|
135
|
+
if (!to || (!from && !assignUnowned)) {
|
|
136
|
+
throw new Error("Usage: fullstackgtm reassign (--from <ownerId> | --assign-unowned) --to <ownerId> [--objects account,contact,deal] [--where <expr> …] [--except-deal-stage <stage>] [--include-closed-deals] [source options] [--reason <text>] [--max-operations <n>] [--save] [--out <path>] [--json]\n\n--assign-unowned claims every OWNERLESS record (ownerId:empty) for --to — the backfill twin of `enrich acquire`'s create-time assignment.");
|
|
137
|
+
}
|
|
138
|
+
const objects = option(args, "--objects")
|
|
139
|
+
?.split(",")
|
|
140
|
+
.map((value) => value.trim())
|
|
141
|
+
.filter(Boolean);
|
|
142
|
+
const snapshot = await readSnapshot(args);
|
|
143
|
+
const plans = buildReassignPlans(snapshot, {
|
|
144
|
+
fromOwnerId: from ?? undefined,
|
|
145
|
+
toOwnerId: to,
|
|
146
|
+
assignUnowned,
|
|
147
|
+
objects,
|
|
148
|
+
where: repeatedOption(args, "--where"),
|
|
149
|
+
exceptDealStage: option(args, "--except-deal-stage") ?? undefined,
|
|
150
|
+
includeClosedDeals: args.includes("--include-closed-deals"),
|
|
151
|
+
reason: option(args, "--reason") ?? undefined,
|
|
152
|
+
maxOperations: numericOption(args, "--max-operations"),
|
|
153
|
+
});
|
|
154
|
+
const out = option(args, "--out");
|
|
155
|
+
if (out) {
|
|
156
|
+
writeFileSync(resolve(process.cwd(), out), `${JSON.stringify(plans, null, 2)}\n`);
|
|
157
|
+
}
|
|
158
|
+
if (args.includes("--json")) {
|
|
159
|
+
console.log(JSON.stringify(plans, null, 2));
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const store = saveRequested(args) ? createFilePlanStore() : null;
|
|
163
|
+
for (const plan of plans) {
|
|
164
|
+
if (store)
|
|
165
|
+
await store.save(plan);
|
|
166
|
+
console.log(`${plan.id} ${String(plan.operations.length).padStart(3)} operation(s) ${plan.title}`);
|
|
167
|
+
console.log(` ${plan.summary}`);
|
|
168
|
+
}
|
|
169
|
+
if (store) {
|
|
170
|
+
console.log(`\nSaved ${plans.length} plan(s). For each: \`fullstackgtm plans show <id>\`, \`fullstackgtm plans approve <id> --operations <ids|all>\`, then \`fullstackgtm apply --plan-id <id> --provider <name>\`.`);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
console.log("\nDry run only — re-run with --save to store the plans for approval.");
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* One-shot composite for a single audit rule: audit → save → suggest →
|
|
178
|
+
* approve only suggestion-backed operations meeting the confidence bar (plus
|
|
179
|
+
* operations that carry concrete values and need no human input) → apply
|
|
180
|
+
* (only with --yes). Every stage goes through the same gates as the manual
|
|
181
|
+
* chain; placeholder values below the bar stay unapproved.
|
|
182
|
+
*/
|
|
183
|
+
export async function fixCommand(args) {
|
|
184
|
+
const ruleId = option(args, "--rule");
|
|
185
|
+
const provider = option(args, "--provider");
|
|
186
|
+
if (!ruleId || !provider) {
|
|
187
|
+
throw new Error("Usage: fullstackgtm fix --rule <ruleId> --provider <name> [--min-confidence high|low] [--include-creates] [--today <iso>] [--yes|--confirm] [--dry-run]");
|
|
188
|
+
}
|
|
189
|
+
const minConfidence = option(args, "--min-confidence") ?? "high";
|
|
190
|
+
if (!["high", "low"].includes(minConfidence)) {
|
|
191
|
+
throw new Error("--min-confidence must be high or low");
|
|
192
|
+
}
|
|
193
|
+
const includeCreates = args.includes("--include-creates");
|
|
194
|
+
const loaded = loadConfig(option(args, "--config") ?? undefined);
|
|
195
|
+
const configured = await resolveConfiguredRules(loaded);
|
|
196
|
+
const rule = configured.find((candidate) => candidate.id === ruleId);
|
|
197
|
+
if (!rule) {
|
|
198
|
+
throw new Error(`Unknown rule: ${ruleId}. Available rules: ${configured.map((r) => r.id).join(", ")}`);
|
|
199
|
+
}
|
|
200
|
+
const policy = mergePolicy(defaultPolicy(), loaded?.config);
|
|
201
|
+
const today = option(args, "--today");
|
|
202
|
+
if (today)
|
|
203
|
+
policy.today = today;
|
|
204
|
+
const snapshot = await readSnapshot(args);
|
|
205
|
+
const plan = auditSnapshot(snapshot, policy, [rule]);
|
|
206
|
+
if (plan.operations.length === 0) {
|
|
207
|
+
console.log(`fix ${ruleId}: audit proposed 0 operations — nothing to fix.`);
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
210
|
+
const store = createFilePlanStore();
|
|
211
|
+
await store.save(plan);
|
|
212
|
+
const suggestions = suggestValues(plan, snapshot);
|
|
213
|
+
const accepted = new Set(minConfidence === "low" ? ["high", "low"] : ["high"]);
|
|
214
|
+
const overrides = {};
|
|
215
|
+
let belowBar = 0;
|
|
216
|
+
for (const suggestion of suggestions) {
|
|
217
|
+
if (suggestion.suggestedValue &&
|
|
218
|
+
(accepted.has(suggestion.confidence) || (includeCreates && suggestion.confidence === "create"))) {
|
|
219
|
+
overrides[suggestion.operationId] = suggestion.suggestedValue;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
belowBar += 1;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
// Approve operations whose placeholder got a qualifying suggested value,
|
|
226
|
+
// plus operations that already carry a concrete value (no human input
|
|
227
|
+
// needed — nothing to guess). Everything else stays unapproved.
|
|
228
|
+
const placeholderIds = new Set(suggestions.map((suggestion) => suggestion.operationId));
|
|
229
|
+
const approvedIds = plan.operations
|
|
230
|
+
.map((operation) => operation.id)
|
|
231
|
+
.filter((id) => overrides[id] !== undefined || !placeholderIds.has(id));
|
|
232
|
+
const lines = [
|
|
233
|
+
`fix ${ruleId} via ${provider}:`,
|
|
234
|
+
` proposed: ${plan.operations.length} operation(s) — plan ${plan.id} (saved)`,
|
|
235
|
+
` suggested: ${Object.keys(overrides).length} value(s) at ${minConfidence}+ confidence${includeCreates ? " (creates included)" : ""}${belowBar > 0 ? `; ${belowBar} below the bar (left unapproved)` : ""}`,
|
|
236
|
+
` approved: ${approvedIds.length} of ${plan.operations.length}`,
|
|
237
|
+
];
|
|
238
|
+
if (approvedIds.length === 0) {
|
|
239
|
+
lines.push(" applied: 0 — no operation met the confidence bar");
|
|
240
|
+
console.log(lines.join("\n"));
|
|
241
|
+
console.log(`\nWiden with --min-confidence low / --include-creates, or approve manually: \`fullstackgtm plans approve ${plan.id} --operations <ids> --value <opId>=<value>\`.`);
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
await store.approveOperations(plan.id, approvedIds, overrides);
|
|
245
|
+
// `--confirm` is the standardized alias for the legacy `--yes`; `--dry-run`
|
|
246
|
+
// wins over both and locks the stop-before-apply path.
|
|
247
|
+
if (!confirmRequested(args, "--yes")) {
|
|
248
|
+
lines.push(" applied: 0 (stopped before apply — pass --yes to write)");
|
|
249
|
+
console.log(lines.join("\n"));
|
|
250
|
+
// The decision moment: frame the exact command that writes. Rich-only —
|
|
251
|
+
// piped output keeps the plain two-line hint byte-for-byte.
|
|
252
|
+
const p = paint(colorEnabled(process.stdout));
|
|
253
|
+
if (p.enabled) {
|
|
254
|
+
console.log("");
|
|
255
|
+
console.log(box([`fullstackgtm apply --plan-id ${plan.id} --provider ${provider}`], p, "Apply with").join("\n"));
|
|
256
|
+
}
|
|
257
|
+
else {
|
|
258
|
+
console.log(`\nApply with:\n fullstackgtm apply --plan-id ${plan.id} --provider ${provider}`);
|
|
259
|
+
}
|
|
260
|
+
return;
|
|
261
|
+
}
|
|
262
|
+
const connector = await connectorFor(provider, args);
|
|
263
|
+
// Live safety ticker on interactive terminals (stderr; inert otherwise).
|
|
264
|
+
const ticker = createStatusLine();
|
|
265
|
+
let run;
|
|
266
|
+
try {
|
|
267
|
+
run = await applyPatchPlan(connector, plan, {
|
|
268
|
+
approvedOperationIds: approvedIds,
|
|
269
|
+
valueOverrides: overrides,
|
|
270
|
+
onOperation: ticker.active
|
|
271
|
+
? (progress) => ticker.set(`Applying ${progress.completed}/${progress.total} · ✓ ${progress.applied} applied · ${progress.failed} failed · ${progress.conflicts} conflict${progress.conflicts === 1 ? "" : "s"}`)
|
|
272
|
+
: undefined,
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
finally {
|
|
276
|
+
ticker.done();
|
|
277
|
+
}
|
|
278
|
+
await store.recordRun(plan.id, run);
|
|
279
|
+
const counts = { applied: 0, conflict: 0, skipped: 0, failed: 0 };
|
|
280
|
+
for (const result of run.results)
|
|
281
|
+
counts[result.status] = (counts[result.status] ?? 0) + 1;
|
|
282
|
+
lines.push(` applied: ${counts.applied} · conflicts: ${counts.conflict} · skipped: ${counts.skipped} · failed: ${counts.failed}`);
|
|
283
|
+
console.log(lines.join("\n"));
|
|
284
|
+
if (run.status === "failed")
|
|
285
|
+
process.exitCode = 1;
|
|
286
|
+
}
|
|
287
|
+
export async function suggest(args) {
|
|
288
|
+
const planId = option(args, "--plan-id");
|
|
289
|
+
const planPath = option(args, "--plan");
|
|
290
|
+
if (!planId && !planPath) {
|
|
291
|
+
// Same legible guess as `apply <id>`: name the exact corrected command.
|
|
292
|
+
const positional = args.find((arg) => !arg.startsWith("-") && !isOptionValue(args, arg));
|
|
293
|
+
if (positional) {
|
|
294
|
+
throw new Error(`suggest takes the plan as a flag, not a positional. Try: fullstackgtm suggest --plan-id ${positional}`);
|
|
295
|
+
}
|
|
296
|
+
throw new Error("suggest requires --plan <path> or --plan-id <id>");
|
|
297
|
+
}
|
|
298
|
+
let plan;
|
|
299
|
+
if (planId) {
|
|
300
|
+
const stored = await createFilePlanStore().get(planId);
|
|
301
|
+
if (!stored)
|
|
302
|
+
throw new Error(`No stored plan with id ${planId}.`);
|
|
303
|
+
plan = stored.plan;
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
plan = JSON.parse(readFileSync(resolve(process.cwd(), planPath), "utf8"));
|
|
307
|
+
}
|
|
308
|
+
const snapshot = await readSnapshot(args);
|
|
309
|
+
const suggestions = suggestValues(plan, snapshot);
|
|
310
|
+
const payload = { planId: planId ?? planPath, suggestions };
|
|
311
|
+
const outPath = option(args, "--out");
|
|
312
|
+
if (outPath)
|
|
313
|
+
writeFileSync(resolve(process.cwd(), outPath), `${JSON.stringify(payload, null, 2)}\n`);
|
|
314
|
+
if (args.includes("--json")) {
|
|
315
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (suggestions.length === 0) {
|
|
319
|
+
console.log("No requires_human_* placeholder operations in this plan — nothing to suggest.");
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
322
|
+
const byConfidence = {};
|
|
323
|
+
for (const s of suggestions)
|
|
324
|
+
byConfidence[s.confidence] = (byConfidence[s.confidence] ?? 0) + 1;
|
|
325
|
+
console.log(`Suggestions for ${suggestions.length} placeholder operation(s):\n`);
|
|
326
|
+
for (const s of suggestions) {
|
|
327
|
+
const marker = s.confidence === "high" ? "✓" : s.confidence === "low" ? "~" : s.confidence === "create" ? "+" : "✗";
|
|
328
|
+
console.log(`${marker} [${s.confidence}] ${s.operationId} ${s.objectName ?? s.objectId}`);
|
|
329
|
+
console.log(` ${s.suggestedValue ? `→ ${s.suggestedValue}` : "(no suggestion)"} — ${s.reason}`);
|
|
330
|
+
}
|
|
331
|
+
console.log(`\n${Object.entries(byConfidence).map(([k, v]) => `${k}: ${v}`).join(" · ")}`);
|
|
332
|
+
if (planId && (byConfidence.high ?? 0) > 0 && !outPath) {
|
|
333
|
+
console.log(`\nChain it:\n fullstackgtm suggest --plan-id ${planId} ${snapshotSourceHint(args)}--out suggestions.json\n fullstackgtm plans approve ${planId} --values-from suggestions.json\n fullstackgtm apply --plan-id ${planId} --provider <name>`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
function snapshotSourceHint(args) {
|
|
337
|
+
const provider = option(args, "--provider");
|
|
338
|
+
if (provider)
|
|
339
|
+
return `--provider ${provider} `;
|
|
340
|
+
const input = option(args, "--input");
|
|
341
|
+
if (input)
|
|
342
|
+
return `--input ${input} `;
|
|
343
|
+
return "";
|
|
344
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Paint } from "./ui.ts";
|
|
2
|
+
export declare function usage(): string;
|
|
3
|
+
export type HelpEntry = {
|
|
4
|
+
summary: string;
|
|
5
|
+
phase: string;
|
|
6
|
+
synopsis: string[];
|
|
7
|
+
detail?: string;
|
|
8
|
+
options?: Array<[string, string]>;
|
|
9
|
+
seeAlso?: string[];
|
|
10
|
+
};
|
|
11
|
+
export declare const HELP: Record<string, HelpEntry>;
|
|
12
|
+
export declare const BESPOKE_HELP: string[];
|
|
13
|
+
export declare function shortUsage(): string;
|
|
14
|
+
/**
|
|
15
|
+
* Interactive-terminal styling for the short front door: a dimmed one-line
|
|
16
|
+
* brand header (name · version · active profile), bold title and command
|
|
17
|
+
* names, cyan lifecycle-group titles. Never applied to piped output — the
|
|
18
|
+
* plain shortUsage() text stays byte-identical (and `capabilities` keeps
|
|
19
|
+
* deriving from the HELP table, which this never touches).
|
|
20
|
+
*/
|
|
21
|
+
export declare function stylizeShortUsage(text: string, p: Paint, info: {
|
|
22
|
+
version: string;
|
|
23
|
+
profile: string;
|
|
24
|
+
}): string;
|
|
25
|
+
export declare function commandHelp(command: string): string;
|