@veleads/gtm 0.2.0 → 0.3.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 +18 -0
- package/README.md +72 -0
- package/dist/cli.js +268 -89
- package/kit/AGENTS.md +63 -63
- package/kit/providers/_template.md +1 -1
- package/kit/schemas/account.schema.json +2 -2
- package/kit/schemas/campaign-config.schema.json +3 -3
- package/kit/schemas/contact.schema.json +2 -2
- package/kit/schemas/icp.schema.json +3 -3
- package/kit/schemas/lead.schema.json +2 -2
- package/kit/schemas/{client.schema.json → project.schema.json} +7 -7
- package/kit/schemas/signal.schema.json +2 -2
- package/kit/schemas/signals.schema.json +3 -3
- package/kit/skills/00-intake/SKILL.md +12 -12
- package/kit/skills/01-icp/SKILL.md +9 -9
- package/kit/skills/02-tam/SKILL.md +5 -5
- package/kit/skills/03-enrich/SKILL.md +1 -1
- package/kit/skills/04-qualify/SKILL.md +4 -4
- package/kit/skills/05-copy/SKILL.md +6 -6
- package/kit/skills/06-qa/SKILL.md +2 -2
- package/kit/skills/07-launch/SKILL.md +3 -3
- package/kit/skills/08-results/SKILL.md +4 -4
- package/kit/templates/campaign/brief.md +1 -1
- package/kit/templates/campaign/config.yaml +3 -3
- package/kit/templates/outputs/qa-report.md +1 -1
- package/kit/templates/outputs/run-log.md +1 -1
- package/kit/templates/outputs/weekly-report.md +1 -1
- package/kit/templates/{client → project}/icp.md +1 -1
- package/kit/templates/{client → project}/icp.yaml +2 -2
- package/kit/templates/{client → project}/intake.md +11 -11
- package/kit/templates/{client → project}/offer.md +1 -1
- package/kit/templates/{client → project}/personas.md +1 -1
- package/kit/templates/project/project.yaml +12 -0
- package/kit/templates/{client → project}/proof.md +1 -1
- package/kit/templates/{client → project}/signals.yaml +2 -2
- package/kit/templates/{client → project}/voice.md +1 -1
- package/kit/templates/repo/{CLIENT.md → PROJECT.md} +5 -5
- package/kit/templates/repo/README.md +9 -9
- package/kit/templates/repo/github/workflows/gtm-upgrade.yml +10 -12
- package/kit/templates/repo/github/workflows/gtm.yml +14 -8
- package/kit/templates/repo/package.json +1 -2
- package/package.json +1 -1
- package/kit/templates/client/client.yaml +0 -12
package/dist/cli.js
CHANGED
|
@@ -40,28 +40,32 @@ var Approval = z.object({
|
|
|
40
40
|
);
|
|
41
41
|
var FieldSources = z.record(z.string(), z.string().min(1));
|
|
42
42
|
|
|
43
|
-
// ../schemas/src/
|
|
43
|
+
// ../schemas/src/project.ts
|
|
44
44
|
import { z as z2 } from "zod";
|
|
45
45
|
|
|
46
46
|
// ../schemas/src/versions.ts
|
|
47
47
|
var FILE_VERSIONS = {
|
|
48
|
-
|
|
49
|
-
icp:
|
|
50
|
-
signals:
|
|
51
|
-
"campaign-config":
|
|
48
|
+
project: 2,
|
|
49
|
+
icp: 2,
|
|
50
|
+
signals: 2,
|
|
51
|
+
"campaign-config": 2
|
|
52
52
|
};
|
|
53
|
+
var FILE_RENAMES = [
|
|
54
|
+
{ from: "client.yaml", to: "project.yaml", since: "0.3.0" },
|
|
55
|
+
{ from: "CLIENT.md", to: "PROJECT.md", since: "0.3.0" }
|
|
56
|
+
];
|
|
53
57
|
|
|
54
|
-
// ../schemas/src/
|
|
55
|
-
var
|
|
56
|
-
version: z2.literal(FILE_VERSIONS.
|
|
57
|
-
|
|
58
|
+
// ../schemas/src/project.ts
|
|
59
|
+
var ProjectFile = z2.object({
|
|
60
|
+
version: z2.literal(FILE_VERSIONS.project),
|
|
61
|
+
project_id: Slug,
|
|
58
62
|
name: z2.string().min(1),
|
|
59
|
-
status: z2.enum(["onboarding", "active", "paused", "
|
|
63
|
+
status: z2.enum(["onboarding", "active", "paused", "closed"]),
|
|
60
64
|
started_at: IsoDate,
|
|
61
65
|
owners: z2.object({
|
|
62
66
|
gtm_engineer: z2.string().min(1),
|
|
63
67
|
sdrs: z2.array(z2.string().min(1)),
|
|
64
|
-
|
|
68
|
+
approver: z2.string().min(1).describe("Person who signs off ICP and copy for this project")
|
|
65
69
|
}),
|
|
66
70
|
regions: z2.array(z2.string().min(2)).min(1).describe("Target regions, e.g. EU, UK, US"),
|
|
67
71
|
features: z2.record(z2.string(), z2.boolean()).default({}).describe("Opt-in core features, see FEATURES")
|
|
@@ -94,7 +98,7 @@ var IcpPersona = z3.object({
|
|
|
94
98
|
});
|
|
95
99
|
var IcpFile = z3.object({
|
|
96
100
|
version: z3.literal(FILE_VERSIONS.icp),
|
|
97
|
-
|
|
101
|
+
project_id: Slug,
|
|
98
102
|
approval: Approval,
|
|
99
103
|
segments: z3.array(IcpSegment).min(1),
|
|
100
104
|
personas: z3.array(IcpPersona).min(1),
|
|
@@ -124,7 +128,7 @@ var SignalDefinition = z4.object({
|
|
|
124
128
|
});
|
|
125
129
|
var SignalsFile = z4.object({
|
|
126
130
|
version: z4.literal(FILE_VERSIONS.signals),
|
|
127
|
-
|
|
131
|
+
project_id: Slug,
|
|
128
132
|
signals: z4.array(SignalDefinition).min(1)
|
|
129
133
|
}).superRefine((f, ctx) => {
|
|
130
134
|
const keys = f.signals.map((s) => s.key);
|
|
@@ -185,7 +189,7 @@ var Compliance = z5.object({
|
|
|
185
189
|
});
|
|
186
190
|
var CampaignConfig = z5.object({
|
|
187
191
|
version: z5.literal(FILE_VERSIONS["campaign-config"]),
|
|
188
|
-
|
|
192
|
+
project_id: Slug,
|
|
189
193
|
campaign_id: Slug,
|
|
190
194
|
owner: z5.string().min(1),
|
|
191
195
|
stage: Stage.describe("The stage currently being worked on"),
|
|
@@ -208,7 +212,7 @@ var CampaignConfig = z5.object({
|
|
|
208
212
|
// ../schemas/src/account.ts
|
|
209
213
|
import { z as z6 } from "zod";
|
|
210
214
|
var Account = z6.object({
|
|
211
|
-
|
|
215
|
+
project_id: Slug,
|
|
212
216
|
tam_id: Slug,
|
|
213
217
|
domain: Domain,
|
|
214
218
|
name: z6.string().min(1),
|
|
@@ -225,7 +229,7 @@ var Account = z6.object({
|
|
|
225
229
|
import { z as z7 } from "zod";
|
|
226
230
|
var EmailStatus = z7.enum(["valid", "catch_all", "invalid", "unknown", "not_found"]);
|
|
227
231
|
var Contact = z7.object({
|
|
228
|
-
|
|
232
|
+
project_id: Slug,
|
|
229
233
|
account_domain: Domain,
|
|
230
234
|
first_name: z7.string().min(1),
|
|
231
235
|
last_name: z7.string().min(1),
|
|
@@ -242,7 +246,7 @@ var Contact = z7.object({
|
|
|
242
246
|
// ../schemas/src/signal.ts
|
|
243
247
|
import { z as z8 } from "zod";
|
|
244
248
|
var SignalEvent = z8.object({
|
|
245
|
-
|
|
249
|
+
project_id: Slug,
|
|
246
250
|
signal_key: Slug,
|
|
247
251
|
account_domain: Domain,
|
|
248
252
|
contact_linkedin_url: z8.string().url().nullable(),
|
|
@@ -255,7 +259,7 @@ var SignalEvent = z8.object({
|
|
|
255
259
|
// ../schemas/src/lead.ts
|
|
256
260
|
import { z as z9 } from "zod";
|
|
257
261
|
var Lead = z9.object({
|
|
258
|
-
|
|
262
|
+
project_id: Slug,
|
|
259
263
|
campaign_id: Slug,
|
|
260
264
|
run_id: z9.uuid(),
|
|
261
265
|
email: z9.email(),
|
|
@@ -282,7 +286,27 @@ var FEATURES = {
|
|
|
282
286
|
};
|
|
283
287
|
|
|
284
288
|
// ../schemas/src/migrations.ts
|
|
285
|
-
|
|
289
|
+
import { isMap, isScalar } from "yaml";
|
|
290
|
+
function renameKey(doc, path, from, to) {
|
|
291
|
+
const node = path.length ? doc.getIn(path, true) : doc.contents;
|
|
292
|
+
if (!isMap(node)) return;
|
|
293
|
+
const pair = node.items.find((p) => (isScalar(p.key) ? p.key.value : p.key) === from);
|
|
294
|
+
if (!pair) return;
|
|
295
|
+
if (isScalar(pair.key)) pair.key.value = to;
|
|
296
|
+
else pair.key = to;
|
|
297
|
+
}
|
|
298
|
+
var renameClientId = (doc) => renameKey(doc, [], "client_id", "project_id");
|
|
299
|
+
var MIGRATIONS = [
|
|
300
|
+
// 0.3.0: "clients" became "projects" (one private repo per project).
|
|
301
|
+
{ kind: "project", from: 1, description: "client_id \u2192 project_id, owners.client_approver \u2192 owners.approver, status churned \u2192 closed", up(doc) {
|
|
302
|
+
renameClientId(doc);
|
|
303
|
+
renameKey(doc, ["owners"], "client_approver", "approver");
|
|
304
|
+
if (doc.get("status") === "churned") doc.set("status", "closed");
|
|
305
|
+
} },
|
|
306
|
+
{ kind: "icp", from: 1, description: "client_id \u2192 project_id", up: renameClientId },
|
|
307
|
+
{ kind: "signals", from: 1, description: "client_id \u2192 project_id", up: renameClientId },
|
|
308
|
+
{ kind: "campaign-config", from: 1, description: "client_id \u2192 project_id", up: renameClientId }
|
|
309
|
+
];
|
|
286
310
|
function migrateDocument(kind, doc, migrations = MIGRATIONS, target = FILE_VERSIONS[kind]) {
|
|
287
311
|
const raw = doc.get("version");
|
|
288
312
|
const from = typeof raw === "number" ? raw : Number(raw);
|
|
@@ -321,11 +345,11 @@ function copyContentHash(markdown) {
|
|
|
321
345
|
|
|
322
346
|
// src/commands/approve.ts
|
|
323
347
|
var USAGE = `Usage:
|
|
324
|
-
gtm approve icp --by "<Name (
|
|
325
|
-
gtm approve copy <campaign> --by "<Name (
|
|
348
|
+
gtm approve icp --by "<Name (role)>" [--notes "<what was approved>"] [--date YYYY-MM-DD]
|
|
349
|
+
gtm approve copy <campaign> --by "<Name (role)>" [--notes "<what was approved>"] [--date YYYY-MM-DD]`;
|
|
326
350
|
function load(file) {
|
|
327
351
|
if (!existsSync(file)) {
|
|
328
|
-
console.error(`${file} not found: run this from the root of a
|
|
352
|
+
console.error(`${file} not found: run this from the root of a project repo`);
|
|
329
353
|
return null;
|
|
330
354
|
}
|
|
331
355
|
const doc = parseDocument(readFileSync(file, "utf8"));
|
|
@@ -361,7 +385,7 @@ function runApprove(root2, args2, opts) {
|
|
|
361
385
|
}
|
|
362
386
|
const by = opts.by?.trim();
|
|
363
387
|
if (!by) {
|
|
364
|
-
console.error('--by is required: the human who approved, e.g. --by "Maria Chen (
|
|
388
|
+
console.error('--by is required: the human who approved, e.g. --by "Maria Chen (VP Marketing)"');
|
|
365
389
|
return false;
|
|
366
390
|
}
|
|
367
391
|
const date = opts.date ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
@@ -502,7 +526,7 @@ import { dirname as dirname3, join as join5 } from "path";
|
|
|
502
526
|
var SHIPPED_DIRS = ["skills", "templates", "providers", "schemas"];
|
|
503
527
|
function agentsMd() {
|
|
504
528
|
const header = `<!-- GENERATED by \`gtm sync\` from ${PACKAGE_NAME} v${GTM_VERSION}. Do not edit: it is overwritten on upgrade.
|
|
505
|
-
|
|
529
|
+
Project-specific rules go in PROJECT.md. Process changes go to the Veleads/gtm-core repo. -->
|
|
506
530
|
|
|
507
531
|
`;
|
|
508
532
|
return header + readFileSync4(join5(KIT_DIR, "AGENTS.md"), "utf8").replaceAll("{{GTM_VERSION}}", GTM_VERSION);
|
|
@@ -545,7 +569,7 @@ function runSync(root2, opts = {}) {
|
|
|
545
569
|
console.log(`in sync with ${PACKAGE_NAME} v${GTM_VERSION}`);
|
|
546
570
|
return true;
|
|
547
571
|
}
|
|
548
|
-
console.log(`out of sync with ${PACKAGE_NAME} v${GTM_VERSION} (run \`
|
|
572
|
+
console.log(`out of sync with ${PACKAGE_NAME} v${GTM_VERSION} (run \`npx gtm sync\`; never edit .gtm/ or AGENTS.md by hand):`);
|
|
549
573
|
for (const d of diffs) console.log(` ${d}`);
|
|
550
574
|
return false;
|
|
551
575
|
}
|
|
@@ -562,18 +586,18 @@ function runSync(root2, opts = {}) {
|
|
|
562
586
|
// src/commands/init.ts
|
|
563
587
|
function runInit(slug, opts) {
|
|
564
588
|
if (!slug || !Slug.safeParse(slug).success || !opts.name) {
|
|
565
|
-
console.error('Usage: gtm init <
|
|
589
|
+
console.error('Usage: gtm init <project-slug> --name "<Project Name>" [--dir <path>] (slug: lowercase, digits, hyphens)');
|
|
566
590
|
return false;
|
|
567
591
|
}
|
|
568
|
-
const root2 = resolve(opts.cwd, opts.dir ?? `gtm-
|
|
592
|
+
const root2 = resolve(opts.cwd, opts.dir ?? `gtm-project-${slug}`);
|
|
569
593
|
if (existsSync4(root2) && readdirSync2(root2).length > 0) {
|
|
570
594
|
console.error(`${root2} exists and is not empty`);
|
|
571
595
|
return false;
|
|
572
596
|
}
|
|
573
|
-
const vars = {
|
|
597
|
+
const vars = { PROJECT_ID: slug, PROJECT_NAME: opts.name, TODAY: (/* @__PURE__ */ new Date()).toISOString().slice(0, 10), GTM_VERSION };
|
|
574
598
|
const tpl = join6(KIT_DIR, "templates");
|
|
575
599
|
copyTemplate(join6(tpl, "repo"), root2, vars);
|
|
576
|
-
copyTemplate(join6(tpl, "
|
|
600
|
+
copyTemplate(join6(tpl, "project"), root2, vars, { rename: (rel) => rel === "project.yaml" ? rel : join6("context", rel) });
|
|
577
601
|
for (const d of ["campaigns", "tam"]) {
|
|
578
602
|
mkdirSync3(join6(root2, d), { recursive: true });
|
|
579
603
|
writeFileSync4(join6(root2, d, ".gitkeep"), "");
|
|
@@ -584,9 +608,9 @@ function runInit(slug, opts) {
|
|
|
584
608
|
Created ${root2} (core v${GTM_VERSION})${git.status === 0 ? " with git initialised" : ""}.
|
|
585
609
|
|
|
586
610
|
Next:
|
|
587
|
-
1. Create a PRIVATE GitHub repo for it (e.g. <your-org>/gtm-
|
|
588
|
-
|
|
589
|
-
2.
|
|
611
|
+
1. Create a PRIVATE GitHub repo for it (e.g. <your-org>/gtm-project-${slug}) and push this folder.
|
|
612
|
+
Project repos hold project context: never make them public.
|
|
613
|
+
2. npm install && npx gtm validate (or pnpm/yarn; TODO warnings remain until intake is done)
|
|
590
614
|
3. Open the repo in Claude Code or Codex and start with stage 00-intake.`);
|
|
591
615
|
return true;
|
|
592
616
|
}
|
|
@@ -602,22 +626,39 @@ Per AGENTS.md this is a mandatory stop: do not improvise the job with ad-hoc scr
|
|
|
602
626
|
}
|
|
603
627
|
|
|
604
628
|
// src/commands/migrate.ts
|
|
605
|
-
import { existsSync as existsSync5, readFileSync as readFileSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
629
|
+
import { existsSync as existsSync5, readFileSync as readFileSync5, renameSync, writeFileSync as writeFileSync5 } from "fs";
|
|
606
630
|
import { join as join7, relative as relative2 } from "path";
|
|
607
631
|
import { parseDocument as parseDocument2 } from "yaml";
|
|
608
|
-
function
|
|
632
|
+
function projectYamlFiles(root2) {
|
|
609
633
|
const files = [
|
|
610
|
-
{ kind: "
|
|
634
|
+
{ kind: "project", file: join7(root2, "project.yaml") },
|
|
611
635
|
{ kind: "icp", file: join7(root2, "context", "icp.yaml") },
|
|
612
636
|
{ kind: "signals", file: join7(root2, "context", "signals.yaml") },
|
|
613
637
|
...subdirs(join7(root2, "campaigns")).map((c) => ({ kind: "campaign-config", file: join7(root2, "campaigns", c, "config.yaml") }))
|
|
614
638
|
];
|
|
615
639
|
return files.filter((f) => existsSync5(f.file));
|
|
616
640
|
}
|
|
641
|
+
function pendingRenames(root2) {
|
|
642
|
+
return FILE_RENAMES.filter((r) => existsSync5(join7(root2, r.from)) && !existsSync5(join7(root2, r.to)));
|
|
643
|
+
}
|
|
617
644
|
function runMigrate(root2, opts = {}) {
|
|
618
645
|
let ok = true;
|
|
619
646
|
let changed = 0;
|
|
620
|
-
for (const {
|
|
647
|
+
for (const { from, to } of pendingRenames(root2)) {
|
|
648
|
+
changed++;
|
|
649
|
+
console.log(` ${opts.dryRun ? "would rename" : "renamed"} ${from} \u2192 ${to}`);
|
|
650
|
+
if (opts.dryRun) continue;
|
|
651
|
+
renameSync(join7(root2, from), join7(root2, to));
|
|
652
|
+
if (to.endsWith(".md")) {
|
|
653
|
+
const p = join7(root2, to);
|
|
654
|
+
writeFileSync5(p, readFileSync5(p, "utf8").replace(new RegExp(`^# ${from.replace(".", "\\.")}`, "m"), `# ${to}`));
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
if (opts.dryRun && changed) {
|
|
658
|
+
console.log("(dry run: YAML migrations are listed after the renames are applied)");
|
|
659
|
+
return ok;
|
|
660
|
+
}
|
|
661
|
+
for (const { kind, file } of projectYamlFiles(root2)) {
|
|
621
662
|
const rel = relative2(root2, file);
|
|
622
663
|
const doc = parseDocument2(readFileSync5(file, "utf8"));
|
|
623
664
|
if (doc.errors.length) {
|
|
@@ -634,7 +675,7 @@ function runMigrate(root2, opts = {}) {
|
|
|
634
675
|
for (const a of res.applied) console.log(` ${opts.dryRun ? "would apply" : "applied"} ${rel}: ${a}`);
|
|
635
676
|
if (pinned) {
|
|
636
677
|
doc.setIn(["approval", "content_hash"], icpContentHash(doc.toJS()));
|
|
637
|
-
console.log(` re-pinned ICP approval hash in ${rel} (core migration, content approved by the
|
|
678
|
+
console.log(` re-pinned ICP approval hash in ${rel} (core migration, content approved by the project approver)`);
|
|
638
679
|
}
|
|
639
680
|
if (!opts.dryRun) writeFileSync5(file, doc.toString({ flowCollectionPadding: false, lineWidth: 0 }));
|
|
640
681
|
} catch (e) {
|
|
@@ -655,9 +696,9 @@ function runNewCampaign(root2, campaign) {
|
|
|
655
696
|
console.error("Usage: gtm new-campaign <campaign-slug>");
|
|
656
697
|
return false;
|
|
657
698
|
}
|
|
658
|
-
const
|
|
659
|
-
if (!existsSync6(
|
|
660
|
-
console.error("
|
|
699
|
+
const projectFile = join8(root2, "project.yaml");
|
|
700
|
+
if (!existsSync6(projectFile)) {
|
|
701
|
+
console.error("project.yaml not found: run this from the root of a project repo");
|
|
661
702
|
return false;
|
|
662
703
|
}
|
|
663
704
|
const dest = join8(root2, "campaigns", campaign);
|
|
@@ -665,8 +706,8 @@ function runNewCampaign(root2, campaign) {
|
|
|
665
706
|
console.error(`campaigns/${campaign} already exists`);
|
|
666
707
|
return false;
|
|
667
708
|
}
|
|
668
|
-
const
|
|
669
|
-
copyTemplate(join8(KIT_DIR, "templates", "campaign"), dest, {
|
|
709
|
+
const projectId = parse(readFileSync6(projectFile, "utf8"))?.project_id ?? "TODO";
|
|
710
|
+
copyTemplate(join8(KIT_DIR, "templates", "campaign"), dest, { PROJECT_ID: projectId, CAMPAIGN_ID: campaign, OWNER: process.env.GTM_USER ?? "TODO" });
|
|
670
711
|
mkdirSync4(join8(dest, "runs"), { recursive: true });
|
|
671
712
|
writeFileSync6(join8(dest, "runs", ".gitkeep"), "");
|
|
672
713
|
console.log(`Created campaigns/${campaign}. Branch: git switch -c campaign/${campaign}`);
|
|
@@ -688,9 +729,9 @@ function campaignStages(root2) {
|
|
|
688
729
|
return subdirs(join9(root2, "campaigns")).map((id) => ({ id, stage: readYaml(join9(root2, "campaigns", id, "config.yaml"))?.stage ?? "?" }));
|
|
689
730
|
}
|
|
690
731
|
function runStatus(root2) {
|
|
691
|
-
const
|
|
732
|
+
const project = readYaml(join9(root2, "project.yaml"));
|
|
692
733
|
const icp = readYaml(join9(root2, "context", "icp.yaml"));
|
|
693
|
-
console.log(`${
|
|
734
|
+
console.log(`${project?.name ?? "?"} (${project?.project_id ?? "?"}) \xB7 status ${project?.status ?? "?"} \xB7 core v${GTM_VERSION}`);
|
|
694
735
|
console.log(`ICP gate: ${icp?.approval?.status ?? "?"}${icp?.approval?.approved_by ? ` by ${icp.approval.approved_by} on ${icp.approval.approved_at}` : ""}
|
|
695
736
|
`);
|
|
696
737
|
const rows = subdirs(join9(root2, "campaigns")).map((id) => {
|
|
@@ -700,7 +741,7 @@ function runStatus(root2) {
|
|
|
700
741
|
return [id, cfg.stage ?? "?", cfg.gates?.copy?.status ?? "?", cfg.gates?.launch?.status ?? "?", cfg.owner ?? "?", last];
|
|
701
742
|
});
|
|
702
743
|
if (!rows.length) {
|
|
703
|
-
console.log("no campaigns yet:
|
|
744
|
+
console.log("no campaigns yet: npx gtm new-campaign <campaign-slug>");
|
|
704
745
|
return true;
|
|
705
746
|
}
|
|
706
747
|
const header = ["campaign", "stage", "copy gate", "launch gate", "owner", "latest run log"];
|
|
@@ -766,8 +807,8 @@ function loadVersioned(r, file, kind, schema) {
|
|
|
766
807
|
}
|
|
767
808
|
const expected = FILE_VERSIONS[kind];
|
|
768
809
|
if (raw?.version !== expected) {
|
|
769
|
-
if (typeof raw?.version === "number" && raw.version < expected) r.err(file, `is version ${raw.version}, core v${GTM_VERSION} expects ${expected}: run \`
|
|
770
|
-
else if (typeof raw?.version === "number") r.err(file, `is version ${raw.version}, newer than core v${GTM_VERSION} (${expected}): run \`
|
|
810
|
+
if (typeof raw?.version === "number" && raw.version < expected) r.err(file, `is version ${raw.version}, core v${GTM_VERSION} expects ${expected}: run \`npx gtm migrate\``);
|
|
811
|
+
else if (typeof raw?.version === "number") r.err(file, `is version ${raw.version}, newer than core v${GTM_VERSION} (${expected}): run \`npx gtm upgrade\``);
|
|
771
812
|
else r.err(file, `missing "version: ${expected}"`);
|
|
772
813
|
return null;
|
|
773
814
|
}
|
|
@@ -784,14 +825,14 @@ function checkCoreSync(r, root2) {
|
|
|
784
825
|
if (!/^\d+\.\d+\.\d+/.test(pinned)) r.err(join10(root2, "package.json"), `${PACKAGE_NAME} must be pinned exactly (got "${pinned}")`);
|
|
785
826
|
const versionFile = join10(root2, ".gtm", "VERSION");
|
|
786
827
|
const synced = existsSync8(versionFile) ? readFileSync8(versionFile, "utf8").trim() : null;
|
|
787
|
-
if (synced !== GTM_VERSION) r.err(versionFile, `is ${synced ?? "missing"}, installed core is ${GTM_VERSION}: run \`
|
|
788
|
-
if (!existsSync8(join10(root2, "AGENTS.md"))) r.err(join10(root2, "AGENTS.md"), "missing: run `
|
|
828
|
+
if (synced !== GTM_VERSION) r.err(versionFile, `is ${synced ?? "missing"}, installed core is ${GTM_VERSION}: run \`npx gtm sync\``);
|
|
829
|
+
if (!existsSync8(join10(root2, "AGENTS.md"))) r.err(join10(root2, "AGENTS.md"), "missing: run `npx gtm sync`");
|
|
789
830
|
}
|
|
790
831
|
function checkNoLeadData(r, root2) {
|
|
791
832
|
for (const rel of walk(root2, SKIP_DIRS)) {
|
|
792
833
|
const file = join10(root2, rel);
|
|
793
834
|
if (DATA_FILES.test(rel)) {
|
|
794
|
-
r.err(file, "data files are not allowed in a
|
|
835
|
+
r.err(file, "data files are not allowed in a project repo (lead data belongs in Postgres)");
|
|
795
836
|
continue;
|
|
796
837
|
}
|
|
797
838
|
if (!/\.(md|ya?ml|txt)$/i.test(rel) || rel === "pnpm-lock.yaml") continue;
|
|
@@ -811,14 +852,14 @@ function checkPinnedApproval(r, contentFile, approval, current, what, approveCmd
|
|
|
811
852
|
r.err(contentFile, `${what} changed after approval (approved ${pinned}, now ${current}). Re-present it to the approver and record it with \`${approveCmd} --by "<name>"\`, or revert the change`);
|
|
812
853
|
return false;
|
|
813
854
|
}
|
|
814
|
-
function validateCampaign(r, root2,
|
|
855
|
+
function validateCampaign(r, root2, projectId, campaign, icp, signals, icpApproved) {
|
|
815
856
|
const dir = join10(root2, "campaigns", campaign);
|
|
816
857
|
console.log(`campaign ${campaign}`);
|
|
817
858
|
for (const f of REQUIRED_CAMPAIGN) if (!existsSync8(join10(dir, f))) r.err(join10(dir, f), "missing");
|
|
818
859
|
const cfgFile = join10(dir, "config.yaml");
|
|
819
860
|
const cfg = loadVersioned(r, cfgFile, "campaign-config", CampaignConfig);
|
|
820
861
|
if (cfg) {
|
|
821
|
-
if (cfg.
|
|
862
|
+
if (cfg.project_id !== projectId) r.err(cfgFile, `project_id "${cfg.project_id}" does not match project.yaml "${projectId}"`);
|
|
822
863
|
if (cfg.campaign_id !== campaign) r.err(cfgFile, `campaign_id "${cfg.campaign_id}" does not match folder "${campaign}"`);
|
|
823
864
|
if (icp) {
|
|
824
865
|
const segs = new Set(icp.segments.map((s) => s.id));
|
|
@@ -841,7 +882,7 @@ function validateCampaign(r, root2, clientId, campaign, icp, signals, icpApprove
|
|
|
841
882
|
r.err(cfgFile, `stage is "${cfg.stage}" but the ICP gate (context/icp.yaml approval) is not approved, or the ICP changed since approval`);
|
|
842
883
|
}
|
|
843
884
|
const copyFile = join10(dir, "copy.md");
|
|
844
|
-
const copyApproved = existsSync8(copyFile) ? checkPinnedApproval(r, copyFile, cfg.gates.copy, copyContentHash(readFileSync8(copyFile, "utf8")), "copy", `
|
|
885
|
+
const copyApproved = existsSync8(copyFile) ? checkPinnedApproval(r, copyFile, cfg.gates.copy, copyContentHash(readFileSync8(copyFile, "utf8")), "copy", `npx gtm approve copy ${campaign}`) : false;
|
|
845
886
|
if (stageIndex(cfg.stage) > stageIndex("copy") && !copyApproved) {
|
|
846
887
|
r.err(cfgFile, `stage is "${cfg.stage}" but gates.copy is not approved, or copy.md changed since approval`);
|
|
847
888
|
}
|
|
@@ -866,22 +907,23 @@ function runValidate(root2, onlyCampaign) {
|
|
|
866
907
|
const r = new Report(root2);
|
|
867
908
|
console.log(`validating ${root2} against core v${GTM_VERSION}`);
|
|
868
909
|
checkCoreSync(r, root2);
|
|
869
|
-
const
|
|
870
|
-
const
|
|
910
|
+
for (const { from, to } of pendingRenames(root2)) r.err(join10(root2, from), `renamed to ${to} in core 0.3.0: run \`npx gtm migrate\``);
|
|
911
|
+
const meta = loadVersioned(r, join10(root2, "project.yaml"), "project", ProjectFile);
|
|
912
|
+
const projectId = meta?.project_id ?? "";
|
|
871
913
|
if (meta) {
|
|
872
914
|
for (const key of Object.keys(meta.features)) {
|
|
873
|
-
if (!FEATURES[key]) r.err(join10(root2, "
|
|
915
|
+
if (!FEATURES[key]) r.err(join10(root2, "project.yaml"), `unknown feature "${key}" (known: ${Object.keys(FEATURES).join(", ") || "none"})`);
|
|
874
916
|
}
|
|
875
917
|
}
|
|
876
|
-
if (!existsSync8(join10(root2, "
|
|
918
|
+
if (!existsSync8(join10(root2, "PROJECT.md"))) r.warn(join10(root2, "PROJECT.md"), "missing (project-specific rules for agents)");
|
|
877
919
|
for (const f of REQUIRED_CONTEXT) if (!existsSync8(join10(root2, "context", f))) r.err(join10(root2, "context", f), "missing");
|
|
878
920
|
const icpFile = join10(root2, "context", "icp.yaml");
|
|
879
921
|
const sigFile = join10(root2, "context", "signals.yaml");
|
|
880
922
|
const icp = existsSync8(icpFile) ? loadVersioned(r, icpFile, "icp", IcpFile) : null;
|
|
881
923
|
const signals = existsSync8(sigFile) ? loadVersioned(r, sigFile, "signals", SignalsFile) : null;
|
|
882
|
-
if (meta && icp && icp.
|
|
883
|
-
if (meta && signals && signals.
|
|
884
|
-
const icpApproved = icp ? checkPinnedApproval(r, icpFile, icp.approval, icpContentHash(parse3(readFileSync8(icpFile, "utf8"))), "ICP", "
|
|
924
|
+
if (meta && icp && icp.project_id !== projectId) r.err(icpFile, `project_id "${icp.project_id}" does not match project.yaml "${projectId}"`);
|
|
925
|
+
if (meta && signals && signals.project_id !== projectId) r.err(sigFile, `project_id "${signals.project_id}" does not match project.yaml "${projectId}"`);
|
|
926
|
+
const icpApproved = icp ? checkPinnedApproval(r, icpFile, icp.approval, icpContentHash(parse3(readFileSync8(icpFile, "utf8"))), "ICP", "npx gtm approve icp") : false;
|
|
885
927
|
checkNoLeadData(r, root2);
|
|
886
928
|
const campaigns = onlyCampaign ? [onlyCampaign] : subdirs(join10(root2, "campaigns"));
|
|
887
929
|
for (const c of campaigns) {
|
|
@@ -889,24 +931,31 @@ function runValidate(root2, onlyCampaign) {
|
|
|
889
931
|
r.err(join10(root2, "campaigns", c), "campaign folder not found");
|
|
890
932
|
continue;
|
|
891
933
|
}
|
|
892
|
-
validateCampaign(r, root2,
|
|
934
|
+
validateCampaign(r, root2, projectId, c, icp, signals, icpApproved);
|
|
893
935
|
}
|
|
894
936
|
r.summary();
|
|
895
937
|
return r.errors === 0;
|
|
896
938
|
}
|
|
897
939
|
|
|
898
940
|
// src/commands/upgrade.ts
|
|
899
|
-
function
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
941
|
+
function detectPackageManager(root2) {
|
|
942
|
+
if (existsSync9(join11(root2, "pnpm-lock.yaml"))) return "pnpm";
|
|
943
|
+
if (existsSync9(join11(root2, "yarn.lock"))) return "yarn";
|
|
944
|
+
return "npm";
|
|
945
|
+
}
|
|
946
|
+
function installExact(root2, pm, spec) {
|
|
947
|
+
const onPath = (bin) => spawnSync2(bin, ["--version"], { stdio: "ignore" }).status === 0;
|
|
948
|
+
const [cmd, args2] = pm === "npm" ? ["npm", ["install", "--save-dev", "--save-exact", spec]] : pm === "pnpm" ? onPath("pnpm") ? ["pnpm", ["add", "-D", "--save-exact", spec]] : ["npx", ["-y", "pnpm@10", "add", "-D", "--save-exact", spec]] : onPath("yarn") ? ["yarn", ["add", "-D", "--exact", spec]] : ["npx", ["-y", "yarn@1", "add", "-D", "--exact", spec]];
|
|
949
|
+
return spawnSync2(cmd, args2, { cwd: root2, stdio: "inherit" }).status ?? 1;
|
|
950
|
+
}
|
|
951
|
+
function gtm(root2, args2) {
|
|
952
|
+
return spawnSync2("npx", ["gtm", ...args2], { cwd: root2, stdio: "inherit" }).status ?? 1;
|
|
904
953
|
}
|
|
905
954
|
var FROZEN_STAGES = ["qa", "launch"];
|
|
906
955
|
function runUpgrade(root2, opts) {
|
|
907
956
|
const before = pinnedVersion(root2);
|
|
908
957
|
if (!before) {
|
|
909
|
-
console.error(`package.json does not depend on ${PACKAGE_NAME}: is this a
|
|
958
|
+
console.error(`package.json does not depend on ${PACKAGE_NAME}: is this a project repo?`);
|
|
910
959
|
return false;
|
|
911
960
|
}
|
|
912
961
|
const frozen = campaignStages(root2).filter((c) => FROZEN_STAGES.includes(c.stage));
|
|
@@ -921,11 +970,12 @@ function runUpgrade(root2, opts) {
|
|
|
921
970
|
return false;
|
|
922
971
|
}
|
|
923
972
|
const target = opts.version ?? "latest";
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
if (
|
|
927
|
-
if (
|
|
928
|
-
|
|
973
|
+
const pm = detectPackageManager(root2);
|
|
974
|
+
console.log(`Upgrading ${PACKAGE_NAME} ${before} \u2192 ${target} (with ${pm})`);
|
|
975
|
+
if (installExact(root2, pm, `${PACKAGE_NAME}@${target}`) !== 0) return false;
|
|
976
|
+
if (gtm(root2, ["sync"]) !== 0) return false;
|
|
977
|
+
if (gtm(root2, ["migrate"]) !== 0) return false;
|
|
978
|
+
const valid = gtm(root2, ["validate"]) === 0;
|
|
929
979
|
const after = pinnedVersion(root2) ?? target;
|
|
930
980
|
const changelog = join11(root2, "node_modules", ...PACKAGE_NAME.split("/"), "CHANGELOG.md");
|
|
931
981
|
if (existsSync9(changelog)) {
|
|
@@ -939,21 +989,134 @@ ${notes || "(no changelog entries)"}
|
|
|
939
989
|
return valid;
|
|
940
990
|
}
|
|
941
991
|
|
|
992
|
+
// src/help.ts
|
|
993
|
+
var COMMANDS = {
|
|
994
|
+
init: {
|
|
995
|
+
usage: 'gtm init <project-slug> --name "<Project Name>" [--dir <path>]',
|
|
996
|
+
summary: "scaffold a new project repo",
|
|
997
|
+
details: [
|
|
998
|
+
"Creates a complete project repo: project.yaml, context/ templates, campaigns/, tam/, PROJECT.md,",
|
|
999
|
+
"CI workflows, .gtm/ (core kit) and AGENTS.md, pinned to this CLI's version, then runs git init.",
|
|
1000
|
+
"Push the result to a PRIVATE repo: it will hold project context."
|
|
1001
|
+
],
|
|
1002
|
+
writes: "A new folder (default ./gtm-project-<slug>). Refuses if it exists and is not empty.",
|
|
1003
|
+
examples: ['npx @veleads/gtm init acme --name "Acme Corp"', 'npx @veleads/gtm init acme --name "Acme Corp" --dir ./projects/acme']
|
|
1004
|
+
},
|
|
1005
|
+
"new-campaign": {
|
|
1006
|
+
usage: "gtm new-campaign <campaign-slug>",
|
|
1007
|
+
summary: "scaffold campaigns/<slug>/",
|
|
1008
|
+
details: ["Copies the campaign template (brief.md, config.yaml, copy.md, runs/). Owner comes from GTM_USER."],
|
|
1009
|
+
writes: "campaigns/<slug>/ (refuses if it exists).",
|
|
1010
|
+
examples: ["git switch -c campaign/q1-uk-cfo && npx gtm new-campaign q1-uk-cfo"]
|
|
1011
|
+
},
|
|
1012
|
+
validate: {
|
|
1013
|
+
usage: "gtm validate [campaign]",
|
|
1014
|
+
summary: "check repo against schemas, gates, data rules",
|
|
1015
|
+
details: [
|
|
1016
|
+
"Checks project.yaml, context/ and campaigns against the schemas; segment/persona/signal references;",
|
|
1017
|
+
"gate order (no stage past an unapproved or changed ICP/copy approval); required files; run-log names;",
|
|
1018
|
+
"no lead data (blocks data files, warns on email addresses); .gtm/ matches the installed core.",
|
|
1019
|
+
"Exit code 1 if there is any error; warnings never fail."
|
|
1020
|
+
],
|
|
1021
|
+
writes: "Nothing (read-only).",
|
|
1022
|
+
examples: ["npx gtm validate", "npx gtm validate q1-uk-cfo"]
|
|
1023
|
+
},
|
|
1024
|
+
status: {
|
|
1025
|
+
usage: "gtm status",
|
|
1026
|
+
summary: "campaigns, stages, gates",
|
|
1027
|
+
details: ["One screen: project, core version, ICP gate, and per campaign the stage, copy/launch gates, owner and latest run log."],
|
|
1028
|
+
writes: "Nothing (read-only).",
|
|
1029
|
+
examples: ["npx gtm status"]
|
|
1030
|
+
},
|
|
1031
|
+
approve: {
|
|
1032
|
+
usage: 'gtm approve icp --by "<Name (role)>" [--notes ".."] [--date YYYY-MM-DD]\ngtm approve copy <campaign> --by "<Name (role)>" [--notes ".."] [--date YYYY-MM-DD]',
|
|
1033
|
+
summary: "record a sign-off and pin a hash of the approved content",
|
|
1034
|
+
details: [
|
|
1035
|
+
"Run ONLY after the named human explicitly approved in this session (AGENTS.md section 4).",
|
|
1036
|
+
"Pins a content hash: if context/icp.yaml or the campaign's copy.md changes afterwards,",
|
|
1037
|
+
"`gtm validate` fails until it is re-approved. Refuses if the file would not validate.",
|
|
1038
|
+
"The launch gate is recorded by hand in config.yaml (its content is the lead set)."
|
|
1039
|
+
],
|
|
1040
|
+
writes: "context/icp.yaml \u2192 approval (icp), or campaigns/<campaign>/config.yaml \u2192 gates.copy (copy).",
|
|
1041
|
+
examples: ['npx gtm approve icp --by "Maria Chen (VP Marketing)" --notes "Segments A+B, personas CFO+AP"', 'npx gtm approve copy q1-uk-cfo --by "Maria Chen (VP Marketing)"']
|
|
1042
|
+
},
|
|
1043
|
+
sync: {
|
|
1044
|
+
usage: "gtm sync [--check]",
|
|
1045
|
+
summary: "copy core kit into .gtm/ + AGENTS.md",
|
|
1046
|
+
details: [
|
|
1047
|
+
"Makes .gtm/ (skills, templates, schemas, provider docs) and AGENTS.md match the installed core.",
|
|
1048
|
+
"--check only compares and exits 1 if they differ (CI uses this; never edit .gtm/ by hand)."
|
|
1049
|
+
],
|
|
1050
|
+
writes: ".gtm/ (replaced entirely) and AGENTS.md; creates CLAUDE.md if missing. With --check: nothing.",
|
|
1051
|
+
examples: ["npx gtm sync", "npx gtm sync --check"]
|
|
1052
|
+
},
|
|
1053
|
+
migrate: {
|
|
1054
|
+
usage: "gtm migrate [--dry-run]",
|
|
1055
|
+
summary: "upgrade YAML files to current schema versions",
|
|
1056
|
+
details: ["Applies core schema migrations to project.yaml, context/*.yaml and campaigns/*/config.yaml, keeping comments."],
|
|
1057
|
+
writes: "Those YAML files, only if a migration applies. With --dry-run: nothing.",
|
|
1058
|
+
examples: ["npx gtm migrate --dry-run", "npx gtm migrate"]
|
|
1059
|
+
},
|
|
1060
|
+
upgrade: {
|
|
1061
|
+
usage: "gtm upgrade [version] [--force]",
|
|
1062
|
+
summary: "install newer core, sync, migrate, validate",
|
|
1063
|
+
details: [
|
|
1064
|
+
"Installs @veleads/gtm@<version|latest> pinned exactly, then sync, migrate and validate, and prints the changelog.",
|
|
1065
|
+
"Refuses while a campaign is in qa or launch, or with uncommitted changes (--force overrides; ask a GTM engineer)."
|
|
1066
|
+
],
|
|
1067
|
+
writes: "package.json, lockfile, node_modules, .gtm/, AGENTS.md and migrated YAML files.",
|
|
1068
|
+
examples: ["npx gtm upgrade", "npx gtm upgrade 0.3.0"]
|
|
1069
|
+
},
|
|
1070
|
+
job: {
|
|
1071
|
+
usage: "gtm job <name> <campaign>",
|
|
1072
|
+
summary: "run a pipeline job (Phase 2)",
|
|
1073
|
+
details: [
|
|
1074
|
+
"Planned: tam, enrich, suppression:import, signals, qualify, qa, push, activate, results, status.",
|
|
1075
|
+
"Not available yet: it exits 1. Per AGENTS.md this is a stop; do not improvise jobs with ad-hoc scripts."
|
|
1076
|
+
],
|
|
1077
|
+
writes: "Nothing yet.",
|
|
1078
|
+
examples: ["npx gtm job tam q1-uk-cfo"]
|
|
1079
|
+
}
|
|
1080
|
+
};
|
|
1081
|
+
function generalHelp(version) {
|
|
1082
|
+
const width = Math.max(...Object.keys(COMMANDS).map((c) => c.length));
|
|
1083
|
+
const lines = Object.entries(COMMANDS).map(([name, c]) => ` ${name.padEnd(width)} ${c.summary}`);
|
|
1084
|
+
return `gtm v${version}: CLI for gtm-brain project repos (@veleads/gtm)
|
|
1085
|
+
|
|
1086
|
+
Usage: gtm <command> [options] (inside a project repo: npx gtm <command>)
|
|
1087
|
+
|
|
1088
|
+
Commands:
|
|
1089
|
+
${lines.join("\n")}
|
|
1090
|
+
|
|
1091
|
+
Run \`gtm <command> --help\` for usage, what it writes, and examples.
|
|
1092
|
+
|
|
1093
|
+
Global options:
|
|
1094
|
+
--root <dir> repo root (default: current directory)
|
|
1095
|
+
-h, --help show help (with a command: that command's help; nothing is run)
|
|
1096
|
+
-v, --version print the version
|
|
1097
|
+
|
|
1098
|
+
Exit codes: 0 success, 1 failure or validation errors.
|
|
1099
|
+
|
|
1100
|
+
For coding agents: inside a project repo, read AGENTS.md first and follow it. Read-only commands
|
|
1101
|
+
(validate, status, sync --check, migrate --dry-run) are always safe to run.`;
|
|
1102
|
+
}
|
|
1103
|
+
function commandHelp(name) {
|
|
1104
|
+
const c = COMMANDS[name];
|
|
1105
|
+
if (!c) return null;
|
|
1106
|
+
return `gtm ${name}: ${c.summary}
|
|
1107
|
+
|
|
1108
|
+
Usage:
|
|
1109
|
+
${c.usage.split("\n").map((u) => ` ${u}`).join("\n")}
|
|
1110
|
+
|
|
1111
|
+
${c.details.map((d) => ` ${d}`).join("\n")}
|
|
1112
|
+
|
|
1113
|
+
Writes: ${c.writes}
|
|
1114
|
+
|
|
1115
|
+
Examples:
|
|
1116
|
+
${c.examples.map((e) => ` ${e}`).join("\n")}`;
|
|
1117
|
+
}
|
|
1118
|
+
|
|
942
1119
|
// src/cli.ts
|
|
943
|
-
var HELP = `gtm v${GTM_VERSION}: gtm-brain core CLI (run inside a client repo)
|
|
944
|
-
|
|
945
|
-
gtm init <client-slug> --name "<Name>" [--dir <path>] scaffold a new client repo
|
|
946
|
-
gtm new-campaign <campaign-slug> scaffold campaigns/<slug>/
|
|
947
|
-
gtm validate [campaign] check repo against schemas, gates, data rules
|
|
948
|
-
gtm status campaigns, stages, gates
|
|
949
|
-
gtm approve icp --by "<Name (side)>" [--notes ..] [--date YYYY-MM-DD]
|
|
950
|
-
gtm approve copy <campaign> --by "<Name (side)>" [..] record a sign-off and pin a hash of the approved content
|
|
951
|
-
gtm sync [--check] copy core kit into .gtm/ + AGENTS.md
|
|
952
|
-
gtm migrate [--dry-run] upgrade YAML files to current schema versions
|
|
953
|
-
gtm upgrade [version] [--force] install newer core, sync, migrate, validate
|
|
954
|
-
gtm job <name> <campaign> run a pipeline job (Phase 2)
|
|
955
|
-
|
|
956
|
-
Global: --root <dir> repo root (default: current directory)`;
|
|
957
1120
|
var { values, positionals } = parseArgs({
|
|
958
1121
|
allowPositionals: true,
|
|
959
1122
|
options: {
|
|
@@ -973,11 +1136,27 @@ var { values, positionals } = parseArgs({
|
|
|
973
1136
|
var [command, ...args] = positionals;
|
|
974
1137
|
var cwd = process.cwd();
|
|
975
1138
|
var root = resolve2(cwd, values.root ?? ".");
|
|
1139
|
+
function help(name) {
|
|
1140
|
+
if (!name) {
|
|
1141
|
+
console.log(generalHelp(GTM_VERSION));
|
|
1142
|
+
return true;
|
|
1143
|
+
}
|
|
1144
|
+
const text = commandHelp(name);
|
|
1145
|
+
if (!text) {
|
|
1146
|
+
console.error(`Unknown command "${name}"
|
|
1147
|
+
|
|
1148
|
+
${generalHelp(GTM_VERSION)}`);
|
|
1149
|
+
return false;
|
|
1150
|
+
}
|
|
1151
|
+
console.log(text);
|
|
1152
|
+
return true;
|
|
1153
|
+
}
|
|
976
1154
|
function run() {
|
|
977
1155
|
if (values.version) {
|
|
978
1156
|
console.log(GTM_VERSION);
|
|
979
1157
|
return true;
|
|
980
1158
|
}
|
|
1159
|
+
if (values.help) return help(command === "help" ? args[0] : command);
|
|
981
1160
|
switch (command) {
|
|
982
1161
|
case "init":
|
|
983
1162
|
return runInit(args[0], { name: values.name, dir: values.dir, cwd });
|
|
@@ -998,13 +1177,13 @@ function run() {
|
|
|
998
1177
|
case "job":
|
|
999
1178
|
return runJob(args[0]);
|
|
1000
1179
|
case void 0:
|
|
1180
|
+
return help(void 0);
|
|
1001
1181
|
case "help":
|
|
1002
|
-
|
|
1003
|
-
return true;
|
|
1182
|
+
return help(args[0]);
|
|
1004
1183
|
default:
|
|
1005
1184
|
console.error(`Unknown command "${command}"
|
|
1006
1185
|
|
|
1007
|
-
${
|
|
1186
|
+
${generalHelp(GTM_VERSION)}`);
|
|
1008
1187
|
return false;
|
|
1009
1188
|
}
|
|
1010
1189
|
}
|