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.
- package/.claude-plugin/marketplace.json +4 -4
- package/.claude-plugin/plugin.json +2 -2
- package/README.md +34 -11
- package/bin/lib/campaign.cjs +12 -8
- package/bin/lib/codebase-scan.cjs +86 -0
- package/bin/lib/config.cjs +129 -0
- package/bin/lib/deploy.cjs +36 -0
- package/bin/lib/deviation.cjs +1 -1
- package/bin/lib/drift-log.cjs +4 -4
- package/bin/lib/health.cjs +32 -31
- package/bin/lib/install-detect.cjs +62 -0
- package/bin/lib/legacy-folder.cjs +100 -0
- package/bin/lib/playwright-check.cjs +26 -0
- package/bin/lib/site-location.cjs +22 -0
- package/bin/lib/state.cjs +3 -3
- package/bin/lib/svg-render.cjs +42 -0
- package/bin/ttm-tools.cjs +136 -4
- package/gates/base-gates.md +8 -8
- package/gates/gate-evaluation.md +8 -8
- package/install.js +37 -3
- package/package.json +10 -6
- package/playbooks/aeo.md +218 -114
- package/playbooks/affiliate.md +225 -160
- package/playbooks/email.md +236 -174
- package/playbooks/events.md +303 -213
- package/playbooks/landing-pages.md +305 -0
- package/playbooks/linkedin.md +264 -142
- package/playbooks/manifesto.md +322 -0
- package/playbooks/paid-ads.md +240 -189
- package/playbooks/positioning.md +340 -0
- package/playbooks/pr-media.md +308 -168
- package/playbooks/pseo.md +426 -0
- package/playbooks/seo.md +251 -158
- package/playbooks/social.md +253 -182
- package/playbooks/youtube.md +286 -181
- package/references/brand-color-theory.md +48 -0
- package/references/codex-image-gen-research.md +58 -0
- package/references/context-loading.md +6 -6
- package/references/humanizer-patterns.md +433 -0
- package/references/inline-education-blurbs.md +461 -0
- package/references/landing-page-anatomy.md +64 -0
- package/references/linkedin-post-patterns.md +174 -0
- package/references/logo-design-principles.md +55 -0
- package/references/meta-gate-evaluation.md +3 -3
- package/references/obra-superpowers-conventions.md +170 -0
- package/references/playbook-leaders.md +472 -0
- package/references/playwright-mcp-setup.md +164 -0
- package/references/positioning-check-report.md +2 -2
- package/references/pseo-page-anatomy.md +56 -0
- package/references/pseo-templates/alternative-anatomy.md +31 -0
- package/references/pseo-templates/alternative-content-playbook.md +32 -0
- package/references/pseo-templates/blog-anatomy.md +28 -0
- package/references/pseo-templates/blog-content-playbook.md +36 -0
- package/references/pseo-templates/comparison-anatomy.md +29 -0
- package/references/pseo-templates/comparison-content-playbook.md +35 -0
- package/references/pseo-templates/use-case-anatomy.md +28 -0
- package/references/pseo-templates/use-case-content-playbook.md +30 -0
- package/skills/ttm-101/SKILL.md +25 -0
- package/skills/ttm-aeo-check/SKILL.md +17 -12
- package/skills/ttm-affiliate-kit/SKILL.md +5 -0
- package/skills/ttm-archive/SKILL.md +5 -0
- package/skills/ttm-brand-refresh/SKILL.md +5 -0
- package/skills/ttm-brief/SKILL.md +5 -0
- package/skills/ttm-competitor-scan/SKILL.md +5 -0
- package/skills/ttm-deploy/SKILL.md +22 -0
- package/skills/ttm-discover/SKILL.md +17 -0
- package/skills/ttm-email-check/SKILL.md +17 -0
- package/skills/ttm-email-preflight/SKILL.md +17 -11
- package/skills/ttm-fix/SKILL.md +5 -0
- package/skills/ttm-health/SKILL.md +6 -1
- package/skills/ttm-humanize/SKILL.md +33 -0
- package/skills/ttm-icp-refresh/SKILL.md +5 -0
- package/skills/ttm-improve-skill/SKILL.md +18 -0
- package/skills/ttm-init/SKILL.md +10 -3
- package/skills/ttm-keyword-map/SKILL.md +17 -11
- package/skills/ttm-landing/SKILL.md +19 -0
- package/skills/ttm-learn/SKILL.md +5 -0
- package/skills/ttm-linkedin-post/SKILL.md +26 -0
- package/skills/ttm-measure/SKILL.md +5 -0
- package/skills/ttm-new-campaign/SKILL.md +5 -0
- package/skills/ttm-next/SKILL.md +5 -0
- package/skills/ttm-playwright-setup/SKILL.md +18 -0
- package/skills/ttm-positioning-check/SKILL.md +5 -0
- package/skills/ttm-positioning-shift/SKILL.md +5 -0
- package/skills/ttm-produce/SKILL.md +5 -0
- package/skills/ttm-pseo/SKILL.md +26 -0
- package/skills/ttm-repurpose/SKILL.md +5 -0
- package/skills/ttm-request-skill/SKILL.md +18 -0
- package/skills/ttm-research/SKILL.md +18 -6
- package/skills/ttm-resume/SKILL.md +5 -0
- package/skills/ttm-review/SKILL.md +5 -0
- package/skills/ttm-seo/SKILL.md +64 -0
- package/skills/ttm-seo-audit/SKILL.md +17 -12
- package/skills/ttm-ship/SKILL.md +5 -0
- package/skills/ttm-state/SKILL.md +5 -0
- package/skills/ttm-update/SKILL.md +152 -4
- package/skills/ttm-verify/SKILL.md +5 -0
- package/templates/agents-md.md +14 -4
- package/templates/campaign-research.md +6 -6
- package/templates/campaign-state.md +1 -1
- package/templates/claude-md.md +14 -4
- package/templates/linkedin-base-template.md +48 -0
- package/templates/next-step-footer.md +13 -0
- package/templates/production-manifest.json +4 -4
- package/templates/pseo/alternative-cms-schema.json +65 -0
- package/templates/pseo/blog-cms-schema.json +55 -0
- package/templates/pseo/comparison-cms-schema.json +56 -0
- package/templates/pseo/use-case-cms-schema.json +62 -0
- package/templates/reference-files/brand.md +51 -0
- package/templates/reference-files/product-dna.md +73 -0
- package/templates/site-scaffold/app/globals.css +2 -0
- package/templates/site-scaffold/app/layout.tsx +17 -0
- package/templates/site-scaffold/app/page.tsx +33 -0
- package/templates/site-scaffold/app/robots.ts +8 -0
- package/templates/site-scaffold/app/sitemap.ts +10 -0
- package/templates/site-scaffold/app/tokens.css +21 -0
- package/templates/site-scaffold/components/Comparison.tsx +14 -0
- package/templates/site-scaffold/components/Faq.tsx +14 -0
- package/templates/site-scaffold/components/Features.tsx +14 -0
- package/templates/site-scaffold/components/FinalCta.tsx +17 -0
- package/templates/site-scaffold/components/Footer.tsx +12 -0
- package/templates/site-scaffold/components/Hero.tsx +22 -0
- package/templates/site-scaffold/components/HowItWorks.tsx +14 -0
- package/templates/site-scaffold/components/PricingTeaser.tsx +14 -0
- package/templates/site-scaffold/components/Problem.tsx +14 -0
- package/templates/site-scaffold/components/SocialProof.tsx +14 -0
- package/templates/site-scaffold/components/Solution.tsx +14 -0
- package/templates/site-scaffold/components/Testimonials.tsx +14 -0
- package/templates/site-scaffold/components/UseCases.tsx +14 -0
- package/templates/site-scaffold/content/.gitkeep +0 -0
- package/templates/site-scaffold/lib/.gitkeep +0 -0
- package/templates/site-scaffold/next.config.mjs +10 -0
- package/templates/site-scaffold/package.json +25 -0
- package/templates/site-scaffold/postcss.config.mjs +3 -0
- package/templates/site-scaffold/public/llms.txt +9 -0
- package/templates/site-scaffold/tsconfig.json +21 -0
- package/templates/verification-report.md +1 -1
- package/workflows/channel/linkedin-post.md +178 -0
- package/workflows/discipline/affiliate-kit.md +65 -6
- package/workflows/discipline/{email-preflight.md → email-check.md} +39 -4
- package/workflows/discipline/repurpose.md +82 -31
- package/workflows/discipline/{aeo-check.md → seo/aeo.md} +13 -6
- package/workflows/discipline/{seo-audit.md → seo/audit.md} +13 -6
- package/workflows/discipline/{keyword-map.md → seo/keyword-map.md} +13 -6
- package/workflows/education/ttm-101.md +114 -0
- package/workflows/lifecycle/brief-positioning-check.md +1 -1
- package/workflows/lifecycle/brief.md +64 -28
- package/workflows/lifecycle/{research.md → discover.md} +61 -19
- package/workflows/lifecycle/fix.md +72 -37
- package/workflows/lifecycle/humanize.md +280 -0
- package/workflows/lifecycle/learn.md +72 -35
- package/workflows/lifecycle/measure.md +54 -18
- package/workflows/lifecycle/produce.md +88 -37
- package/workflows/lifecycle/review.md +71 -25
- package/workflows/lifecycle/ship.md +62 -18
- package/workflows/lifecycle/verify.md +72 -26
- package/workflows/reference-mgmt/brand-refresh.md +50 -13
- package/workflows/reference-mgmt/competitor-scan.md +51 -15
- package/workflows/reference-mgmt/icp-refresh.md +48 -12
- package/workflows/reference-mgmt/positioning-check.md +55 -20
- package/workflows/reference-mgmt/positioning-shift.md +53 -17
- package/workflows/setup/init-brand-colors.md +75 -0
- package/workflows/setup/init-logo.md +113 -0
- package/workflows/setup/init-product-dna.md +83 -0
- package/workflows/setup/init-questions.md +166 -30
- package/workflows/setup/init-validation.md +22 -0
- package/workflows/setup/init.md +144 -39
- package/workflows/setup/new-campaign.md +48 -12
- package/workflows/site/deploy.md +98 -0
- package/workflows/site/landing.md +156 -0
- package/workflows/site/pseo.md +96 -0
- package/workflows/site/quality-gates.md +88 -0
- package/workflows/utility/archive.md +45 -9
- package/workflows/utility/health.md +77 -3
- package/workflows/utility/improve-skill.md +233 -0
- package/workflows/utility/next.md +38 -2
- package/workflows/utility/playwright-setup.md +128 -0
- package/workflows/utility/request-skill.md +218 -0
- package/workflows/utility/resume.md +40 -3
- package/workflows/utility/state.md +42 -7
package/bin/lib/health.cjs
CHANGED
|
@@ -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 .
|
|
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: `.
|
|
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: `.
|
|
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: `.
|
|
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 .
|
|
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: `.
|
|
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: `.
|
|
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: `.
|
|
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: `.
|
|
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 .
|
|
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: '.
|
|
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: '.
|
|
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: '.
|
|
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: '.
|
|
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: `.
|
|
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: `.
|
|
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 .
|
|
295
|
+
* Validate .taketomarket/ directory structure.
|
|
295
296
|
*
|
|
296
297
|
* Checks:
|
|
297
|
-
* 1. .
|
|
298
|
-
* 2. .
|
|
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 .
|
|
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(), '.
|
|
317
|
+
const marketingDir = path.resolve(process.cwd(), '.taketomarket');
|
|
317
318
|
const campaignsDir = path.resolve(marketingDir, 'CAMPAIGNS');
|
|
318
319
|
const checks = [];
|
|
319
320
|
|
|
320
|
-
// Check .
|
|
321
|
+
// Check .taketomarket/ directory
|
|
321
322
|
const marketingExists = dirExists(marketingDir);
|
|
322
323
|
checks.push({
|
|
323
|
-
name: '
|
|
324
|
+
name: 'taketomarket_dir',
|
|
324
325
|
status: marketingExists ? 'pass' : 'fail',
|
|
325
|
-
path: '.
|
|
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: '.
|
|
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: `.
|
|
350
|
+
path: `.taketomarket/${file}`,
|
|
350
351
|
detail: isValid ? undefined : 'frontmatter unparseable',
|
|
351
352
|
});
|
|
352
353
|
} else {
|
|
353
|
-
checks.push({ name, status: 'pass', path: `.
|
|
354
|
+
checks.push({ name, status: 'pass', path: `.taketomarket/${file}` });
|
|
354
355
|
}
|
|
355
356
|
} else {
|
|
356
|
-
checks.push({ name, status: 'missing', path: `.
|
|
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,
|
|
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(), '.
|
|
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
|
-
|
|
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 .
|
|
26
|
+
* @returns {string} Absolute path to .taketomarket/STATE.md
|
|
27
27
|
*/
|
|
28
28
|
function resolveStatePath() {
|
|
29
|
-
const statePath = path.resolve(process.cwd(), '.
|
|
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 .
|
|
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 };
|