taketomarket 2.2.0 → 2.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.
Files changed (180) hide show
  1. package/.claude-plugin/marketplace.json +4 -4
  2. package/.claude-plugin/plugin.json +2 -2
  3. package/README.md +34 -11
  4. package/bin/lib/campaign.cjs +12 -8
  5. package/bin/lib/codebase-scan.cjs +86 -0
  6. package/bin/lib/config.cjs +129 -0
  7. package/bin/lib/deploy.cjs +36 -0
  8. package/bin/lib/deviation.cjs +1 -1
  9. package/bin/lib/drift-log.cjs +4 -4
  10. package/bin/lib/health.cjs +32 -31
  11. package/bin/lib/install-detect.cjs +62 -0
  12. package/bin/lib/legacy-folder.cjs +100 -0
  13. package/bin/lib/playwright-check.cjs +26 -0
  14. package/bin/lib/site-location.cjs +22 -0
  15. package/bin/lib/state.cjs +3 -3
  16. package/bin/lib/svg-render.cjs +42 -0
  17. package/bin/ttm-tools.cjs +136 -4
  18. package/gates/base-gates.md +8 -8
  19. package/gates/gate-evaluation.md +8 -8
  20. package/install.js +37 -3
  21. package/package.json +10 -6
  22. package/playbooks/aeo.md +218 -114
  23. package/playbooks/affiliate.md +225 -160
  24. package/playbooks/email.md +236 -174
  25. package/playbooks/events.md +303 -213
  26. package/playbooks/landing-pages.md +305 -0
  27. package/playbooks/linkedin.md +264 -142
  28. package/playbooks/manifesto.md +322 -0
  29. package/playbooks/paid-ads.md +240 -189
  30. package/playbooks/positioning.md +340 -0
  31. package/playbooks/pr-media.md +308 -168
  32. package/playbooks/pseo.md +426 -0
  33. package/playbooks/seo.md +251 -158
  34. package/playbooks/social.md +253 -182
  35. package/playbooks/youtube.md +286 -181
  36. package/references/brand-color-theory.md +48 -0
  37. package/references/codex-image-gen-research.md +58 -0
  38. package/references/context-loading.md +6 -6
  39. package/references/humanizer-patterns.md +433 -0
  40. package/references/inline-education-blurbs.md +461 -0
  41. package/references/landing-page-anatomy.md +64 -0
  42. package/references/linkedin-post-patterns.md +174 -0
  43. package/references/logo-design-principles.md +55 -0
  44. package/references/meta-gate-evaluation.md +3 -3
  45. package/references/obra-superpowers-conventions.md +170 -0
  46. package/references/playbook-leaders.md +472 -0
  47. package/references/playwright-mcp-setup.md +164 -0
  48. package/references/positioning-check-report.md +2 -2
  49. package/references/pseo-page-anatomy.md +56 -0
  50. package/references/pseo-templates/alternative-anatomy.md +31 -0
  51. package/references/pseo-templates/alternative-content-playbook.md +32 -0
  52. package/references/pseo-templates/blog-anatomy.md +28 -0
  53. package/references/pseo-templates/blog-content-playbook.md +36 -0
  54. package/references/pseo-templates/comparison-anatomy.md +29 -0
  55. package/references/pseo-templates/comparison-content-playbook.md +35 -0
  56. package/references/pseo-templates/use-case-anatomy.md +28 -0
  57. package/references/pseo-templates/use-case-content-playbook.md +30 -0
  58. package/skills/ttm-101/SKILL.md +25 -0
  59. package/skills/ttm-aeo-check/SKILL.md +17 -12
  60. package/skills/ttm-affiliate-kit/SKILL.md +5 -0
  61. package/skills/ttm-archive/SKILL.md +5 -0
  62. package/skills/ttm-brand-refresh/SKILL.md +5 -0
  63. package/skills/ttm-brief/SKILL.md +5 -0
  64. package/skills/ttm-competitor-scan/SKILL.md +5 -0
  65. package/skills/ttm-deploy/SKILL.md +22 -0
  66. package/skills/ttm-discover/SKILL.md +17 -0
  67. package/skills/ttm-email-check/SKILL.md +17 -0
  68. package/skills/ttm-email-preflight/SKILL.md +17 -11
  69. package/skills/ttm-fix/SKILL.md +5 -0
  70. package/skills/ttm-health/SKILL.md +6 -1
  71. package/skills/ttm-humanize/SKILL.md +33 -0
  72. package/skills/ttm-icp-refresh/SKILL.md +5 -0
  73. package/skills/ttm-improve-skill/SKILL.md +18 -0
  74. package/skills/ttm-init/SKILL.md +10 -3
  75. package/skills/ttm-keyword-map/SKILL.md +17 -11
  76. package/skills/ttm-landing/SKILL.md +19 -0
  77. package/skills/ttm-learn/SKILL.md +5 -0
  78. package/skills/ttm-linkedin-post/SKILL.md +26 -0
  79. package/skills/ttm-measure/SKILL.md +5 -0
  80. package/skills/ttm-new-campaign/SKILL.md +5 -0
  81. package/skills/ttm-next/SKILL.md +5 -0
  82. package/skills/ttm-playwright-setup/SKILL.md +18 -0
  83. package/skills/ttm-positioning-check/SKILL.md +5 -0
  84. package/skills/ttm-positioning-shift/SKILL.md +5 -0
  85. package/skills/ttm-produce/SKILL.md +5 -0
  86. package/skills/ttm-pseo/SKILL.md +26 -0
  87. package/skills/ttm-repurpose/SKILL.md +5 -0
  88. package/skills/ttm-request-skill/SKILL.md +18 -0
  89. package/skills/ttm-research/SKILL.md +18 -6
  90. package/skills/ttm-resume/SKILL.md +5 -0
  91. package/skills/ttm-review/SKILL.md +5 -0
  92. package/skills/ttm-seo/SKILL.md +64 -0
  93. package/skills/ttm-seo-audit/SKILL.md +17 -12
  94. package/skills/ttm-ship/SKILL.md +5 -0
  95. package/skills/ttm-state/SKILL.md +5 -0
  96. package/skills/ttm-update/SKILL.md +152 -4
  97. package/skills/ttm-verify/SKILL.md +5 -0
  98. package/templates/agents-md.md +14 -4
  99. package/templates/campaign-research.md +6 -6
  100. package/templates/campaign-state.md +1 -1
  101. package/templates/claude-md.md +14 -4
  102. package/templates/linkedin-base-template.md +48 -0
  103. package/templates/next-step-footer.md +13 -0
  104. package/templates/production-manifest.json +4 -4
  105. package/templates/pseo/alternative-cms-schema.json +65 -0
  106. package/templates/pseo/blog-cms-schema.json +55 -0
  107. package/templates/pseo/comparison-cms-schema.json +56 -0
  108. package/templates/pseo/use-case-cms-schema.json +62 -0
  109. package/templates/reference-files/brand.md +51 -0
  110. package/templates/reference-files/product-dna.md +73 -0
  111. package/templates/site-scaffold/app/globals.css +2 -0
  112. package/templates/site-scaffold/app/layout.tsx +17 -0
  113. package/templates/site-scaffold/app/page.tsx +33 -0
  114. package/templates/site-scaffold/app/robots.ts +8 -0
  115. package/templates/site-scaffold/app/sitemap.ts +10 -0
  116. package/templates/site-scaffold/app/tokens.css +21 -0
  117. package/templates/site-scaffold/components/Comparison.tsx +14 -0
  118. package/templates/site-scaffold/components/Faq.tsx +14 -0
  119. package/templates/site-scaffold/components/Features.tsx +14 -0
  120. package/templates/site-scaffold/components/FinalCta.tsx +17 -0
  121. package/templates/site-scaffold/components/Footer.tsx +12 -0
  122. package/templates/site-scaffold/components/Hero.tsx +22 -0
  123. package/templates/site-scaffold/components/HowItWorks.tsx +14 -0
  124. package/templates/site-scaffold/components/PricingTeaser.tsx +14 -0
  125. package/templates/site-scaffold/components/Problem.tsx +14 -0
  126. package/templates/site-scaffold/components/SocialProof.tsx +14 -0
  127. package/templates/site-scaffold/components/Solution.tsx +14 -0
  128. package/templates/site-scaffold/components/Testimonials.tsx +14 -0
  129. package/templates/site-scaffold/components/UseCases.tsx +14 -0
  130. package/templates/site-scaffold/content/.gitkeep +0 -0
  131. package/templates/site-scaffold/lib/.gitkeep +0 -0
  132. package/templates/site-scaffold/next.config.mjs +10 -0
  133. package/templates/site-scaffold/package.json +25 -0
  134. package/templates/site-scaffold/postcss.config.mjs +3 -0
  135. package/templates/site-scaffold/public/llms.txt +9 -0
  136. package/templates/site-scaffold/tsconfig.json +21 -0
  137. package/templates/verification-report.md +1 -1
  138. package/workflows/channel/linkedin-post.md +178 -0
  139. package/workflows/discipline/affiliate-kit.md +65 -6
  140. package/workflows/discipline/{email-preflight.md → email-check.md} +39 -4
  141. package/workflows/discipline/repurpose.md +82 -31
  142. package/workflows/discipline/{aeo-check.md → seo/aeo.md} +13 -6
  143. package/workflows/discipline/{seo-audit.md → seo/audit.md} +13 -6
  144. package/workflows/discipline/{keyword-map.md → seo/keyword-map.md} +13 -6
  145. package/workflows/education/ttm-101.md +114 -0
  146. package/workflows/lifecycle/brief-positioning-check.md +1 -1
  147. package/workflows/lifecycle/brief.md +64 -28
  148. package/workflows/lifecycle/{research.md → discover.md} +61 -19
  149. package/workflows/lifecycle/fix.md +72 -37
  150. package/workflows/lifecycle/humanize.md +280 -0
  151. package/workflows/lifecycle/learn.md +72 -35
  152. package/workflows/lifecycle/measure.md +54 -18
  153. package/workflows/lifecycle/produce.md +88 -37
  154. package/workflows/lifecycle/review.md +71 -25
  155. package/workflows/lifecycle/ship.md +62 -18
  156. package/workflows/lifecycle/verify.md +72 -26
  157. package/workflows/reference-mgmt/brand-refresh.md +50 -13
  158. package/workflows/reference-mgmt/competitor-scan.md +51 -15
  159. package/workflows/reference-mgmt/icp-refresh.md +48 -12
  160. package/workflows/reference-mgmt/positioning-check.md +55 -20
  161. package/workflows/reference-mgmt/positioning-shift.md +53 -17
  162. package/workflows/setup/init-brand-colors.md +75 -0
  163. package/workflows/setup/init-logo.md +113 -0
  164. package/workflows/setup/init-product-dna.md +83 -0
  165. package/workflows/setup/init-questions.md +166 -30
  166. package/workflows/setup/init-validation.md +22 -0
  167. package/workflows/setup/init.md +144 -39
  168. package/workflows/setup/new-campaign.md +48 -12
  169. package/workflows/site/deploy.md +98 -0
  170. package/workflows/site/landing.md +156 -0
  171. package/workflows/site/pseo.md +96 -0
  172. package/workflows/site/quality-gates.md +88 -0
  173. package/workflows/utility/archive.md +45 -9
  174. package/workflows/utility/health.md +77 -3
  175. package/workflows/utility/improve-skill.md +233 -0
  176. package/workflows/utility/next.md +38 -2
  177. package/workflows/utility/playwright-setup.md +128 -0
  178. package/workflows/utility/request-skill.md +218 -0
  179. package/workflows/utility/resume.md +40 -3
  180. package/workflows/utility/state.md +42 -7
@@ -14,7 +14,7 @@ const path = require('path');
14
14
  const { output, safeReadFile, parseFrontmatter } = require('./core.cjs');
15
15
 
16
16
  /**
17
- * Expected reference files in .marketing/ directory.
17
+ * Expected reference files in .taketomarket/ directory.
18
18
  */
19
19
  const REFERENCE_FILES = [
20
20
  'POSITIONING.md',
@@ -26,6 +26,7 @@ const REFERENCE_FILES = [
26
26
  'COMPETITORS.md',
27
27
  'METRICS.md',
28
28
  'LEARNINGS.md',
29
+ 'PRODUCT-DNA.md',
29
30
  ];
30
31
 
31
32
  /**
@@ -85,7 +86,7 @@ function checkCampaignStateConsistency(campaignsDir) {
85
86
  checks.push({
86
87
  name: `campaign_state_${entry.name}`,
87
88
  status: 'fail',
88
- path: `.marketing/CAMPAIGNS/${entry.name}/STATE.md`,
89
+ path: `.taketomarket/CAMPAIGNS/${entry.name}/STATE.md`,
89
90
  detail: 'STATE.md missing',
90
91
  });
91
92
  continue;
@@ -95,14 +96,14 @@ function checkCampaignStateConsistency(campaignsDir) {
95
96
  checks.push({
96
97
  name: `campaign_state_${entry.name}`,
97
98
  status: 'fail',
98
- path: `.marketing/CAMPAIGNS/${entry.name}/STATE.md`,
99
+ path: `.taketomarket/CAMPAIGNS/${entry.name}/STATE.md`,
99
100
  detail: `invalid phase: ${frontmatter.phase || 'undefined'}`,
100
101
  });
101
102
  } else {
102
103
  checks.push({
103
104
  name: `campaign_state_${entry.name}`,
104
105
  status: 'pass',
105
- path: `.marketing/CAMPAIGNS/${entry.name}/STATE.md`,
106
+ path: `.taketomarket/CAMPAIGNS/${entry.name}/STATE.md`,
106
107
  });
107
108
  }
108
109
  }
@@ -111,7 +112,7 @@ function checkCampaignStateConsistency(campaignsDir) {
111
112
 
112
113
  /**
113
114
  * Check reference file staleness based on mtime.
114
- * @param {string} marketingDir - Path to .marketing/ directory
115
+ * @param {string} marketingDir - Path to .taketomarket/ directory
115
116
  * @param {number} thresholdDays - Days threshold for staleness warning (default 90)
116
117
  * @returns {Array} Array of check objects
117
118
  */
@@ -131,14 +132,14 @@ function checkReferenceStaleness(marketingDir, thresholdDays) {
131
132
  checks.push({
132
133
  name: `staleness_${file.toLowerCase().replace('.md', '')}`,
133
134
  status: 'warn',
134
- path: `.marketing/${file}`,
135
+ path: `.taketomarket/${file}`,
135
136
  detail: `not updated in ${ageDays} days`,
136
137
  });
137
138
  } else {
138
139
  checks.push({
139
140
  name: `staleness_${file.toLowerCase().replace('.md', '')}`,
140
141
  status: 'pass',
141
- path: `.marketing/${file}`,
142
+ path: `.taketomarket/${file}`,
142
143
  });
143
144
  }
144
145
  } catch {
@@ -183,14 +184,14 @@ function checkCampaignVelocity(campaignsDir, thresholdDays) {
183
184
  checks.push({
184
185
  name: `velocity_${entry.name}`,
185
186
  status: 'warn',
186
- path: `.marketing/CAMPAIGNS/${entry.name}/STATE.md`,
187
+ path: `.taketomarket/CAMPAIGNS/${entry.name}/STATE.md`,
187
188
  detail: `stuck in ${frontmatter.phase} for ${ageDays} days`,
188
189
  });
189
190
  } else {
190
191
  checks.push({
191
192
  name: `velocity_${entry.name}`,
192
193
  status: 'pass',
193
- path: `.marketing/CAMPAIGNS/${entry.name}/STATE.md`,
194
+ path: `.taketomarket/CAMPAIGNS/${entry.name}/STATE.md`,
194
195
  });
195
196
  }
196
197
  }
@@ -199,7 +200,7 @@ function checkCampaignVelocity(campaignsDir, thresholdDays) {
199
200
 
200
201
  /**
201
202
  * Check DRIFT-LOG.md structural integrity.
202
- * @param {string} marketingDir - Path to .marketing/ directory
203
+ * @param {string} marketingDir - Path to .taketomarket/ directory
203
204
  * @returns {Array} Array of check objects
204
205
  */
205
206
  function checkDriftLogIntegrity(marketingDir) {
@@ -211,7 +212,7 @@ function checkDriftLogIntegrity(marketingDir) {
211
212
  checks.push({
212
213
  name: 'drift_log_integrity',
213
214
  status: 'warn',
214
- path: '.marketing/DRIFT-LOG.md',
215
+ path: '.taketomarket/DRIFT-LOG.md',
215
216
  detail: 'no drift log yet',
216
217
  });
217
218
  return checks;
@@ -222,21 +223,21 @@ function checkDriftLogIntegrity(marketingDir) {
222
223
  checks.push({
223
224
  name: 'drift_log_integrity',
224
225
  status: 'fail',
225
- path: '.marketing/DRIFT-LOG.md',
226
+ path: '.taketomarket/DRIFT-LOG.md',
226
227
  detail: 'missing audit trail marker',
227
228
  });
228
229
  } else if (markerCount > 1) {
229
230
  checks.push({
230
231
  name: 'drift_log_integrity',
231
232
  status: 'fail',
232
- path: '.marketing/DRIFT-LOG.md',
233
+ path: '.taketomarket/DRIFT-LOG.md',
233
234
  detail: 'duplicate audit trail markers',
234
235
  });
235
236
  } else {
236
237
  checks.push({
237
238
  name: 'drift_log_integrity',
238
239
  status: 'pass',
239
- path: '.marketing/DRIFT-LOG.md',
240
+ path: '.taketomarket/DRIFT-LOG.md',
240
241
  });
241
242
  }
242
243
  return checks;
@@ -276,14 +277,14 @@ function checkGateConsistency(campaignsDir) {
276
277
  checks.push({
277
278
  name: `gate_consistency_${entry.name}`,
278
279
  status: 'fail',
279
- path: `.marketing/CAMPAIGNS/${entry.name}/STATE.md`,
280
+ path: `.taketomarket/CAMPAIGNS/${entry.name}/STATE.md`,
280
281
  detail: `invalid gate values: ${invalidGates.join(', ')}`,
281
282
  });
282
283
  } else {
283
284
  checks.push({
284
285
  name: `gate_consistency_${entry.name}`,
285
286
  status: 'pass',
286
- path: `.marketing/CAMPAIGNS/${entry.name}/STATE.md`,
287
+ path: `.taketomarket/CAMPAIGNS/${entry.name}/STATE.md`,
287
288
  });
288
289
  }
289
290
  }
@@ -291,11 +292,11 @@ function checkGateConsistency(campaignsDir) {
291
292
  }
292
293
 
293
294
  /**
294
- * Validate .marketing/ directory structure.
295
+ * Validate .taketomarket/ directory structure.
295
296
  *
296
297
  * Checks:
297
- * 1. .marketing/ directory exists
298
- * 2. .marketing/CAMPAIGNS/ directory exists
298
+ * 1. .taketomarket/ directory exists
299
+ * 2. .taketomarket/CAMPAIGNS/ directory exists
299
300
  * 3. Each expected reference file exists
300
301
  * 4. STATE.md has valid frontmatter (parseable)
301
302
  *
@@ -306,23 +307,23 @@ function checkGateConsistency(campaignsDir) {
306
307
  * 8. DRIFT-LOG integrity
307
308
  * 9. Gate result consistency
308
309
  *
309
- * healthy = true when .marketing/ and CAMPAIGNS/ both exist and no 'fail' checks.
310
+ * healthy = true when .taketomarket/ and CAMPAIGNS/ both exist and no 'fail' checks.
310
311
  * Reference files use "missing" status (expected before /ttm-init runs).
311
312
  *
312
313
  * @param {boolean} raw - Whether to output raw summary string
313
314
  * @param {boolean} full - Whether to run extended audit checks
314
315
  */
315
316
  function cmdHealth(raw, full) {
316
- const marketingDir = path.resolve(process.cwd(), '.marketing');
317
+ const marketingDir = path.resolve(process.cwd(), '.taketomarket');
317
318
  const campaignsDir = path.resolve(marketingDir, 'CAMPAIGNS');
318
319
  const checks = [];
319
320
 
320
- // Check .marketing/ directory
321
+ // Check .taketomarket/ directory
321
322
  const marketingExists = dirExists(marketingDir);
322
323
  checks.push({
323
- name: 'marketing_dir',
324
+ name: 'taketomarket_dir',
324
325
  status: marketingExists ? 'pass' : 'fail',
325
- path: '.marketing/',
326
+ path: '.taketomarket/',
326
327
  });
327
328
 
328
329
  // Check CAMPAIGNS/ directory
@@ -330,7 +331,7 @@ function cmdHealth(raw, full) {
330
331
  checks.push({
331
332
  name: 'campaigns_dir',
332
333
  status: campaignsExists ? 'pass' : 'fail',
333
- path: '.marketing/CAMPAIGNS/',
334
+ path: '.taketomarket/CAMPAIGNS/',
334
335
  });
335
336
 
336
337
  // Check each reference file
@@ -346,14 +347,14 @@ function cmdHealth(raw, full) {
346
347
  checks.push({
347
348
  name,
348
349
  status: isValid ? 'pass' : 'fail',
349
- path: `.marketing/${file}`,
350
+ path: `.taketomarket/${file}`,
350
351
  detail: isValid ? undefined : 'frontmatter unparseable',
351
352
  });
352
353
  } else {
353
- checks.push({ name, status: 'pass', path: `.marketing/${file}` });
354
+ checks.push({ name, status: 'pass', path: `.taketomarket/${file}` });
354
355
  }
355
356
  } else {
356
- checks.push({ name, status: 'missing', path: `.marketing/${file}` });
357
+ checks.push({ name, status: 'missing', path: `.taketomarket/${file}` });
357
358
  }
358
359
  }
359
360
 
@@ -402,12 +403,12 @@ function cmdHealth(raw, full) {
402
403
 
403
404
  /**
404
405
  * Lightweight init check.
405
- * Returns: { initialized, marketing_dir, reference_files_count, total_expected }
406
+ * Returns: { initialized, taketomarket_dir, reference_files_count, total_expected }
406
407
  *
407
408
  * @param {boolean} raw - Whether to output raw summary string
408
409
  */
409
410
  function cmdInit(raw) {
410
- const marketingDir = path.resolve(process.cwd(), '.marketing');
411
+ const marketingDir = path.resolve(process.cwd(), '.taketomarket');
411
412
  const marketingExists = dirExists(marketingDir);
412
413
 
413
414
  let refCount = 0;
@@ -424,7 +425,7 @@ function cmdInit(raw) {
424
425
 
425
426
  const result = {
426
427
  initialized,
427
- marketing_dir: marketingExists,
428
+ taketomarket_dir: marketingExists,
428
429
  reference_files_count: refCount,
429
430
  total_expected: totalExpected,
430
431
  };
@@ -0,0 +1,62 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ const SENTINEL_FILENAME = '.install-method';
7
+
8
+ function readSentinel(homeDir = os.homedir()) {
9
+ const sentinelPath = path.join(homeDir, '.taketomarket', SENTINEL_FILENAME);
10
+ if (!fs.existsSync(sentinelPath)) return null;
11
+ try {
12
+ const raw = fs.readFileSync(sentinelPath, 'utf8').trim();
13
+ if (!raw) return null;
14
+ const parsed = JSON.parse(raw);
15
+ if (parsed && typeof parsed.method === 'string') return parsed;
16
+ } catch (_) {
17
+ // Fall through to heuristic.
18
+ }
19
+ return null;
20
+ }
21
+
22
+ function isTakeToMarketRoot(dir) {
23
+ try {
24
+ const pkgPath = path.join(dir, 'package.json');
25
+ if (!fs.existsSync(pkgPath)) return false;
26
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'));
27
+ return pkg && pkg.name === 'taketomarket';
28
+ } catch (_) {
29
+ return false;
30
+ }
31
+ }
32
+
33
+ function findPluginRoot() {
34
+ if (process.env.CLAUDE_PLUGIN_ROOT && isTakeToMarketRoot(process.env.CLAUDE_PLUGIN_ROOT)) {
35
+ return process.env.CLAUDE_PLUGIN_ROOT;
36
+ }
37
+ const candidate = path.join(
38
+ process.env.HOME || os.homedir(),
39
+ '.claude',
40
+ 'plugins',
41
+ 'cache',
42
+ 'claude-plugins-official',
43
+ 'taketomarket',
44
+ );
45
+ if (isTakeToMarketRoot(candidate)) return candidate;
46
+ return null;
47
+ }
48
+
49
+ function detectInstallMethod() {
50
+ const sentinel = readSentinel();
51
+ if (sentinel) {
52
+ return { method: sentinel.method, root: sentinel.source || null, source: 'sentinel' };
53
+ }
54
+ const root = findPluginRoot();
55
+ if (!root) return { method: 'unknown', root: null, source: 'heuristic' };
56
+ if (fs.existsSync(path.join(root, '.git'))) {
57
+ return { method: 'clone', root, source: 'heuristic' };
58
+ }
59
+ return { method: 'npm', root, source: 'heuristic' };
60
+ }
61
+
62
+ module.exports = { detectInstallMethod, findPluginRoot, readSentinel, isTakeToMarketRoot, SENTINEL_FILENAME };
@@ -0,0 +1,100 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const LEGACY = '.marketing';
7
+ const CURRENT = '.taketomarket';
8
+
9
+ function legacyFolderCheck(cwd) {
10
+ const legacyPath = path.join(cwd, LEGACY);
11
+ const currentPath = path.join(cwd, CURRENT);
12
+ const legacyExists = fs.existsSync(legacyPath);
13
+ const currentExists = fs.existsSync(currentPath);
14
+
15
+ if (legacyExists && currentExists) {
16
+ return { state: 'conflict', legacyPath, currentPath };
17
+ }
18
+ if (legacyExists) {
19
+ return { state: 'legacy', legacyPath, currentPath };
20
+ }
21
+ if (currentExists) {
22
+ return { state: 'current', currentPath };
23
+ }
24
+ return { state: 'none' };
25
+ }
26
+
27
+ function migrateLegacyFolder(cwd) {
28
+ const check = legacyFolderCheck(cwd);
29
+ if (check.state === 'conflict') {
30
+ return { ok: false, error: `Both .marketing/ and .taketomarket/ already exist in ${cwd}. Resolve manually.` };
31
+ }
32
+ if (check.state !== 'legacy') {
33
+ return { ok: false, error: `Nothing to migrate. State: ${check.state}.` };
34
+ }
35
+ try {
36
+ fs.renameSync(check.legacyPath, check.currentPath);
37
+ } catch (e) {
38
+ if (e.code === 'EXDEV') {
39
+ // Cross-device rename not allowed: copy-then-verify-then-delete.
40
+ try {
41
+ fs.cpSync(check.legacyPath, check.currentPath, { recursive: true });
42
+ } catch (copyErr) {
43
+ return { ok: false, error: `Cross-device migration copy failed: ${copyErr.message}` };
44
+ }
45
+ if (!fs.existsSync(check.currentPath)) {
46
+ return { ok: false, error: `Cross-device migration copy did not produce ${check.currentPath}; legacy folder preserved.` };
47
+ }
48
+ try {
49
+ fs.rmSync(check.legacyPath, { recursive: true, force: true });
50
+ } catch (rmErr) {
51
+ return { ok: false, error: `Copied to ${check.currentPath} but failed to remove legacy folder ${check.legacyPath}: ${rmErr.message}. Both directories now exist; remove the legacy one manually.` };
52
+ }
53
+ } else {
54
+ return { ok: false, error: `Migration failed (${e.code || 'unknown'}): ${e.message}` };
55
+ }
56
+ }
57
+ return { ok: true, from: check.legacyPath, to: check.currentPath };
58
+ }
59
+
60
+ /**
61
+ * Decide whether a state-reading command can safely proceed.
62
+ *
63
+ * Returns { ok: true } when state is `current` or `none` (a fresh project that
64
+ * the command itself may create state in). Returns { ok: false, message } when
65
+ * state is `legacy` (user has not migrated) or `conflict` (both folders
66
+ * present). Callers should print `message` to stderr and exit non-zero so the
67
+ * user gets actionable guidance instead of a generic "STATE.md not found".
68
+ */
69
+ function requireMigratedState(cwd) {
70
+ const check = legacyFolderCheck(cwd);
71
+ if (check.state === 'legacy') {
72
+ return {
73
+ ok: false,
74
+ state: check.state,
75
+ message:
76
+ `Legacy '.marketing/' state directory detected. Run '/ttm-update' (preferred) or ` +
77
+ `'node ${path.basename(process.argv[1] || 'ttm-tools.cjs')} legacy-folder migrate' to ` +
78
+ `rename it to '.taketomarket/' before using this command.`,
79
+ };
80
+ }
81
+ if (check.state === 'conflict') {
82
+ return {
83
+ ok: false,
84
+ state: check.state,
85
+ message:
86
+ `Conflict: both '.marketing/' and '.taketomarket/' exist. Resolve manually: ` +
87
+ `diff -r .marketing .taketomarket, merge any unique files, remove the legacy ` +
88
+ `'.marketing/' once '.taketomarket/' is complete, then re-run.`,
89
+ };
90
+ }
91
+ return { ok: true, state: check.state };
92
+ }
93
+
94
+ module.exports = {
95
+ legacyFolderCheck,
96
+ migrateLegacyFolder,
97
+ requireMigratedState,
98
+ LEGACY,
99
+ CURRENT,
100
+ };
@@ -0,0 +1,26 @@
1
+ 'use strict';
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ function defaultSettingsPath() {
7
+ return path.join(os.homedir(), '.claude', 'settings.json');
8
+ }
9
+
10
+ function checkPlaywrightMcp(opts = {}) {
11
+ const settingsPath = opts.settingsPath || defaultSettingsPath();
12
+ let detected = false;
13
+ if (fs.existsSync(settingsPath)) {
14
+ try {
15
+ const settings = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
16
+ if (settings.mcpServers && settings.mcpServers.playwright) detected = true;
17
+ } catch {}
18
+ }
19
+ return {
20
+ detected,
21
+ settingsPath,
22
+ setupHint: detected ? 'Playwright MCP is configured.' : 'Run /ttm-playwright-setup to install.',
23
+ };
24
+ }
25
+
26
+ module.exports = { checkPlaywrightMcp };
@@ -0,0 +1,22 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ const { detectMonorepo } = require('./codebase-scan.cjs');
7
+
8
+ function suggestSitePath(cwd) {
9
+ const monorepo = detectMonorepo(cwd);
10
+ if (!monorepo) {
11
+ return { monorepo: false, default: path.join(cwd, 'landing') };
12
+ }
13
+ if (fs.existsSync(path.join(cwd, 'apps'))) {
14
+ return { monorepo: true, default: path.join(cwd, 'apps', 'site') };
15
+ }
16
+ if (fs.existsSync(path.join(cwd, 'packages'))) {
17
+ return { monorepo: true, default: path.join(cwd, 'packages', 'site') };
18
+ }
19
+ return { monorepo: true, default: path.join(cwd, 'apps', 'site') };
20
+ }
21
+
22
+ module.exports = { suggestSitePath };
package/bin/lib/state.cjs CHANGED
@@ -23,10 +23,10 @@ const {
23
23
  /**
24
24
  * Resolve and validate the STATE.md path.
25
25
  * Security: uses path.resolve() and rejects paths containing '..' after resolution.
26
- * @returns {string} Absolute path to .marketing/STATE.md
26
+ * @returns {string} Absolute path to .taketomarket/STATE.md
27
27
  */
28
28
  function resolveStatePath() {
29
- const statePath = path.resolve(process.cwd(), '.marketing', 'STATE.md');
29
+ const statePath = path.resolve(process.cwd(), '.taketomarket', 'STATE.md');
30
30
  // Reject paths that escape the project directory
31
31
  const projectRoot = path.resolve(process.cwd());
32
32
  if (!statePath.startsWith(projectRoot)) {
@@ -36,7 +36,7 @@ function resolveStatePath() {
36
36
  }
37
37
 
38
38
  /**
39
- * Read .marketing/STATE.md, parse frontmatter, output as JSON.
39
+ * Read .taketomarket/STATE.md, parse frontmatter, output as JSON.
40
40
  * If STATE.md does not exist, outputs { exists: false, error: "..." }.
41
41
  *
42
42
  * @param {boolean} raw - Whether to output raw string
@@ -0,0 +1,42 @@
1
+ 'use strict';
2
+
3
+ const { execSync, execFileSync } = require('child_process');
4
+
5
+ // `cmd` is always a hardcoded literal (rsvg-convert, inkscape, magick).
6
+ // Not user input, so the shell expansion is safe.
7
+ function commandExists(cmd) {
8
+ try {
9
+ execSync(`command -v ${cmd}`, { stdio: 'ignore' });
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ function detectRenderer() {
17
+ if (commandExists('rsvg-convert')) return 'rsvg-convert';
18
+ if (commandExists('inkscape')) return 'inkscape';
19
+ if (commandExists('magick')) return 'magick';
20
+ return 'none';
21
+ }
22
+
23
+ function renderSvgToPng(svgPath, pngPath, renderer) {
24
+ const r = renderer || detectRenderer();
25
+ if (r === 'none') {
26
+ return { ok: false, error: 'No renderer available. Install rsvg-convert (brew install librsvg) or Inkscape.' };
27
+ }
28
+ try {
29
+ if (r === 'rsvg-convert') {
30
+ execFileSync('rsvg-convert', [svgPath, '-o', pngPath]);
31
+ } else if (r === 'inkscape') {
32
+ execFileSync('inkscape', [svgPath, '--export-type=png', `--export-filename=${pngPath}`]);
33
+ } else if (r === 'magick') {
34
+ execFileSync('magick', [svgPath, pngPath]);
35
+ }
36
+ return { ok: true, renderer: r };
37
+ } catch (e) {
38
+ return { ok: false, error: e.message };
39
+ }
40
+ }
41
+
42
+ module.exports = { detectRenderer, renderSvgToPng };