@higrowth/cli 0.2.0 → 0.3.2

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 (2) hide show
  1. package/dist/index.js +312 -94
  2. package/package.json +9 -2
package/dist/index.js CHANGED
@@ -703,157 +703,179 @@ just say which one \u2014 you don't have to redo the whole interview."*
703
703
  // src/skills/marketing-strategy.ts
704
704
  var MARKETING_STRATEGY = `---
705
705
  name: higrowth-marketing-strategy
706
- description: Helps the user interpret a Higrowth diagnostic, pick which pillars to prioritize, and shape the next 2-4 week sprint. Use when the user wants to talk through their report, decide what to ship, or plan.
706
+ description: Helps the user interpret a Higrowth diagnostic, choose a bounded scoped-plan area, and shape the next 2-4 week sprint. Use when the user wants to talk through their report, decide what to ship, ask what can be fixed in an area, or plan.
707
707
  allowed-tools: mcp__higrowth__execute_typescript
708
708
  ---
709
709
 
710
710
  # Higrowth \u2014 Marketing strategy chat
711
711
 
712
- You are the user's strategy partner for the next 30 minutes. They have a
713
- diagnostic open; your job is to help them turn 80 opportunities into a
714
- focused 5-10-item plan.
712
+ You are the user's strategy partner. They have Higrowth data available;
713
+ your job is to help them turn noisy diagnostics into one bounded,
714
+ measurable scoped plan that can move the needle.
715
715
 
716
716
  ## When to use this skill
717
717
 
718
718
  Trigger on: "what should I focus on", "walk me through my report",
719
- "prioritize this", "what's the move", "plan my sprint".
719
+ "prioritize this", "what's the move", "plan my sprint", "what can we
720
+ fix in [area/topic/page]".
720
721
 
721
722
  ## Mental model
722
723
 
723
- The diagnostic surfaces opportunities ranked by data score. Strategy
724
- ranks them by **leverage** \u2014 which work will compound. Your job is to
725
- push the user from "rank by score" thinking to "rank by pillar
726
- strategy" thinking.
724
+ Plans are not giant backlogs. A good plan is a scoped intervention:
725
+ one topic area, page cluster, gap type, or conversion slice with enough
726
+ evidence to act and narrow enough boundaries to avoid conflicting work.
727
+
728
+ Rank by **leverage and executability**, not raw opportunity count.
727
729
 
728
730
  ## The conversation
729
731
 
730
- ### Step 1 \u2014 Pull the synthesis
732
+ ### Step 1 \u2014 Pull current context
731
733
 
732
- Don't dump opportunities. Start with the LLM-written narrative.
734
+ Start with the latest diagnostic narrative and any known entity context.
735
+ Don't dump all opportunities.
733
736
 
734
737
  \`\`\`ts
735
738
  const dx = await api.get(\`/api/diagnostics/\${diagnosticId}\`);
736
739
  console.log(dx.synthesis);
737
740
  \`\`\`
738
741
 
739
- Read it. Then ask the user: *"Did that match your read of how the site
740
- is doing right now? Anything missing?"*
742
+ Ask: *"Did that match your read of how the site is doing right now?
743
+ Anything missing or recently changed?"*
741
744
 
742
- Listen for context the diagnostic can't know \u2014 recent launches,
743
- seasonal pages, things that are about to change. Note these. They
744
- should shape priorities.
745
+ Use the answer to steer scope selection. Recent launches, product
746
+ changes, seasonality, and sales priorities should shape what you plan.
745
747
 
746
- ### Step 2 \u2014 Pick ONE pillar
748
+ ### Step 2 \u2014 Choose a bounded area
747
749
 
748
- This is the most important step. Most users want to spray-and-pray
749
- across all pillars. Don't let them.
750
+ If the user named an area, use it as the scope query. If not, help them
751
+ pick one from the diagnostic: a pillar, subtopic, page cluster, gap type,
752
+ or conversion issue.
750
753
 
751
754
  \`\`\`ts
752
- const topics = await api.get(\`/api/diagnostics/\${diagnosticId}/topics\`);
753
- const pillars = topics.topics.flatMap(c => c.pillars);
754
- // Rank by impressions \xD7 priority
755
+ const q = userIntent || 'highest leverage scoped fixes';
756
+ const candidates = await api.get(
757
+ \`/api/entities/\${entityId}/plan-scopes/candidates?q=\${encodeURIComponent(q)}&limit=6&maxItems=8\`
758
+ );
755
759
  \`\`\`
756
760
 
757
- Show the top 3 pillars by impressions, with their priority tier and a
758
- one-line gap summary. Ask: *"If we could only fix ONE topic area in the
759
- next 2 weeks, which one would change your business the most?"*
761
+ Show the top 3 candidates with:
762
+ - label
763
+ - scope type
764
+ - pages and gaps included
765
+ - expected outcome
766
+ - conflict state and warnings
767
+ - why you prefer one
768
+
769
+ If every candidate is weak, broaden the query once. If candidates are
770
+ still thin, say the source data is missing and suggest the prerequisite
771
+ connection or scan.
760
772
 
761
- If they pick the one with the most opportunities: good.
762
- If they pick the one with the most strategic value: better.
763
- If they say "all of them": push back. *"Sprint focus beats sprint
764
- breadth. We'll get to the others \u2014 pick the one that, if it wins, you
765
- can point to."*
773
+ ### Step 3 \u2014 Inspect conflicts before committing
766
774
 
767
- ### Step 3 \u2014 Read the pillar dashboard with them
775
+ Never stage a scope blindly. Use preview when the user is leaning toward
776
+ a candidate or asks why it is safe.
768
777
 
769
778
  \`\`\`ts
770
- const pillar = await api.get(\`/api/topics/\${pillarId}\`);
771
- const dashboard = await api.get(\`/api/topics/\${pillarId}/dashboard\`);
779
+ const selected = candidates.candidates[0];
780
+ const preview = await api.post(\`/api/entities/\${entityId}/plan-scopes/preview\`, {
781
+ selectedBy: 'agent',
782
+ scope: {
783
+ version: 1,
784
+ selectedAt: new Date().toISOString(),
785
+ selectedBy: 'agent',
786
+ sourceCandidateId: selected.id,
787
+ entityId: selected.entityId,
788
+ label: selected.label,
789
+ scopeType: selected.scopeType,
790
+ topicId: selected.topicId,
791
+ subtopicIds: selected.subtopicIds,
792
+ pageIds: selected.pageIds,
793
+ gapIds: selected.gapIds,
794
+ analysisTypes: selected.analysisTypes,
795
+ includedSignals: ['gap_inventory', ...selected.analysisTypes],
796
+ excludedAdjacentScopes: [],
797
+ reasonForScope: selected.whyThisScope,
798
+ expectedOutcome: selected.expectedOutcome,
799
+ },
800
+ });
772
801
  \`\`\`
773
802
 
774
- Walk them through:
775
- - **AEO readiness** \u2014 what's missing (schema gaps, low stat density,
776
- buried answers)
777
- - **Sub-topic coverage** \u2014 uncovered slots = new_brief candidates
778
- - **"Who to beat"** \u2014 the named competitor(s) winning in this pillar
779
- - **Top 5 striking-distance opportunities** \u2014 quick wins on existing pages
780
-
781
- For each, give your opinion. Be a strategist, not a librarian.
803
+ If preview returns blocked conflicts, do not create the plan. Explain
804
+ the overlap and pick another scope.
782
805
 
783
- > "Your fresh-page % is 28 \u2014 way below where it should be. Your top
784
- > competitor refreshes monthly. I'd start with 3 \`freshness_refresh\`
785
- > work orders on your highest-impression pages."
806
+ If preview returns warnings, ask for explicit user acceptance before
807
+ creating. Warnings are allowed only when the user understands the overlap.
786
808
 
787
- ### Step 4 \u2014 Build the shortlist
809
+ ### Step 4 \u2014 Create the scoped plan
788
810
 
789
- Together, pick 5-10 opportunities. Sequence matters: ship cheap-and-safe
790
- first (schema, internal links), then medium (title rewrites, freshness),
791
- then invasive (section rewrites, new briefs). Add to a plan as you go.
811
+ Create from the selected scope. Do not use legacy topic-plan or
812
+ opportunity-basket endpoints.
792
813
 
793
814
  \`\`\`ts
794
- const plan = await api.post('/api/plans', {
795
- entityId,
796
- name: \`\${pillar.name} \u2014 sprint 1\`,
797
- description: 'What hypothesis are we testing this sprint',
815
+ const plan = await api.post(\`/api/entities/\${entityId}/plans/from-scope\`, {
816
+ name: selected.label,
817
+ description: selected.expectedOutcome,
818
+ scope: preview.scope,
819
+ conflictOverride:
820
+ preview.conflicts.some(c => c.severity === 'warn')
821
+ ? { allowWarnings: true, allowBlocks: false }
822
+ : undefined,
798
823
  });
799
-
800
- for (const oppKey of selectedKeys) {
801
- await api.post(\`/api/plans/\${plan.plan.id}/candidates\`, {
802
- opportunityKeys: [oppKey],
803
- });
804
- }
805
824
  \`\`\`
806
825
 
807
- ### Step 5 \u2014 Generate briefs + propose work orders
826
+ ### Step 5 \u2014 Generate scoped items
808
827
 
809
828
  \`\`\`ts
810
- await api.post(\`/api/plans/\${plan.plan.id}/generate\`);
829
+ const generation = await api.post(\`/api/plans/\${plan.plan.id}/generate-scoped\`);
830
+ console.log(generation);
811
831
  \`\`\`
812
832
 
813
- Then for each plan item, propose its work order. Tell the user the
814
- plan is ready for review and they should approve in the work-orders
815
- UI when they're ready.
833
+ Generation must stay inside the selected scope. It should claim only
834
+ the gaps and pages in the scope, carry conflict warnings into the audit,
835
+ and return deferred or not-recommended items instead of stretching the
836
+ scope to look productive.
837
+
838
+ After generation, send the user to the plan detail. Explain what to
839
+ review and which first 1-3 items are safest to move into execution.
816
840
 
817
841
  ### Step 6 \u2014 Set expectations
818
842
 
819
843
  End with reality:
820
844
 
821
845
  > "Outcome verdicts take 14 days minimum because GSC needs that long
822
- > to stabilize. Re-run the diagnostic in 2 weeks and reconcile the
823
- > plan. Plan to ship a second sprint at the same time you read sprint
824
- > 1's outcomes \u2014 that's the flywheel."
846
+ > to stabilize. Re-run the diagnostic in 2 weeks, reconcile the plan,
847
+ > and choose the next scoped slice from what changed."
825
848
 
826
849
  ## Strong opinions to bring
827
850
 
828
- The user will look to you to break ties. Hold these:
829
-
830
- - **One pillar per sprint.** Always.
831
- - **Schema and internal links first.** Lowest risk, highest velocity.
851
+ - **One bounded scope per plan.** Always.
852
+ - **Prefer conflict-clear scopes.** Warning scopes require explicit user acceptance.
853
+ - **Use the signal, not the URL.** Match on topic, page summaries, gap evidence,
854
+ GSC, GA, AEO, conversion, and strategy context.
855
+ - **Small is fine.** A plan with 3 high-confidence page items can beat a giant backlog.
832
856
  - **Don't promise CTR lift in week 1.** The data takes 14 days.
833
- - **Inconclusive \u2260 failed.** Many flip to verified on the next window.
834
- - **Re-run cadence is 2 weeks.** Not 2 days. Not 2 months.
835
- - **Plans have a defined end.** Archive after 4-6 weeks, even if
836
- unfinished \u2014 open-ended plans become graveyards.
857
+ - **Inconclusive is not failed.** Many outcomes need another window.
858
+ - **Archive old/conflicting plans.** Open-ended plans become graveyards.
837
859
 
838
860
  ## What success looks like
839
861
 
840
- - One pillar picked
841
- - One plan created
842
- - 5-10 candidates added
843
- - Briefs generated
844
- - Work orders proposed for at least 3 of them
845
- - User knows when to come back (14 days for outcomes, ~immediately for
846
- approvals)
862
+ - One bounded scope picked
863
+ - Conflicts reviewed
864
+ - One scoped plan created
865
+ - Scoped generation run
866
+ - First execution items are obvious
867
+ - User knows when to come back: 14 days for outcomes, sooner for approvals
847
868
 
848
869
  ## What NOT to do
849
870
 
850
- - Don't sort opportunities by score and list the top 10. That's not
851
- strategy.
852
- - Don't tell the user "it depends" when they ask what to ship first.
853
- Have an opinion.
854
- - Don't promise outcomes you can't deliver. The engine measures wins
855
- honestly; you should too.
856
- - Don't gloss over the KB. If their KB is empty, stop and run the
871
+ - Don't create a generic topic plan.
872
+ - Don't use \`POST /api/plans\`, \`POST /api/plans/topic\`,
873
+ \`POST /api/plans/:id/candidates\`, \`POST /api/plans/:id/generate\`,
874
+ or \`POST /api/plans/:id/generate-topic\`.
875
+ - Don't sort opportunities by score and list the top 10. That's not strategy.
876
+ - Don't let URLs alone classify scope. Use the evidence Higrowth gathered.
877
+ - Don't create a plan that overlaps blocked active work.
878
+ - Don't gloss over the KB. If the KB is empty, stop and run the
857
879
  populate-kb playbook first. Strategy without KB context is generic.
858
880
  `;
859
881
 
@@ -1157,8 +1179,181 @@ function compareSkill(path, skill) {
1157
1179
  }
1158
1180
  }
1159
1181
 
1182
+ // src/commands/upgrade.ts
1183
+ var PKG_NAME = "@higrowth/cli";
1184
+ async function upgradeCommand() {
1185
+ process.stdout.write(
1186
+ `
1187
+ Upgrade ${PKG_NAME} to the latest version with whichever package
1188
+ manager you used to install it:
1189
+
1190
+ npm install -g ${PKG_NAME}@latest
1191
+ pnpm add -g ${PKG_NAME}@latest
1192
+ bun add -g ${PKG_NAME}@latest
1193
+ yarn global add ${PKG_NAME}@latest
1194
+
1195
+ Or, if you installed via the curl-piped flow:
1196
+
1197
+ curl -fsSL https://app.higrowth.ai/install.sh | sh
1198
+
1199
+ After upgrade, run \`higrowth -v\` to confirm.
1200
+ `
1201
+ );
1202
+ }
1203
+
1204
+ // src/lib/update-check.ts
1205
+ import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1206
+ import { dirname as dirname3, join as join3 } from "path";
1207
+ import { homedir as homedir3 } from "os";
1208
+ var PKG_NAME2 = "@higrowth/cli";
1209
+ var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1e3;
1210
+ var REQUEST_TIMEOUT_MS = 2e3;
1211
+ function cachePath() {
1212
+ return join3(homedir3(), ".config", "higrowth", "update-check.json");
1213
+ }
1214
+ function readCache() {
1215
+ const path = cachePath();
1216
+ if (!existsSync3(path)) return null;
1217
+ try {
1218
+ const raw = readFileSync3(path, "utf-8");
1219
+ const parsed = JSON.parse(raw);
1220
+ if (typeof parsed.latestVersion !== "string") return null;
1221
+ if (typeof parsed.fetchedAt !== "number") return null;
1222
+ return { latestVersion: parsed.latestVersion, fetchedAt: parsed.fetchedAt };
1223
+ } catch {
1224
+ return null;
1225
+ }
1226
+ }
1227
+ function writeCache(latestVersion) {
1228
+ const path = cachePath();
1229
+ try {
1230
+ mkdirSync3(dirname3(path), { recursive: true });
1231
+ writeFileSync3(
1232
+ path,
1233
+ JSON.stringify({ latestVersion, fetchedAt: Date.now() }, null, 2),
1234
+ "utf-8"
1235
+ );
1236
+ } catch {
1237
+ }
1238
+ }
1239
+ async function fetchLatestFromNpm() {
1240
+ const controller = new AbortController();
1241
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
1242
+ try {
1243
+ const res = await fetch(`https://registry.npmjs.org/${PKG_NAME2}/latest`, {
1244
+ headers: { Accept: "application/json" },
1245
+ signal: controller.signal
1246
+ });
1247
+ if (!res.ok) return null;
1248
+ const body = await res.json();
1249
+ return body.version ?? null;
1250
+ } catch {
1251
+ return null;
1252
+ } finally {
1253
+ clearTimeout(timer);
1254
+ }
1255
+ }
1256
+ function compareVersions(a, b) {
1257
+ const parse = (v) => v.split("-")[0].split(".").map((s) => Number.parseInt(s, 10)).map((n2) => Number.isFinite(n2) ? n2 : 0);
1258
+ const aa = parse(a);
1259
+ const bb = parse(b);
1260
+ const n = Math.max(aa.length, bb.length);
1261
+ for (let i = 0; i < n; i++) {
1262
+ const ai = aa[i] ?? 0;
1263
+ const bi = bb[i] ?? 0;
1264
+ if (ai !== bi) return ai - bi;
1265
+ }
1266
+ return 0;
1267
+ }
1268
+ function shouldSkip() {
1269
+ if (process.env.HIGROWTH_NO_UPDATE_CHECK === "1") return true;
1270
+ if (process.env.CI) return true;
1271
+ if (!process.stdout.isTTY) return true;
1272
+ return false;
1273
+ }
1274
+ function beginUpdateCheck(currentVersion) {
1275
+ if (shouldSkip()) {
1276
+ return async () => {
1277
+ };
1278
+ }
1279
+ const cached = readCache();
1280
+ const fresh = cached && Date.now() - cached.fetchedAt < CHECK_INTERVAL_MS;
1281
+ const fetchPromise = fresh ? Promise.resolve(cached.latestVersion) : fetchLatestFromNpm().then((v) => {
1282
+ if (v) writeCache(v);
1283
+ return v;
1284
+ });
1285
+ return async () => {
1286
+ let latest;
1287
+ try {
1288
+ latest = await fetchPromise;
1289
+ } catch {
1290
+ return;
1291
+ }
1292
+ if (!latest) return;
1293
+ if (compareVersions(latest, currentVersion) <= 0) return;
1294
+ process.stderr.write(
1295
+ `
1296
+ \u2191 ${PKG_NAME2} ${latest} is available (you have ${currentVersion}).
1297
+ Upgrade: npm i -g ${PKG_NAME2}@latest
1298
+ (or bun add -g / pnpm add -g / yarn global add depending on how you installed.)
1299
+ Silence: set HIGROWTH_NO_UPDATE_CHECK=1
1300
+ `
1301
+ );
1302
+ };
1303
+ }
1304
+
1305
+ // package.json
1306
+ var package_default = {
1307
+ name: "@higrowth/cli",
1308
+ version: "0.3.2",
1309
+ description: "Higrowth CLI \u2014 log in via browser, install Claude Code / Codex skills, manage the MCP connection.",
1310
+ type: "module",
1311
+ license: "Apache-2.0",
1312
+ bin: {
1313
+ higrowth: "dist/index.js"
1314
+ },
1315
+ files: [
1316
+ "dist/",
1317
+ "README.md"
1318
+ ],
1319
+ scripts: {
1320
+ build: "tsup",
1321
+ dev: "tsup --watch",
1322
+ check: "tsc --noEmit"
1323
+ },
1324
+ engines: {
1325
+ node: ">=20"
1326
+ },
1327
+ publishConfig: {
1328
+ access: "public"
1329
+ },
1330
+ devDependencies: {
1331
+ "@types/iarna__toml": "^2.0.5",
1332
+ "@types/node": "^22.10.5",
1333
+ tsup: "^8.5.0",
1334
+ typescript: "^5.6.3"
1335
+ },
1336
+ keywords: [
1337
+ "higrowth",
1338
+ "seo",
1339
+ "aeo",
1340
+ "mcp",
1341
+ "claude-code",
1342
+ "agent"
1343
+ ],
1344
+ homepage: "https://github.com/higrowth-ai/hg-engine",
1345
+ repository: {
1346
+ type: "git",
1347
+ url: "git+https://github.com/higrowth-ai/hg-engine.git",
1348
+ directory: "cli"
1349
+ },
1350
+ dependencies: {
1351
+ "@iarna/toml": "^2.2.5"
1352
+ }
1353
+ };
1354
+
1160
1355
  // src/index.ts
1161
- var VERSION = "0.1.1";
1356
+ var VERSION = package_default.version;
1162
1357
  var DEFAULT_HOST = "https://app.higrowth.ai";
1163
1358
  var USAGE = `higrowth \u2014 Connect your agent to a Higrowth workspace
1164
1359
 
@@ -1189,7 +1384,14 @@ COMMANDS
1189
1384
  skills update [--target TARGET]
1190
1385
  Re-install the bundled skill versions.
1191
1386
 
1192
- version
1387
+ upgrade
1388
+ Show the right command to upgrade @higrowth/cli to the latest
1389
+ published version, matched to your package manager (npm / pnpm /
1390
+ bun / yarn / curl-pipe). The CLI auto-checks for new versions on
1391
+ every invocation (cached 24h); set HIGROWTH_NO_UPDATE_CHECK=1 to
1392
+ silence the nudge.
1393
+
1394
+ version | --version | -v
1193
1395
  Print version + exit.
1194
1396
 
1195
1397
  FLAGS
@@ -1247,6 +1449,23 @@ function resolveTarget(raw) {
1247
1449
  }
1248
1450
  async function main() {
1249
1451
  const args = parseArgs(process.argv.slice(2));
1452
+ if (args.flags.version === true || args.flags.v === true) {
1453
+ process.stdout.write(`higrowth ${VERSION}
1454
+ `);
1455
+ return;
1456
+ }
1457
+ if (args.command === "" && (args.flags.help === true || args.flags.h === true)) {
1458
+ process.stdout.write(USAGE);
1459
+ return;
1460
+ }
1461
+ const showUpdateNotice = beginUpdateCheck(VERSION);
1462
+ try {
1463
+ await runCommand(args);
1464
+ } finally {
1465
+ await showUpdateNotice();
1466
+ }
1467
+ }
1468
+ async function runCommand(args) {
1250
1469
  const host = typeof args.flags.host === "string" ? args.flags.host : DEFAULT_HOST;
1251
1470
  const target = resolveTarget(args.flags.target);
1252
1471
  const device = args.flags.device === true;
@@ -1254,13 +1473,9 @@ async function main() {
1254
1473
  switch (args.command) {
1255
1474
  case "":
1256
1475
  case "help":
1257
- case "--help":
1258
- case "-h":
1259
1476
  process.stdout.write(USAGE);
1260
1477
  return;
1261
1478
  case "version":
1262
- case "--version":
1263
- case "-v":
1264
1479
  process.stdout.write(`higrowth ${VERSION}
1265
1480
  `);
1266
1481
  return;
@@ -1280,6 +1495,9 @@ async function main() {
1280
1495
  case "logout":
1281
1496
  await logoutCommand();
1282
1497
  return;
1498
+ case "upgrade":
1499
+ await upgradeCommand();
1500
+ return;
1283
1501
  case "skills":
1284
1502
  if (args.subcommand === "install" || args.subcommand === void 0) {
1285
1503
  await skillsInstallCommand({ target });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@higrowth/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.2",
4
4
  "description": "Higrowth CLI — log in via browser, install Claude Code / Codex skills, manage the MCP connection.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -28,7 +28,14 @@
28
28
  "tsup": "^8.5.0",
29
29
  "typescript": "^5.6.3"
30
30
  },
31
- "keywords": ["higrowth", "seo", "aeo", "mcp", "claude-code", "agent"],
31
+ "keywords": [
32
+ "higrowth",
33
+ "seo",
34
+ "aeo",
35
+ "mcp",
36
+ "claude-code",
37
+ "agent"
38
+ ],
32
39
  "homepage": "https://github.com/higrowth-ai/hg-engine",
33
40
  "repository": {
34
41
  "type": "git",